DEV Community

Josh Quezada
Josh Quezada

Posted on

4 1 1 1 1

Developing an app using CQRS and Mediator pattern.

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).

SeparationRepresentation

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.

MediatorRepresentation

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; }
}
Enter fullscreen mode Exit fullscreen mode
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; }
}
Enter fullscreen mode Exit fullscreen mode
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; }
}
Enter fullscreen mode Exit fullscreen mode

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);
            });
        }
    }
Enter fullscreen mode Exit fullscreen mode

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"))
);
Enter fullscreen mode Exit fullscreen mode

Then on out appsettings.Develpoment.json let's add that DefaultConnection:

  "ConnectionStrings": {
    "DefaultConnection": "YourConnectionStringFromSQLServer"
  }
Enter fullscreen mode Exit fullscreen mode

Then open you package Manager Console and run the next command:

dotnet ef migrations add FirstMigrationDBCreation --startup-project WebApiRoute --project ClassLibraryRoute
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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();
        }
    }
Enter fullscreen mode Exit fullscreen mode

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;
        }
    }
Enter fullscreen mode Exit fullscreen mode

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);
        }
    }
Enter fullscreen mode Exit fullscreen mode

And that's it! Try running the application and see how swagger ui can handle trying to call the queries and commands.

SwaggerExample

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

Hostinger image

Get n8n VPS hosting 3x cheaper than a cloud solution

Get fast, easy, secure n8n VPS hosting from $4.99/mo at Hostinger. Automate any workflow using a pre-installed n8n application and no-code customization.

Start now

Top comments (0)

Eliminate Context Switching and Maximize Productivity

Pieces.app

Pieces Copilot is your personalized workflow assistant, working alongside your favorite apps. Ask questions about entire repositories, generate contextualized code, save and reuse useful snippets, and streamline your development process.

Learn more

👋 Kindness is contagious

If this post resonated with you, feel free to hit ❤️ or leave a quick comment to share your thoughts!

Okay