Applying CQRS and Mediator in Asp.Net Core

I am an enthusiastic software developer focusing on C#, .NET, Asp.NET, Azure and Angular.
Search for a command to run...

I am an enthusiastic software developer focusing on C#, .NET, Asp.NET, Azure and Angular.
Thanks!
gRPC, a high-performance, open-source RPC (Remote Procedure Call) framework developed by Google, has gained immense popularity among developers for building efficient and robust APIs. This blog post will explore how to get started with gRPC services ...

GraphQL is a powerful query language and runtime that efficiently fetches and manipulates data from APIs. This blog post will explore how to get started with GraphQL in Asp.Net Core. What is GraphQL? GraphQL is a query language and runtime for APIs t...

Observability is a key concept in modern software development. It refers to the ability of a system to be monitored, measured, and understood in order to identify and address issues. In the context of ASP.NET Core, observability can help developers a...

Introduction to Blazor Blazor is a web framework from Microsoft that allows developers to build interactive web applications using C# and HTML. With Blazor, we can create web applications that run entirely in the browser without the need for plugins ...

In this previous article, we learned about caching in Asp.Net Core. We talked about In-Memory cache and Distributed cache using SQL Server cache. This article will discuss how to use Redis for caching in Asp.Net Core. What is Redis? Redis (REmote DIc...

This blog post will discuss CQRS and Mediator patterns and apply them in an Asp.Net Core application.
CQS is a design pattern, and the acronym stands for Command Query Separation. It states that every method should either be a command that executes a task or a query that returns data to the caller, but not both. Methods should return a value only if they create no side effects.
Command Query Responsibility Segregation (CQRS) is the segregation of the responsibilities of the commands and queries in a system. It divides a system’s actions into commands and queries, and it is related to CQS.
Because of the flexibility created by migrating to CQRS, the system evolves better over time and prevents update commands from causing merge conflicts at the domain level.
For Martin Fowler: Like any pattern, CQRS is useful in some places but not others. CQRS is a significant mental leap for all concerned, so it should only be tackled if the benefit is worth the jump. In particular, CQRS should only be used on specific portions of a system and not the system as a whole.
CQRS is an excellent pattern to use when the solution has rich/complex business logic.
Also, in scenarios where:
performance of data reads must be fine-tuned separately from the performance of data writes.
one team of developers can focus on the complex domain model that is part of the write model, and another team can concentrate on the read model and the user interfaces.
the system is expected to evolve and might contain multiple versions of the model or where business rules change regularly.
The core principle of CQRS performs fundamentally different roles within a system. Separating them means that each can be optimized as needed, which can be hugely beneficial for distributed systems.
A command is an instruction, a directive to perform a specific task. It is an intention to change something and is created when data is to be mutated.
For Alexey Zimarev: handling a command should result in one transaction on one aggregate; basically, each command should clearly state one well-defined change.
A query is a request for information and is only created when data is to be retrieved from the data store.
It is used to get data or the status of data, and the request should not change the data.
In software engineering, the Mediator pattern defines an object that encapsulates how objects interact.
Instead of having two or more objects that take a direct dependency on each other, they interact with a mediator, who is in charge of sending those interactions to the other component/object.
This pattern is considered a behavioural pattern because it can change the program’s running behaviour.
First, let’s create a new Asp.Net Core Web API project; in my case, I will make it a minimal API project.
Before implementing the commands and queries, let's create some structure for the app.
In the Domain project, it was created the entities that are going to be used for now.
public class Exercise
{
public int Id { get; set; }
public string Name { get; set; }
public int MuscleId { get; set; }
public virtual Muscle Muscle { get; set; }
}
public class Muscle
{
public int Id { get; set; }
public string Name { get; set; }
public virtual List<Exercise> Exercises { get; set; }
}
And also the interfaces for the repository and the service.
public interface IMuscleRepository
{
Task<IEnumerable<Muscle>> GetAll();
Task<Muscle> GetById(int id);
Task<int> Create(Muscle muscle);
Task<int> Update(Muscle muscle);
Task<int> Delete(Muscle muscle);
}
public interface IMuscleService
{
Task<IEnumerable<Muscle>> GetAll();
Task<Muscle> GetById(int id);
Task<int> Create(Muscle muscle);
Task<int> Update(Muscle muscle);
Task<int> Delete(Muscle muscle);
}
For the Data project, the Entity Framework was used as an ORM. The following code shows the implementation of the context.
public class CoachPlanContext : DbContext
{
public virtual DbSet<Muscle> Muscles { get; set; }
public virtual DbSet<Exercise> Exercises { get; set; }
public CoachPlanContext(DbContextOptions<CoachPlanContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Exercise>(entity =>
{
entity.HasOne(d => d.Muscle)
.WithMany(d => d.Exercises)
.HasForeignKey(d => d.MuscleId);
entity.Property(e => e.Name)
.IsRequired(true)
.HasMaxLength(200);
});
modelBuilder.Entity<Muscle>(entity =>
{
entity.Property(e => e.Name)
.IsRequired(true)
.HasMaxLength(200);
});
}
}
Also, was created a repository for the entity Muscle.
public class MuscleRepository : IMuscleRepository
{
private readonly CoachPlanContext _coachPlanContext;
public MuscleRepository(CoachPlanContext coachPlanContext)
{
_coachPlanContext = coachPlanContext;
}
public async Task<int> Create(Muscle muscle)
{
_coachPlanContext.Muscles.Add(muscle);
return await _coachPlanContext.SaveChangesAsync();
}
public async Task<int> Delete(Muscle muscle)
{
_coachPlanContext.Remove(muscle);
return await _coachPlanContext.SaveChangesAsync();
}
public async Task<IEnumerable<Muscle>> GetAll()
{
return await _coachPlanContext.Muscles.ToListAsync();
}
public async Task<Muscle> GetById(int id)
{
return await _coachPlanContext.Muscles.Where(m => m.Id == id).SingleOrDefaultAsync();
}
public async Task<int> Update(Muscle muscle)
{
_coachPlanContext.Muscles.Update(muscle);
return await _coachPlanContext.SaveChangesAsync();
}
}
So far was created only the MuscleService for this project.
public class MuscleService : IMuscleService
{
private readonly IMuscleRepository _muscleRepository;
public MuscleService(IMuscleRepository muscleRepository)
{
_muscleRepository = muscleRepository;
}
public async Task<int> Create(Muscle muscle)
{
return await _muscleRepository.Create(muscle);
}
public async Task<int> Delete(Muscle muscle)
{
return await _muscleRepository.Delete(muscle);
}
public async Task<IEnumerable<Muscle>> GetAll()
{
return await _muscleRepository.GetAll();
}
public async Task<Muscle> GetById(int id)
{
return await _muscleRepository.GetById(id);
}
public async Task<int> Update(Muscle muscle)
{
return await _muscleRepository.Update(muscle);
}
}
I will create feature-wise folders to implement the Commands and Queries using the CQRS Pattern. All CQRS commands, queries, handlers, and validators related to one feature can be grouped under one folder. For example, to create all our commands and queries related to Muscles, we can have a Muscles folder, and inside that folder, we can have separate folders for Commands and Queries.
All the queries will implement the IRequest<T> interface available in the MediatR library, and it is necessary to specify what it will return.
Next, we need to define a handler class that implements the IRequestHandler interface. This class can be defined outside the class; I am implementing it as a nested class.
The IMuscleService service is injected into the constructor of GetAllMusclesQuery and GetMuscleByIdQuery.
public class GetAllMusclesQuery : IRequest<IEnumerable<Domain.Entities.Muscle>>
{
public class GetAllMusclesQueryHandler : IRequestHandler<GetAllMusclesQuery, IEnumerable<Domain.Entities.Muscle>>
{
private readonly IMuscleService _muscleService;
public GetAllMusclesQueryHandler(IMuscleService muscleService)
{
_muscleService = muscleService;
}
public async Task<IEnumerable<Domain.Entities.Muscle>> Handle(GetAllMusclesQuery query, CancellationToken cancellationToken)
{
return await _muscleService.GetAll();
}
}
}
public class GetMuscleByIdQuery : IRequest<Domain.Entities.Muscle>
{
private readonly int id;
public GetMuscleByIdQuery(int id)
{
this.id = id;
}
public class GetMuscleByIdQueryHandler : IRequestHandler<GetMuscleByIdQuery, Domain.Entities.Muscle>
{
private readonly IMuscleService _muscleService;
public GetMuscleByIdQueryHandler(IMuscleService muscleService)
{
_muscleService = muscleService;
}
public async Task<Domain.Entities.Muscle> Handle(GetMuscleByIdQuery query, CancellationToken cancellationToken)
{
return await _muscleService.GetById(query.id);
}
}
}
The commands will follow the same idea as the queries; the difference is that the commands can change the data.
It was created the CreateMuscleCommand, DeleteMuscleCommand and UpdateMuscleCommand and their respective handlers.
Because of the example's simplicity, the domain entity was used as the query return type and as a parameter for the commands. It is a better practice to use DTOs to hide a domain entity from the public API.
public class CreateMuscleCommand : IRequest<Unit>
{
private readonly Domain.Entities.Muscle muscle;
public CreateMuscleCommand(Domain.Entities.Muscle muscle)
{
this.muscle = muscle;
}
public class CreateMuscleCommandHandler : IRequestHandler<CreateMuscleCommand, Unit>
{
private readonly IMuscleService _muscleService;
public CreateMuscleCommandHandler(IMuscleService muscleService)
{
_muscleService = muscleService;
}
public async Task<Unit> Handle(CreateMuscleCommand command, CancellationToken cancellationToken)
{
await _muscleService.Create(command.muscle);
return Unit.Value;
}
}
}
public class DeleteMuscleCommand : IRequest<Unit>
{
private readonly int id;
public DeleteMuscleCommand(int id)
{
this.id = id;
}
public class DeleteMuscleCommandHandler : IRequestHandler<DeleteMuscleCommand, Unit>
{
private readonly IMuscleService _muscleService;
public DeleteMuscleCommandHandler(IMuscleService muscleService)
{
_muscleService = muscleService;
}
public async Task<Unit> Handle(DeleteMuscleCommand command, CancellationToken cancellationToken)
{
var muscle = await _muscleService.GetById(command.id);
if (muscle == null)
return default;
await _muscleService.Delete(muscle);
return Unit.Value;
}
}
}
public class UpdateMuscleCommand : IRequest<Unit>
{
private readonly int id;
private readonly Domain.Entities.Muscle muscle;
public UpdateMuscleCommand(int id, Domain.Entities.Muscle muscle)
{
this.id = id;
this.muscle = muscle;
}
public class UpdateMuscleCommandHandler : IRequestHandler<UpdateMuscleCommand, Unit>
{
private readonly IMuscleService _muscleService;
public UpdateMuscleCommandHandler(IMuscleService muscleService)
{
_muscleService = muscleService;
}
public async Task<Unit> Handle(UpdateMuscleCommand command, CancellationToken cancellationToken)
{
var muscle = await _muscleService.GetById(command.id);
if (muscle == null)
return default;
muscle.Name = command.muscle.Name;
await _muscleService.Update(muscle);
return Unit.Value;
}
}
}
The MediatR library describes itself as a “Simple, unambitious mediator implementation in .NET.” All communication between the user interface and the data store happens via MediatR; this helps us to build CQRS systems.
We need to install two nugget packages: MediatR and MediatR.Extensions.Microsoft.DependencyInjection. After that, we can register the MediatR in our application.
MediatR Requests are straightforward request-response style messages where a single handler synchronously handles a single request.
First, it is necessary to register everything the application will use. The following code registers the MediatR, service, repository and the EF DbContext.
builder.Services.AddMediatR(Assembly.GetExecutingAssembly());
builder.Services.AddScoped<IMuscleRepository, MuscleRepository>();
builder.Services.AddScoped<IMuscleService, MuscleService>();
builder.Services.AddDbContext<CoachPlanContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
All the endpoints receive the IMediator instance, and this one will send the request using the correct command or query.
app.MapGet("/muscles", async (IMediator _mediator) =>
{
return await _mediator.Send(new GetAllMusclesQuery());
});
app.MapGet("/muscles/{id}", async (IMediator _mediator, int id) =>
{
return await _mediator.Send(new GetMuscleByIdQuery(id));
});
app.MapPost("/muscles", async (IMediator _mediator, Muscle muscle) =>
{
await _mediator.Send(new CreateMuscleCommand(muscle));
return Results.Created($"/muscles/{muscle.Id}", muscle);
});
app.MapPut("/muscles/{id}", async (IMediator _mediator, int id, Muscle muscle) =>
{
await _mediator.Send(new UpdateMuscleCommand(id, muscle));
return Results.NoContent();
});
app.MapDelete("/muscles/{id}", async (IMediator _mediator, int id) =>
{
await _mediator.Send(new DeleteMuscleCommand(id));
return Results.Ok();
});
This was a simple example of how to use CQRS and Mediator patterns. More things can be added, like MediatR Behaviors and Notifications. I will keep updating this project.
You can find the complete code on my GitHub.