Skip to content

pierregillon/Crafty.CQRS

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

25 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Crafty.CQRS

Status Version Nuget

A simple and crafty library to dispatch command and query to appropriate handlers.

Installation

Nuget package:

dotnet add package Crafty.CQRS

Only a syntactical sugar on top of the MediatR library

One of the main aspect of Domain Driven Design, is to align the language with the domain we want to model.

Why don't we do the same for our technical architecture?

When using Command and Query Responsability Segregation (CQRS) pattern, we would love to have Command and Query defined in our code base, not an implementation details IRequest that could be sometimes the first or the second.

This is the purpose of this library: abstract MediatR IRequest into ICommand and IQuery, keeping all features of this library.

Advantages

All is about readability 😌.

  • align the language: stop using the "request" keyword when you are doing CQRS
  • everything is based on interfaces (thanks to default implementation in interface) :
    • you prefer a record for your command and your handler?
    • you want to implement multiple handling in a single class?
    • => do it.
  • simplify: remove CancellationToken from Handle() method.
    • most of process cannot be cancelled (or we don't want to implement a cancellation process).
  • simplify bis: hide the Unit.Value concept of MediatR

Schema of the 2 flows

flowchart TB
    classDef query fill:#DFFFE3

    CD[ICommandDispatcher] -->|dispatches| C
    C[ICommand] -.->|handled by a single| CH
    CH[ICommandHandler<>]

    QD[IQueryDispatcher] -->|dispatches| Q
    Q[IQuery<>] -.->|handled by a single| QH
    QH[IQueryHandler<>]

    class QD,Q,QH query;
    

Show me the code, no talk.

Installation

services
    .AddCqrs(options => options.RegisterServicesFromAssemblyContaining<XXX>())

Command dispatching from api controller

public class UserController : ControllerBase
{
    private ICommandDispatcher _commandDispatcher;
    
    public UserController(ICommandDispatcher commandDispatcher) => _commandDispatcher = commandDispatcher;
  
    [HttpPost]
    public async Task<IActionResult> RegisterUser()
    {
        await _commandDispatcher.Dispatch(new RegisterUserCommand());
    }
}

The command :

public record RegisterUserCommand : ICommand;

The handler :

public record RegisterUserCommandHandler : ICommandHandler<RegisterUserCommand>
{

    public Task Handle(RegisterUserCommand command)
    {
        ...
    }
}

Query dispatching from api controller

public class UserController : ControllerBase
{
    private IQueryDispatcher _queryDispatcher;
    
    public UserController(IQueryDispatcher queryDispatcher) => _queryDispatcher = queryDispatcher;
  
    [HttpPost]
    public async Task<IActionResult> ListAllRegisteredUsers()
    {
        var users = await _queryDispatcher.Dispatch(new ListAllRegisteredUsersQuery());
        
        return Ok(users);
    }
}

The query :

public record ListAllRegisteredUsersQuery : IQuery<IEnumerable<UserListItem>>;

The handler :

public record ListAllRegisteredUsersQueryHandler : IQueryHandler<ListAllRegisteredUsersQuery, IEnumerable<UserListItem>>
{

    public Task<IEnumerable<UserListItem>> Handle(ListAllRegisteredUsersQuery query)
    {
        ...
    }
}

And ... what about cancellation?

In certain case, if you want to access the CancellationToken in an handler, you can implement ICommandCancellableHandler or IQueryCancellableHandler, that add the token as a second parameter.

public record RegisterUserCommandHandler : ICommandCancellableHandler<RegisterUserCommand>
{

    public Task Handle(RegisterUserCommand command, CancellationToken token)
    {
        ...
    }
}

Using processors

You can also implement ICommandPreProcessor and ICommandPostProcessor to interact before and after the ICommandHandler. The same can be achieved for queries.

flowchart TB
    classDef optionalCommand stroke-dasharray: 2 2
    classDef query fill:#DFFFE3
    classDef optionalQuery fill:#DFFFE3,stroke-dasharray: 2 2

    CD[ICommandDispatcher] -->|dispatches| C
    C[ICommand] -.->|is preprocessed| CPre
    CPre[ICommandPreProcessor<>] -->|handled by a single| CH
    CH[ICommandHandler<>] -.->|is post processed| CPost
    CPost[ICommandPostProcessor<>]

    QD[IQueryDispatcher] -->|dispatches| Q
    Q[IQuery<>] -.->|is preprocessed| QPre
    QPre[IQueryPreProcessor<>] -->|handled by a single| QH
    QH[IQueryHandler<>] -.->|is post processed| QPost
    QPost[IQueryPostProcessor<>]

    class CPre,CPost optionalCommand;
    class QD,Q,QPre,QH,QPost query;
    class QPre,QPost optionalQuery;
    

What about decorating handlers? Like IPipelineBehaviour<,>

As this library is built on top MediatR, you can directly use IPipelineBehaviour<,> to create decorators for your command and queries.

public class LogAllRequests<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse> where TRequest : notnull
{
    public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken cancellationToken)
    {
        Console.WriteLine("some log");
        return await next();
    }
}

For now, there is no override like ICommandDecorator<> because of IoC limitation (OpenGeneric declaration on type définition).

About

Simple library to dispatch command and query

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages