The repository pattern has recently taken on a life of its own in the echo chamber of PHP architecture and development blogs. It seems that just about every PHP developer with a blog has ingested the proverbial flavoured drink mix and written about how the repository pattern can used in a MVC-based web application (this one included!).
But the reasons typically quoted for using the repository pattern are somewhat lacking in number and robustness.
The “Swapping out the Backend” Argument
Many developers writing on the topic of the repository pattern talk about how it makes it easy for you to swap out the technology driving your database, for example, moving from a relational database (such as MySQL) to a key-value store (Mongo, etc).
Ignoring the fact that this rarely ever happens in real world projects, the argument is still weak: relational stores and distributed NoSQL services have fundamental differences that can’t be resolved simply by using the repository pattern. This decision will fundamentally impact the architecture of your web application as a whole.
With that said, there are some real reasons for the repository pattern*.
The Voice of Reason
So what is the primary reason for using the repository pattern in your projects?
Simply put, we want to place a layer of abstraction between your application and what ever is being used to persist data objects.
Why? Because interacting with a data store is a separate concern from other parts of your application: it isn’t related to the transportation or the routing of requests, anything to do with business logic or presenting things to the user. The data layer is concerned with persistence and querying. By keeping that tightly-coupled functionality in a single place, it makes it easier to restrict schema-related changes to one place.
A code example – putting all your queries for a specific entity in one place (a repository) and then accessing THAT from the controller, like this:
Is much nicer than doing everything in the controller, like this:
If the schema changes (perhaps “date” is changed to “posted_at”), we aren’t left frantically digging through controllers looking for eloquent queries.
The Logical First Step
The next question: why not just do this on the Eloquent model?
This is a fair question, and for smaller projects, I would say there’s no problem doing it this way. Example:
But for larger projects, this starts to become problematic for a couple of reasons.
Firstly, we encounter the issues related to static methods. In a nutshell: they’re difficult to test** and dependent on global state.
Secondly, for large projects, using facades (such as those provided for the Eloquent models) is a bad idea. There are a couple of reasons why this is the case which I may detail in a later post. Until then, the main reason for avoiding facades is that they permit the developer to avoid injecting dependencies.
Dependency Injection (DI) becomes more important as the size and complexity of a project increases. DI makes it explicit to the developer and (just as important) to the developer’s IDE exactly where in the project a class and associated class members are being used. This makes it significantly easier to modify code without experiencing unpredicted breakages.
Wrapping Up (TLDR;)
The two major benefits of using a repository layer are that they provide a single point of contact for database access, and they force the developer to use dependency injection.
But these benefits only actually become benefits when your project reaches a certain size and complexity. Before this point, they’re actually liabilities, because they slow down speed of development and increase the size of your project (in terms of lines of code) without adding any additional functionality.
So for small or throw-away projects, I recommend that you do not use the repository pattern.
For larger projects, I think it’s almost a requirement to use something like the repository pattern to manage the growing complexity of the data access layer, otherwise it spirals out of control.
*: although the more (large) projects I work on, the more I’m convinced the standard formulation espoused by the Laravel community is not conducive to well-designed applications. In particular, I don’t think it’s necessary to use interfaces or depend on the IoC for resolving concrete implementations of those interfaces. Just use vanilla classes.
**: even though some will debate this, I hold that though there may be ways to test static methods, it’s ALWAYS more complicated than testing instance methods, and therefore negatively impacts testability