이벤트 소싱-여러 이벤트 또는 하나의 집계에 대한 변경 하나?
CQRS / ES (이벤트 소싱)를 구현하는 체크리스트 시스템이 있습니다. 우리는 명령이 있습니다
updateStatus(taskId: string, status: boolean)
작업 또는 하위 작업을 완료된 것으로 표시합니다. 하위 작업이 완료되고 형제 하위 작업도 모두 완료되었다는 명령을 받으면 상위 작업도 완료로 표시해야합니다. 따라서 아래 예 (작업 A의 하위 작업 1-3)에서 :
- [] 작업 A-열기
- [] 작업 1-열기
- [*] 작업 2-완료
- [*] 작업 3-완료
작업 A와 1이 모두 처음에 열린 다음 명령을받습니다.
updateStatus(task1, completed)
CommandHandler는 taskCompleted (task1) 이벤트를 생성해야합니다.
내 질문은 올바른 CQRS / ES 요구 사항입니다.
- 단일 이벤트 생성 : taskCompleted (task1)
- 두 개의 이벤트 생성 : taskCompleted (task1), taskCompleted (taskA)
첫 번째 옵션에서 이벤트 소비자는 집계가 완료되기 위해 자체적으로 업데이트되어야 함을 알 수 있습니다. 두 번째에서는 명령 핸들이 처리합니다.
옵션 1의 주요 단점은 명령 처리기에 대한 처리가 더 많고 집계에 대한 더 깊은 지식입니다. 또 다른 단점은 이벤트를 재사용하는 것입니다 (예 : 작업이 완료되었을 때 작업 소유자에게 이메일을 보내는 논리가 있다고 가정 해 보겠습니다. 옵션 2를 사용하면 이벤트를 수신하고 이벤트에 대해 알지 못하는 사이에 조치를 취하는 두 번째 이벤트 핸들러가있을 것입니다. 전체 논리).
옵션 2의 주요 단점은 이벤트 수가 훨씬 많다는 것입니다.
CQRS / ES를 사용하는 더 올바른 접근 방식에 대한 제안이 있습니까?
답변
짧은 대답 : 두 개의 이벤트를 생성해야합니다.
단일 명령 호출로 여러 이벤트가 발생할 수 있으므로 더 많은 이벤트를 생성하는 것은 실제로 문제가되지 않습니다. 그러나 당신의 경우에는 정확히 왜 그렇게하고 싶습니까? 책임 분산을 방지합니다.
매우 기본적인 이벤트 소스 프로젝트에서 애플리케이션에 최소한 두 가지 작업 부분이 있다고 상상할 수 있습니다.
- 이벤트 소스 모델,
- 프로젝터는 읽기를위한 데이터를 생성하기 위해 애플리케이션의 읽기면을 업데이트합니다.
하나의 이벤트 만 생성 한 경우 (하위 작업이 완료된 경우) 이제 모든 하위 작업을 완료 할 때 상위 작업도 완료되도록 프로젝터에 로직을 도입해야합니다. 모든 하위 작업이 완료되면 상위 작업 집계를 완료하기 위해 쓰기 / 도메인 레이어에도 동일하게 존재하므로 도메인 로직을 복제하고 있습니다. 또한 이러한 논리는 도메인과는 완전히 다른 언어로 작성 될 가능성이 높습니다 (예 : 읽기 모델이 SQL 데이터베이스에있는 경우 SQL).
응용 프로그램이 내가 설명한 단계 (즉, 읽기 쪽 프로젝터가있는 쓰기 쪽)에있는 경우 도메인 논리를 복제하는 것이 실제로 문제가되지 않는다고 말할 수 있습니다. 결국 많은 프로젝트에서 SQL 구현에는 도메인 규칙도 포함될 수 있습니다. 애플리케이션이 커지거나 마이크로 서비스간에 분할 될 때 문제가 더욱 분명해집니다.
작업이 완료 될 때 작업의 모든 감시자에게 알려야하는 알림 마이크로 서비스를 추가하면 (하위 작업 완료의) 단일 이벤트와 함께 작업의 완료 여부를 결정하는 방법은 작업의 도메인 로직을 다시 복사합니다. 하위 작업이 이미 완료되었습니다. 이를 더욱 복잡하게 만드는 것은 프로젝터와 달리이 마이크로 서비스는 작업 관리가 포함 된 마이크로 서비스 프로젝트와는 별개로 완전히 다른 프로젝트에있을 가능성이 매우 높습니다. 따라서 전체 인프라에 흩어져 있지 않은 손상된 도메인 논리를 추적하기가 매우 어렵습니다.
두 개의 이벤트를 사용하여 프로젝터에서 상위 작업을 표시하는 것은 다음과 같이 간단합니다.
fun changeTaskToCompleted(event: TaskCompletedEvent) {
database.executeUpdate('UPDATE task SET completed = true WHERE id = ?', event.taskId)
}
알림 마이크로 서비스에서 구현은 다음 항목에만 반응하여 크게 단순화됩니다 TaskCompletedEvent
.
fun processEvent(event: Event) {
when(event) {
is TaskCompletedEvent -> sendTaskCompletedNotificationEmail(event)
}
}
@Andy 의 답변 에서 제기 된 요점 외에도 두 개의 이벤트가있는 경우 모든 형제 작업이 완료되었는지 확인이 이벤트 처리기로 이동되도록 코드를 정렬 할 수 있습니다.
이것은 행동의 흐름을 만들 것입니다
- 명령 핸들러 수신
updateStatus(task1, completed)
- 명령 처리기가 이벤트를 내보냄
taskCompleted(task1)
- TaskCompleted 이벤트 처리기가 Task1에 대한 이벤트를 수신합니다.
- 이벤트 핸들러는 모든 형제 작업이 완료된 것을 확인합니다.
- 이벤트 핸들러
updateStatus(taskA, completed)
가 명령 핸들러에 명령을 내리거나 - 이벤트 핸들러가 이벤트를 내보냄
taskCompleted(taskA)
- 이벤트 핸들러
이렇게하면 모든 하위 작업이 완료 될 때 명령 처리기가 상위 작업의 완료에 대해 알 필요가 없습니다. 이는 모두 전용 이벤트 핸들러에서 처리됩니다.
옵션 2의 주요 단점은 이벤트 수가 훨씬 많다는 것입니다.
CQRS / ES를 사용하는 더 올바른 접근 방식에 대한 제안이 있습니까?
갖는 일이 다른 것들에 대한 여러 이벤트 것은 입니다 단점하지 않지만 디자인을 향상시킵니다. 이를 통해 비즈니스 관점에서 발생한 일을 표현하기 위해 데이터 변경을 해석하는 논리가 서비스에 캡슐화되어 여러 프로젝터로 유출되지 않습니다. Andy의 대답 은 이미 이것을 잘 설명했습니다.
물론 단일 명령이 실행 된 후 여러 이벤트 를 생성 하는 것은 완전히 괜찮습니다 . 후속 이벤트가 트리거되는 방법은 구현 세부 사항입니다.
SubTaskCompleted의 이벤트는 검사 작업의 모든 하위 작업이 이제 완료되면 몇 가지 다른 코드를 실행 한 후 게재 할 수 TaskCompleted 이벤트를. 그러나 하위 작업 완료로 인해 발생해야하는 두 이벤트를 모두 결정하는 명령을 실행하는 동일한 메서드 내에있을 수도 있습니다.
참고 : 전체 기본 작업이 완료된 것으로 확인 될 때 이러한 하위 작업 진행이 더 이상 흥미롭지 않기 때문에 전체 기본 작업이 별도의 사용자 상호 작용으로 완료되었을 때 후속 SubTaskCompleted 이벤트를 트리거하지 않습니다. 이벤트는 시스템에서 실제로 일어난 일을 반영해야하므로 한 번의 클릭으로 메인 작업이 완료된 것으로 표시하면 내 관점에서 모든 해당 하위 작업에 대해 하위 작업 완료 이벤트를 생성하는 것은 의미가 없습니다.
귀하의 질문과 답변은 사건에 강하게 초점을 맞추고 있지만 (물론 좋습니다) 나는 귀하의 명령 과 관련하여 잠재적 인 냄새가 있음을 지적하고 싶습니다 .
우리는 명령이 있습니다
updateStatus(taskId: string, status: boolean)
작업 또는 하위 작업을 완료된 것으로 표시합니다.
나는 updateStatus 가 귀하의 비즈니스 언어를 반영 하지 않으므로 귀하의 도메인에서 강력한 의미가 없다고 확신 합니다.
차라리 명령을 다음과 같이 변경하는 것이 좋습니다.
completeSubTask(taskId: string)
이것은 비즈니스 로직을 훨씬 더 잘 표현할뿐만 아니라 이벤트에 맞는 강력한 명령을 제공합니다. 또한 부울 플래그로 시작하여 이후에 더 많은 매개 변수로 변경되는 명령 / 메소드를 자주 보아 해당 비즈니스 로직을 이해하기 어렵고 어렵게 만듭니다.