CQRS 명령의 유효성을 정확히 확인하고 도메인 개체로 변환해야합니까?


22

나는 한 데이터 저장소에 세분화 된 데이터를 보유 할 수있는 유연성을 좋아해 분석에 대한 큰 가능성을 제공하여 비즈니스 가치를 높이고 필요할 때 성능 향상을 위해 비정규 화 된 데이터를 포함하는 읽기에 다른 데이터를 제공하기 때문에 가난한 사람의 CQRS 1 을 꽤 오랫동안 적응해 왔습니다. .

그러나 불행히도 처음부터 나는이 유형의 아키텍처에 비즈니스 로직을 정확하게 배치 해야하는 문제로 어려움을 겪었습니다.

내가 이해 한 바에 따르면, 명령은 의도를 전달하는 수단이며 도메인 자체와는 관련이 없습니다. 기본적으로 데이터 (원하는 경우 바보) 전송 개체입니다. 이는 서로 다른 기술간에 명령을 쉽게 전송할 수 있도록하기위한 것입니다. 성공적으로 완료된 이벤트에 대한 응답으로 이벤트에도 동일하게 적용됩니다.

일반적인 DDD 애플리케이션에서 비즈니스 로직은 엔터티, 값 개체, 집계 루트 내에 있으며 데이터와 동작이 풍부합니다. 그러나 명령은 도메인 객체가 아니므로 데이터의 도메인 표현으로 제한되어서는 안됩니다.

실제 질문은 다음과 같습니다. 논리가 정확히 어디에 있습니까?

나는 값의 조합에 대한 규칙을 설정하는 매우 복잡한 집계를 만들려고 할 때이 문제에 가장 자주 직면하는 것을 알았습니다. 또한 도메인 객체를 모델링 할 때 객체가 메소드에 도달하는 시점을 알고 있으면 오류없는 패러다임 을 따르고 싶습니다 .

골재 Car가 두 가지 구성 요소를 사용 한다고 가정 해 봅시다 .

  • Transmission,
  • Engine.

모두 TransmissionEngine값 객체 슈퍼 타입으로 표현하고있어서, 서브 타입을 가지고있다 AutomaticManual전송하거나 PetrolElectric엔진 각각.

이 도메인에서 성공적으로 생성 자체 A의 생활 Transmission이라고해도, Automatic또는 Manual, 또는 유형 중 하나는 Engine완전히 괜찮습니다. 그러나 Car집계에는 몇 가지 새로운 규칙이 도입 되었으며, 동일한 컨텍스트에서 TransmissionEngine개체를 사용하는 경우에만 적용 할 수 있습니다. 즉:

  • 자동차가 Electric엔진을 사용 하는 경우 허용되는 변속기 유형은 Automatic입니다.
  • 자동차가 Petrol엔진을 사용할 때 두 가지 유형 중 하나를 가질 수 있습니다 Transmission.

명령을 만드는 수준 에서이 구성 요소 조합 위반을 잡을 수는 있지만 이전에 언급했듯이 명령에 도메인 계층으로 제한되어야하는 비즈니스 논리가 포함되어 있기 때문에 수행해서는 안되는 것으로 이해합니다.

옵션 중 하나는이 비즈니스 로직 유효성 검사를 명령 유효성 검사기 자체로 옮기는 것입니다. 그러나 이것이 옳지 않은 것 같습니다. 명령을 해체하고 getter를 사용하여 검색 된 속성을 확인하고 유효성 검사기 내에서 속성을 비교하고 결과를 검사하는 것처럼 느껴집니다. 그건 데메테르 법칙을 어기는 것 같아요 .

언급 된 유효성 검사 옵션은 실행 가능하지 않은 것으로 보이기 때문에 명령을 사용하고 집계를 구성해야합니다. 그러나이 논리는 어디에 존재해야 하는가? 구체적인 명령을 처리하는 명령 처리기 내에 있어야합니까? 아니면 명령 유효성 검사기 내에 있어야합니까 (이 방법은 마음에 들지 않습니다)?

