It’s cathartic and whiny and all to bemoan the repository silliness as I have, but what about a real alternative? What is one supposed to do? Is there a better way? I’m glad you asked that!
While I don’t claim to have all of the answers here, I do have something I’ve been shaping over the last few months that is in real working code on a real enterprise project. Let’s first take a look at the benefits that the repository pattern typically brings and decide which ones are actually worth having in practice and how we can still get them without aiming for the lowest common denominator by hiding everything in a highly abstracted interface.
Isolating the actual data access code from the rest of the system
It’s important that you aren’t creating
SqlConnection objects in your ASPX files or ViewModels or whatever. This is just bad, bad, bad and we all know it. One of the main benefits of wrapping it up in a DAL of some kind is to keep all this database crap (or whatever it may be) in one place instead of littered throughout the code base. If only just for the sake of re-use, so that we don’t have to manually spin up connections, build/execute SQL, or whatever. Better to be declarative about what we want and let some DAL do the actual work of materializing it.
It is here where the repository methods of Find and Get and whatever shine. Our controllers, services, and whatnot know what they need and can just ask for it from a DAL service. Even better, when using Entity Framework or NHibernate or other ORMs, it becomes even easier since really they do most of the work. Our DAL thingy can just create a context, do some includes and wheres, and then
ToList that baby. Done! So easy, right?
Generic repositories make this super simple: just get yourself an
IRepository<Customer> and off you go.
Bottom line: isolation, encapsulation, separation of concerns, etc., are definitely all good things.
Testability, mockability, or whatever you want to call it
Part of the reason for using an interface abstraction is for the “program to a contract” paradigm and so we can easily replace it with something else. Number one reason for this is for unit testing purposes. If we write tests for our service, we’d like to isolate the tests to just the service itself and mock its dependencies (thus removing them as a variable for why the result is wrong). It’s really easy to do this when your service expects an
IRepository<Customer> and not a
Bottom line: testing is definitely a good thing, so if you can make that easier all the better.
But what we’re really talking about is the larger feature of…
Modularity, replaceability, or injectable dependencies
What we’re really wanting is to leverage a dependency injection container that can provide concrete implementations for everybody at run-time. This typically requires you stick to the “program to a contract” bit to really work well and it also has the effect of making things much more testable since your tests can provide dummy dependencies with deterministic pre-defined behavior suited to the test at hand.
Bottom line: you had best be doing this already, not just for your DAL stuff
Plug-and-play persistence layer
Some might try to also tell you that it’s good in case you want to rip out the Entity Framework implementation and replace it with something else. Technically, you are doing this in the unit test but in a very rigidly defined way that suits the needs of the test and no more. A true “replacement” would need to be a full alternate implementation.
And while it is true this is a powerful ability you gain by using the interface, my bet is that this is extremely unlikely to ever happen in the real world. I’m not saying it never does, though. But you will probably have a good idea whether that is even a remote possibility or real need for your project. My guess is in 90% of cases or more it isn’t. As we’ll see later, this “ripping out” process won’t be as bad as it sounds even without the lowest-common-denominator way of doing things. And in fact, as I’ve already said once before, there are likely to be subtle (or not so subtle) differences in implementation that will require a rewrite anyway.
Just as an example, if you were to switch from
DbContext (we’re not even talking about shifting away from Entity Framework here), there are differences even there:
DbContext has a lovely little Find method that can take params key values, making a generic repository Find method super easy.
ObjectContext on the other hand does not, requiring a bit more work to support such a method. So much so that you may not have included such a method in your
IRepository interface when you originally wrote your DAL with
ObjectContext in mind.
Bottom line: extremely unlikely to actually be needed or used, so almost always not worth the trouble
Decorators, adapters, strategies, and other advanced patterns
Another benefit to isolating things to a single repository class implementation behind an interface is that it becomes easier to do advanced fanciness like writing a
CachingRepository<T> decorator class. It is often enough you’ll want to do this sort of thing and it’s a very powerful ability even though there are still many times it isn’t necessary. Still, if we can keep the door open to having them should the need ever arise with hardly any extra effort to keep that possibility open then why not?
I like to think of this as the “accretion” pattern more generally. Architect things in such a way that you can add on new things as specs and features change during development, with little to no rewriting of existing things. If you find that performance is bad, well just add in a new caching decorator or whatever. If you need better logging or you’ve got a new fancy logging service then slap together a logging decorator or an adapter to get your existing Log4net calls to go to the new centralized company-wide logging server.
Bottom line: always a plus, even if not always exploited, especially if we get the option for basically free
Well! Not a bad list. Lots of these are really more general guidelines for all of your architecting anyway, but they certainly apply with regards to your DAL and the Repository Pattern does give you all of these great benefits. What we want is a way to get the worthwhile benefits without the additional limitations that taking the typical repository pattern usually entails…
The answer has actually been right in our programmer faces for a few years now.