I am using Mediatr to implement the CQRS pattern in dotnet core 3.0. I had some questions regarding how to coordinate different multiple queries and commands. From what I have read online, here are some best practices that people describe.
- Query handlers should not depend on command handlers
- Command handlers should not depend on query handlers
- Instead of using decorators, use
IPipelineBehaviour - Each command should map one to one to an
HTTP Request(Ex: Create User would send the command to aCreateUserCommandHandler, which would take care of all the work) - For command handlers, return void . Using the Mediatr framework, I would return
Task<Unit>
Here is my issue, right now, I am implementing IUserStore<TUser> for AspNet.Identity and lets say we're looking at this example
public async Task<IdentityResult> DeleteAsync(User user, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
if (user == null) return IdentityResult.Failed(new ArgumentNullError(nameof(user)));
bool userExists = await _mediator.Send(new UserExistsQuery {UserId = user.Id}, cancellationToken);
if (!userExists) return IdentityResult.Failed(new EntityDoesNotExistError(user));
await _mediator.Send(new DeleteUserCommand {User = user}, cancellationToken);
return IdentityResult.Success;
}
In this method, I am basically using the implementing user store to coordinate different queries and commands in order to delete a user. In this case, I could see doing the following in order to make it thin as possible and more closely follow the 'one command per http request'
- Get rid of
UserExistsQuery&UserExistsQueryQueryHandler, move that query into theDeleteUserCommandHandlerand query using a repository (whichUserExistsQueryHandleralready does now) rather than being dependent on a query handler - Instead of returning
Task<Unit>, return something like anIdentityResult
The reason I am hesitant to do #2 is that it feels like I am returning something on based on the context that the command is being used. I am returning an IdentityResult just because I need it for this one instance.
Furthermore, the reason I had it split out like this in the first place was for re-usability. I wanted to be able to make a number of queries and commands that I could re-use elsewhere. If I return an IdentityResult, then this kind of defeats the purpose as I wouldn't really need this anywhere but in UserStore<TUser>
I've been reading about IPipelineBehaviour but it seems like that is more of a generic solution for all query/command handlers (i.e.: that pipeline could be run for every command/query if the appropriate types exist in your assembly). But could IPipelineBehaviour be used to implement a custom pipeline? In my example, I would move all that logic to a pipeline that would only run for DeleteUserCommand?
I've searched for articles on this subject but couldn't really find anything useful - or maybe I am searching for the wrong terms. My jargon may be wrong, but I could create coordinating services that are only dependent on IMediatr to complete deleting a user. Any feedback and/or reading material would be appreciated.