What is CQRS!?
CQRS for the acronyms of
Command Query Responsibility Segregation
is a design pattern that segregates read and write operations for a data store into separate data models.
That is what Microsoft can tell us on their documentation. As it says you'll take all the queries (GET requests) in one place and all the commands (POST, PUT, DELETE requests) in another place BUT not randomly (There's a lot of places where you can find more deep information but I took this as a simple base for what we'll made).
Why separate queries from commands? It's a question I've asked myself. Well, this is because the more complex your application is, the more complex it will be to maintain CRUD. When you have multiple layers of information in your application or simply add new features, you may need more resources or this may become a challenge.
CQRS helps us with latency, throughput, or consistency issues. The use of CQRS is closely linked to Event Sourcing
and DDD (Domain Driven Design)
but this is something we will not discuss in this article.
If we were to look for advantages of using CQRS in our application, we could summarize it as scalability independently.
How does Mediator pattern fits?
I like using real-life examples so let's look at one... People know how to buy food, is kind of simple: just go to the place you want and buy it. It's really simple, right? Yeah! but, what if you don't want to go? There's when you need a mediator to go for you and this mediator can go and buy for other people too without you talking to them, and then buy the food and gets your and other people food. In fact, there comes the idea of an app where you can buy food at home (cof cof uber eats cof cof)
Mediator pattern is exactly the same! One of my favorites places to learn about patterns is Refactoring Guru so, you can go and learn a lot of this pattern with detail there.
The Mediator pattern suggests that you should cease all direct communication between the components which you want to make independent of each other.
I like the mediator pattern because it follows the Single Responsibility Principle extracting communication of multiple components and putting them into one single place and Open/Close principle introducing new components without touching actual components.
See in previous image how both services uses the same mediator without knowing each other but both of them know the mediator to make calls and get information. Can you see now how CQRS and mediator are related? Imagine that handler 1
is a query and handler 2
is a command, there you have a relation between them.
Something you must have in mind...
- Mediator pattern must NOT be used in tiny applications like microservices because those applications are expected not to grow and be complicated to maintain.
- You should use it when your applications are growing too large and it becomes difficult to know where a component should be called or used and where it should not be used.
Implementation of Database (Using .NET 8)
I'll create an app where you can login as user and subscribe to be able of watch movies and series (I'm not going to create the video player, but the idea is to create the interface and access information about movies and series).
Create an ASP.NET Core Web API
configured for HTTPS, OpenAPI Support Enabled and using Controllers. The idea here is to create a real application that can scale and properly use these elements. I called my app as CinemaWatcher
and called my web api project as CinemaWatcher.API
.
Delete all the default classes of weather, they won't be needed.
After creating your web application add a Class Library and call it as you want. I called as CinemaWatcher.Infrastructure
this following the clean architecture structure.
Add a reference on our WebAPI project to our library.
Then create a DataAccess folder and create a folder for our entities with the name of EntitiesModels
.
So we would have something like this:
CinemaWatcher
├ CinemaWatcher.API
├── Controllers []
├── appsettings.json
├── Program.cs
├ CinemaWatcher.Infrastructure
├── DataAccess[]
├── EntitiesModels []
NOTE:
Install by the NuGet packager the next packages:
- Microsoft.EntityFrameworkCore (V 9.0.3)[Both projects]
- Microsoft.EntityFrameworkCore.SqlServer (V 9.0.3)[Only on Infrastructure]
- Microsoft.EntityFrameworkCore.Tools (V 9.0.3)[Both projects]
- MediatR (V 12.4.1)[Both projects]
Let's continue and create three classes inside the Entities folder, one for Users
, Movies
and Series
which will be our main entities for now.
public class User
{
[Required]
public int UserId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public string Password { get; set; }
public string UserName { get; set; }
public int Age { get; set; }
}
public class Series
{
[Required]
public int SerieId { get; set; }
public string Title { get; set; }
public string Category { get; set; }
public string Duration { get; set; }
public DateTime DateOfPublish { get; set; }
}
public class Movies
{
public int MovieId { get; set; }
public string Title { get; set; }
public string Category { get; set; }
public string Duration { get; set; }
public DateTime DateOfPublish { get; set; }
}
As you see there is nothing fancy now, just basic stuff. Add a class on our DataAccess folder called AppDbContext
. We created our entities, so to add them just add next code:
public class MyAppDbContext : DbContext
{
public MyAppDbContext(DbContextOptions<MyAppDbContext> options) : base(options)
{ }
public DbSet<Movies> Movies => Set<Movies>();
public DbSet<Series> Series => Set<Series>();
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Movies>(builder =>
{
builder.HasKey(p => p.MovieId);
builder.Property(p => p.Title).IsRequired().HasMaxLength(200);
builder.Property(p => p.Category).IsRequired().HasMaxLength(200);
builder.Property(p => p.Duration);
builder.Property(p => p.DateOfPublish);
});
modelBuilder.Entity<Series>(builder =>
{
builder.HasKey(p => p.SerieId);
builder.Property(p => p.Title).IsRequired().HasMaxLength(200);
builder.Property(p => p.Category).IsRequired().HasMaxLength(200);
builder.Property(p => p.Duration);
builder.Property(p => p.DateOfPublish);
});
modelBuilder.Entity<User>(builder =>
{
builder.HasKey(p => p.UserId);
builder.Property(p => p.FirstName).IsRequired().HasMaxLength(100);
builder.Property(p => p.LastName).IsRequired().HasMaxLength(100);
builder.Property(p => p.Email).IsRequired().HasMaxLength(200);
builder.Property(p => p.Password).IsRequired().HasMaxLength(10);
builder.Property(p => p.UserName).IsRequired().HasMaxLength(100);
builder.Property(p => p.Age);
});
}
}
In this app we'll use migrations, that's why you can see that we setup the entities using a builder. To create our DB let's go to our Program.cs class in our WebApi project and add next lines:
builder.Services.AddDbContext<MyAppDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection"))
);
Then on out appsettings.Develpoment.json
let's add that DefaultConnection:
"ConnectionStrings": {
"DefaultConnection": "YourConnectionStringFromSQLServer"
}
Then open you package Manager Console and run the next command:
dotnet ef migrations add FirstMigrationDBCreation --startup-project WebApiRoute --project ClassLibraryRoute
After that you may see that you have a new folder called "Migrations", to finalize the DB creation, run next command to apply the migration:
dotnet ef database update --startup-project WebApiRoute --project ClassLibraryRoute
Implementation of Mediator and CQRS (Using MediatR)
NOTE:
In previous versions of MediatR, the class library used to setup MediatR.Extensions.Microsoft.DependencyInjection
but in MediatR version 12 they improve the extension and is not necessary to install DependencyInjection.
With the Database created we can go to implement the MediatR, to do that
add the next line of code on our Program.cs:
After setting up the MediatR add a new empty controller on the Controllers folder called Movies and create a Movies folder. On the Movies folder let's ad two more folders one of them will be Queries
and the other one will be Commands
.
For now we'll just add a command handler to add movies, and a query handler to get all movies.
CinemaWatcher
├ CinemaWatcher.API
├── Controllers []
├──── MoviesController.cs
├── Application[]
├──── Movies[]
├────── Queries []
├──────── GetMoviesQuery.cs
├────── Commands[]
├──────── CreateMovieCommand.cs
├── appsettings.json
├── Program.cs
Our GetMoviesQuery would look like this:
public record GetMoviesQuery() : IRequest<List<Entities.EntitiesModels.Movies>>;
public class GetMoviesQueryHandler : IRequestHandler<GetMoviesQuery, List<Entities.EntitiesModels.Movies>>
{
private readonly MyAppDbContext _context;
public GetMoviesQueryHandler(MyAppDbContext context)
{
_context = context;
}
public async Task<List<Entities.EntitiesModels.Movies>> Handle(GetMoviesQuery request, CancellationToken cancellationToken)
{
return await _context.Movies.ToListAsync();
}
}
Our command would look like this:
public record CreateMovieCommand(
string Title,
string Category,
string Duration,
DateTime DateOfPublish) :
IRequest<Entities.EntitiesModels.Movies>;
public class CreateMovieCommandHandler : IRequestHandler<CreateMovieCommand, Entities.EntitiesModels.Movies>
{
private readonly MyAppDbContext _context;
private readonly IMediator _mediator;
public CreateMovieCommandHandler(MyAppDbContext context, IMediator mediator)
{
_context = context;
_mediator = mediator;
}
public async Task<Entities.EntitiesModels.Movies> Handle(CreateMovieCommand request, CancellationToken cancellationToken)
{
Entities.EntitiesModels.Movies newMovie = new()
{
Title = request.Title,
Category = request.Category,
Duration = request.Duration,
DateOfPublish = request.DateOfPublish
};
_context.Movies.Add(newMovie);
await _context.SaveChangesAsync();
return newMovie;
}
}
Notice that I decided to use a record for the information we are passing to the handler and what is going to be added to the data.
Finally our controller have the call to those requests and those processes. How do we call them? is easy, in the constructor we need to call MediatR so, if we have a nice structure in our project with the respective names of process, MediatR will find them and use them:
[Route("api/[controller]")]
[ApiController]
public class MoviesController : ControllerBase
{
private readonly IMediator _mediator;
public MoviesController(MyAppDbContext context, IMediator mediator)
{
_mediator = mediator;
}
[HttpGet]
[Route("api/movies")]
public async Task<List<Movies>> GetMovies()
{
return await _mediator.Send(new GetMoviesQuery());
}
[HttpPost]
[Route("api/movies")]
public async Task<Entities.EntitiesModels.Movies> CreateMovie([FromBody] Entities.EntitiesModels.Movies movie)
{
var newMovie = new CreateMovieCommand(movie.Title, movie.Category, movie.Duration, movie.DateOfPublish);
return await _mediator.Send(newMovie);
}
}
And that's it! Try running the application and see how swagger ui can handle trying to call the queries and commands.
Conclusion! What did I learn from this lesson?
- I learned how to create a project from scratch and setup the dependencies to add a migration and create the database.
- I configured the MediatR extension and we setup it.
- I configured CQRS.
- I learned to encapsulate each functionality of the independent components.
- I created queries and commands to handle our requests and with this we can take a path to solve issues for events that can happen in our application.
NOTE:
I'll add the project to GitHub and as soon as I publish it I'll add the link to this article.
In next lessons we'll review some of the topics that could be on the air like DDD, Events, Authentication, etc.
Hope this can help you and please let me know if I can improve something in my articles <3
Top comments (0)