Cloud

Following my previous post of Setting up the application core, in this post I’m going introducing CQRS in the architecture of the project.

The source code of this post is on GitHub. Because one single post is too long, I have created the following posts:

Recap and next scenario

Now, our MyTicket application will become a large application. And in the larger application, we’ll have quite a lot of code. Most of the time, we write model code and more code and more code. And we typically use the same model classes for reading and writing data. This is not an issue when your application isn’t too large, but again, we all know what happens with small applications. They become large applications.

Having the same model for reading and writing data can become overwhelming. You’ll get a lot of query methods, each returning probably a different type based on what it’s being used for, be that a list or a detail. That same model is then also used for all the save operations, so the create or update methods, which can end up containing quite a lot of logic.

On top of that, in the same model, we may need to apply different security configurations for different actions. We may end up with large model classes, which are going to do simply too much.

In brief…

  • Same model is used to read and write data
  • Issues in larger applications
    • Different queries
    • Different object being returned
    • Complex logic for saving entities
    • Security may be different

CQRS

For this reason, I want Introducing CQRS in the architecture; we rely often on CQRS, or Command and Query Responsibility Segregation. So, I’m going to introduce a simple form of CQRS in our application. Now, before I do so, let’s make sure you understand what it does exactly. It’s mostly an organization thing in our architecture. I’m going to split up our logic over different model classes.

Commands will be the ones changing data, so inserting an event or updating an event. They will modify data. Next, the query models are the ones to be reading data. So indeed, splitting up thing’s really into smaller, more manageable parts. Look at what I’ve said about commands. I’ll create a command to insert an event, or another one to update an event. They’re typically task‑based and can be also placed on a queue for asynchronous processing.

Introducing CQRS in the architecture
Introducing CQRS in the architecture

Of course, separation of concerns comes to mind here again, since indeed, read and write will all be split up. Because of this, we could also scale them independently and apply different security constraints on them. And yes, again, smaller methods will be easier to make changes to without having the risk of breaking things.

In brief…

  • Advantages
    • Separation of concerns
    • Scaling
    • Security
    • Easy to make a change, no further impact
  • Disadvantages
    • Added complexity
    • Targeted at more complex applications

Now, it’s not all perfect, though. Introducing CQRS does introduce some extra complexity, but I must say, it’s limited. Because of this, I do recommend using it for more complex, larger applications.

Why CQRS?

So, why Introducing CQRS in the architecture and what problem are we going to be solving using CQRS then? Normally, without CQRS, we would probably write service classes like this. I have an event service and that will do everything around events. Perhaps it will communicate with different message handlers, like the ones we have already created.

Usual structure for services in an application - Introducing CQRS in the architecture
Usual structure for services in an application

Probably one service will also invoke another service. To be clear, there is nothing wrong with this approach. This will work just fine. But I think once the project becomes larger, this service, so the model, might become brittle. If I make a change to this service and another service depends on it, I need to be careful. My model is doing too much, and I risk breaking things.

Organization with CQRS - Introducing CQRS in the architecture
Organization with CQRS

That’s where CQRS will come in handy. Here you see the structure I would like to introduce for our application. I’m going to create more smaller classes again that each have a certain responsibility, getting all events, getting the details of an event, adding an event, updating an event, and deleting an event. Small model classes, just like I want them. We’ll basically create separate requests to represent the query or command and create separate handlers for each. There might be some overlap being introduced here, so we might actually be violating the DRY principle a little bit here.

Back to Visual Studio for CQRS

Okay, time to head back to Visual Studio. I’ll now show you how we have introduced CQRS into the application. Now, let’s take a look in the Solution Explorer. What we haven’t been doing is creating large service classes. In fact, we have made a separate class for the handling of getting a list of events, as well as for getting the details of an event.

So, that is actually already quite a good step. We have been doing, without really knowing, a good job in terms of not creating large service classes. But what I am a bit worried about is, take a look at this Events folder, there’s so many classes already in there with just two functionalities, GetEventList and GetEventDetails.

Too many classes in the Events folder - Introducing CQRS in the architecture
Too many classes in the Events folder

Then, it’s going to be very confusing later on when we have extra functionality. So, I’m going to start splitting up things basically by just creating a couple of folders under Events that specify what these classes are used for. First, I’m going to reorganize things, and I’ll show you the results. So here’s what I’ve come up with. I’ve basically implemented CQRS on a folder level.

