Reply for @Constantin Galbenu, I faced limit.
@Misanthrope And what exactly do you do with those events?
@Constantin Galbenu, in most cases I don't need them as result of the command, of course. In some cases -- I need to notify client in response of this API request.
It's extremely useful when:
- You need to notify about error via events instead of exceptions.
It happens usually when your model need to be saved (for example, it counts number of attempts with wrong code/password) even if error happened.
Also, some guys doesn't use exceptions for business errors at all -- only events (http://andrzejonsoftware.blogspot.com/2014/06/custom-exceptions-or-domain-events.html)
There is no particular reason to think that throwing business exceptions from command handler is OK, but returning domain events is not.
- When event happens only with some circumstances inside of your aggregate root.
And I can provide example for the second case.
Imagine we make Tinder-like service, we have LikeStranger command.
This command MAY result in StrangersWereMatched if we like person who already liked us before.
We need to notify mobile client in response whether match was happened or not.
If you just want to check matchQueryService after the command, you may find match there, but there is no gurantee that match was happened right now,
because SOMETIMES Tinder shows already matched strangers (probably, in unpopulated areas, maybe inconsistency, probably you just have 2nd device, etc.).
Checking response if StrangersWereMatched really happened right now is so straightforward:
$events = $this->commandBus->handle(new LikeStranger(...));
if ($events->contains(StrangersWereMatched::class)) {
return LikeApiResponse::matched();
} else {
return LikeApiResponse::unknown();
}
Yes, you can introduce command id, for example, and make Match read model to keep it:
// ...
$commandId = CommandId::generate();
$events = $this->commandBus->handle(
$commandId,
new LikeStranger($strangerWhoLikesId, $strangerId)
);
$match = $this->matchQueryService->find($strangerWhoLikesId, $strangerId);
if ($match->isResultOfCommand($commandId)) {
return LikeApiResponse::matched();
} else {
return LikeApiResponse::unknown();
}
... but think about it: why do you think that first example with straightforward logic is worse?
It doesn't violate CQRS anyway, I just made the implicit explicit.
It is stateless immutable approach. Less chances to hit a bug (e.g. if matchQueryService
is cached/delayed [not instantly consistent], you have a problem).
Yes, when the fact of matching is not enough and you need to get data for response, you have to use query service.
But nothing prevents you to receive events from command handler.