현재 명령을 사용하고 있으며 담당 명령 핸들러 내에서 집계를 만듭니다. 그러나이 작업을 수행 할 때 명령 유효성 검사기가 있으면 아무것도 포함하지 않을 것입니다. CreateCar명령이 존재하면 별도의 경우에 유효한 것으로 알고 있지만 구성 요소가 다르게 말할 수있는 구성 요소가 포함 되기 때문 입니다.


CreateUser명령을 사용하여 새 사용자를 만드는 다른 유효성 검사 프로세스를 혼합 한 다른 시나리오를 상상해 봅시다 .

이 명령에는 Id생성 될 사용자와 해당 사용자가 포함되어 있습니다 Email.

시스템은 사용자의 이메일 주소에 대해 다음 규칙을 명시합니다.

  • 특별해야 해,
  • 비워 둘 수 없습니다
  • 최대 100 자 (db 열의 최대 길이) 여야합니다.

이 경우 고유 한 전자 메일을 갖는 것이 비즈니스 규칙이지만 시스템에서 현재 전자 메일 전체를 메모리에로드하고 명령에서 전자 메일을 확인해야하기 때문에 집계로 확인하는 것은 거의 의미가 없습니다. 집합에 대한 ( Eeeek! 뭔가, 뭔가, 성능). 이 때문에이 검사를 명령 유효성 검사기로 옮기면 UserRepository종속성으로 사용되며 리포지토리를 사용하여 명령에 전자 메일이있는 사용자가 이미 존재하는지 확인합니다.

이와 관련하여 갑자기 다른 두 개의 전자 메일 규칙을 명령 유효성 검사기에도 넣는 것이 합리적입니다. 그러나 규칙이 실제로 User집계 내에 있어야한다는 느낌이 들며 명령 유효성 검사기는 고유성에 대해서만 확인해야하며 유효성 검사가 성공하면 User집계 를 작성하여 CreateUserCommandHandler저장 될 저장소로 전달해야합니다.

리포지토리의 save 메서드는 집계가 전달되면 일단 불변이 충족되도록 보장하는 집계를 허용하기 때문에 이런 느낌이 듭니다. 논리 (예 : 비어 있지 않음)가 명령 유효성 검증 자체에만 존재하는 경우 다른 프로그래머가이 유효성 검증을 완전히 건너 뛰고 오브젝트 UserRepository와 함께 save 메소드를 User직접 호출하여 치명적인 데이터베이스 오류가 발생할 수 있습니다. 너무 오래되었습니다.

이러한 복잡한 검증 및 변환을 개인적으로 어떻게 처리합니까? 나는 대부분 내 솔루션에 만족하지만 내 아이디어와 접근 방식이 선택에 매우 만족하기 위해 완전히 어리석지 않다는 확신이 필요하다고 생각합니다. 나는 완전히 다른 접근법에 전적으로 개방되어 있습니다. 당신이 개인적으로 시도하고 당신을 위해 잘 일한 것이 있다면 나는 당신의 해결책을보고 싶습니다.


1 RESTful 시스템 생성을 담당하는 PHP 개발자로 일하면서 CQRS에 대한 해석은 표준 비동기 명령 처리 방식과 약간 다릅니다 . 명령을 동기식으로 처리해야하기 때문에 명령에서 결과를 반환하는 경우가 있습니다.


내가 생각하는 예제 코드가 필요합니다. 명령 객체는 어떻게 생겼으며 어디서 만들 수 있습니까?
Ewan

@Ewan 오늘이나 내일 코드 샘플을 추가하겠습니다. 몇 분 안에 여행을 떠납니다.
Andy

PHP 프로그래머이기 때문에 CQRS + ES 구현을 살펴 보는 것이 좋습니다 : github.com/xprt64/cqrs-es
Constantin Galbenu