CQRS folder organization - Introducing CQRS in the architecture
CQRS folder organization

Queries and Commands folders

I’ve created the Queries folder, in which I’m going to put all my Application logic. You have the logic GetEventDetails and GetEventList, and that contains small application logic containing the logic only for a specific functionality, in this case getting information about events.

We’ll also, of course, need to create events, update events, those aren’t queries. So under Events, I’ll also create another folder where I’ll put application logic that will be used to create new events and so on. So that’s basically Commands that I’m going to put in there.

Now, without really specifying it yet, we had already been preparing somewhat to go the CQRS route. I hadn’t been creating large service classes, I had already created small handler classes, each containing a certain functionality, and the query logic, I already had written in separate classes. I will now do the same for all the Command logic, so adding, updating, and deleting events. And just to prepare us for the next demos, I’m going to already create a folder here called CreateEvent, which is going to contain only the logic, well, to create a new event, but we’ll see that very soon. So here you see how I’m using here a simple version of CQRS in my Application logic.

CQRS folder structure - Introducing CQRS in the architecture
CQRS folder structure

Understanding features

Now, next step in Introducing CQRS in the architecture is to understand features. So, we have looked at the code for working with events, but we’ll need to also add code for working with categories, and we’ll need code for handling orders as well, and I assume a lot more will be coming into the application.

If you place all requests and request handlers in one large folder, I think things will become cluttered again very soon, so I would like to introduce a feature‑based organization in our application. A feature is a vertical slice to the functionality, so I’d say a context.

Example of the CQRS folder implementation

It can be seen a bit as a bounded context, which is a concept of domain‑driven design. It’s basically a feature, something that is pretty much standalone. I will then create feature folders inside a Features main folder. So I have a feature Events, other feature Categories, and a future Orders. Know that I’ll probably also let these features contain their own view models they’ll use. View models are the types returned to the caller, and even if they could be shared, I will typically not share them. This way, I can make changes to a given feature, knowing that I won’t be impacting another feature or functionality.

CQRS folders in brief…

  • Vertical slice
  • Features folder and subfolders
  • Own the view models they will use
    • Not shared typically, even if identical

Organizing the Code Using Features

Then, introducing CQRS in the architecture is on its way! Let’s return once again to Visual Studio and take a look at how our application is now split up using features. Now, without really focusing your attention onto it, I had already put in a Features folder, maybe you had noticed, and inside of that I had created already an Events folder.

So, we were already working feature based, but now you see it more clearly. I have added the other features that we’ll have in our application, so that we’ll be working with Categories and working with Orders. So, under Features I have now added a Categories folder and an Orders folder, and we already had the Events folder. For each of these I have also added again the Commands and Queries folder, in which we’ll put our application logic. Let’s do that now already for Categories.

CQRS for Categories

So, we already have some basic Queries for Events in place, but I’m going to do the same thing for Categories. I’m going to paste in a bit of code that you can again find in the assets, and I’ll take you through that code.

Categories CQRS folders and its implementation
Categories CQRS folders and its implementation

So, I’ve now added again two queries for working with Categories, the GetCategoriesList, which is very similar to what we had with the Events. So I will need in the application the functionality to list out all categories. Then, I have again a CategoriesListVm, a specific ViewModel for displaying information about the category in a list.

public class CategoryListVm
{
    public Guid CategoryId { get; set; }
    public string Name { get; set; }
}

Then, I have a GetCategoriesListQuery, which contains no parameters, it will just return all categories.

public class GetCategoriesListQuery 
    : IRequest<List<CategoryListVm>>
{
}

And in the Handler for that, so the GetCategoriesListQueryHandler, I’m going to handle the GetCategoriesListQuery message, and in the handle method I’m going to get all the Categories, order them by name, and map them to a list of CategoriesList here:

public class GetCategoriesListQueryHandler 
    : IRequestHandler<GetCategoriesListQuery, List<CategoryListVm>>
{
    private readonly IAsyncRepository<Category> _categoryRepository;
    private readonly IMapper _mapper;

    public GetCategoriesListQueryHandler(IMapper mapper, 
        IAsyncRepository<Category> categoryRepository)
    {
        _mapper = mapper;
        _categoryRepository = categoryRepository;
    }

    public async Task<List<CategoryListVm>> Handle(GetCategoriesListQuery request, 
        CancellationToken cancellationToken)
    {
        var allCategories = (await _categoryRepository.ListAllAsync())
            .OrderBy(x => x.Name);
        return _mapper.Map<List<CategoryListVm>>(allCategories);
    }
}

