9

When I first learnt about Domain Driven Design, I was also introduced to the repository and unit of work patterns that once seemed to be top notch for the cool kids that threw SQL queries like cavemans against databases. The deeper I got into that topic, the more I learnt that they don't seem to be necessary anymore because of ORMs like EF and NHibernate that implement both unit of work and repositories into one API, called session or context.

Now I'm unsure what to do. To repository or not to repository. I really understand the argument that such leaky abstractions only over-complicate things while adding absolutely nothing that may simplify data access, however, it doesn't feel right to couple every possible aspect of my application to e.g. Entity Framework. Usually, I follow a few simple guidelines:

  1. The domain layer is the heart of the system, containing entities, services, repositories...
  2. The infrastructure layer provides implementations of domain interfaces of a infrastructural concern, e.g. file, database, protocols..
  3. The application layer hosts a composition root that wire things up and orchestrates everything.

My solutions usually look like this:

Domain.Module1
Domain.Module2
    IModule2Repo
    IModule2Service
    Module2
Infrastructure.Persistence
    Repositories
        EntityFrameworkRepositoryBase
MyApp
    Boostrapper
        -> inject EntityFrameworkRepositoryBase into IRepository etc.

I keep my domain layer clean by using a IRepository<'T> which is also a domain concern not depending on anything else that tells me how to access data. When I now would make a concrete implementation of IModule2Service that requires data access, I would have to inject DbContext and by this, coupling it directly to the infrastructure layer. (Coming to Visual Studio project, this can end up really tricky because of circular dependencies!)

Additionally What can be an alternative to depositories and fucktons of works? CQRS? How does one abstract a pure infrastructural framework?

3

Engineering is all about compromises. And so is software development. Right now, I believe only other option, that would be simpler, is to work directly with ORM. But like you said, that might lock you into specific persistence framework.

So you have to ask yourself "Is the additional complexity worth the decoupling of your code from persistence". Every time I hear people say they want to decouple their code from persistence I ask "How many times in your career did you change your persistence framework?"

The problems I see with repositories is that they make common changes harder. Eg. adding new way to query an aggregate. And they make uncommon changes (supposedly) easier. Eg. changing implementation of repositories.

Then there is also the unit-testing argument, to which I say "If persistence framework doesn't allow you to mock a database, either in memory or locally, then it is not worth using the framework at all."

  • 1
    The point of the repository is to decouple app from persistence and there's no complexity involved. And I change the persistence details at least once, because the db access is the last thing I code, until that point I'm using in memory repos. Besides, there is a very subtle trap when you're using a persistence detail directly. Your business objects will tend to be designed to be compatible with it, instead of agnostic. And that's because a rich, properly encapsulated entity can't be directly restored from any storage (except memory) without (some ugly) workarounds – MikeSW May 6 '14 at 13:50
  • About adding a new way to query an aggregate root or any entity, that's why CQRS appeared. Personally, I keep the Repository for domain purposes and use query handlers for actual querying. And those handlers are very tightly coupled to the db and ofc very efficient at what they do. – MikeSW May 6 '14 at 13:53
2

I am pro-repository, though I have moved away from generic repository patterns. Instead I align my repositories with the business function they serve. The repositories are not aimed at abstracting away the ORM, as this isn't anything I expect to change out, and at the same time I avoid making a repository too granular. (I.e. CRUD) Instead my repositories serve two-to-three key purposes:

  • Data retrieval
  • Data creation
  • Hard deletion

For data retrieval, the repository always returns IQueryable<TEntity>. For data creation it returns TEntity. The repository handles my base-level filtering such as authorization "active" state for systems that use soft-delete patterns, and "current" state for systems that use historical data. Data creation is responsible just for ensuring that required references are resolved and associated and that the entity is set up and ready to go.

Data update is the responsibility of the business logic working with the entities(s) in question. This can include things like validation rules. I don't try encapsulating that in a repository method.

Deletion in most of my systems is soft-delete so it would fall under data update. (IsActive = false) In the case of hard-deletes this would be a one-liner in the Repository.

Why repositories? Test-ability. Sure, DbContext can be mocked, but it is simpler to mock a class that returns IQueryable<TEntity>. They also play nice with the UoW pattern, personally I use Mehdime's DbContextScope pattern to scope out the unit of work at the level I want (I.e. Controllers in MVC) and let my controllers and helper service classes utilize the repositories without needing to pass references to the UoW/dbContext around. Using IQueryable means that you don't need a lot of wrapper methods in the repository, and your code can optimize how the data is going to be consumed. For instance the repository doesn't need to expose methods like "Exists" or "Count", or try to wrap entities with other POCOs in cases where you want sub-sets of data. They don't even need to handle eager-loading options for related data you may or may not need. By passing IQueryable, the calling code can:

.Any()
.Count()
.Include() // Generally avoided, instead I use .Select()
.Where()
.Select(x => new ViewModel or Anon. Type)
.Skip().Take()
.FirstOrDefault() / .SingleOrDefault() / .ToList()

Very flexible, and from a testing P.o.V. my mocked repository simply needs to return Lists of populated entity objects.

As for generic repositories, I've moved away from these for the most part because when you end up with a repository per table your controllers/services end up with references to several repositories to do one business action. In most cases only one or two of these repositories are actually doing write operations (provided you're using navigation properties properly) while the rest are supporting those with Reads. I'd rather have something like an OrdersRepository that is capable of reading and creating orders, and reading any relevant lookups etc. (lightweight customer objects, products, etc.) for reference when creating an order, than hitting 5 or 6 different repositories. It may violate DNRY purists, but my argument for that is that the repository's purpose is to serve the creation of Orders which includes the related references. I don't need to go to a Repository<Product> to get products where for the basis of an order I only need an entity with a handful of fields. My OrderRepository might have a .GetProducts() method returning IQueryable<ProductSummary> which I find nicer than a Repository<Product> that ends up having several "Get" methods to try and serve different areas of the application's needs, and/or some complex pass-in filtering expression.

I opt for simpler code that's easy to follow, test, and tune. It can be abused in bad ways, but I'd rather have something where abuses are easy to spot and correct than try to "lock down" the code in a way that cannot be abused, fail at it, and then have something that is a nightmare to get to actually do what the client pays to have it do in the end. :)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.