@ConstantinGALBENU CQRS에 대한 Greg Young의 해석이 옳다고 생각한다면 (아마도 우리가해야 할) CQRS에 대한 이해가 잘못되었거나 적어도 PHP 구현이 잘못되었습니다. 명령은 집계에 의해 직접 처리되지 않습니다. 명령은 집계에서 변경을 생성 한 다음 상태 복제에 사용될 이벤트를 생성 할 수있는 명령 핸들러에 의해 처리됩니다.
Andy

나는 우리의 해석이 다르다고 생각하지 않습니다. DDD (전술 수준의 집계)를 더 파거나 눈을 크게 뜨면됩니다. CQRS 구현에는 최소한 두 가지 스타일이 있습니다. 나는 그들 중 하나를 사용합니다. 내 구현은 Actor 모델과 더 비슷하고 Application 레이어를 매우 얇게 만듭니다. 항상 좋은 것입니다. 해당 앱 서비스 내부에 많은 코드 중복이 있음을 관찰하고로 대체하기로 결정했습니다 CommandDispatcher.
Constantin Galbenu

답변:


22

다음 답변은 cqrs.nu 에 의해 승격 된 CQRS 스타일과 관련하여 명령이 집계에 직접 도착합니다. 이 아키텍처 스타일에서 애플리케이션 서비스는 집계를 식별하고로드하고 명령을 보낸 다음 집계를 유지 하는 인프라 구성 요소 ( CommandDispatcher ) 로 대체됩니다 (이벤트 소싱이 사용되는 경우 일련의 이벤트로).

실제 질문은 다음과 같습니다. 논리가 정확히 어디에 있습니까?

여러 종류의 (유효성 검사) 논리가 있습니다. 일반적인 아이디어는 가능한 빨리 로직을 실행하는 것입니다. 원한다면 빨리 실패하십시오. 따라서 상황은 다음과 같습니다.

  • 명령 객체 자체의 구조; 명령의 생성자에는 명령을 작성하는 데 필요한 몇 가지 필수 필드가 있습니다. 이것은 최초이자 가장 빠른 검증입니다. 이것은 분명히 명령에 포함되어 있습니다.
  • 사용자 이름과 같은 일부 필드의 비공식 또는 형식 (유효한 이메일 주소)과 같은 낮은 수준의 필드 유효성 검사 이러한 종류의 유효성 검사는 생성자에서 명령 자체 내에 포함되어야합니다. 방법이있는 또 다른 스타일 isValid이 있지만 실제로 성공적인 명령 인스턴스화가 충분해야 할 때 누군가 가이 방법을 호출해야한다는 것을 기억해야하므로 이것은 나에게 의미가 없습니다.
  • command validators명령을 검증해야 할 책임이있는 별도 의 클래스. 여러 집계 또는 외부 소스에서 정보를 확인해야 할 때 이러한 종류의 유효성 검사를 사용합니다. 이를 사용하여 사용자 이름의 고유성을 확인할 수 있습니다. Command validators리포지토리와 같은 종속성을 주입 할 수 있습니다. 이 유효성 검사는 결국 집계와 일치합니다 (즉, 사용자가 생성되면 그 동안 동일한 사용자 이름을 가진 다른 사용자가 생성 될 수 있음). 또한 집계 내부에 있어야하는 논리를 여기에 넣지 마십시오! 명령 유효성 검사기는 이벤트를 기반으로 명령을 생성하는 Sagas / Process 관리자와 다릅니다.
  • 명령을 받고 처리하는 집계 메소드 이것은 마지막으로 발생하는 검증입니다. 집계는 명령에서 데이터를 추출하고 일부 핵심 비즈니스 로직을 사용하여 수락 (상태 변경) 또는 거부합니다. 이 논리는 강력하고 일관된 방식으로 점검됩니다. 이것이 마지막 방어선입니다. 귀하의 예에서 When a car uses Electric engine the only allowed transmission type is Automatic여기 에서 규칙 을 확인해야합니다.