So, that is very similar to what we already had. I have also implemented another query here, which is going to not just get the list of Categories, but for each category it’s also going to fetch the list of Events. So now I have a CategoryEventListVm, which contains the information about the category, but also all the events for that specific category, which I have now wrapped in a CategoryEventDto.

public class CategoryEventDto
{
    public Guid EventId { get; set; }
    public string Name { get; set; }
    public int Price { get; set; }
    public string Artist { get; set; }
    public DateTime Date { get; set; }
    public Guid CategoryId { get; set; }
}

This will contain basic information about an event. In the query, so in the IRequest, I also have the option to include the history, so basically specifying if I want to get all events or just the ones in the future. And this is also going to be returning a list of CategoryEventListVms.

public class CategoryEventListVm
{
    public Guid CategoryId { get; set; }
    public string Name { get; set; }
    public ICollection<CategoryEventDto> Events { get; set; }
}
public class GetCategoriesListWithEventsQuery
        : IRequest<List<CategoryEventListVm>>
{
    public bool IncludeHistory { get; set; }
}

That’s this one here. Lets take a look at a handler for this. Now, what you see is different here is I’m not using the IAsyncRepository, but for the first time, I’m going to be using a specific repository, the ICategoryRepository, which we already created, but we haven’t added any extra method declarations in there yet.

public class GetCategoriesListWithEventsQueryHandler 
    : IRequestHandler<GetCategoriesListWithEventsQuery, List<CategoryEventListVm>>
{
    private readonly IMapper _mapper;
    private readonly ICategoryRepository _categoryRepository;

    public GetCategoriesListWithEventsQueryHandler(IMapper mapper, 
        ICategoryRepository categoryRepository)
    {
        _mapper = mapper;
        _categoryRepository = categoryRepository;
    }

    public async Task<List<CategoryEventListVm>> Handle(
        GetCategoriesListWithEventsQuery request, 
        CancellationToken cancellationToken)
    {
        var list = await _categoryRepository.GetCategoriesWithEvents(request.IncludeHistory);
        return _mapper.Map<List<CategoryEventListVm>>(list);
    }
}

And I’m actually getting an error here because we don’t have the method yet GetCategoriesWithEvents on that specific repository.

This is the reason why I need a specific repository. I will need to write extra methods, not just the ones defined on the AsyncRepository, the base repository, but specific methods that will, in this case, get the categories with their related events, passing in also that Boolean whether or not I want to include the history.

So, this is extra business logic I also need to contain in my handler. And then I’m going to use the mapper again to return that again as a list of, in this case, CategoryEventListVms. Now I do need to add that on my CategoryRepository, but this is a quick thing to add, so let’s go back to the Contracts, Persistence, CategoryRepository.

public interface ICategoryRepository : IAsyncRepository<Category>
{
    Task<List<Category>> GetCategoriesWithEvents(bool includePassedEvents);
}

I’ll now bring in this new method here, GetCategoriesWithEvents, passing in that Boolean if I want to get the historical events or not. And now this will build fine. One thing to do, one thing not to forget, let’s say, is making sure that these new types that I have now brought in also will be mappable via AutoMap. So let’s go back to the Profiles, and bring in two more MappingProfiles between Category and CategoryListVm and Category and CategoryEventListVm.

public MappingProfile()
{
    CreateMap<Event, EventListVm>().ReverseMap();
    CreateMap<Event, EventDetailVm>().ReverseMap();
    CreateMap<Category, CategoryDto>();
    CreateMap<Category, CategoryListVm>();
    CreateMap<Category, CategoryEventListVm>();
}

Now, AutoMapper knows about these, and if the properties map in terms of name, it will automatically map between them. So, there we go, you now see that I’ve wrapped the functionality for Categories, for working with Categories that is, inside of the Categories Feature folder. We have the Events Feature folder, which already contained our queries, and we’ll later on add functionality for the Orders as well.

So, I’ve now split up things nicely in these vertical slices in these Feature folders that will contain everything that has to do with Categories, with Events, with Orders, and so on. This is a nice way of structuring the business code that we’re writing.

Using Commands to Create a New Entity

So far, we have looked at the code for getting data, so a query, but what does the code look like for creating an entity, say, an event? Well, it is pretty similar. Take a look at this schema first.

