Mediator Pattern - CQRS with MediatR

The famous mediator pattern. Used so often especially in combination with CQRS.

In this article I will show how the mediator pattern itself works, what CQRS is and its advantages. For that we will write a small application, which demonstrates how to use the pattern and what we gain from using it.

Mediator pattern

To explain the pattern lets first have a look what it tries to solve. You start your application or web service normally fairly small. You have your domain object nicely encapsulated to the place they belong to. Over time you get more and more (domain) objects. As your domain becomes more complex so does the communication between them. That has an inherit problems. For starters it is hard for someone to read and understand your code as the stuff is all over the place. You also have an increased risk of coupling your objects more together. If they directly speak to each other, they have to know each other. So adding or refactoring parts of that logic can be difficult.

What would happen if we have a "man in the middle"? A person who moderates or facilitates communication? Let's call him mediator. So instead of component A directly speaking to component B, we always go to the mediator. The mediator knows exactly where to pass on your request. And that does solve both things from above. The only thing a component know has to know is the mediator and the mediator alone. ? Less coupling! We can change components independently as they are now less coupled!

A super simple form would look like this:

Mediator simple UML

We can see that every component is only talking to the mediator and not to another component directly. Also it is important to note, that this is a bidirectional communication. So the mediator can also answer my requests! Those properties are super good. Why? Well we can model our domain that way and gain a lot of flexibility!

CQRS

CQRS stands for Command query responsibility segregation. That is separating Command (write) and Query (read) models so that the application can scale read and write operations independently. The main way CQRS works is via the command pattern. Now I showed you 4 seconds ago how the mediator pattern works and now I come across with a totally different pattern. Well yes but give me a minute and you will see how everything comes together (spoiler alert: CQRS is using both patterns).

Command pattern

One day you start to build a nice UI-Button because you need one for your application. It is really fancy and has nice effects. As this is a general purpose button you would like to use this as your primary button. But now you are faced with a problem: You want to keep the style but also want to do different things at different places when the user clicks your absolute gorgeous button. Of course you could just subclass that button and give it new behaviour in that way. But then on second thought, you said to yourself: "I am a responsible programmer who writes maintainable code." ad wushhh that idea is gone. So we need something better. You want to separate those concerns Small refresher on Solid Principles needed? so that you have one button but multiple ways to do something with that button.

So we introduce a command(-interface) on that button. So the button can invoke that command but the logic itself is passed on from the outside world.

Mediator simple UML

To break it down simple: If you go to a restaurant you are using the command pattern. You tell the waiter what the chef should cook for you. Your order is the command. It holds all the necessary details to perform the operation. Well and guess what the waiter is? Yes, he is the mediator! So if you understand how ordering and eating food in a restaurant works you understand to some degree CQRS.

Back to CQRS

Now that we understand the mediator and the command pattern we can finally bring everything together. In principle we only have 3 actors: Our component A which wants something. That component A creates a command and passes that on to the mediator. The mediator knows who is responsible for that command and passes that one to that component. If that component is done it returns the value to the mediator and the mediator then can return it to component A. In its core Query is a command and follows that command pattern. CQRS decouples those two for scalability and maintenance reasons! Giving them two different names makes it easier to talk to your colleagues. Of course there are differences beyond that. For starters in CQRS you would have dedicated instances of databases for read and write to enable scalability. Now in the following example I will not have separate read and write databases. I will show you fairly simple how to use the pattern with the MediatR library. As always you will find the source code to the given samples at the end of that blog post.

I created a new console application and imported the MediatR package. But we need a bit more to play out the whole magic of MediatR. I additionally introduce the following two packages:

  • Microsoft.Extensions.DependencyInjection to create a DI container
  • MediatR.Extensions.Microsoft.DependencyInjection to let MediatR plug everything together

To make it easier to follow what is going on and where we are now I use color codes like that:

public static class ColorConsole
{
    public static void WriteLineCommandHandler(string print) => WriteLineColored(print, ConsoleColor.Blue);
    public static void WriteLineQueryHandler(string print) => WriteLineColored(print, ConsoleColor.Yellow);
    public static void WriteLineProgram(string print) => WriteLineColored(print, ConsoleColor.Green);
    public static void WriteLineRepository(string print) => WriteLineColored(print, ConsoleColor.DarkMagenta);
    
    private  static void WriteLineColored(string toPrint, ConsoleColor color)
    {
        Console.ForegroundColor = color;
        Console.WriteLine(toPrint);
        Console.ResetColor();
    }
}

What exactly those handlers are, I will tell you in a second. My console application will just create a user, allows to query for users and allows to delete everything. So What we need is a domain object and our repository to save and query stuff:

public class User
{
    public int Id { get; set; }

    public string Name { get; set; }
}

Fairly trivial. Now the repository:

public class UserRepository
{
    private readonly List<User> _users = new();

    public User AddUser(User user)
    {
        ColorConsole.WriteLineRepository($"Adding user with name: '{user.Name}'");
        var currentMaxId = !_users.Any() ? 0 : _users.Max(u => u.Id);
        user.Id = currentMaxId + 1;
        _users.Add(user);

        return user;
    }

