CQRS with MediatR and the Microsoft dependency injection container in .NET 6
CQRS with MediatR and the Microsoft dependency injection container in .NET 6
• .NET
• 8 min read
CQRS is an application architecture pattern and stands for Command query responsibility segregation.
My intent with this article is not to go through the architecture pattern and explain the details involved. Martin Fowler1 and Microsoft2 have already done that, and I am not repeating it here. The purpose of this article is to show how you can leverage CQRS with MediatR and Microsoft dependency injection container in .NET 6.
What is MediatR?
MediatR is a library that was created by Jimmy Boggard3 based on the Mediator pattern4, which promotes loose coupling by keeping objects from referring to each other explicitly.
MediatR deals with two kinds of messages it dispatches:
Request/response messages, dispatched to a single handler.
With requests and responses we implement Command to perform actions and Queries to retrieve information.
In this article we have Queries and Commands: GetProductQuery, GetProductsQuery, AddOrUpdateProductCommand and DeleteProductCommand.
Notification messages, dispatched to multiple handlers.
It works basically as a broadcast, where the notification is sent to multiple listeners that wish to be notified.
In this article, we have Notifications available on PublishProductNotify.
Why Service Collection?
When you think about IoC (Inversion of Control), the built-in ASP.NET Core Service Collection, the Microsoft dependency injection container comes in handy.
MediatR works really well with the Microsoft dependency injection container due to the extension libraries available. In this particular article, we use MediatR.Extensions.Microsoft.DependencyInjection, which helps connect MediatR to the Service Collection.
If you want to explore a different IoC, Autofac for instance, check this other article.
How does it work?
Looking at the solution we have an API with a Product Controller and a Class Library that has the Command, Query and Notification implementations.
usingCQRSAndMediatrSampleApplication.Product;varbuilder=WebApplication.CreateBuilder(args);// Add services to the container.
builder.Services.AddControllers();// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();builder.Services.AddSwaggerGen();builder.Services.AddProductModule();varapp=builder.Build();// Configure the HTTP request pipeline.
if(app.Environment.IsDevelopment()){app.UseSwagger();app.UseSwaggerUI();}app.UseAuthorization();app.MapControllers();app.Run();
In particular with the Microsoft dependency injection container and MediatR, the initialization requires much less, as the Service Collection can be easily extended, check this line builder.Services.AddProductModule();.
This is only possible because the Service Collection was extended on ProductModule.cs:
Note that on Create, Update and Remove we also notify consumers. Depending on how solutions are integrated, you need to notify other parts (consumers) about the actions executed via Publish methods in MediatR.
To handle in-memory data, the following ProductsInMemory.cs library was created to help with data:
I was thinking about breakfast when I wrote this article. :D
Queries
It is important to observe that IRequest and IRequestHandler are available on the same file. This makes our dev life easier, as you can more easily drill down to the references and code implementation when you need to go to the Declaration or Implementation.
The notification follows a similar implementation by using INotification and INotificationHandler. Again, keep them on the same file to make your life easier.
usingMediatR;namespaceCQRSAndMediatrSampleApplication.Product.Notify{publicclassPublishProductNotify:INotification{publicstringMessage{get;set;}}publicclassPublishProductNotifyMessageHandler:INotificationHandler<PublishProductNotify>{publicTaskHandle(PublishProductNotifynotification,CancellationTokencancellationToken){//TODO: Send message
Console.WriteLine($"Message: {notification.Message}");returnTask.CompletedTask;}}publicclassPublishProductNotifyTextHandler:INotificationHandler<PublishProductNotify>{publicTaskHandle(PublishProductNotifynotification,CancellationTokencancellationToken){//TODO: Send text
Console.WriteLine($"Text: {notification.Message}");returnTask.CompletedTask;}}}
Running the solution
Run the solution and open Swagger to perform the operations:
When you execute Create, Update and Remove you can see the notifications:
Using MediatR makes CQRS much easier to be implemented and digested in solutions.
Make sure you create a logical hierarchy in your solution, (using the example I shared) as this implementation requires a methodic approach. A common understanding of the team to work on it is also needed, as it is not trivial for developers to jump on it without knowing the pattern.
Join the conversation! Share your thoughts and connect with other readers.