Creating a new entity
Creating a new entity

The creation of a new event will come in as a request again. This time this will be the command, containing all the data for the event to be created. A handler will pick up on this, creating the event in our persistent store using the repository. A value will then be returned. There are different options for what we can return. It could be just a primitive value, like a Boolean indicating if the equation was successful, or a full blown response containing more info about the creation. And note that we’ll also need to bring in validation, but we’ll look at that in a minute.

Create event implementation

First, I’m going to show you the code for creating, updating, and deleting an event. Now so far we’ve only created queries, and most applications, including ours, also need to be able to enter new data, so that’s what I’ll do next. I’ll now include the code to create a new event, and creating a new event, well, that’s really an action, that’s a command, and so we’ll create the code for that in this Commands/CreateEvent folder.

Then, the first thing I’ll need is, again, the message. What am I going to be doing? I’ll need to let MediatR know what it needs to react to. And it’s going to be the Command, and I’m going to wrap that again in a class that implements the IRequest interface. So, I’m going to create a class, and it’s going to be the CreateEventCommand. Notice, that I’ve now suffixed this with Command to be clear that this is going to be an action, a command. And let me put in the code for this. So the CreateEventCommand will be the message about the new event that needs to be created, and it’s going to be a set implementing the IRequest.

public class CreateEventCommand : IRequest<Guid>
{
    public string Name { get; set; }
    public int Price { get; set; }
    public string Artist { get; set; }
    public DateTime Date { get; set; }
    public string Description { get; set; }
    public string ImageUrl { get; set; }
    public Guid CategoryId { get; set; }
    public override string ToString()
    {
        return $"Event name: {Name}; Price: {Price}; By: {Artist}; " +
                "On: {Date.ToShortDateString()}; Description: {Description}";
    }
}

So, the return type is going to be a Guid. I’ll talk about return types later, but this basically means that when I create a new event, I’m going to be returning to the consumer the ID, the GUID, of the newly created event. So, this is the message about the to‑be‑created event, so it will contain all the information about the event that I want to create, so the Name, the Price, the Artist, and so on.

Implement CreateEventCommandHandler

And just like we did with the Queries, where we want to get Events, I’m also going to create a Handler to handle this CreateEventCommand coming in. It’s pretty much the same thing. So let us create another class here. It’s going to be the CreateEventCommandHandler. Now, I’m going to be handling the CreateEventCommand. So I need to implement here the IRequestHandler in CreateEventCommand, and it was going to be returning a Guid. Let’s add MediatR here and implement also the Handle method. Let me show you the completed code for this.

public class CreateEventCommandHandler 
    : IRequestHandler<CreateEventCommand, Guid>
{
    private readonly IEventRepository _eventRepository;
    private readonly IMapper _mapper;
    private readonly IEmailService _emailService;
    private readonly ILogger<CreateEventCommandHandler> _logger;


    public CreateEventCommandHandler(IMapper mapper, 
        IEventRepository eventRepository, 
        IEmailService emailService, 
        ILogger<CreateEventCommandHandler> logger)
    {
        _mapper = mapper;
        _eventRepository = eventRepository;
        _emailService = emailService;
        _logger = logger;
    }

    public async Task<Guid> Handle(CreateEventCommand request, 
        CancellationToken cancellationToken)
    {
        var validator = new CreateEventCommandValidator(_eventRepository);
        var validationResult = await validator.ValidateAsync(request);
            
        if (validationResult.Errors.Count > 0)
            throw new Exceptions.ValidationException(validationResult);

        var @event = _mapper.Map<Event>(request);

        @event = await _eventRepository.AddAsync(@event);

        //Sending email notification to admin address
        var email = new Email() { 
            To = "info@puresourcecode.com", 
            Body = $"A new event was created: {request}", 
            Subject = "A new event was created" 
        };

        try
        {
            await _emailService.SendEmail(email);
        }
        catch (Exception ex)
        {
            //this shouldn't stop the API from doing else so this can be logged
            _logger.LogError($"Mailing about event {@event.EventId} failed " + 
                $"due to an error with the mail service: {ex.Message}");
        }

        return @event.EventId;
    }
}

