Implementing the CQRS pattern

CQRS simply works on the separation between queries (to read) and commands (to modify). Command-Query Separation (CQS) is an approach to Object-oriented Design (OOD).

CQRS was introduced for the first time by Bertrand Meyer (https://en.wikipedia.org/wiki/Bertrand_Meyer). He mentioned this term in his book, Object-Oriented Software Construction, during the late 1980s: https://www.amazon.in/Object-Oriented-Software-Construction-Prentice-hall-International/dp/0136291554.

CQRS does fit well with some scenarios and has some useful factors to it:

  • Model separation: In modeling terms, we are able to have multiple representations for our data model. The clear separation allows for choosing different frameworks or techniques over others that are more suitable for query or command. Arguably, this is achievable with create, read, update, and delete (CRUD)-style entities, although the single data layer assembly often emerges.
  • Collaboration: In some enterprises, a separation between query and command would benefit the teams involved in building complex systems, particularly when some teams are more suited for different aspects of an entity. For example, a team that is more concerned about presentation could concentrate on the query model, while another team that is more focused on data integrity could maintain the command model.
  • Independent scalability: Many solutions tend to either require more reads against the model, or more writes, depending on the business requirements.
For CQRS, remember that commands update data and queries read data.

Some important things to note while working on CQRS are as follows:

  • Commands should be placed asynchronously rather than as synchronous operations.
  • Databases should never be modified with queries.

CQRS simplifies the design with the use of separate commands and queries. Also, we can physically separate read data from write data operations. In this arrangement, a read database could use a separate database schema, or in other words, we can say that it could use a read-only database that is optimized for queries.

As the database uses a physical separation approach, we can visualize the CQRS flow of the application, as depicted in the following diagram:

The preceding diagram depicts an imaginary workflow of the CQRS application, in which an application has physically separate databases for write operations and read operations. This imaginary application is based on RESTful web services (.NET Core APIs). No APIs have been exposed directly to the client/end user who is consuming these APIs. There is an API gateway exposed to users, and any requests for applications will come through the API gateway.

The API Gateway provides an entry point to groups with similar types of services. You can also simulate it with the facade pattern, which is part of the distributed system.

In the previous diagram, we have the following:

  • User interface: This could be any client (who is consuming the APIs), web application, desktop application, mobile application, or any other application.
  • API Gateway: Any request from UI and response to UI is delivered from the API Gateway. This is the main part of CQRS, as business logic can be incorporated by using the Commands and Persistence layers.
  • Database(s): The diagram shows two physically separated databases. In real applications, this depends upon the requirements of the product, and you can use the database for both write and read operations.
  • Queries are generated with Read operations that are Data Transfer Objects (DTOs).

You can now go back to the Use case section, in which we discussed the new features/extensions of our FlixOne inventory application. In this section, we will create a new FlixOne application with the features discussed previously using the CQRS pattern. Please note that we will be developing APIs first. If you did not install the pre-requisites, I suggest revisiting the Technical requirements section, gathering all of the required software, and installing them onto your machine. If you have completed the pre-requisites, then let's start by following these steps:

  1. Open Visual Studio.
  2. Click File | New Project to create a new project.
  3. On the New Project window, select Web and then select ASP.NET Core Web Application.
  4. Give a name to your project. I have named our project FlixOne.API and ensured that the Solution Name is FlixOne.
  1. Select the Location of your Solution folder, then click on the OK button as shown in the following screenshot:

  1. Now you should be on the New ASP.NET Web Core Application - FlixOne.API screen. Make sure that on this screen, you select ASP.NET Core 2.2. Select Web Application (Model-View-Controller) from the available templates, and uncheck the Configure for HTTPS checkbox, as shown in the following screenshot:

  1. You will see a default page appear, as shown in the following screenshot:

  1. Expand Solution Explorer and click on Show All files. You will see the default folders/files created by Visual Studio. Refer to the following screenshot:

We have selected the ASP.NET Core Web (Model-View-Controller) template. Therefore, we have the default folders, Controllers, Models, and Views. This is a default template provided by Visual Studio. To check this default template, hit F5 and run the project. Then, you will see the following default page:

The previous screenshot is the default Home screen of our web application. You may be thinking is it a website? and be expecting an API documentation page here instead of a web page. This is because, when we select the template, Visual Studio adds MVC Controller instead of API Controller by default. Please note that in ASP.NET Core, both MVC Controller and API Controller use the same Controller Pipeline (see the Controller class: https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.controller?view=aspnetcore-2.2).

Before discussing API projects in detail, let's first add a new project to our FlixOne solution. To do so, expand Solution Explorer, right-click on the Solution Name, and then click on Add New Project. Refer to the following screenshot:

In the New Project window, add the new FlixOne.CQRS project, and click on the OK button. Refer to the following screenshot:

The previous screenshot is of the Add New Project window. On it, select .NET Core and then select the Class Library(.NET Core) project. Enter the name FlixOne.CQRS and click the OK button. A New Project has been added to the solution. You can then add folders to the new solution, as shown in the following screenshot:

The previous screenshot is showing that I have added four new folders: Commands, Queries, Domain, and Helper. In the Commands folder, I have the Command and Handler sub-folders. Similarly, for the Queries folder, I have added sub-folders called Handler and Query.

To get started with the project, let's first add two Domain Entities in the project. The following is the required code:

public class Product
{
public Guid Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string Image { get; set; }
public decimal Price { get; set; }
}

The preceding code is a Product domain entity that has the following properties:

  • Id: A unique identifier
  • Name: A product name
  • Description: A product description
  • Image: An image of the product
  • Price: The price of the product

We also need to add the CommandResponse database. This plays an important role when interacting with database/repository, in that it ensures that the system gets a response. The following is the code-snippet of the CommandResponse Entity Model:

public class CommandResponse
{
public Guid Id { get; set; }
public bool Success { get; set; }
public string Message { get; set; }

}

The preceding CommandResponse class contains the following properties:

  • Id: Unique identifier.
  • Success: With values of True or False, it tells us whether the operation is successful or not.
  • Message: A message as a response to the operation. If Success if false, this message contains Error.

Now, it's time to add interfaces for a query. To add interfaces, follow these steps:

  1. From Solution Explorer, right-click on the Queries folder, click on Add, and then click on New Item, as per the following screenshot:

  1. From the Add New Item window, choose Interface, name it IQuery, and click on the Add button:

  1. Follow the previous steps and add the IQueryHandler interface as well. The following is the code from the IQuery interface:
public interface IQuery<out TResponse>
{
}
  1. The previous interface works as a skeleton for querying for any kind of operation. This is a generic interface using an out parameter of the TResponse type.

The following is code from our ProductQuery class:

public class ProductQuery : IQuery<IEnumerable<Product>>
{
}

public class SingleProductQuery : IQuery<Product>
{
public SingleProductQuery(Guid id)
{
Id = id;
}

public Guid Id { get; }

}

The following is code from our ProductQueryHandler class:

public class ProductQueryHandler : IQueryHandler<ProductQuery, IEnumerable<Product>>
{
public IEnumerable<Product> Get()
{
//call repository
throw new NotImplementedException();
}
}
public class SingleProductQueryHandler : IQueryHandler<SingleProductQuery, Product>
{
private SingleProductQuery _productQuery;
public SingleProductQueryHandler(SingleProductQuery productQuery)
{
_productQuery = productQuery;
}

public Product Get()
{
//call repository
throw new NotImplementedException();
}
}

The following is code from our ProductQueryHandlerFactory class:

public static class ProductQueryHandlerFactory
{
public static IQueryHandler<ProductQuery, IEnumerable<Product>> Build(ProductQuery productQuery)
{
return new ProductQueryHandler();
}

public static IQueryHandler<SingleProductQuery, Product> Build(SingleProductQuery singleProductQuery)
{
return new SingleProductQueryHandler(singleProductQuery);
}
}

Similarly to Query interfaces and Query classes, we need to add interfaces for commands and their classes.

At the point by which we have created CQRS for a product domain entity, you can follow this workflow and add more entities as many times as you like. Now, let's move on to our FlixOne.API project and add a new API controller by following these steps:

  1. From Solution Explorer, right-click on the Controllers folder.
  2. Select Add | New Item.
  1. Select API Controller Class and name it ProductController; refer to the following screenshot:

  1. Add the following code in the API controller:
[Route("api/[controller]")]
public class ProductController : Controller
{
// GET: api/<controller>
[HttpGet]
public IEnumerable<Product> Get()
{
var query = new ProductQuery();
var handler = ProductQueryHandlerFactory.Build(query);
return handler.Get();
}

// GET api/<controller>/5
[HttpGet("{id}")]
public Product Get(string id)
{
var query = new SingleProductQuery(id.ToValidGuid());
var handler = ProductQueryHandlerFactory.Build(query);
return handler.Get();
}

The following code is for saving the product:


// POST api/<controller>
[HttpPost]
public IActionResult Post([FromBody] Product product)
{
var command = new SaveProductCommand(product);
var handler = ProductCommandHandlerFactory.Build(command);
var response = handler.Execute();
if (!response.Success) return StatusCode(500, response);
product.Id = response.Id;
return Ok(product);

}

The following code is for deletion of products:


// DELETE api/<controller>/5
[HttpDelete("{id}")]
public IActionResult Delete(string id)
{
var command = new DeleteProductCommand(id.ToValidGuid());
var handler = ProductCommandHandlerFactory.Build(command);
var response = handler.Execute();
if (!response.Success) return StatusCode(500, response);
return Ok(response);
}

We have created Product APIs, and we are not going to creates UI in this section. To view what we have done, we will be adding Swagger support to our API project.

Swagger is a tool that can be used for documentation purposes, and provides all of the information regarding the API endpoints on one screen, where you can visualize the API and test it by setting parameters as well.

To get started with the implementation of Swagger in our API project, follow these steps:

  1. Open Nuget Package Manager.

  2. Go to Nuget Package Manager | Browse and search for Swashbuckle.ASPNETCore; refer to the following screenshot:

  1. Open the Startup.cs file and add the following code to the ConfigureService method:
//Register Swagger
services.AddSwaggerGen(swagger =>
{
swagger.SwaggerDoc("v1", new Info { Title = "Product APIs", Version = "v1" });
});
  1. Now, add the following code to the Configure method:
// Enable middleware to serve generated Swagger as a JSON endpoint.
app.UseSwagger();

// Enable middleware to serve swagger-ui (HTML, JS, CSS, etc.), specifying the Swagger JSON endpoint.
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "Product API V1");
});

We have now completed all of the changes that serve to showcase the power of CQRS in the application. Hit F5 in Visual Studio and open the Swagger documentation page by accessing the following URL: http://localhost:52932/swagger/ (please note that port number 52932 may vary as per your setting of the project). You will see the following Swagger Documentation page:

Here, you can test Product APIs.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.221.245.196