Implementing CQRS with MediatR – Part 1
This article explains the CQRS and Mediator patterns, compares CQRS to CRUD, discusses trade‑offs, and provides a step‑by‑step guide to configuring MediatR in an ASP.NET Core Web API, creating commands, queries, handlers, and testing the setup with Postman.
CQRS and Mediator Pattern
MediatR helps developers quickly implement two architectural patterns: CQRS and Mediator. Although they appear similar, they have distinct characteristics.
CQRS
CQRS (Command Query Responsibility Segregation) separates write (command) and read (query) responsibilities into different models, unlike the traditional CRUD approach where a single data store handles all four operations.
The diagram shows the application splitting query and command models. CQRS does not prescribe a specific way to separate them; the separation can be logical within a single application or physical across different servers, depending on the scenario.
The design of CQRS resembles database read‑write splitting.
While attractive, CQRS introduces trade‑offs such as managing separate systems, potential data staleness, and added complexity, which can lead to over‑design if not needed.
Mediator Pattern
The Mediator pattern defines an object that encapsulates how other objects interact, removing direct dependencies between them. It enables loose coupling, similar to Inversion of Control, making code simpler and easier to test.
In the illustration, SomeService sends a message to the Mediator, which then invokes multiple handlers without the services directly depending on each other. This is similar to a publish/subscribe broker.
Using MediatR
MediatR acts as an in‑process mediator, allowing all communication between the UI and data store to go through it. It is limited to in‑process messaging; for cross‑process scenarios, a message broker such as Kafka, RabbitMQ, or Azure Service Bus is recommended.
Configuring MediatR in an ASP.NET Core API Project
Project Setup
Create a new ASP.NET Core Web API project named CqrsMediatrExample.
Install Packages
PM> install-package MediatRIf using a version prior to 12, also install:
MediatR.Extensions.Microsoft.DependencyInjectionRegister Services
In Program.cs add:
builder.Services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(typeof(Program).Assembly));Now MediatR is ready for injection.
Data Store
Define a simple Product class and a fake in‑memory data store:
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
}
public class FakeDataStore
{
private static List<Product> _products;
public FakeDataStore()
{
_products = new List<Product>
{
new Product { Id = 1, Name = "Test Product 1" },
new Product { Id = 2, Name = "Test Product 2" },
new Product { Id = 3, Name = "Test Product 3" }
};
}
public async Task AddProduct(Product product)
{
_products.Add(product);
await Task.CompletedTask;
}
public async Task<IEnumerable<Product>> GetAllProducts() => await Task.FromResult(_products);
}Register the store as a singleton:
builder.Services.AddSingleton<FakeDataStore>();Separate Commands, Queries, Handlers
Create three folders: Commands, Queries, and Handlers to physically separate the models.
Query Example
Define a query record:
public record GetProductsQuery() : IRequest<IEnumerable<Product>>;Implement its handler:
public class GetProductsHandler : IRequestHandler<GetProductsQuery, IEnumerable<Product>>
{
private readonly FakeDataStore _fakeDataStore;
public GetProductsHandler(FakeDataStore fakeDataStore) => _fakeDataStore = fakeDataStore;
public async Task<IEnumerable<Product>> Handle(GetProductsQuery request, CancellationToken cancellationToken) =>
await _fakeDataStore.GetAllProducts();
}Add an action in ProductsController to invoke the query:
[HttpGet]
public async Task<ActionResult> GetProducts()
{
var products = await _mediator.Send(new GetProductsQuery());
return Ok(products);
}Command Example
Define a command record: public record AddProductCommand(Product Product) : IRequest; Implement its handler:
public class AddProductHandler : IRequestHandler<AddProductCommand>
{
private readonly FakeDataStore _fakeDataStore;
public AddProductHandler(FakeDataStore fakeDataStore) => _fakeDataStore = fakeDataStore;
public async Task Handle(AddProductCommand request, CancellationToken cancellationToken)
{
await _fakeDataStore.AddProduct(request.Product);
return;
}
}Add a POST action in the controller:
[HttpPost]
public async Task<ActionResult> AddProduct([FromBody] Product product)
{
await _mediator.Send(new AddProductCommand(product));
return StatusCode(201);
}Testing with Postman
Run the project, then use Postman to send a GET request to retrieve the product list, a POST request to add a new product, and finally another GET to verify the new product appears.
Conclusion
The article demonstrated how to set up MediatR for CQRS in ASP.NET Core, covering both query and command flows, and highlighted the trade‑offs of CQRS. The next part will explore MediatR notifications.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