리포지토리의 save 메서드는 집계가 전달되면 일단 불변이 충족되도록 보장하는 집계를 허용하기 때문에 이런 느낌이 듭니다. 논리 (예 : 비어 있지 않음)가 명령 유효성 검증 자체에만 존재하는 경우 다른 프로그래머가이 유효성 검증을 완전히 건너 뛰고 UserRepository에서 User 메소드를 사용하여 save 메소드를 직접 호출하면 치명적인 데이터베이스 오류가 발생할 수 있습니다. 너무 길었을 수도 있습니다.

위의 기술을 사용하면 아무도 잘못된 명령을 만들 거나 집계 내부의 논리를 무시할 수 없습니다 . 명령 유효성 검사기는 자동으로로드 및 호출 CommandDispatcher되므로 아무도 집계에 직접 명령을 보낼 수 없습니다. 명령을 전달하는 집계에서 메소드를 호출 할 수 있지만 변경 사항을 지속 할 수 없으므로 무의미하거나 무해합니다.

RESTful 시스템을 만드는 PHP 개발자로 일하면서 CQRS를 해석하면 명령을 동기식으로 처리해야하기 때문에 명령에서 결과를 반환하는 것과 같은 표준 비동기 명령 처리 방식과 약간 다릅니다.

나는 또한 PHP 프로그래머이며 명령 처리기 (형식의 집계 메소드)에서 아무것도 반환하지 않습니다 handleSomeCommand. 그러나 종종 HTTP response새로 생성 된 집계 루트의 ID 또는 읽기 모델의 무언가와 같은 정보를 클라이언트 / 브라우저에 반환하지만 집계 명령 메서드에서 아무것도 반환 하지 않습니다 . 명령이 받아 들여지고 처리되었다는 단순한 사실만으로도 충분합니다 (동기식 PHP 처리에 대해 이야기하고 있습니까?!).

CQRS는 높은 수준의 아키텍처가 아니기 때문에 브라우저로 무언가를 반환합니다 (그리고 여전히 책에서 CQRS를 수행함 ) .

명령 검사기가 작동하는 방법의 예 :

집계로가는 명령 유효성 검사기를 통한 명령의 경로


유효성 검사 전략과 관련하여 로직 2가 자주 복제 될 수있는 장소로 2 번 포인트가 나옵니다. 확실하게 사용자 집계가 비어 있지 않은 전자 메일의 유효성을 검사하기를 원합니까? 이것은 ChangeEmail 명령을 도입 할 때 분명해집니다.
킹 사이드 슬라이드

EmailAddress자체 검증 하는 값 객체 가 있으면 @ king-side-slide가 아닙니다 .
Constantin Galbenu

그것은 전적으로 맞습니다. EmailAddress중복을 줄이기 위해를 캡슐화 할 수 있습니다. 더 중요한 것은 그렇게 할 때 논리를 명령에서 도메인으로 옮기는 것입니다. 이것이 너무 멀리 걸릴 수 있다는 점은 주목할 가치가 있습니다. 종종 유사한 지식 (값 개체)은 사용하는 사람에 따라 다른 유효성 검사 요구 사항을 가질 수 있습니다. EmailAddress이 값의 전체 개념에 글로벌 검증 요구 사항이 있으므로 편리한 예입니다.
킹 사이드 슬라이드

마찬가지로 "명령 유효성 검사기"라는 아이디어는 불필요 해 보입니다. 목표는 유효하지 않은 명령이 작성 및 발송되는 것을 방지하지 않습니다. 목표는 실행을 막는 것입니다. 예를 들어 URL로 원하는 데이터를 전달할 수 있습니다. 유효하지 않은 경우 시스템은 요청을 거부합니다. 명령이 여전히 작성되어 발송됩니다. 명령에 유효성 검사를 위해 여러 집계가 필요한 경우 (즉, 전자 메일 고유성을 확인하기 위해 사용자 모음) 도메인 서비스가 더 적합합니다. "x 유효성 검사기"와 같은 개체는 종종 데이터가 동작과 분리되는 빈혈 모델의 신호입니다.
킹 사이드 슬라이드

