Implementing CQRS With MediatR In ASP.NET Core Application

The Command and Query Responsibility Segregation(CQRS) is an architectural pattern that separates the read and writes operations of a data source. Here Query refers to querying data from a source and Command refers to database command that can be either Insert/Update or Delete operations.

CQRS

So the main idea with CQRS is to allow an application to work with different models. In traditional architectures, the same data model is used to query and update data sources. But in practical applications, there is a difference between reading and writing forms of data, like extra validation or properties, which are needed for an update or insert operation whereas the read model doesn't need any of these. But with CQRS you can have different data models for update/insert and query which provides flexibility for complex scenarios. Even in more complex applications like microservices or any kind of application that has high demand of data consumption(like StackOverflow) in that case the traditional architecture doesn't fit well because you will stick with the same data source and having much writing and reading in the same data source can affect the performance of the system. In that scenario, we can think about CQRS because it gives us the flexibility to use a separate data source that ensures the scalability of our application.

Advantages of CQRS

  1. Independent scaling
  2. Optimized data transfer objects
  3. Provides separation of concern
  4. High scalability

Here for the Implementation of the CQRS, we will use the MediatR Library that helps the implementation of Mediator Pattern in .NET. The mediator is a behavioral pattern that let us reduce dependencies between objects by restricting direct communications between the objects and forces them to collaborate only via a mediator object.

Now let's go for our coding part

Setting up the Project

Open up Visual Studio and create a new ASP.NET Core Web application with Web API Template and after creating the Web API in the same solution create a new C# Library project. 

Installing required library in the project

In the library, the project installs the MediatR library from the NuGet Package Manager.

And in the web API project install MediatR.Extension.Microsoft.DependencyInjection library as we will add the dependency of the MediatR in the startup class of the web API.

Now let's create the folder structure of the Library project.

Inside the Models folder, we will add the "Student.cs" file with the following Student Model.

public class Student
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public double Age { get; set; }
}

Now inside the DataAccess, we will have all our code for accessing data from our data sources. But to keep the example simple we will not do any database connection rather we will create a List with List<Student> for mimicking the data access-related operation. For this, we will create an interface IDataAccess and a concrete class DataAccess that implements IDataAccess interface inside the DataAccess Folder.

IDataAccess.cs

public interface IDataAccess
{
    List<Student> GetStudents();
    Student AddStudent(string firstName, string lastName, double age);
    Student GetStudentById(int id);
}

DataAccess.cs 

public class DataAccess : IDataAccess
{
    private List<Student> student = new List<Student>();

    public DataAccess()
    {
        student.Add(new Student { Id = 1, FirstName = "Jhon", LastName = "Doe", Age = 18});
        student.Add(new Student { Id = 2, FirstName = "Amelia", LastName = "Amy", Age = 16 });
    }

    public Student GetStudentById(int id)
    {
        var stu = student.Where(t => t.Id == id).FirstOrDefault();
        return stu;
    }

    public List<Student> GetStudents()
    {
        return student;
    }

    public Student AddStudent(string firstName, string lastName, double age)
    {
        Student s = new Student();
        s.FirstName = firstName;
        s.LastName = lastName;
        s.Age = age;
        s.Id = student.Count() + 1;
        student.Add(s);
        return s;
    }
}

Creating Query

Now inside the Queries folder, we will write all our queries. So let's create a query by adding a GetStudentListQuery.cs file inside the Queries folder.

public class GetStudentListQuery : IRequest<List<Student>>
{
}

Here the GetStudenListQuery implements the IRequest interface that confirms the MediatR library that this class is a query/command. As this GetStudentListQuery will return us a List of Students so put the List<student> in the IRequest generic interface.

Creating Handler

For every request(Query/Command) there must be a handler. Handler defines what to do when the client sends a specific request. So for our "GetStudenListQuery" we must define a handler. Lets create a "GetStudentListHandler.cs" file inside Handlers folder.

public class GetStudentListHandler : IRequestHandler<GetStudentListQuery, List<Student>>
{
    private readonly IDataAccess _data;
    public GetStudentListHandler(IDataAccess data)
    {
        _data = data;
    }
    public Task<List<Student>> Handle(GetStudentListQuery request, CancellationToken cancellationToken)
    {
        return Task.FromResult(_data.GetStudents());
    }
}

Here the handler must implement the IRequestHandler interface where we need to pass two generics the first one is the request name that the handler will process (for our example as we will handle GetStudentListQuery so we are passing this) and in the second generic we will pass the output of the request(as this request will return a list of students so we are passing List<Student>).

Creating Command

Now let's do the AddStudent operation as this is an insert operation so this will be our command. so in the Commands folder create a class AddStudentCommand.cs file.

public record AddStudentCommand(string firstName, string lastName, double age) : IRequest<Student>;

As this is also a request so it will implement the IRequest interface. And for this command, we also have to create a handler.

AddStudentHandler.cs

public class AddStudentHandler : IRequestHandler<AddStudentCommand, Student>
{
    private readonly IDataAccess _data;
    public AddStudentHandler(IDataAccess data)
    {
        _data = data;
    }
    public Task<Student> Handle(AddStudentCommand request, CancellationToken cancellationToken)
    {
        return Task.FromResult(_data.AddStudent(request.firstName, request.lastName, request.age));
    }
}

Adding dependency

Now in the API Project inside the startup.cs at ConfigureServices() method register the services for MediatR.

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers();
    services.AddSwaggerGen(c =>
    {
        c.SwaggerDoc("v1", new OpenApiInfo { Title = "CQRS_Mediator", Version = "v1" });
    });
    services.AddSingleton<IDataAccess, DataAccess>();
    services.AddMediatR(typeof(LibraryEntrypoint).Assembly);
}

Look here while registering the MediatR we load the assembly of the whole Library project(here "LibraryEntrypoint" is an empty class created in the Library project, this is created to just refer to the assembly of the Library project. You can add any of the class if you wish.)

Adding Controller

Now let's create a StudentController and request our "GetStudentListQuery" and "AddStudentCommand" in the controller's GET and POST method.

[Route("api/[controller]")]
[ApiController]
public class StudentController : ControllerBase
{
    private readonly IMediator _mediator;

    public StudentController(IMediator mediator)
    {
        _mediator = mediator;
    }

    [HttpGet]
    public async Task<List<Student>> Get()
    {
        return await _mediator.Send(new GetStudentListQuery());
    }

    [HttpPost]
    public async Task<Student> Post([FromBody] Student value)
    {
        var model = new AddStudentCommand(value.FirstName, value.LastName, value.Age);
        return await _mediator.Send(model);
    }
}

 So here we are just calling _mediator.Send() method and just passing the Request name that we want and the mediator will call the Request's corresponding Handler to handle the request. So basically the controller doesn't need to know anything about the implementation that makes our controller much more cleaner as the heavy lifting stuff has been managed by the Mediator.

Now if we run the application we can test our API method with Swagger.

Implementing CQRS with MediatR in ASP.NET Core Application

So that's a quick introduction and implementation of CQRS in ASP.NET core. Though there are some other important concepts like Event Sourcing in CQRS we will skip it for now. But will try to explain it in the later article.

Thank you. Happy coding!!!