Repository Pattern: Retrospective and Clarification

Photo by Quentin Dr

I read a simple article today about a leadership principle Bezos believes in. It reflects the purpose of Part 1 and Part 2.

You really can’t accomplish anything important if you aren’t stubborn on vision, but you need to be flexible about the details because you gotta be experimental to accomplish anything important, and that means you’re gonna be wrong a lot. You’re gonna try something on your way to that vision, and that’s going to be the wrong thing, you’re gonna have to back up, take a course correction, and try again.

Jeff Bezos – 2016

There is some duality in this statement and this exercise for me. First, thing to understand is working through the details of my current vision state. Second, why didn’t anyone have course correction with this pattern?

What is important to me isn’t what pattern people use, but they can be successful for their first release all the way until the product’s last. From an Enterprise Architecture stand point, limiting immediate risk and ensuring longevity. More specifically can my current team deliver, and can I hire any junior developer off the street to maintain? If your current team cannot deliver anything maintainable, you need a new team.

This raises a grey area. Where something can be developed using something a-typical or a version behind or ahead the current recommend approach. The question that must be answered is, am I sure in 5 years someone fresh can still be productive in it?

There is another important concept here. If a development team had a hard time being successfully with a simpler solution, how can I believe recommending a different or more complicated would lead to success?

Don’t check box

Leadership check a box that they have something, and developers check a box that they did something. No-one confirms the desired outcome was actually achieved.

Two problematic leaders are the Buzz-word Architect, and the List-driven manager. Both have the same underlining flaw, they don’t evaluate what they are recommending is applicable or achieving a goal.

Side Story: I once took ownership of a product. During KT (Knowledge Transfer) from the previous architect, I was reviewing his code. He used the keyword dynamic, and I asked him why. It was new at the time. I don’t remember his specific answer, but I remembered thinking, he read some article and used it because of it being a buzz. It was loading the dynamic run-time and there was no reason for it.

Check-box developers are not innately a problem, leadership giving a fundamental task to check-box developer is a problem. Check-box developers are good at working in cookie cutter molds. If you tell a check-box developer to add a fundamental item, most likely they will add it and say it is done. They usually don’t confirm the goals are being achieved unless you specify them.

Forget Everything

At this point forget everything you know about whatever implementations you have used. If you are reading this with any bias you probably will not see the point. I’ve got many questions from the previous articles that were exactly opposite from what I was saying.

As a thought exercise, this is of course relevant to defending the repository pattern, but for me its understanding where things can go wrong.

Deconstruction before construction

Before using a pattern, one must deconstruct it’s purpose and define your goals. This is the step I performed that arrived me at the divergent Repository Pattern.

Purpose

  • Repository – Responsible for CRUD.
  • Unit of Work – Responsible for Scope.
  • Data Layer – Table Specific Functions
  • Business Layer – Domain, defining operation scope, and using CRUD.

Goals

  • Abstraction
    • Decoupling from ORM
    • Trying to achieve a percentage of re-usability
    • Providing a simpler facade for development
    • Providing a location for enhancements over the ORM
    • Testability
  • Code Quality
    • Reducing code duplication
    • Enabling developers
  • Extensibility
  • Ease of Use

If you read these goals and think, the Repository Pattern doesn’t achieve them, it’s probably because you didn’t list your goals before implementing.

Tweak for your needs

The reasonable man adapts himself to the world: the unreasonable one persists in trying to adapt the world to himself. Therefore all progress depends on the unreasonable man.

George Bernard Shaw

Be methodical, organize good resources, select best of class and adapt to your needs. Who cares if you follow a blog or msdn post specifically, if it leads you to a path that is undesirable. Don’t trust every resource, be skeptical of everything.

With something as core to your application and potentially other company applications, take your time do it right. Don’t worry if your definition of right is different from an online source. Only worry if your team has concerns. Your acceptance, is from the local development team not from which ever “Guru” wrote a blog post.

Exceptions

For me I do have a set of rules I will not violate, and it pains me greatly when I do. This isn’t typically concerning the specific pattern design, but designing itself. SOLID and Framework Design Guidelines are two things you try not to tweak.

That being said, nothing in finite. One thing I know I violate is a rule laid out somewhere in that book about interfaces. The rule states something like, ~Make sure to have one implementation in you assembly of any interface you design. This was problematic for this design.

Clarification on the proposed changes

Because a lot of people didn’t understand the design I suggested, and some actually found it useful, I wanted to clarify exactly what I was saying. (This is not a recommendation, this is problem solving.)

I want to make two things very clear in the design I laid out:

  • No Specific Repositories Per Type
  • No Custom Unit Of Work

IRepository

The generic IRepository is CRUD. It is implemented once per provider, not type. It uses IQueryable, not IEnumerable. There are two ways to enhance it’s capabilities extension methods and rules.

EntityRepository

For clarity on how this design actually should function 99% of the time, the EntityRepository is sealed. It is vanilla, it isn’t special. Only thing different is, it has to ask EntityUnitOfWork for the DbContext, or have a mechanism to share the DbContext.

IUnitOfWork

IUnitOfWork is responsible for Start, Save, and End. It does not have each Repository in it, it does not have any additional concerns. It implements IDisposable to trigger end.

