Reputation: 930
I am playing around with CQRS and the MediatR library, trying to learn some of the best practices. One problem I have is code duplication in Command/Query handlers. I would like to know what is the best way to share logic between handlers.
Example: I have an abstract Entity class that defines an ID property. All entities inherit from that class.
public abstract class Entity
{
public long Id { get; private set; }
protected Entity(long id)
{
Id = id;
}
...
}
Then for every entity, I want to create a GetById query. One of those queries looks like so:
public class GetUserByIdQuery : IRequest<UserDto>
{
public long UserId { get; set; }
public class Handler : IRequestHandler<GetUserByIdQuery, UserDto>
{
private readonly IRepository<User> repository;
private readonly IMapper mapper;
public Handler(IUnitOfWork unitOfWork, IMapper mapper)
{
repository = unitOfWork.GetRepository<User>();
this.mapper = mapper;
}
public async Task<UserDto> Handle(GetUserByIdQuery request, CancellationToken cancellationToken)
{
var user = await repository.FindAsync(request.UserId, null, cancellationToken);
if (user is null)
{
throw new EntityNotFoundException();
}
return mapper.Map<UserDto>(user);
}
}
}
The problem is that this class looks exactly the same for all the entities. Without CQRS I would probably have something like this:
public class EntityFinder<TEntity, TDto> where TEntity : Entity
{
private readonly IRepository<TEntity> repository;
private readonly IMapper mapper;
public EntityFinder(IUnitOfWork unitOfWork, IMapper mapper)
{
repository = unitOfWork.GetRepository<TEntity>();
this.mapper = mapper;
}
public async Task<TDto> GetByIdAsync(long id)
{
var entity = await repository.FindAsync(id);
if (entity is null)
{
throw new EntityNotFoundException();
}
return mapper.Map<TDto>(entity);
}
}
I tried doing something similar with a generic query and handler but MediatR had trouble finding the handler (even when I tried registering it manually to the DI container).
What is the best way to avoid such duplication?
Upvotes: 4
Views: 4850
Reputation: 2000
Can you try below code. This way, you reuse the loading code, and at the same time, provide an end point to handle the request.
public class EntityFinder<TEntity, TDto> where TEntity : Entity
{ ... // Same as your code }
public class GetUserByIdQuery : IRequest<UserDto>
{
public long UserId { get; set; }
public class Handler : IRequestHandler<GetUserByIdQuery, UserDto>, EntityFinder<User, UserDto>
{
public Handler(IUnitOfWork unitOfWork, IMapper mapper) : base(unitOfWork, mapper)
{ }
public async Task<UserDto> Handle(GetUserByIdQuery request, CancellationToken cancellationToken)
=> await base.GetByIdAsync(request.UserId);
}
}
Upvotes: 2