1
@ king-side-slide 구체적인 예는 UserCanPlaceOrdersOnlyIfHeIsNotLockedValidator입니다. 이 도메인은 주문과 별도의 도메인이므로 OrderAggregate 자체에서 확인할 수 없습니다.
Constantin Galbenu

6

DDD의 기본 전제는 도메인 모델이 자체 검증한다는 것입니다. 비즈니스 규칙을 적용하기 위해 책임있는 당사자로 도메인을 높이기 때문에 이는 중요한 개념입니다. 또한 도메인 모델을 개발의 초점으로 유지합니다.

CQRS 시스템 (정확하게 지적한대로)은 자체 응집 메커니즘을 구현하는 일반 하위 도메인을 나타내는 구현 세부 사항입니다. 귀하의 모델은 어떠한 방식으로도에 의존해야 모든 비즈니스 규칙에 따라 행동하는 CQRS 인프라의 조각. DDD의 목표는 시스템 의 동작 을 모델링하여 결과가 핵심 비즈니스 도메인의 기능 요구 사항을 유용하게 추상화하는 것입니다. 유혹을 느끼면서 모델에서이 동작의 단일 부분을 옮기면 모델의 무결성과 응집력이 떨어지고 유용성이 떨어집니다.

ChangeEmail명령 을 포함하도록 예제를 확장하기 만하면 규칙을 복제해야하므로 명령 인프라에서 비즈니스 로직을 원하지 않는 이유를 완벽하게 설명 할 수 있습니다.

  • 이메일은 비워 둘 수 없습니다
  • 이메일은 100자를 초과 할 수 없습니다
  • 이메일은 고유해야합니다

이제 논리가 도메인에 있어야한다는 것을 확신 할 수있게되었으므로 "where"문제를 해결하겠습니다. 처음 두 규칙은 User집계에 쉽게 적용 할 수 있지만 마지막 규칙은 조금 더 미묘합니다. 좀 더 심층적 인 통찰력을 얻으려면 약간의 지식이 필요합니다. 표면적으로이 규칙이에 적용되는 것처럼 보이지만 User실제로는 그렇지 않습니다. 이메일의 "고유성"은 Users(일부 범위에 따라) 모음에 적용됩니다 .

아하! 이를 염두에두고 귀하의 UserRepository(메모리 내 컬렉션 Users)이이 불변을 적용하기에 더 좋은 후보가 될 수 있다는 것이 분명해 졌습니다. "저장"방법은 수표를 포함하는 가장 합리적인 장소 일 것입니다 ( UserEmailAlreadyExists예외를 던질 수있는 곳 ). 또는 도메인 UserService을 새로 작성 Users하고 속성을 업데이트 해야 할 수도 있습니다 .

빠른 실패는 좋은 접근 방법이지만 나머지 모델과 함께 언제 어디서나 수행 할 수 있습니다. 사용자 (개발자)가 프로세스 깊숙한 곳에서 호출이 실패 할 경우 실패를 포착하기 위해 추가 처리를하기 전에 애플리케이션 서비스 메소드 (또는 명령)에서 매개 변수를 확인하는 것이 매우 유혹적 일 수 있습니다. 그러나 그렇게하면 비즈니스 규칙이 변경 될 때 코드를 두 번 이상 업데이트해야하는 방식으로 지식을 복제 (및 유출)하게됩니다.


2
동의합니다. 지금까지 (CQRS가없는) 독서는 불변을 보호하기 위해 유효성 검사가 항상 도메인 모델로 가야한다고 말합니다. 이제 CQRS를 읽고 Command 객체에 유효성 검사를하도록 지시하고 있습니다. 이것은 직관적이지 않은 것 같습니다. 예를 들어 명령 대신 도메인 모델에서 유효성 검사가 수행되는 GitHub의 예를 알고 있습니까? +1.
w0051977
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.