-
-
Notifications
You must be signed in to change notification settings - Fork 11
CQRS custom handlers
anton-martyniuk edited this page Oct 26, 2022
·
1 revision
If needed a custom Modern generic CQRS Query and Command handlers can be created.
Lets have a look at example of custom Query handler for a regular GetByIdQuery
:
/// <summary>
/// The mediator query handler that returns an entity with the given id
/// </summary>
/// <exception cref="ArgumentNullException">Thrown if provided id is null</exception>
/// <exception cref="EntityNotFoundException">Thrown if an entity does is not found</exception>
/// <exception cref="InternalErrorException">If a service internal error occurred</exception>
/// <returns>The entity</returns>
public class GetByIdQueryHandler<TEntityDto, TEntityDbo, TId, TRepository> :
BaseMediatorHandler<TEntityDto, TEntityDbo>,
IRequestHandler<GetByIdQuery<TEntityDto, TId>, TEntityDto>
where TEntityDto : class
where TEntityDbo : class
where TId : IEquatable<TId>
where TRepository : class, IModernQueryRepository<TEntityDbo, TId>
{
private const string HandlerName = nameof(GetByIdQueryHandler<TEntityDto, TEntityDbo, TId, TRepository>);
/// <summary>
/// The repository instance
/// </summary>
protected readonly TRepository Repository;
/// <summary>
/// The repository instance
/// </summary>
protected readonly ILogger Logger;
/// <summary>
/// Initializes a new instance of the class
/// </summary>
/// <param name="repository">The generic repository</param>
/// <param name="logger">The logger</param>
public GetByIdQueryHandler(TRepository repository, ILogger<GetByIdQueryHandler<TEntityDto, TEntityDbo, TId, TRepository>> logger)
{
ArgumentNullException.ThrowIfNull(repository, nameof(repository));
ArgumentNullException.ThrowIfNull(logger, nameof(logger));
Repository = repository;
Logger = logger;
}
/// <summary>
/// <inheritdoc cref="IRequestHandler{TRequest,TResponse}.Handle"/>
/// </summary>
public async Task<TEntityDto> Handle(GetByIdQuery<TEntityDto, TId> request, CancellationToken cancellationToken)
{
try
{
ArgumentNullException.ThrowIfNull(request, nameof(request));
ArgumentNullException.ThrowIfNull(request.Id, nameof(request.Id));
cancellationToken.ThrowIfCancellationRequested();
if (Logger.IsEnabled(LogLevel.Trace))
{
Logger.LogTrace("{serviceName}.{method} id: {id}", EntityName, HandlerName, request.Id);
}
var entityDbo = await Repository.GetByIdAsync(request.Id, null, cancellationToken).ConfigureAwait(false);
return MapToDto(entityDbo);
}
catch (Exception ex)
{
Logger.LogError(ex, "Could not get {name} entity by id '{id}': {reason}", EntityName, request.Id, ex.Message);
throw CreateProperException(ex);
}
}
}
Lets have a look at example of custom Query handler for a GetByIdQuery
with caching support:
/// <summary>
/// The mediator query handler that returns an entity with the given id
/// </summary>
/// <exception cref="ArgumentNullException">Thrown if provided id is null</exception>
/// <exception cref="EntityNotFoundException">Thrown if an entity does is not found</exception>
/// <exception cref="InternalErrorException">If a service internal error occurred</exception>
/// <returns>The entity</returns>
public class GetByIdQueryHandler<TEntityDto, TEntityDbo, TId, TRepository> :
BaseMediatorHandler<TEntityDto, TEntityDbo, TId>,
IRequestHandler<GetByIdQuery<TEntityDto, TId>, TEntityDto>
where TEntityDto : class
where TEntityDbo : class
where TId : IEquatable<TId>
where TRepository : class, IModernQueryRepository<TEntityDbo, TId>
{
private const string HandlerName = nameof(GetByIdQueryHandler<TEntityDto, TEntityDbo, TId, TRepository>);
/// <summary>
/// The repository instance
/// </summary>
protected readonly TRepository Repository;
/// <summary>
/// The cache
/// </summary>
protected readonly IModernCache<TEntityDto, TId> Cache;
/// <summary>
/// The repository instance
/// </summary>
protected readonly ILogger Logger;
/// <summary>
/// Initializes a new instance of the class
/// </summary>
/// <param name="repository">The generic repository</param>
/// <param name="cache">Cache</param>
/// <param name="logger">The logger</param>
public GetByIdQueryHandler(TRepository repository, IModernCache<TEntityDto, TId> cache,
ILogger<GetByIdQueryHandler<TEntityDto, TEntityDbo, TId, TRepository>> logger)
{
ArgumentNullException.ThrowIfNull(repository, nameof(repository));
ArgumentNullException.ThrowIfNull(logger, nameof(logger));
Repository = repository;
Cache = cache;
Logger = logger;
}
/// <summary>
/// <inheritdoc cref="IRequestHandler{TRequest,TResponse}.Handle"/>
/// </summary>
public async Task<TEntityDto> Handle(GetByIdQuery<TEntityDto, TId> request, CancellationToken cancellationToken)
{
try
{
ArgumentNullException.ThrowIfNull(request, nameof(request));
ArgumentNullException.ThrowIfNull(request.Id, nameof(request.Id));
cancellationToken.ThrowIfCancellationRequested();
if (Logger.IsEnabled(LogLevel.Trace))
{
Logger.LogTrace("{serviceName}.{method} id: {id}", EntityName, HandlerName, request.Id);
}
var entityDto = await Cache.TryGetByIdAsync(request.Id).ConfigureAwait(false);
if (entityDto is not null)
{
return entityDto;
}
var entityDbo = await Repository.GetByIdAsync(request.Id, null, cancellationToken).ConfigureAwait(false);
entityDto = MapToDto(entityDbo);
await Cache.AddOrUpdateAsync(request.Id, entityDto).ConfigureAwait(false);
return entityDto;
}
catch (Exception ex)
{
Logger.LogError(ex, "Could not get {name} entity by id '{id}': {reason}", EntityName, request.Id, ex.Message);
throw CreateProperException(ex);
}
}
}
Lets have a look at example of custom Command handler for a regular CreateEntityCommand
:
/// <summary>
/// The mediator command handler that creates the new entity
/// </summary>
/// <exception cref="ArgumentNullException">Thrown if provided entity is null</exception>
/// <exception cref="EntityAlreadyExistsException">Thrown if an entity already exists in the data store</exception>
/// <exception cref="InternalErrorException">Thrown if an error occurred while saving the entity in the data store</exception>
/// <returns>Updated entity by the data store (primary key, for example)</returns>
public class CreateEntityCommandHandler<TEntityDto, TEntityDbo, TId, TRepository> :
BaseMediatorHandler<TEntityDto, TEntityDbo>,
IRequestHandler<CreateEntityCommand<TEntityDto>, TEntityDto>
where TEntityDto : class
where TEntityDbo : class
where TId : IEquatable<TId>
where TRepository : class, IModernCrudRepository<TEntityDbo, TId>
{
private const string HandlerName = nameof(CreateEntityCommandHandler<TEntityDto, TEntityDbo, TId, TRepository>);
/// <summary>
/// The repository instance
/// </summary>
protected readonly TRepository Repository;
/// <summary>
/// The repository instance
/// </summary>
protected readonly ILogger Logger;
/// <summary>
/// Initializes a new instance of the class
/// </summary>
/// <param name="repository">The generic repository</param>
/// <param name="logger">The logger</param>
public CreateEntityCommandHandler(TRepository repository, ILogger<CreateEntityCommandHandler<TEntityDto, TEntityDbo, TId, TRepository>> logger)
{
ArgumentNullException.ThrowIfNull(repository, nameof(repository));
ArgumentNullException.ThrowIfNull(logger, nameof(logger));
Repository = repository;
Logger = logger;
}
/// <summary>
/// <inheritdoc cref="IRequestHandler{TRequest,TResponse}.Handle"/>
/// </summary>
public async Task<TEntityDto> Handle(CreateEntityCommand<TEntityDto> request, CancellationToken cancellationToken)
{
try
{
ArgumentNullException.ThrowIfNull(request, nameof(request));
ArgumentNullException.ThrowIfNull(request.Entity, nameof(request.Entity));
cancellationToken.ThrowIfCancellationRequested();
Logger.LogTrace("{serviceName}.{method} entity: {@entity}", EntityName, HandlerName, request.Entity);
Logger.LogDebug("Creating {name} entity in db...", EntityName);
var entityDbo = MapToDbo(request.Entity);
entityDbo = await Repository.CreateAsync(entityDbo, cancellationToken).ConfigureAwait(false);
Logger.LogDebug("Created {name} entity. {@entityDbo}", EntityName, entityDbo);
return MapToDto(entityDbo);
}
catch (Exception ex)
{
Logger.LogError(ex, "Unable to create a new {name} entity: {reason}. {@entity}", EntityName, ex.Message, request.Entity);
throw CreateProperException(ex);
}
}
}
Lets have a look at example of custom Command handler for a CreateEntityCommand
with caching support:
/// <summary>
/// The mediator command handler that creates the new entity
/// </summary>
/// <exception cref="ArgumentNullException">Thrown if provided entity is null</exception>
/// <exception cref="EntityAlreadyExistsException">Thrown if an entity already exists in the data store</exception>
/// <exception cref="InternalErrorException">Thrown if an error occurred while saving the entity in the data store</exception>
/// <returns>Updated entity by the data store (primary key, for example)</returns>
public class CreateEntityCommandHandler<TEntityDto, TEntityDbo, TId, TRepository> :
BaseMediatorHandler<TEntityDto, TEntityDbo, TId>,
IRequestHandler<CreateEntityCommand<TEntityDto>, TEntityDto>
where TEntityDto : class
where TEntityDbo : class
where TId : IEquatable<TId>
where TRepository : class, IModernCrudRepository<TEntityDbo, TId>
{
private const string HandlerName = nameof(CreateEntityCommandHandler<TEntityDto, TEntityDbo, TId, TRepository>);
/// <summary>
/// The repository instance
/// </summary>
protected readonly TRepository Repository;
/// <summary>
/// The cache
/// </summary>
protected readonly IModernCache<TEntityDto, TId> Cache;
/// <summary>
/// The repository instance
/// </summary>
protected readonly ILogger Logger;
/// <summary>
/// Initializes a new instance of the class
/// </summary>
/// <param name="repository">The generic repository</param>
/// <param name="cache">Cache</param>
/// <param name="logger">The logger</param>
public CreateEntityCommandHandler(TRepository repository, IModernCache<TEntityDto, TId> cache,
ILogger<CreateEntityCommandHandler<TEntityDto, TEntityDbo, TId, TRepository>> logger)
{
ArgumentNullException.ThrowIfNull(repository, nameof(repository));
ArgumentNullException.ThrowIfNull(logger, nameof(logger));
Repository = repository;
Cache = cache;
Logger = logger;
}
/// <summary>
/// <inheritdoc cref="IRequestHandler{TRequest,TResponse}.Handle"/>
/// </summary>
public async Task<TEntityDto> Handle(CreateEntityCommand<TEntityDto> request, CancellationToken cancellationToken)
{
try
{
ArgumentNullException.ThrowIfNull(request, nameof(request));
ArgumentNullException.ThrowIfNull(request.Entity, nameof(request.Entity));
cancellationToken.ThrowIfCancellationRequested();
Logger.LogTrace("{serviceName}.{method} entity: {@entity}", EntityName, HandlerName, request.Entity);
var entityDbo = MapToDbo(request.Entity);
Logger.LogDebug("Creating {name} entity in db...", EntityName);
entityDbo = await Repository.CreateAsync(entityDbo, cancellationToken).ConfigureAwait(false);
Logger.LogDebug("Created {name} entity. {@entityDbo}", EntityName, entityDbo);
var entityDto = MapToDto(entityDbo);
var entityId = GetEntityId(entityDto);
Logger.LogDebug("Creating {name} entity with id '{id}' in cache...", EntityName, entityId);
await Cache.AddOrUpdateAsync(entityId, entityDto).ConfigureAwait(false);
Logger.LogDebug("Created {name} entity with id '{id}'. {@entityDto}", EntityName, entityId, entityDto);
return entityDto;
}
catch (Exception ex)
{
Logger.LogError(ex, "Unable to create a new {name} entity: {reason}. {@entity}", EntityName, ex.Message, request.Entity);
throw CreateProperException(ex);
}
}
}