Skip to content

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 NuGet version (LeanCode.CQRS.AspNetCore) Configuration
LeanCode.CQRS.MassTransitRelay NuGet version (LeanCode.CQRS.MassTransitRelay) MassTransit related middlewares

Configuration

CQRS objects need to be registered in two places:

  1. Handlers need to be registered in DI,
  2. 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:

  1. One that contains all available contracts,
  2. 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.