Rules

The Rules Service is an event aggregator, functional paradigm, publish subscribe pattern (Not Cloud), queue or something custom. It’s purpose it to apply rules to different types. For example an entity is being updated, it is going to apply all rules for the entity type any any interface the entity implements.

An easy conventional rule to understand is Soft Delete. You would define a common rule in the framework. The Cross App Convention would be an interface for ISoftDelete. Your Cross App Rule would specify how to handle this. On Delete it would undo the delete, mark IsDeleted to True and update instead. On Read the rule would add a predicate to exclude deleted items.

You can also have Repository Extensions in the Rules DLL, but they are rare.
For example:
public static IQueryable<TEntity> QueryIncludingDeleted<TEntity>
(this IRepository<TEntity> repository) where TEntity : ISoftDelete

One thing that is powerful with Rules, is that a Framework update or adding a DLL with a container, can adjust your data access behavior. Your Rules are not coupled to EF and it is powerful to add functionality without changing your code. IE: adding a new rule across your micro-services doesn’t require re-writing your micro-services.

EntityUnitOfWork

Entity Unit Of Work is pretty basic. It caches a DbContext once created, can dispose, and has a Save method.

For EF specifically, the rules should be applied just before Save. Doing it earlier has the chance to miss things. This needs to be handled in EntityUnitOfWork.

I have also overridden the ToString method on this class in the past. In Debug it would output pending changes and submitted changes.

Application Data Layer

The data layer does not have custom implementations of Repositories. It does not modify the UnitOfWork in anyway, it doesn’t even use it. Did I say this enough yet?

Repository Extensions

Although I agree it is a bit strange, as generic extensions are rarely used. But this is the only work around without creating specific interfaces and classes for the Repositories. Remember you are exposing the Queryable object, so any data layer queries that are not specific to a business operation can go here. Pretty much a query reuse point.

If your arguments to these functions are of Type Expression or Func, you probably are doing something wrong. IQueryable is exposed.

App Specific Rules

Anything that doesn’t fit into a common rule, things that might be table specific or domain specific should be implemented in the application data layer.

Business Layer

Here of course you should be still doing domain driven design. The business layer is responsible to determining the scope of the business operation, IE the UnitOfWork. It will through DI resolve the UnitOfWork, and IRepositories<T>. IRepository<T> will have the custom extension methods available, and since IQueryable is exposed, you can still project to your domain object. Additionally you can map from your domain object into your entities.

Here is a vanilla code example and UML diagram

using(var unitOfWork = _locator.GetService<IUnitOfWork>())
{
    var repository = _locator.GetService<IRepository<Person>>();

    var query = repository.CustomQuery(....);

    repository.Insert(...);

    unitOfWork.Save();
}

Potential changes from Part 1

One thing that I think makes sense as a change from the original write-up from Part one is restructuring the DI. The EntityUnitOfWork should take in the DbContext in the constructor, and share it between the EntityRepositories. The generic TDbContext can then be removed. This only works if you only ever plan to have 1 DbContext in an AppDomain. It may get confusing otherwise, but I guess you can use named instances.

Additionally the need for a UnitOfWorkManager seems to be limited. As long as you can share an instance of the UnitOfWork per operation somehow its fine. I like this to be implicit instead of explicit. Once you are in a UnitOfWork everything should be using the same context. This although may have problems with the newest version of EF as they automatically dispose of the context… wtf.

Potential issues with this design

Besides everyone hating you because they believe they know best. And, besides all the none issues that people will typically argue there are a few flaws I didn’t discuss.

  1. A UnitOfWork within another UnitOfWork. Not hard to solve, I just didn’t for you.
  2. The need for a IAdvanceRepository with Undo functions or other operations.
  3. Configuring your container correctly.
  4. It’s a fucking blog post with pictures of monkeys.

Conclusion

Do what’s right for you and your team. Don’t be scared to change something to fit your needs.

If your interested in more information, I talk about adding Conventions with EFCore as well. Soon, I’ll table about adding rules to your conventions. These posts though, are not targeting a Repository Pattern specifically.

Photo by Adam King

6 thoughts on “Repository Pattern: Retrospective and Clarification

  1. I think it would be very useful if you set up a sample GitHub repository with your ideas. Blog posts will only get you so far. Keep up the great blogging, however.

    Like

      1. Tell you what. I’ll put out on GitHub my implementation of the UnitOfWork, Repository, and Specification patterns, and I’ll write about it to help spark conversation around #Real-World-Software. I have recently started looking into DDD, and I do think that it offers improvements to our approach to software design, so these implementations to need a refresh, I think.

        Like

      2. I have an implementation for DDD something like BusinessLogic<TDomainModel, TRootEntity> : IBusinessLogic<TDomainModel>

        I touched on it. I believe the abstract BusinessLogic class has template method style extension points for overriding automatic mapping between the Domain Model and the Entities. Additionally, you would customize the interface to the needs of the domain. I guess potentially there is a few base implementations of BusinessLogic that can handle different scenarios. I have never went back to extract those out though.

        Right now though, I’m more interested in GraphQL and how that can sit on top of DDD. The .NET implementation seems to leave much to be desired.

        Like

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.