Command and Query Responsibility Segregation (CQRS) is a pattern that segregates the operations that read data (Queries) from the operations that update data (Commands) by using separate interfaces.
public class GetUserByIdQuery(int id) : IQuery<UserModel>
{
public int Id { get; } = id;
}
public class UserController : Controller
{
private readonly IQueryDispatcher _queryDispatcher;
[HttpGet, Route("{id:int}")]
public async Task<UserModel> GetAsync(int id, CancellationToken cancellationToken)
{
GetUserByIdQuery query = new GetUserByIdQuery(id);
UserModel model = await _queryDispatcher.DispatchAsync(query, cancellationToken);
return model;
}
}
public class GetUserByIdQueryHandler : IQueryHandler<GetUserByIdQuery, UserModel>
{
private readonly IUserRepository _userRepository;
private readonly IUserModelMapper _userModelMapper;
public async Task<UserModel> HandleAsync(GetUserByIdQuery query, CancellationToken cancellationToken)
{
User user = await _userRepository.FindByIdAsync(query.Id, cancellationToken);
if (user == null)
return null;
UserModel model = _userModelMapper.MapToModel(user);
return model;
}
}
public class RenameUserCommand(int id, string name) : ICommand
{
public int Id { get; } = id;
public string Name { get; } = name;
}
public class UserController : Controller
{
private readonly ICommandDispatcher _commandDispatcher;
[HttpPost, Route("{id:int}")]
public async Task<IHttpResult> RenameAsync(int id, string name, CancellationToken cancellationToken)
{
RenameUserCommand command = new RenameUserCommand(id, name);
await _commandDispatcher.DispatchAsync(command, cancellationToken);
return Ok();
}
}
public class RenameUserCommandHandler : ICommandHandler<RenameUserCommand>
{
private readonly IUserRepository _userRepository;
public async Task HandleAsync(RenameUserCommand command, CancellationToken cancellationToken)
{
using (ITransaction transaction = Transaction.Begin())
{
User user = _userRepository.FindByIdAsync(command.Id, cancellationToken);
if (user == null)
throw new EntityNotFoundException("User", command.Id);
user.Rename(command.Name);
await _userRepository.ModifyAsync(user, cancellationToken);
await transaction.CommitAsync(cancellationToken);
}
}
}
Library has default implementations of ICommandDispatcher and IQueryDispatcher based on IServiceProvider
.
public class CommandDispatcher : ICommandDispatcher
{
private readonly IServiceProvider _serviceProvider;
public Task DispatchAsync<TCommand>(TCommand command, CancellationToken cancellationToken) where TCommand : ICommand
{
ICommandHandler<TCommand> handler = _serviceProvider.GetService(typeof (ICommandHandler<TCommand>));
return handler.HandleAsync(command, cancellationToken);
}
}
public class QueryDispatcher : IQueryDispatcher
{
private readonly IServiceProvider _serviceProvider;
public Task<TResult> DispatchAsync<TResult>(IQuery<TResult> query, CancellationToken cancellationToken)
{
Type concreteHandlerType = typeof(IQueryHandler<,>).MakeGenericType(query.GetType(), typeof(TResult));
object handler = _serviceProvider.GetService(concreteHandlerType);
MethodInfo method = handler.GetType().GetRuntimeMethod("HandleAsync", new[] {query.GetType(), cancellationToken.GetType()});
return (Task<TResult>) method.Invoke(handler, new object[] {query, cancellationToken});
}
}
The simplest way to impplement handler factory is to use IoC container. Here is an example uses ServiceCollection
public static class CqrsServiceCollectionExtensions
{
public void RegisterCQRS(this IServiceCollection services)
{
services.TryAddTransient<ICommandDispatcher, CommandDispatcher>();
services.TryAddTransient<IQueryDispatcher, QueryDispatcher>();
return services;
);
}