Refactoring Towards Repositories

I’ve been working on projects recently that make heavy use of the repository pattern. I frequently wonder if the added layer of abstraction is worth the indirection and I’ve found there is a large continuum between writing large queries in controllers and abstracting all the way to repositories.

What is a repository anyway?

Martin Fowler defines a repository as

[a layer of abstraction that] mediates between the domain and the data mapping layers

Data mappers are an additional layer of abstraction one can use to put room between the data store and the domain models. In the Rails world, ActiveRecord takes care of the data mapping for us. In applications and frameworks not following the active record pattern there can be different entities for models, data mappers, and repositories (instead of a single one for all three). For a nice explanation of implementing a data mapper (in Go) check out this post from MeetSpace.

Back to repositories… A repository’s (or store’s) job is to be the interface to the database. When a model needs to be CRUDed it should be passed to the repository. When a complex query is needed (for example, it has multiple where or group by clauses) it should be defined on the repository and not directly on the model. This idea by definition introduces indirection because a model can no longer find or save itself. This pattern though can keep business logic in the right place and keep things from getting overly complex.

When does query logic became non-trivial enough to warrant more layers? It depends!

Steps for refactoring complex queries

While the repository pattern can be helpful in keeping concerns in a domain model seperate they do add an additional layer of indirection with which developers must get comfortable. There are other more light weight refactorings that we can apply first.

Here’s a progression to keep in mind next time you find yourself staring at a code base with complex queries all over the place but you’re not ready to jump to repositories:

  1. Strive towards skinny controllers and pushing queries down into models to keep queries out of business logic
  2. Encapsulate query logic into class methods on models to encourage reuse and easier testing
  3. Extract particularly complex queries into query objects to declutter models
  4. Things still complicated? Consider implementing repositories