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 theDeleteUserCommandHandler
and query using a repository (whichUserExistsQueryHandler
already 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.
I think you misunderstand the CQRS concept. The DeleteAsync Operation is a command itself. So if you need to read some data to proceed with your operation, this is not a query, but, just a read operation. So whenever you need to read some data in your command operation you have to get it through your repositories. Be aware that, by using CQRS, you separate paths to user queries and commands not read and writes. In commands, every write and read must pass through your domain model. So, probably there exist a repository in your domain layer to fetch data from your write database. But for queries, there is no need to use the Domain model. so your code must be like below:
And DeleteUserCommand Handler is like: