CQRS : 명령 반환 값
명령이 반환 값을 가져야하는지 여부에 대해 끝없는 혼란이있는 것 같습니다. 참가자들이 자신의 상황이나 상황을 언급하지 않았기 때문에 혼란이 발생했는지 알고 싶습니다.
혼란
다음은 혼란의 예입니다 ...
Udi Dahan은 명령이 "클라이언트에 오류를 반환하지 않는다"고 말하지만 같은 기사 에서 명령이 실제로 클라이언트에 오류를 반환하는 다이어그램을 보여줍니다.
Microsoft Press Store 기사에 "명령은 ... 응답을 반환하지 않습니다"라는 내용이 있지만 계속해서 모호한주의를 기울입니다.
전장 경험이 CQRS를 중심으로 성장함에 따라 일부 사례는 통합되어 모범 사례가되는 경향이 있습니다. 우리가 방금 말한 것과 부분적으로는 반대로 ... 오늘날 명령 처리기와 응용 프로그램 모두 트랜잭션 작업이 어떻게 진행되었는지 알아야한다고 생각하는 것이 일반적인 견해입니다. 결과를 알고 있어야합니다 ...
- Jimmy Bogard는 " 명령에는 항상 결과가 있습니다 "라고 말하지만 명령이 void를 반환하는 방법을 보여주기 위해 추가 노력을 기울입니다.
음, 명령 처리기가 값을 반환합니까?
대답?
Jimmy Bogard의 " CQRS Myths "에서 힌트를 얻었 을 때이 질문에 대한 답은 당신이 말하는 프로그램 적 / 문맥 적 "사분면"에 달려 있다고 생각합니다.
+-------------+-------------------------+-----------------+
| | Real-time, Synchronous | Queued, Async |
+-------------+-------------------------+-----------------+
| Acceptance | Exception/return-value* | <see below> |
| Fulfillment | return-value | n/a |
+-------------+-------------------------+-----------------+
수락 (예 : 유효성 검사)
"수락"명령은 대부분 유효성 검사를 나타냅니다. 아마도 유효성 검사 결과는 "fulfillment"명령이 동기식인지 대기열에 있는지 여부에 관계없이 호출자에게 동기식으로 제공되어야합니다.
그러나 많은 실무자가 명령 처리기 내에서 유효성 검사를 시작하지 않는 것 같습니다. 내가 본 것은 (1) 이미 응용 프로그램 계층 (즉, 데이터 주석을 통해 유효한 상태를 확인하는 ASP.NET MVC 컨트롤러)에서 유효성 검사를 처리하는 환상적인 방법을 찾았거나 (2) 아키텍처 때문입니다. 명령이 (처리되지 않은) 버스 또는 큐에 제출되었다고 가정하는 위치에 있습니다. 이러한 후자의 비동기 형식은 일반적으로 동기 유효성 검사 의미 나 인터페이스를 제공하지 않습니다.
요컨대, 많은 설계자는 명령 처리기가 (동기) 반환 값으로 유효성 검사 결과를 제공하기를 원할 수 있지만 사용중인 비동기 도구의 제한 사항을 준수해야합니다.
이행
명령의 "이행"과 관련하여 명령을 발행 한 클라이언트는 새로 생성 된 레코드의 scope_identity 또는 "계정 초과 인출"과 같은 실패 정보를 알아야 할 수 있습니다.
실시간 설정에서는 반환 값이 가장 의미가있는 것 같습니다. 비즈니스 관련 실패 결과를 전달하는 데 예외를 사용해서는 안됩니다. 그러나 "대기열"컨텍스트에서 반환 값은 당연히 의미가 없습니다.
여기에서 모든 혼란을 요약 할 수 있습니다.
많은 (대부분?) CQRS 실무자는 현재 또는 미래에 비동기 프레임 워크 또는 플랫폼 (버스 또는 대기열)을 통합 할 것이라고 가정하므로 명령 처리기에 반환 값이 없다고 선언합니다. 그러나 일부 실무자는 이러한 이벤트 기반 구조를 사용할 의도가 없으므로 값을 (동 기적으로) 반환하는 명령 처리기를 보증합니다.
예를 들어, Jimmy Bogard가이 샘플 명령 인터페이스를 제공 할 때 동기 (요청-응답) 컨텍스트가 가정되었다고 생각합니다 .
public interface ICommand<out TResult> { }
public interface ICommandHandler<in TCommand, out TResult>
where TCommand : ICommand<TResult>
{
TResult Handle(TCommand command);
}
그의 Mediatr 제품은 결국 인 메모리 도구입니다. 이 모든 것을 감안할 때 Jimmy 가 명령에서 void 리턴을 생성하는 데 신중하게 시간을 들인 이유 는 "명령 핸들러가 리턴 값을 가져서는 안된다"때문이 아니라 단순히 Mediator 클래스가 일관된 인터페이스를 갖기를 원했기 때문이라고 생각합니다.
public interface IMediator
{
TResponse Request<TResponse>(IQuery<TResponse> query);
TResult Send<TResult>(ICommand<TResult> query); //This is the signature in question.
}
... 모든 명령에 의미있는 반환 값이있는 것은 아니지만.
반복 및 마무리
이 주제에 혼란이있는 이유를 정확히 파악하고 있습니까? 내가 놓친 것이 있습니까?
Vladik Khononov의 Tackling Complexity in CQRS 의 조언에 따르면 명령 처리가 결과와 관련된 정보를 반환 할 수 있다고 제안합니다.
[CQRS] 원칙을 위반하지 않고 명령은 다음 데이터를 안전하게 반환 할 수 있습니다.
- 실행 결과 : 성공 또는 실패;
- 오류 발생시 오류 메시지 또는 유효성 검사 오류
- 성공한 경우 집계의 새 버전 번호입니다.
이 정보는 다음과 같은 이유로 시스템의 사용자 경험을 크게 향상시킵니다.
- 명령 실행 결과를 위해 외부 소스를 폴링 할 필요가 없습니다. 바로 사용할 수 있습니다. 명령의 유효성을 검사하고 오류 메시지를 반환하는 것은 간단합니다.
- 표시된 데이터를 새로 고치려는 경우 집계의 새 버전을 사용하여 뷰 모델이 실행 된 명령을 반영하는지 여부를 결정할 수 있습니다. 더 이상 오래된 데이터를 표시하지 않습니다.
Daniel Whittaker는 이 정보를 포함하는 명령 처리기에서 " 공통 결과 "개체를 반환하는 것을 옹호 합니다.
음, 명령 처리기가 값을 반환합니까?
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.
명령 실행이 차단 (일명 동기식) 인 경우 명령이 성공적으로 실행되었는지 여부를 알아야합니다. 그런 다음 상위 계층에서 요구 사항에 가장 적합한 쿼리 모델을 사용하여 새 애플리케이션의 상태에 대해 알아야 할 정확한 정보를 쿼리합니다.
그렇지 않으면 명령 처리기에서 무언가를 반환하면 두 가지 책임이 있습니다. 1. 집계 상태를 수정하고 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.
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.
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.
ReferenceURL : https://stackoverflow.com/questions/43433318/cqrs-command-return-values
'program story' 카테고리의 다른 글
C # 4.0의 명명 된 인수 및 제네릭 형식 유추 (0) | 2020.12.26 |
---|---|
매크로를 사용한 훌륭한 응용 프로그램 및 프로그램 모음 (0) | 2020.12.26 |
C ++ 로깅 프레임 워크 제안 (0) | 2020.12.26 |
.NET Webbrowser 컨트롤에서 메모리 누수를 피하는 방법은 무엇입니까? (0) | 2020.12.26 |
Python Pandas로 들어오는 실시간 데이터를 처리하는 방법 (0) | 2020.12.26 |