I’ve picked up a new coding technique recently. I’ve always been a bit uncomfortable with the 1:1 relationship between class and interface. I’ve also been uncomfortable with large service classes like a repository. When you take a dependency on a large service interface you are hiding all the details about what you are actually dependent on, which can cause you problems down the line with your tests and refactorings.
Repositories in particular quickly escalate to large numbers of functions. If I am working on tests for a piece of code that takes a dependency on that repository I have no option other than to read the code to see what methods it is calling. Tedious.
So when I stumbled across the long forgotten (by me) practice of explicitly implementing an interface I saw potential to take a fresh look at some of these concerns.
I still don’t know exactly where this train of thought will take me but I’d like to throw it out there and see if it gets the gears turning for anyone else. I am using this heavily in a new personal project, so by the end of it I should have a good idea of whether it has helped or hindered me.
A quick refresh on explicitly implementing and interface.
When you implement an interface like this (note the interface name in front of the method name) then it is only accessible when your instance of SqlRepository is cast as an IsEmailAlreadyInUse. Through a dependency injection container, which preferably includes an AsImplemetedInterfaces() option, this is very easily wired up.
Notice how the SqlRepository is internal, this is saying that you shouldn’t be giving out instances cast as this type. You should be handing out a single instance (per appropriate lifetime scope) cast as its various interfaces.
With this done, your code that is applying business logic around these persistence calls suddenly needs to be explicit about what functions it needs. It can no longer say “give me a repository and I will do what I please with it”
When you are writing tests (whether before or after writing the implementation) it becomes very obvious what your code is actually dependent on. Which makes it easier to mock and easier to refactor or replace.
An additional benefit is that if you end up with a lot of dependencies you should be stopping to consider if your pushing too much logic into a single code unit. Perhaps things can be broken down further and some reusability gained.
In my personal application I do all this in two code files. I have one where I define all my interfaces and one where I implement them all. Overall the approach is working well for me so far though, it has not been proven to be a burden to organise the code. I have very simple integration tests that ensure each command is doing what I expect it to. All my “complicated” logic is then easily unit tested with FakeItEasy.
Finally I chose the repository as an example because it is a pattern I see often which could really benefit from a new approach. It is by no means the only example. At the same time don’t just go breaking interfaces apart for the sake of it, use them correctly as your units of abstraction.
There is another interesting pattern you can use when implementing your interfaces like this. I will hopefully go into that next time.
2 comments:
"Repositories in particular quickly escalate to large numbers of functions.".
They shouldn't. Such a class/interface is violating the Interface Segregation Principle (http://bit.ly/adLLFt) in that case, so this is a design smell at least. Did you consider a design like the following?: http://bit.ly/s3UUyv
I think this can be useful. I've used something similar when I worked with a class which handled both invoice payments and part payments. And it was done like that because the payment provider handled both of those. The class was poorly abstracted with an interface like IKlarnaService.
Instead of renaming it to something like IInvoiceAndPartPaymentService I created one interface for invoice and one for part payment and let the old class implement both.
Could have separate classes as well, but it mostly just wrapped the providers API. And separating it later if you need to is a lot simpler when the contracts are separated.
Post a Comment