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 Autofac 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 Autofac?
When you think about MediatR, the Inversion of Control (IoC) container that comes handy is Autofac.
MediatR works really well with Autofac due to the extension libraries available. In this particular article, we use MediatR.Extensions.Autofac.DependencyInjection, which helps connecting MediatR to Autofac.
Autofac fits well in any solutions where you need dependency injection.
If you want to explore a different IoC, the Microsoft dependency injection container 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.
usingAutofac;usingAutofac.Extensions.DependencyInjection;usingCQRSAndMediatrSampleApplication.Product;usingMediatR.Extensions.Autofac.DependencyInjection;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();// Call UseServiceProviderFactory on the Host sub property
builder.Host.UseServiceProviderFactory(newAutofacServiceProviderFactory());builder.Host.ConfigureContainer<ContainerBuilder>(containerBuilder=>{containerBuilder.RegisterMediatR(typeof(Program).Assembly);containerBuilder.RegisterModule<ProductModule>();});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 Autofac and MediatR, libraries work really well together to simplify how to IoC a .NET application, and how it handles modules and the MediatR registration itself.
The ProductController.cs contains all endpoints using HTTP verbs, and they use MediatR for all Queries, Commands and Notification:
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.