In this episode, we’ll take a look at (yet) another approach to organizing data access code, very likely overkill.
Note: depending on your preference, you can check out the following video or skip it to the written version below.
The playlist for the whole series is here.
As in the past couple of episodes we’ve been playing around with the redesign of our group management API internal architecture, in this one we’ll take a look at an alternative approach to data access code organization.
This alternative is very inspired by the same idea of the individual request handlers per use case, having an individual query handler for each type of query we want to make.
To put it simply, the idea is based on a “classic” repository approach, which would expose all the methods required to interact with the database, decomposing it, having a dedicated query handler for each type of query.
This decomposition, at least right now, is only on the read side. On the write side (create, update, delete) we’ll still be using the typical repository implementation.
In the previous episode, while implementing the create group use case, we used this approach to get information about the user (and to write the newly created group):
Now let’s take a look at the implementation. If you already implemented the repository pattern in the past, it should all feel familiar, minus splitting up all the queries.
Let’s start with the repository/write side, which is the most traditional part of this implementation. In the
Domain project, where we put the domain logic plus the interfaces for it to interact with the infrastructure, we create an
Writing to the database
Pretty classic stuff, a method for each letter of CRUD, except read 🙂.
Now the implementation, goes into the
Infrastructure project, along with the
Again, with some knowledge of EF Core (for instance by looking at episode 011), most of this should be familiar.
The only slightly different thing going on here, is the code to manipulate the entity version, as EF uses the version present in the original values, if we’re flowing it to frontend, we must ensure that value is up to date. I’m still not completely happy with this approach (with the interface), so this may change in the future.
Querying the database
Now for the read side. Like I mentioned, this is inspired by the individual request handlers for the use cases, so we could again make use of MediatR, but in this instance I prefer not to mix everything, so we’ll keep MediatR only for the use cases.
Let’s begin with the things on the
Domain side. We’ll have a couple of interfaces,
IQueryHandler, plus one class for each type of query we want to make.
IQuery is basically a marker interface, that we’ll use on the classes that represent each type of query.
IQueryHandler will be implemented in the infrastructure layer, using whatever data access technology we desire.
Using the create command example we saw previously, still on the
Domain project, we have a class representing the get user by id query.
This class is just a DTO, with the required information to actually perform the query.
For the implementation, in the
Infrastructure project, we have a class with a pretty straightforward call to EF’s
For a slightly more complex example, we can look at getting a group for a given user.
The idea is the same, a DTO with the query parameters and a query handler. In this case, the query is slightly more complex, including navigation properties of eager loading, as well as a some more conditions in the where clause (part of the
Pros and cons
I can imagine some looking at this and thinking, why? Overengineering! Again!
Well, yeah, I wrote that at the beginning 😛. It’s very likely overkill, but I see some advantages (as well as disadvantages).
For the main pros, I’d say:
- Similarly to the use case segregation, no big repository class with lots of methods.
- Clearer dependencies - in the use case handler, we get dependencies that clearly represent specific queries, instead of repositories with a bunch of methods. This is interesting, for instance, for unit testing the use case handler, as instead of mocking everything or having to figure out which method(s) will be called, we know exactly what can be called from the dependency injected in the constructor.
As the main con, I think it’s very verbose. The discussion around the need to implement an abstraction on top of Entity Framework is really common, as some consider it to be unneeded. With the approach used in this post, the verbosity is even greater, having to create two classes per query, one of which requires an interface implementation and dependencies injected.
The group management API will go forward with this strategy, for the other services, we’ll see what comes to mind when it’s time to implement them 🙂.
Before wrapping up, just wanted to leave here some approaches that are saner than this one.
The simplest one is to just use the ORM directly. Even though I like me some abstractions, it’s true that ORMs like EF, EF Core, NHibernate already implement the repository and unit of work patterns, so it’s acceptable to use them directly. For an example implementation, take a look at Jimmy Bogard’s ContosoUniversity project.
If you like abstractions and want to implement something like repository pattern, you could go with it old-school, or you could try to include the specification pattern for the query bits. You can see examples of this by Vladimir Khorikov and Steve Smith (Steve’s is included in the Microsoft eShopOnWeb reference application).
Before going with the strategy presented in this post, I was thinking about following the example provided by Steve Smith in the eShopOnWeb application, but ended up avoiding it as I wanted to not be tied to using an ORM (as the way the specifications are implemented in the example end up being).
That does it for this episode. We’ve seen an alternative approach to organizing the data access code, which is probably overkill, particularly when using an ORM.
The main reason for this approach is to explore alternatives, particularly trying to avoid as much as possible being tied to an ORM, as I’m imagining for future services to not use EF Core, using something like Dapper instead, as well as not even using a SQL database.
I’m still not completely happy with this strategy though, so I’ll continue thinking about tweaks I can do to improve upon it, particularly to reduce the verbosity as much as possible.
Links in the post:
- ContosoUniversity on ASP.NET Core with .NET Core
- Specification pattern
- Specification pattern: C# implementation
- Specification Pattern
- Microsoft eShopOnWeb ASP.NET Core Reference Application
- Episode 011 - Data access with Entity Framework Core - ASP.NET Core: From 0 to overkill
The source code for this post is in the GroupManagement repository, tagged as
episode033 (didn’t really change anything for this post).
Sharing and feedback always appreciated!
Thanks for stopping by, cyaz!