CQRS: Command Return Values [closed]

旧城冷巷雨未停 提交于 2020-07-16 11:00:07

问题


There seems to be endless confusion about whether commands should or should not have return values. I would like to know if the confusion is simply because the participants have not stated their context or circumstances.

The Confusion

Here are examples of the confusion...

  • Udi Dahan says commands are "not returning errors to the client," but in the same article he shows a diagram where commands indeed return errors to the client.

  • A Microsoft Press Store article states "the command...doesn't return a response" but then goes on to give an ambiguous caution:

As battlefield experience grows around CQRS, some practices consolidate and tend to become best practices. Partly contrary to what we just stated... it is a common view today to think that both the command handler and the application need to know how the transactional operation went. Results must be known...

  • Jimmy Bogard says "commands always have a result" but then makes extra effort to show how commands return void.

Well, do command handlers return values or not?

The Answer?

Taking a cue from Jimmy Bogard's "CQRS Myths," I think the answer(s) to this question depends on what programmatic/contextual "quadrant" you are speaking of:

+-------------+-------------------------+-----------------+
|             | Real-time, Synchronous  |  Queued, Async  |
+-------------+-------------------------+-----------------+
| Acceptance  | Exception/return-value* | <see below>     |
| Fulfillment | return-value            | n/a             |
+-------------+-------------------------+-----------------+

Acceptance (e.g. validation)

Command "Acceptance" mostly refers to validation. Presumably validation results must be given synchronously to the caller, whether or not the command "fulfillment" is synchronous or queued.

However, it seems many practitioners don't initiate validation from within the command handler. From what I've seen, it is either because (1) they've already found a fantastic way to handle validation at the application layer (i.e. an ASP.NET MVC controller checking valid state via data annotations) or (2) an architecture is in place which assumes commands are submitted to an (out of process) bus or queue. These latter forms of asynchrony generally don't offer synchronous validation semantics or interfaces.

In short, many designers might want the command handler to provide validation results as a (synchronous) return value, but they must live with the restrictions of they asynchrony tools they are using.

Fulfillment

Regarding "fulfillment" of a command, the client who issued the command might need to know the scope_identity for a newly created record or perhaps failure information - such as "account overdrawn."

In a real-time setting it seems that a return value makes the most sense; exceptions should not be used to communicate business-related failure outcomes. However, in a "queuing" context...return values naturally make no sense.

This is where all the confusion can perhaps be summarized:

Many (most?) CQRS practitioners assume they will now, or in the future, incorporate an asynchrony framework or platform (a bus or queue) and thus proclaim that command handlers do not have return values. However, some practitioners have no intention of using such event-driven constructs, and so they will endorse command handlers that (synchronously) return values.

So, for example, I believe a synchronous (request-response) context was assumed when Jimmy Bogard provided this sample command interface:

public interface ICommand<out TResult> { }

public interface ICommandHandler<in TCommand, out TResult>
    where TCommand : ICommand<TResult>
{
    TResult Handle(TCommand command);
}

His Mediatr product is, after all, an in-memory tool. Given all this, I think the reason Jimmy carefully took the time to produce a void return from a command was not because "command handlers should not have return values," but instead because he simply wanted his Mediator class to have a consistent interface:

public interface IMediator
{
    TResponse Request<TResponse>(IQuery<TResponse> query);
    TResult Send<TResult>(ICommand<TResult> query);  //This is the signature in question.
}

...even though not all commands have a meaningful value to return.

Repeat and Wrap Up

Am I correctly capturing why there is confusion on this topic? Is there something I'm missing?

Update (6/2020)

With the help of the answers given, I think I've untangled the confusion. Put simply, if a CQRS command is capable of returning a success/failure indicating completion status, then a return value makes sense. This includes returning a new DB row identity, or any result that does not read or return domain model (business) content.

I think where "CQRS command" confusion emerges, is over the definition and role of "asynchrony". There is a big difference between "task based" async IO, and an asynchronous architecture (e.g. queue based middle-ware). In the former, the async "task" can and will provide the completion result for the async command. However, a command sent to RabbitMQ will not similarly receive a request/reponse completion notification. It is this latter context of async-architecture that causes some to say "there is no such thing as an async command" or "commands do not return values."


回答1:


Following the advice in Tackling Complexity in CQRS by Vladik Khononov suggests command handling can return information relating to its outcome.

Without violating any [CQRS] principles, a command can safely return the following data:

  • Execution result: success or failure;
  • Error messages or validation errors, in case of a failure;
  • The aggregate’s new version number, in case of success;

This information will dramatically improve the user experience of your system, because:

  • You don’t have to poll an external source for command execution result, you have it right away. It becomes trivial to validate commands, and to return error messages.
  • If you want to refresh the displayed data, you can use the aggregate’s new version to determine whether the view model reflects the executed command or not. No more displaying stale data.

Daniel Whittaker advocates returning a "common result" object from a command handler containing this information.




回答2:


Well, do command handlers return values or not?

They should not return Business Data, only meta data (regarding the success or failure of executing the command). CQRS is CQS taken to a higher level. Even if you would break the purist's rules and return something, what would you return? In CQRS the command handler is a method of an application service that loads the aggregate then calls a method on the aggregate then it persists the aggregate. The intend of the command handler is to modify the aggregate. You wouldn't know what to return that would be independent of the caller. Every command handler caller/client would want to know something else about the new state.

If the command execution is blocking (aka synchronous) then all you would need to know if whether the command executed successfully or not. Then, in a higher layer, you would query the exact thing that you need to know about the new application's state using a query-model that is best fitted to your needs.

Think otherwise, if you return something from a command handler you give it two responsibilities: 1. modify the aggregate state and 2. query some read-model.

Regarding command validation, there are at least two types of command validation:

  1. command sanity check, that verifies that a command has the correct data (i.e. an email address is valid); this is done before the command reaches the aggregate, in the command handler (the application service) or in the command constructor;
  2. domain invariants check, that is performed inside the aggregate, after the command reaches the aggregate (after a method is called on the aggregate) and it checks that the aggregate can mutate to the new state.

However, if we go some level up, in the Presentation layer (i.e. a REST endpoint), the client of the Application layer, we could return anything and we wont' break the rules because the endpoints are designed after the use cases, you know exactly what you want to return after a command is executed, in every use case.




回答3:


CQRS and CQS are like microservices and class decomposition: the main idea is the same ("tend to small cohesive modules"), but they lie on different semantic levels.

The point of CQRS is to make write/read models separation; such low-level details like return value from specific method is completely irrelevant.

Take notice on the following Fowler's quote:

The change that CQRS introduces is to split that conceptual model into separate models for update and display, which it refers to as Command and Query respectively following the vocabulary of CommandQuerySeparation.

It is about models, not methods.

Command handler may return anything excepting read models: status (success/failure), generated events (it's primary goal of command handlers, btw: to generate events for the given command), errors. Command handlers very often throw unchecked exception, it is example of output signals from command handlers.

Moreover, author of the term, Greg Young, says that commands are always sync (otherwise, it becomes event): https://groups.google.com/forum/#!topic/dddcqrs/xhJHVxDx2pM

Greg Young

actually I said that an asynchronous command doesn't exist :) its actually another event.




回答4:


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:

  1. 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.
  2. 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.



来源:https://stackoverflow.com/questions/43433318/cqrs-command-return-values

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!