So, here you see now the finished code, where I’m again using a repository and AutoMapper. And the rest of the code is actually pretty simple. What I’m getting in is a CreateEventCommand, and I need to map that to an entity. So basically we are going to do, well, the opposite in this case. I need to map now from a CreateEventCommand into an Event. That’s going to be the event that I want to store in the database. So, I’m going to use the AddAsync method here to store that event later on in the data store. And I’m supposed to return the ID, the GUID, of the newly created event, and that is what I’m doing here. Now since I’m mapping here from CreateEventCommand to Event, I need to go back to my Profiles and include another MappingProfile that will do exactly that.

public MappingProfile()
{
    CreateMap<Event, EventListVm>().ReverseMap();
    CreateMap<Event, CreateEventCommand>().ReverseMap();
    CreateMap<Event, EventDetailVm>().ReverseMap();

    CreateMap<Category, CategoryDto>();
    CreateMap<Category, CategoryListVm>();
    CreateMap<Category, CategoryEventListVm>();
}

So, with this in place, I think we can now handle creating a new event. The actual implementation, that will come in later. I’m going to bring in the code for the updating and deleting of an event as well. Here you see the code for updating an event.

UpdateEvent folder
UpdateEvent folder

Update implementation

The UpdateEventCommand is another command that implements IRequest. And I actually don’t return anything, because if you think about it, an update call over an API, if everything works okay, will not return anything, so I don’t need to actually return something here.

public class UpdateEventCommand: IRequest
{
    public Guid EventId { get; set; }
    public string Name { get; set; }
    public int Price { get; set; }
    public string Artist { get; set; }
    public DateTime Date { get; set; }
    public string Description { get; set; }
    public string ImageUrl { get; set; }
    public Guid CategoryId { get; set; }
}

The UpdateEventCommandHandler is again pretty similar. It will first fetch an event by ID, and then we’ll update it later on.

public class UpdateEventCommandHandler 
    : IRequestHandler<UpdateEventCommand>
{
    private readonly IAsyncRepository<Event> _eventRepository;
    private readonly IMapper _mapper;

    public UpdateEventCommandHandler(IMapper mapper, 
        IAsyncRepository<Event> eventRepository)
    {
        _mapper = mapper;
        _eventRepository = eventRepository;
    }

    public async Task<Unit> Handle(UpdateEventCommand request, 
        CancellationToken cancellationToken)
    {

        var eventToUpdate = await _eventRepository.GetByIdAsync(request.EventId);

        if (eventToUpdate == null)
        {
            throw new NotFoundException(nameof(Event), request.EventId);
        }

        var validator = new UpdateEventCommandValidator();
        var validationResult = await validator.ValidateAsync(request);

        if (validationResult.Errors.Count > 0)
            throw new ValidationException(validationResult);

        _mapper.Map(request, eventToUpdate, typeof(UpdateEventCommand), typeof(Event));

        await _eventRepository.UpdateAsync(eventToUpdate);

        return Unit.Value;
    }
}

Delete implementation

Now, the DeleteEventCommand. Well, that’s pretty simple as well. That is going to contain in the EventCommand the ID of the event to be deleted.

public class DeleteEventCommand: IRequest
{
    public Guid EventId { get; set; }
}

And in the DeleteEventCommandHandler, we’re going to fetch the event that we want to delete, call DeleteAsync on the eventRepository, and I’m basically returned the default value again. And in the Profiles I’ve also added now one more mapping for everything to work fine.

public class DeleteEventCommandHandler 
    : IRequestHandler<DeleteEventCommand>
{
    private readonly IAsyncRepository<Event> _eventRepository;
    private readonly IMapper _mapper;
        
    public DeleteEventCommandHandler(IMapper mapper, 
        IAsyncRepository<Event> eventRepository)
    {
        _mapper = mapper;
        _eventRepository = eventRepository;
    }

    public async Task<Unit> Handle(DeleteEventCommand request, 
        CancellationToken cancellationToken)
    {
        var eventToDelete = await _eventRepository.GetByIdAsync(request.EventId);

        if (eventToDelete == null)
        {
            throw new NotFoundException(nameof(Event), request.EventId);
        }

        await _eventRepository.DeleteAsync(eventToDelete);

        return Unit.Value;
    }
}

By Enrico

My greatest passion is technology. I am interested in multiple fields and I have a lot of experience in software design and development. I started professional development when I was 6 years. Today I am a strong full-stack .NET developer (C#, Xamarin, Azure)

8 thoughts on “Introducing CQRS in the architecture”

This site uses Akismet to reduce spam. Learn how your comment data is processed.