I have long considered the repository pattern to be a foundational design pattern for database connected systems. Recently, however, I’ve started to reconsider this opinion. Read on to find out why.
Remember that design patterns are found by capturing established good practice - they don’t fall fully formed from the brow of a fevered architect.
The Repository design pattern was formulated at a time when writing database connected systems involved a lot of difficult and repetitive coding. Connection strings, authentication and connection management. SQL query generation through string concatenation, commands, queries, recordsets and parameters - there was a lot of code to write.
Isolating all of that complexity behind the wall of a convenient interface, keeping the complexity of database access away from any actual business logic made a lot of sense.
Smart developers have always been productively lazy - given the chance to write the same thing a dozen times, most all will find a way to simplify things through good design and reuse. Early attempts at code reuse within data access layers led to a number of different approaches. Some of these were very simple, some were complex and mature (1) and, I’m sure, some were unmitigated disasters.
(1) Back in 1997 I was working with a mature library known simply as “Mocom Data Access” (or MDA). Even then, it was mature, cross platform, performant and robust. Bypassing the MDA with hand generated SQL was seldom necessary, but could be done easily when required. It had some features (such as the ability to tell if an association had been loaded) that modern ORMs, with their devotion to POCO objects, can’t easily achieve.
Enter the modern Object Relational Mapper, or ORM.
For new projects we start in 2011, we can drop in any one of a large number of mature ORM libraries. These libraries take care of our connection strings, authentication and connection management. They handle SQL query generation, creating command objects and parameters for our queries and transforming recordsets returned from the database into our domain objects.
Hang on, doesn’t that description seem somewhat familiar?
Modern object relational mappers take care of wrapping up (almost) all the complexity that we originally wrapped up by creating repositories.
Doesn’t this mean that the ORM could, in effect, be our repository? Why should we wrap up the ORM and incur the cost of creating and maintaining another abstraction layer if the ORM is already doing this job for us?
There’s a broader lesson here too. The abstractions we employ in our applications have to deliver significant value, else the overhead of defining and maintaining them ends up as a net-negative for our project. Every abstraction we employ needs to pass this threshold of viability - any abstraction whose costs exceed the benefits is hampering our work, not benefiting it.