Crafty.CQRS
1.1.0
dotnet add package Crafty.CQRS --version 1.1.0
NuGet\Install-Package Crafty.CQRS -Version 1.1.0
<PackageReference Include="Crafty.CQRS" Version="1.1.0" />
paket add Crafty.CQRS --version 1.1.0
#r "nuget: Crafty.CQRS, 1.1.0"
// Install Crafty.CQRS as a Cake Addin #addin nuget:?package=Crafty.CQRS&version=1.1.0 // Install Crafty.CQRS as a Cake Tool #tool nuget:?package=Crafty.CQRS&version=1.1.0
Crafty.CQRS
A simple and crafty library to dispatch command and query to appropriate handlers.
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 you command and your handler? Please do it, you have no limitation.
- you want to implement multiple handling in a single class? please do it.
- simplify: remove
CancellationToken
fromHandle()
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 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 ... 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 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;
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. |
.NET Core | netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
.NET Standard | netstandard2.1 is compatible. |
MonoAndroid | monoandroid was computed. |
MonoMac | monomac was computed. |
MonoTouch | monotouch was computed. |
Tizen | tizen60 was computed. |
Xamarin.iOS | xamarinios was computed. |
Xamarin.Mac | xamarinmac was computed. |
Xamarin.TVOS | xamarintvos was computed. |
Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.1
- MediatR (>= 12.0.1)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.