Pipeline
The LeanCode CoreLibrary utilizes ASP.NET middlewares to create customized pipelines for handling commands/queries/operations. This section intends to showcase the setup of a basic pipeline and explore its inner workings.
Packages
| Package | Link | Application in section |
|---|---|---|
| LeanCode.CQRS.AspNetCore | Configuration | |
| LeanCode.CQRS.MassTransitRelay | MassTransit related middlewares |
Configuration
CQRS objects need to be registered in two places:
- Handlers need to be registered in DI,
- Routes for CQRS objects execution need to be registered in ASP.NET Core routing as endpoints.
DI
To register handlers in DI, use AddCQRS(...) calls. There, you need to specify two TypesCatalogs:
- One that contains all available contracts,
- One that contains all necessary handlers.
You cannot call AddCQRS(...) twice - all objects need to be registered in one go.
public override void ConfigureServices(IServiceCollection services)
{
services.AddCQRS(TypesCatalog.Of<ExampleCommand>(), TypesCatalog.Of<ExampleHandler>());
}
AddCQRS(...) returns aa CQRSServicesBuilder that allows to further modify CQRS registration by, e.g., adding objects one-by-one, registering predefined libraries like force update or registering local executors.
Endpoints
CQRS objects can be registered in the ASP.NET request pipeline via endpoint routing. To register, use MapRemoteCQRS(...) extension method. In MapRemoteCQRS(...) you can configure the inner CQRS pipeline. In the following example, app is configured to handle:
- Commands at
/api/command/FullyQualifiedName - Queries at
/api/query/FullyQualifiedName - Operations at
/api/operation/FullyQualifiedName
Endpoint routing cannot execute handlers that are not in DI, thus the MapRemoteCQRS(...) call will ignore objects that were not found by AddCQRS(...).
protected override void ConfigureApp(IApplicationBuilder app)
{
// . . .
app.UseEndpoints(endpoints =>
{
endpoints.MapRemoteCQRS(
"/api",
cqrs =>
{
cqrs.Commands = c =>
c.Secure()
.Validate()
.CommitTransaction<CoreDbContext>()
.PublishEvents();
cqrs.Queries = c =>
c.Secure()
.CacheOutput();
cqrs.Operations = c =>
c.Secure()
.CacheOutput()
.CommitTransaction<CoreDbContext>()
.PublishEvents();
}
);
});
}
Tip
To learn about ASP.NET middlewares and how you can implement them, visit the ASP.NET Core Middleware documentation.
In this code snippet, you can specify which middlewares to use for handling commands, queries, and operations. Several middlewares are added in the example:
| Method | Middleware | Responsibility |
|---|---|---|
| Secure() | CQRSSecurityMiddleware | Authorization |
| Validate() | CQRSValidationMiddleware | Validation |
| CacheOutput() | OutputCacheMiddleware | Caching query and operation responses |
| CommitTransaction<T>() | CommitDatabaseTransactionMiddleware | Saving changes to the database |
| PublishEvents() | EventsPublisherMiddleware | Publishing domain events to MassTransit |
The order in which these middlewares are added determines the sequence of execution. Additionally, there are a few other middlewares provided by library that can be incorporated into CQRS pipeline, although they are not covered in this basic example:
| Method | Middleware | Responsibility |
|---|---|---|
| LogCQRSResponses() | ResponseLoggerMiddleware | Logging responses |
| LogCQRSResponsesOnNonProduction() | NonProductionResponseLoggerMiddleware | Logging responses on non-production environments |
| TranslateExceptions() | CQRSExceptionTranslationMiddleware | Capturing and translating exceptions into error codes |
Request handling
The process of request handling can be illustrated by diagram below:
sequenceDiagram
participant aspnet as ASP.NET middlewares
participant start as CQRSMiddleware
participant middle as CQRS specific middlewares
participant final as CQRSPipelineFinalizer
Note over aspnet: Common ASP.NET middlewares, e.g. <br/> UseAuthentication, UseCors
aspnet ->> start: next()
Note over start: Deserialize request according <br/> to CQRSEndpointMetadata
Note over start: Set CQRSRequestPayload
start ->> middle: next()
Note over middle: Custom middlewares, e.g. validation, security
middle ->> final: next()
Note over final: Execute handler
Note over final: Set ExecutionResult
final ->> middle: #0160;
Note over middle: Custom middlewares, e.g. events publication
middle ->> start: #0160;
Note over start: Serialize ExecutionResult to response
start ->> aspnet: #0160;
The process begins with the invocation of common ASP.NET middlewares, such as UseAuthentication and UseCors, prior to the execution of the MapRemoteCQRS(...) method. This method, adds the CQRSMiddleware, initiating the pipeline. During this stage, the request undergoes deserialization and the CQRSRequestPayload is set on HttpContext.
Subsequently, the pipeline executes additional custom middlewares, responsible for tasks like authorization, validation or output-caching. Following the successful execution of these middlewares, the specific handler is invoked inside CQRSPipelineFinalizer. Upon handler execution, the ExecutionResult is set on the HttpContext.
EventsPublisherMiddleware then facilitates the publication of events (assuming it's added to the pipeline in MapRemoteCQRS(...)). Towards the conclusion of the pipeline, the ExecutionResult is serialized to the response inside CQRSMiddleware. Finally, the serialized result is returned to the client, completing the request handling process.
Serialization with Output Caching
When output caching is enabled via .CacheOutput(), the serialization is handled differently. A CQRSResponseSerializerMiddleware is added after the OutputCache middleware to ensure proper serialization timing for cache storage. In this case, CQRSMiddleware delegates serialization to that middleware.