CQRS with MediatR and AutoMapper
CQRS is a simple pattern – two objects for command/queries where once there was one. These days just about every system I build utilizes CQRS, as it’s a natural progression from refactoring your apps around the patterns arising from reads and writes. I’ve been refactoring a Microsoft sample app to techniques I use on my apps (mainly because I want something public to look at for new projects) at my github.
Remember, CQRS is not an architecture, it’s a pattern, which makes it very easy to introduce into your applications. You can use CQRS in some, most, or all of your application and it’s easy to move towards or away from.
Even in simple apps, I like to keep my read models separate from my write models, mainly because the demands for each are drastically different. Since CQRS is just a pattern, we can introduce it just by refactoring.
First, let’s look at refactoring a complex GET/read scenario.
My initial controller action for a complex read is…well, complex:
In order to derive our read models, I center around building query objects and result objects. The Query model represents the “inputs” to the query and the Result model represents the “outputs” from the query. This also fits very well into the “one-model-in-one-model-out” concept I use in my apps these days.
Looking at our controller, the inputs are pretty obvious – it’s the parameters to the controller action!
To make my life easier with this pattern, I’ll use MediatR as a simple means of providing a way to have “one model in goes to something to get one model out” without creating bloated service layers. Uniform interfaces are great!
The next piece I need are the output – the result. I take *all* the results, including those “ViewBag” pieces as the Result object from my query:
Finally, I take the inside part of that controller action and place it in a handler that takes in a Query and returns a Result:
My handler now completely encapsulates the work to take the input and build the output, making it very easy to test the logic of my system. I can refactor the contents of this handler as much as I want and the external interface remains input/output. In fact, if I wanted to make this a view or stored procedure, my input/output and tests don’t change at all!
One slight change was to switch to AutoMapper projection at the bottom with the ProjectToPagedList method:
I build a few helper methods to project from a queryable to my read model. The AutoMapper projections completely bypass my write model and craft a query that only reads in the information I need for this screen:
Some folks prefer to create SQL Views for their “Read” models, but that seems like a lot of work. AutoMapper projections have the same concept of a SQL View, except it’s defined as a projected C# class instead of a SQL statement with joins. The result is the same, except I now define the projection once (in my read model) instead of twice in my model and in a SQL view.
My controller action becomes quite a bit slimmed down as a result:
Slimmed down to the point where my controller action is really just a placeholder for defining a route (though helpful when my actions do more interesting things like Toastr popups etc).
Now that we’ve handled reads, what about writes?
Write models tend to be a bit easier, however, many of my write models tend to have a read component to them. The page with the form of data is still a GET, even if it’s followed by a POST. This means that I have some duality between GET/POST actions, and they’re a bit intertwined. That’s OK – I can still handle that with MediatR. First, let’s look at what we’re trying to refactor:
Ah, the notorious “Bind” attribute with magical request binding to entities. Let’s not do that. First, I need to build the models for the GET side, knowing that the result is going to be my command. I create an input for the GET action with the POST model being my result:
One nice side effect of building around queries/commands is it’s easy to layer on tools like FluentValidation. The command itself is based on exactly what information is needed to process the command, and nothing more. My views are built around this model, projected from the database as needed:
Again, I skip the write model and go straight to SQL to project to my write model’s read side.
Finally, for the POST, I just need to build out the handler for the command:
Okay so this command handler is very, very simple. Simple enough I can use AutoMapper to map values back in. Most of the time in my systems, they’re not so simple. Approving invoices, notifying downstream systems, keeping invariants satisfied. Unfortunately Contoso University is a simple application, but I could have something more complex like updating course credits:
I have no idea why I’d need to do this action, but you get the idea. However complex my write side becomes, it’s scoped to this. In fact, I can often refactor my domain model to handle its own command handling:
All the logic in processing the command is inside my domain model, fully encapsulated and unit-testable. My handler just acts as a means to get the domain model out of the persistent store. The advantage to my command handler now is that I can refactor towards a fully encapsulated, behavioral domain model without changing anything else in my application. My controller is none the wiser:
That’s why I don’t worry too much about behavioral models up front – it’s just a refactoring exercise when I see code smells pop up inside my command handlers. When command handlers get gnarly (NOT BEFORE), just push the behavior down to the domain objects as needed using decades-old refactoring techniques.
That’s it! MediatR and AutoMapper together to help refactor towards CQRS, encapsulating logic and behavior together into command/query objects and handlers. Our domain model on the read side merely becomes a means to derive projections, just as Views are means to build SQL projections. We have a common interface with one model in, one model out to center around and any cross-cutting concerns like validation can be defined around those models.