Sponsor: Do you build complex software systems? See how NServiceBus makes it easier to design, build, and manage software systems that use message queues to achieve loose coupling. Get started for free.
I’m not a fan of fat controllers. One of the reasons I dislike having my core application logic in controllers is because I like using the web frameworks for what they are good at in my context. My context is usually creating web api’s. For me I use web frameworks as infrastructure that handles- HTTP routing and endpoints
- Deserialization of input payloads
- Serialization of output payloads
- HTTP Headers and Status Codes
- Web Stuff (eg File Uploads)
Thin Controllers
The way that I remove any of my feature code from my controllers is thru commands and queries. If you are familiar with CQRS, then this will make sense. But I’m not talking about Event Sourcing, Domain Driven Design, or having multiple data stores or any of the overly complex version people think it is. I’m simply talking about splitting up incoming requests into commands and queries.Starting with CQRS, CQRS is simply the creation of two objects where there was previously only one. The separation occurs based upon whether the methods are a command or a query (the same definition that is used by Meyer in Command and Query Separation, a command is any method that mutates state and a query is any method that returns a value). – Greg YoungThis is pretty straight forward. I’d guess most actually apply CQRS without really knowing it most of the time.
Repositories
I’m also not a a fan of fat repositories. They generally end up as a giant low cohesive classes full of data access code that is used in one place.Commands & Queries
I’ve previously posted about using Query Objects instead of Repositories. The same applies for commands. We will create a message (class) that will represent the command/query we want to execute. The message will contain the arguments needed for processing the message. In essence it’s like taking the parameters of a method and turning it into a class. In order to facilitate mapping and handling the execution of command and queries, I use the MediatR.MediatR
Simple mediator implementation in .NET In-process messaging with no dependencies. Supports request/response, commands, queries, notifications and events, synchronous and async with intelligent dispatching via C# generic variance.
Command
Let’s take a look at a simple command for changing the pricing level of our customer. Pretty straight forward. It contains the minimum amount of data needed in order for us to perform the given task.Handler
Now we will create a handler for this command message. Now all that is left is to wire up our command to our web framework.Controller
In this example I’m using NancyFX which uses Modules instead of Controllers. This shouldn’t be too hard to translate if you are more familiar with Web Api.More…
Here are some more helpful posts in regards to create commands, queries and MediatR.- Query Objects instead of Repositories
- Mediator Pattern using MediatR and Unity
- Query Objects with a Mediator
Pingback: The Morning Brew - Chris Alcock » The Morning Brew #2052
Nice article Derek – I too have been using this for the WebApis I have been writing, and another bonus I found was that if I decide to implement another type of API (e.g. websocket with Fleck), you can re-use the same commands and query objects, meaning much less duplication of code between all the controllers (or modules, handlers etc.)
Thanks.
In regard to your comment of different endpoints, that’s spot on. I’ve had to create different webapi routes/endpoints that ultimately use the same command or query but the difference being the input from the either the query string or the payload that dictates how I populate the request (cmd/query) object.
I had a few more thoughts on this Derek, so wrote a post of my own to elaborate on it, if you’re interested:
http://andydote.co.uk/2016/03/19/cqs-with-mediatr.html
Vertical Slicing is a very good point and allows you to develop by feature instead of layers. Nice post!
Have used the exact same pattern successfully before, and we implemented a solution where commands can be sent either to QueryBus, CommandBus or ServiceBus. So queries returns results, command bus will write/update in the same process, while ServiceBus allows us to send commands on the Azure Service Bus.
Thanks for the comment and great point about being to then use your commands to be be sent to service bus etc. I’ve done this as well with Azure Service Bus and had the actual processing done in a worker role.
Another suggestion is to apply Correlation Id and Message Id to the BaseCommand object, which can be used for tracing and logging. Whenever the user does an action in the web app, it will generate a correlation ID that is distributed across the messages that flows in the system. E.g. if a user deletes a “customer” object, it might additionally send delete operations to delete all “users” that are related, and it might send a message to audit log. If some error occurs during the orchestration of the whole system, it can be traced with the correlation ID. It’s a great feature when the system is in production, and the management tool for support personnel gives visibility into what the user was trying to do when errors occurs.
This is a great idea for another blog post. Thanks!
I also use the command ID to make commands idempotent.
Pingback: Les liens de la semaine – Édition #176 | French Coding
Pingback: Identify Commands & Events - CodeOpinion
Pingback: Idempotent Aggregates - CodeOpinion
Pingback: Validating Commands
Pingback: Mediator Pattern with Hangfire - CodeOpinion
MediatR is a great tool, if you like using a mediator in your applications I’d love for you to check out one I’ve been working on called MicroBus https://github.com/Lavinski/Enexure.MicroBus
Sure, I’ll take look. Thanks!
What’s the difference between the IRequest and IRequest / IRequestHandler and IRequestHandler convention? I am using the former case to handle command-type operations (i.e. requests without a return type) and the latter for query-type operations (which have a return type, so my question is why in this example, void/command are handled by IRequestHandler ?
These examples used the MediatR v2.0. If you are using the latest version (v3) of MediatR, it removed the need for the Unit return type. So now you can just use IRequestHandler for commands that have no return and IRequestHandler for those that have a return (queries).
Ah awesome – thanks for the tip.
Does has suport for RabitMQ?
No. MediatR is purely in-process. However, check out Brighter. https://github.com/BrighterCommand/Brighter
Quick question. A co-worker asked me an interesting question that maybe you can help me with. I’m trying to do a CQS similar to this. The point he made was should we be putting direct DB access in the actual handlers? If we end up switching databases or need to support more than one (i.e. Oracle and Sql Server, depends on client’s needs), how would you go about doing that? Obviously, if you use straight EF it isn’t as big of a concern, but many times you have to write custom queries due to EF inefficiencies. I don’t want to make a repository since that would defeat the purpose of this. But if you did change, that would cause a lot of havoc here. Would it be better to have some sort of support class for each specific command that you could inject into the handler and let that class deal with the database part? The only problem with that is the handlers would end up as a single call to this class often times, making the handlers feel unnecessary. Any thoughts? Thanks!
“If” you were to change underlying database technologies. then that maybe something you want to abstract out. For me, the real question is what’s the probability and risk of changing database technologies. To me that would determine if you need some abstraction or not.
So I have been in systems for 17 years, and Love the “if” we change databases. I have not ran into changing a database infrastructure in those years, and I have worked at many different industries. 🙂 So I have seen zero probability in my years. 🙂