    public int GetUserCount(string? name)
    {
        ColorConsole.WriteLineRepository($"Filtering users where name is '{name}'");
        return name == null ? _users.Count : _users.Count(u => u.Name.Contains(name));
    }

    public void DeleteAll()
    {
        ColorConsole.WriteLineRepository($"Deleting all users");
        _users.Clear();
    }
}

You see I use my color codes in the repository. That could be any given service. I just want to show you when who gets called from whom.

Now let's plug in all the stuffs:

Console.WriteLine("Legend:");

ColorConsole.WriteLineCommandHandler("Command - Handlers are blue");
ColorConsole.WriteLineQueryHandler("Query - Handlers are yellow");
ColorConsole.WriteLineProgram("Program code is green");
ColorConsole.WriteLineRepository("Repository code is magenta");
Console.WriteLine("------------------------");
Console.WriteLine();

var serviceProvider = CreateServiceProvider();
await serviceProvider.GetRequiredService<Execution>().RunAsync();

ServiceProvider CreateServiceProvider()
{
    var collection = new ServiceCollection();
    // This will execute our main logic
    collection.AddScoped<Execution>();
    
    // This comes from the MediatR package.
    // It looks for all commands, queries and handlers and registers
    // them in the container
    collection.AddMediatR(typeof(Program).Assembly);
    
    // Our repository
    collection.AddScoped<UserRepository>();
    return collection.BuildServiceProvider();
}

The magic happens in AddMediatR. This looks through your assembly for very specific types and automatically registers them so that you as a user don't have to do anything. In theory this is possible without dependency injection but it is way harder to achieve. DI makes it such an easy task. There is also our Execution class. Basically that is our Main class where all the logic lives.

public class Execution
{
    private readonly IMediator _mediator;

    public Execution(IMediator mediator)
    {
        _mediator = mediator;
    }
    
    public async Task RunAsync()
    {
        // We will do the logic in a second

The first time we see some mediator. So our class only knows the IMediator interface and not a single service itself. Let's create a new user via this pattern. To do so we have to create the command first:

public class AddNewUserCommand : IRequest<User>
{
    public string Name { get; set; }
}

IRequest comes from the MediatR package. The generic argument tells what the return value of that command will be. To unpack that a bit lets go back to the restaurant. The command or IRequest would be your order you give the waiter aka mediator. The order would be for example one soup with less salt. Once the dish is ready you receive the meal aka. So we see the command is pretty self-contained. It has all the request-objects but we also know what to expect. So we have the order (aka command) and the waiter (aka mediator) but we also need a third actor: the chef (aka the handler).

Handlers take the request (the order) from the mediator (the waiter) and return the expected type (the meal). In the case of creating a user it would look like this:

public class AddNewUserCommandHandler : IRequestHandler<AddNewUserCommand, User>
{
    private readonly UserRepository _userRepository;

    public AddNewUserCommandHandler(UserRepository userRepository)
    {
        _userRepository = userRepository;
    }
    
    public Task<User> Handle(AddNewUserCommand request, CancellationToken cancellationToken)
    {
        ColorConsole.WriteLineCommandHandler($"Passing user '{request.Name}' to repository");
        var user = new User { Name = request.Name };
        var userFromRepo = _userRepository.AddUser(user);
        ColorConsole.WriteLineCommandHandler($"Saved user to repository and return result");
        return Task.FromResult(userFromRepo);
    }
}

So the handler knows all the services, which it needs to fulfil the task. The one initially starting the task does not have to know anything. Super nice separated!

The output until now would be:

Creating a new user with name 'Steven'
Passing user 'Steven' to repository
Adding user with name: 'Steven'
Saved user to repository and return result
User has id: 1

Now I added a few more cases:

ColorConsole.WriteLineProgram("Creating a new user with name 'Steven'");
var user = await _mediator.Send(new AddNewUserCommand { Name = "Steven" });
ColorConsole.WriteLineProgram($"User has id: {user.Id}");

Console.WriteLine();

ColorConsole.WriteLineProgram($"Get count of users with name 'Rebecca'");
var count = await _mediator.Send(new GetUserCountQuery { NameFilter = "Rebecca" });
ColorConsole.WriteLineProgram($"Found {count} entries");

Console.WriteLine();

ColorConsole.WriteLineProgram($"Deleting all users");
await _mediator.Send(new DeleteAllUsersCommand());
ColorConsole.WriteLineProgram($"All users deleted");

But they will follow exactly the same rules. For fun I created a query, which has its corresponding QueryHandler. The result will look like this:

Result

If you are interested just check out the source code in the last part of this blog post.

When not to use CQRS or mediator

CQRS is fairly complex and does not makes sense if you are alone and working on a fairly small project. Imagine you have simple CRUD logic with zero to super small amount of business logic. Here it makes no sense as you increased the complexity by a whole lot without gaining much. So consider it from case to case (or bounded context to bounded context if you like DDD terms).

My tip: Start without CQRS. It is something you can built in later if the need arises. I saw applications which suffered from over-engineered CQRS systems even though they were fairly simple.

Conclusion

I hope I could give you a good start into the mediator pattern, as well as CQRS and how one could easily use it.

Resources

  • The source code to this blog post: here
  • Repository with examples of my blog: here
7
An error has occurred. This application may no longer respond until reloaded. Reload x