관계형 데이터베이스에 데이터의 현재 상태만 저장하는 대신 개체에 대해 수행된 전체 일련의 작업을 추가 전용 저장소에 저장합니다. 저장소는 도메인 개체를 구체화하는 데 사용할 수 있는 레코드 시스템 역할을 합니다. 이 방법은 복잡한 시스템에서 감사 가능성 및 쓰기 성능을 향상시킬 수 있습니다.
중요합니다
이벤트 소싱은 상당한 장단을 초래하는 복잡한 패턴입니다. 데이터를 저장하고, 동시성을 처리하고, 스키마를 발전시키고, 쿼리 상태를 변경하는 방법을 변경합니다. 이벤트 소싱 솔루션으로 또는 이벤트 소싱 솔루션에서 마이그레이션하는 데 비용이 많이 들며, 패턴을 채택한 후에는 이를 사용하는 시스템 부분에서 향후 설계 결정을 제한합니다. 감사 가능성 및 역사적 재구성과 같은 이점이 패턴의 복잡성을 정당화할 때 이벤트 소싱을 채택합니다. 대부분의 시스템과 시스템의 대부분의 부분에서는 기존 데이터 관리로 충분합니다.
컨텍스트 및 문제점
대부분의 애플리케이션은 데이터로 작동합니다. 애플리케이션은 일반적으로 데이터의 최신 상태를 관계형 데이터베이스에 저장하고 필요에 따라 데이터를 삽입하거나 업데이트합니다. 예를 들어 기존 CRUD(만들기, 읽기, 업데이트 및 삭제) 모델에서 애플리케이션은 저장소에서 데이터를 읽고, 수정하고, 데이터의 현재 상태를 새 값으로 업데이트합니다. 일반적으로 데이터를 잠그는 트랜잭션을 사용합니다.
CRUD 접근 방식은 대부분의 시나리오에서 간단하고 빠릅니다. 그러나 부하가 높은 시스템에서 이 접근 방식은 다음과 같은 과제를 제시합니다.
쓰기 경합: 업데이트에는 행 수준 잠금이 있는 읽기-수정-쓰기 주기가 필요하기 때문에 동일한 엔터티에 대한 동시 쓰기는 성능을 저하시키고 부하가 발생하는 병목 상태가 됩니다.
감사 가능성: CRUD 시스템은 데이터의 최신 상태만 저장합니다. 각 작업의 세부 정보를 별도의 로그에 기록하는 감사 메커니즘을 구현하지 않으면 데이터 기록이 손실됩니다.
해결 방법
이벤트 소싱 패턴은 이벤트 시퀀스가 구동하는 데이터에 대한 작업을 처리하는 방법을 정의합니다. 각 이벤트는 추가 전용 저장소에 기록됩니다. 애플리케이션 코드는 개체에 대해 수행된 각 작업을 설명하는 이벤트를 발생합니다. 일반적으로 이벤트를 큐에 보내면 별도의 프로세스인 이벤트 처리기가 그 큐를 수신 대기하고, 이벤트를 이벤트 저장소에 영구히 저장합니다. 각 이벤트는 개체에 대한 논리적 변경(예: AddedItemToOrder 또는 OrderCanceled.)을 나타냅니다.
이벤트는 데이터의 현재 상태에 대한 레코드 시스템 또는 신뢰할 수 있는 데이터 원본 역할을 하는 이벤트 저장소에 유지됩니다. 추가 이벤트 처리기는 특정 이벤트를 수신 대기하고 필요에 따라 조치를 취할 수 있습니다. 예를 들어 소비자는 이벤트의 작업을 다른 시스템에 적용하는 작업을 시작하거나 작업을 완료하는 데 필요한 다른 관련 작업을 수행할 수 있습니다. 이벤트를 생성하는 애플리케이션 코드는 이벤트를 구독하는 시스템에서 분리됩니다.
이벤트 원본 시스템의 각 엔터티에는 해당 엔터티에 대한 모든 변경 내용을 기록하는 순서가 지정된 이벤트 시퀀스인 자체 eventstream이 있습니다. 언제든지 애플리케이션은 이벤트의 기록을 읽을 수 있습니다. 애플리케이션은 해당 스트림의 모든 이벤트를 재생하여 엔터티의 현재 상태를 파생합니다. 이 프로세스를 리하일레이션이라고 합니다. 애플리케이션이 요청을 처리할 때 요청 시 발생할 수 있습니다.
애플리케이션은 일반적으로 이벤트를 읽고 재생하는 데 비용이 많이 들기 때문에 구체화된 뷰 를 구현합니다. 구체화된 뷰는 쿼리에 최적화된 이벤트 저장소의 읽기 전용 프로젝션입니다. 예를 들어 시스템은 UI를 채우는 데 사용하는 모든 고객 주문의 구체화된 보기를 유지할 수 있습니다. 애플리케이션이 새 주문을 추가하거나, 주문에 따라 항목을 추가하거나 제거하거나, 배송 정보를 추가하면 애플리케이션이 이벤트를 발생시키고 처리기가 구체화된 뷰를 업데이트합니다.
다음 다이어그램에서는 CQRS(명령 쿼리 책임 분리) 패턴과 결합된 이 패턴의 개요를 보여 줍니다. 프레젠테이션 계층은 별도의 읽기 전용 저장소에서 읽고 명령 처리기에 명령을 씁니다. 명령 처리기는 이벤트 저장소에서 엔터티의 eventstream을 검색하고, 비즈니스 논리를 실행하고, 새 이벤트를 큐에 푸시합니다. 이벤트 처리기는 큐의 이벤트를 사용하고 이벤트를 이벤트 저장소에 쓰거나, 읽기 전용 저장소를 업데이트하거나, 외부 시스템과 통합합니다.
이 아키텍처의 Visio 파일을 다운로드합니다.
워크플로
다음 워크플로는 이전 다이어그램에 해당합니다.
프레젠테이션 계층은 읽기 전용 저장소에서 읽는 개체를 호출합니다. 반환된 데이터를 사용하여 UI를 채웁다.
프레젠테이션 계층은 명령 처리기를 호출하여 카트를 만들 거나 카트에 항목을 추가하는 등의 작업을 수행합니다.
명령 처리기는 이벤트 저장소에서 이벤트 스트림을 검색하여 엔터티를 로드합니다. 예를 들어 모든 카트 이벤트를 검색할 수 있습니다. 새 작업이 발생하기 전에 엔터티에 대해 해당 이벤트를 재생하여 현재 상태를 다시 구성합니다.
비즈니스 논리가 실행되고 이벤트가 발생합니다. 대부분의 구현에서 이벤트는 이벤트 생산자와 이벤트 소비자를 분리하기 위해 큐 또는 토픽으로 푸시됩니다.
이벤트 처리기는 특정 이벤트를 수신 대기하고 해당 처리기에 적절한 조치를 취합니다. 이 예제에서 이벤트 처리기는 다음 작업을 수행합니다.
이벤트 저장소에 이벤트 쓰기
쿼리에 최적화된 읽기 전용 저장소 업데이트
외부 시스템과 통합
패턴 이점
이벤트 소싱 패턴에는 다음과 같은 장점이 있습니다.
이벤트는 변경할 수 없으며 추가 전용 작업을 사용하여 저장할 수 있습니다. 이벤트를 시작하는 UI, 워크플로 또는 프로세스는 계속할 수 있으며 이벤트를 처리하는 작업은 백그라운드에서 실행할 수 있습니다. 추가 전용 쓰기 작업은 현재 위치에서 업데이트하는 시스템이 발생시키는 행 수준 잠금 경합을 방지하기 때문에, 특히 프레젠테이션 계층에서는 쓰기 처리량이 향상됩니다.
이벤트는 이벤트가 나타내는 작업을 설명하는 데 필요한 연결된 데이터와 함께 발생하는 작업을 설명하는 간단한 개체입니다. 이벤트는 데이터 저장소를 직접 업데이트하지 않습니다. 이벤트 핸들러는 핸들러가 사용 가능하고 시스템에서 부하를 처리할 수 있을 때 기록된 이벤트를 수신하고 처리합니다. 이벤트를 사용하여 구현 및 관리를 간소화합니다.
이벤트는 일반적으로 도메인 전문가에게 의미가 있는 반면, 개체 관계형 임피던스 불일치로 인해 복잡한 데이터베이스 테이블을 이해하기 어려울 수 있습니다. 테이블은 발생하는 이벤트가 아니라 시스템의 현재 상태를 나타내는 인공 구문입니다.
이벤트 소싱을 사용하면 데이터 저장소의 개체를 직접 업데이트하지 않아도 되기 때문에 동시 업데이트로 인한 충돌을 방지할 수 있습니다. 명령 처리기는 새 이벤트를 추가하기 전에 비즈니스 규칙을 적용하기 위해 eventstream에서 엔터티를 리하이딩하므로 동일한 엔터티를 동시에 로드하는 두 개의 처리기가 동일한 상태에서 작동할 수 있습니다.
예를 들어 각 핸들러에게는 남은 좌석이 5개 있으며, 그리고 두 핸들러 모두 예약을 수락할 수 있습니다. 이벤트 저장소는 낙관적 동시성 제어를 사용하여 이 시나리오를 해결하고 스트림이 읽은 후 변경된 경우 추가를 거부합니다. 거부 시 처리기는 엔터티를 다시 로드하고, 재평가하고, 다시 시도합니다.
추가 전용 이벤트 스토리지는 애플리케이션이 데이터 저장소에 대해 수행된 작업을 모니터링하는 데 사용할 수 있는 감사 내역을 제공합니다. 언제든지 이벤트를 재생하여 현재 상태를 구체화된 뷰 또는 프로젝션으로 다시 생성할 수 있으며 시스템을 테스트하고 디버그하는 데 도움이 될 수 있습니다.
보상 이벤트를 사용하여 변경 내용을 취소해야 하는 요구 사항은 변경 내용이 반전된 기록을 제공할 수 있습니다. 모델이 현재 상태만 저장하는 경우 이 기록은 존재하지 않습니다. 이벤트 목록을 사용하여 애플리케이션 성능을 분석하고, 사용자 동작 추세를 검색하고, 다른 유용한 비즈니스 정보를 얻을 수도 있습니다.
명령 처리기는 이벤트를 발생시키고 태스크는 해당 이벤트에 대한 응답으로 작업을 수행합니다. 이렇게 태스크와 이벤트를 분리하면 유연성과 확장성이 제공됩니다. 태스크는 이벤트 유형 및 이벤트 데이터에 대해 알고 있지만 이벤트를 트리거하는 작업에 대해서는 알 수 없습니다.
여러 태스크는 각 이벤트를 처리할 수 있으므로 이벤트 저장소에서 발생하는 새 이벤트만 수신 대기하는 다른 서비스 및 시스템과 쉽게 통합할 수 있습니다. 그러나 이벤트 소싱 이벤트는 일반적으로 낮은 수준이며 대신 특정 통합 이벤트를 생성해야 할 수 있습니다.
팁 (조언)
이벤트 소싱은 일반적으로 이벤트에 대한 응답으로 데이터 관리 작업을 수행하고 저장된 이벤트의 뷰를 구체화하여 CQRS 패턴 과 결합됩니다. 추가 전용 이벤트 수집 및 쿼리 최적화 프로젝션이 별도로 작동하기 때문에 읽기 및 쓰기 크기를 독립적으로 조정하려면 이 조합을 사용합니다.
문제 및 고려 사항
이 패턴을 구현하는 방법을 결정할 때 다음 사항을 고려합니다.
이벤트 디자인: 결과 상태 외에도 각 변경 뒤에 있는 비즈니스 의도를 캡처하는 이벤트를 디자인합니다. 예를 들어, 좌석 예약 시스템에서 는 2개의 좌석이 예약된 것을 기록하는 이벤트가 나머지 좌석을 42로 변경한 이벤트보다 더 가치가 있습니다. 첫 번째 이벤트는 무슨 일이 있었는지 알려줍니다. 두 번째 이벤트는 결과 상태만 알려줍니다. 상태 중심 이벤트는 이벤트 저장소를 비즈니스 의미가 없는 변경 로그로 줄입니다. 의도 중심 이벤트는 더 자세한 프로젝션, 의미 있는 감사 내역 및 쓰기 환경을 변경하지 않고도 기록 이벤트에서 새 읽기 모델을 빌드할 수 있는 유연성을 제공합니다.
최종 일관성: 시스템은 구체화된 뷰를 만들거나 이벤트를 재생하여 데이터의 프로젝션을 생성할 때만 일관성이 있습니다. 애플리케이션이 요청을 처리하고 이벤트 저장소에 이벤트를 추가하는 경우, 이벤트가 게시되는 경우 및 소비자가 이벤트를 처리하는 시점 사이에 지연이 발생합니다. 이 기간 동안 엔터티에 대한 추가 변경 내용을 설명하는 새 이벤트가 이벤트 저장소에 도착할 수 있습니다. 고객이 데이터가 최종적으로 일관되고 시스템이 이러한 시나리오에서 최종 일관성을 고려하도록 설계되어 있음을 이해해야 합니다.
버전 관리 이벤트: 이벤트 저장소는 정보의 영구 원본이므로 이벤트 데이터를 업데이트해서는 안 됩니다. 엔터티를 업데이트하거나 변경을 실행 취소하는 유일한 방법은 이벤트 저장소에 보상 이벤트를 추가하는 것입니다. 보정 이벤트는 이전 이벤트의 효과를 되돌리거나 수정하는 새 이벤트입니다. 예를 들어
ReservationCanceled이벤트는 이전의SeatsReserved이벤트를 보상합니다. 원래 이벤트는 스트림에 남아 있으며 보상 이벤트는 실행 취소되었음을 기록합니다.또한 이러한 불변성은 버그가 잘못된 이벤트를 생성하는 경우 해당 이벤트가 저장소에 유지됨을 의미합니다. 애플리케이션 코드에서 버그를 수정해도 기록 이벤트가 수정되지 않으므로 재생 중에 잘못된 데이터를 처리하기 위해 이벤트 또는 업캐스터를 보상해야 할 수도 있습니다. 마이그레이션 중에 지속형 이벤트의 스키마(데이터 대신)를 변경해야 하는 경우 저장소의 기존 이벤트를 새 버전과 결합하기가 어려울 수 있습니다.
다음 전략을 개별적으로 또는 조합하여 사용할 수 있습니다.
관용적 역직렬화: 알 수 없는 필드를 무시하고 누락된 필드에 기본값을 사용하도록 이벤트 소비자의 설계를 하십시오. 이 방법은 저장된 이벤트를 변환할 필요 없이 선택적 필드 추가와 같은 부가적이고 깨지지 않는 변경 내용을 처리합니다.
이벤트 버전 관리: 이벤트 봉투의 메타데이터 또는 이벤트 유형 이름의 일부로 각 이벤트에 버전 식별자를 포함합니다. 소비자는 버전을 사용하여 적절한 처리 논리를 선택합니다.
업캐스팅: 역직렬화하는 동안 이전 이벤트 스키마를 현재 스키마로 변환하는 변환 함수를 등록합니다. 애플리케이션 코드에서 최신 버전만 처리하도록 업캐스터를 연결할 수 있습니다. 저장된 이벤트는 변경되지 않은 상태로 유지되어 불변성을 유지합니다.
현재 위치 마이그레이션: 이벤트 저장소에서 기록된 이벤트를 새 스키마로 직접 다시 작성합니다. 이 방법은 불변성을 깨뜨리며 감사 내역을 훼손하기 때문에 최후의 수단이어야 합니다.
이벤트 순서: 다중 스레드 애플리케이션 및 여러 애플리케이션 인스턴스는 이벤트 저장소에 이벤트를 저장할 수 있습니다. 이벤트 저장소의 이벤트 일관성과 특정 엔터티의 현재 상태에 영향을 주는 이벤트의 순서는 매우 중요합니다. 모든 이벤트에 타임스탬프를 추가하면 문제를 방지하는 데 도움이 될 수 있습니다. 또 다른 일반적인 방법은 증분 식별자를 사용하여 요청에서 발생하는 각 이벤트에 주석을 추가하는 것입니다. 두 작업이 동일한 엔터티에 대한 이벤트를 동시에 추가하려고 하면 이벤트 저장소에서 기존 엔터티 식별자 및 이벤트 식별자와 일치하는 이벤트를 거부할 수 있습니다.
이벤트 쿼리: 정보를 얻기 위해 이벤트를 읽기 위한 표준 접근 방식이나 SQL 쿼리와 같은 기존 메커니즘은 없습니다. 추출할 수 있는 유일한 데이터는 이벤트 식별자를 조건으로 사용하여 이벤트 스트림입니다. 이벤트 ID는 일반적으로 개별 엔터티에 매핑됩니다. 엔터티와 관련된 모든 이벤트를 해당 엔터티의 원래 상태에 대해 재생해야만 엔터티의 현재 상태를 확인할 수 있습니다.
이벤트 저장소 옵션: 이벤트 저장소는 추가 전용 이벤트 스트림용으로 설계된 용도로 작성된 데이터베이스이거나 추가 전용 테이블이 있는 범용 관계형 또는 문서 데이터베이스일 수 있습니다.
특별히 빌드된 이벤트 저장소는 엔터티별 스트림 읽기, 낙관적 동시성 및 스냅샷과 같은 작업에 대한 기본 제공 지원을 제공합니다.
관계형 데이터베이스는 친숙하고 널리 사용할 수 있지만 이러한 동작을 직접 빌드해야 합니다.
각 엔터티에는 고유한 독립 이벤트 스트림이 있으므로 이벤트는 엔터티 ID별로 파티션을 자연스럽게 저장하므로 필요한 경우 수평 크기 조정 또는 분할이 간소화됩니다.
중요합니다
이벤트 저장소를 eventstream 메시지 브로커와 혼동하지 마세요. Apache Kafka와 같은 메시지 브로커는 일반적으로 엔터티별 스트림 쿼리 및 낙관적 동시성이 부족합니다. 프로젝션 및 외부 소비자에게 이벤트를 팬아웃하는 배포 계층으로 잘 작동하지만 이벤트 저장소를 대체하는 것은 아닙니다.
엔터티 상태 다시 만들기: 각 이벤트 스트림의 길이는 시스템을 관리하고 업데이트하는 방법에 영향을 줍니다. 스트림이 큰 경우, 모든 이벤트를 다시 재생하여 엔터티를 재구성하는 것은 시간과 컴퓨팅 리소스 모두에서 비용이 많이 듭니다. 이 비용을 완화하려면 모든 N 이벤트와 같은 특정 간격으로 스냅샷을 만듭니다. 스냅샷은 이벤트 스트림의 특정 지점에서 엔터티 상태를 직렬화한 표현입니다. 엔터티를 리하이드하려면 처음부터 전체 스트림을 재생하지 않고 가장 최근의 스냅샷을 로드하고 이후에 발생하는 이벤트만 재생합니다. 스냅샷 빈도를 선택하는 경우 스냅샷의 스토리지 비용을 리하이드레이션 중에 저장된 시간과 균형 조정합니다.
메모
스냅샷은 이벤트 스트림을 대체하는 것이 아니라 최적화입니다. 이벤트 스트림은 진실의 원본으로 남아 있으며 언제든지 스냅샷을 다시 생성할 수 있습니다.
충돌 처리: 낙관적 동시성 제어는 동일한 이벤트 스트림에 충돌하는 쓰기를 방지하지만 애플리케이션은 여전히 여러 엔터티에 걸쳐 있는 충돌을 처리해야 합니다. 예를 들어 재고가 감소했음을 나타내는 이벤트는 고객이 해당 항목에 대한 주문을 하는 동안 데이터 저장소에 도착할 수 있습니다. 고객에게 조언하거나 백 오더를 만드는 등 이러한 상황을 조정하도록 시스템을 디자인합니다.
Idempotency 요구 사항: 소비자에게 이벤트 배달은 일반적으로 한 번 이상이므로 소비자는 동일한 이벤트를 두 번 이상 받을 수 있습니다. 중복 이벤트를 처리해도 결과가 변경되지 않도록 이벤트 처리기는 idempotent여야 합니다. 예를 들어 사용 가능한 좌석 수를 유지하기 위해 소비자 프로세스가 좌석 예약 이벤트의 여러 인스턴스를 처리하는 경우, 중복된 예약 이벤트는 결과로 하나만 감소해야 합니다. 멱등성이 없으면 프로젝션이 이벤트 스트림에서 벗어나고, 결제 또는 알림과 같은 부작용이 여러 번 트리거됩니다. 각 소비자에 대해 마지막으로 처리된 이벤트 시퀀스 번호를 추적하고 중복을 건너뛰거나 본질적으로 반복하기에 안전한 상태 변형을 디자인합니다.
순환 논리: 한 이벤트를 처리하려면 하나 이상의 새 이벤트를 생성해야 하는 시나리오를 염두에 두어야 합니다. 이 시퀀스는 무한 루프가 될 수 있습니다.
테스트: 특정 테스트 스타일은 이벤트 기반 시스템에 가장 적합합니다. 과거 이벤트를 설정하고, 명령을 실행하고, 생성된 새 이벤트에 대해 어설션합니다. 이 지정된 시기 접근 방식은 데이터베이스, 큐 또는 프로젝션 없이 비즈니스 논리를 테스트합니다. 하지만 프로젝션, 멱등성 동작 및 스키마 진화 경로에 대한 통합 테스트도 필요합니다. 그러면 CRUD 시스템에 비해 테스트 화면이 추가됩니다.
개인 데이터 및 규정 준수: 이벤트 저장소의 변경 불가능한 추가 전용 특성은 잊혀질 수 있는 권리와 같은 개인 데이터를 삭제해야 하는 데이터 보호 규정과 충돌합니다. 이벤트를 완전히 삭제하면 스트림 무결성이 완전히 깨지므로 처음부터 이 긴장을 위해 디자인합니다.
일반적인 방법은 이벤트 저장소 외부에 개인 데이터를 저장하고 이벤트의 식별자를 통해 참조하는 것입니다. 이 방법을 사용하면 이벤트 스트림에 영향을 주지 않고도 삭제가 독립적으로 수행될 수 있습니다.
개인 데이터를 이벤트와 구분할 수 없는 경우 암호화 분할을 사용합니다. 주체별 키를 사용하여 이벤트에서 개인 데이터를 암호화합니다. 이벤트 구조를 그대로 유지하면서 데이터를 복구할 수 없도록 키를 삭제합니다. 이 방법은 모든 읽기 및 쓰기에 암호화 오버헤드를 추가하고 강력한 키 관리가 필요합니다.
이 패턴을 사용하는 경우
다음 경우에 이 패턴을 사용합니다.
데이터의 의도, 목적 또는 이유를 캡처하려고 합니다. 예를 들어 고객 엔터티에 대한 변경 내용을 이동된 홈, 폐쇄된 계정 또는 고인과 같은 일련의 특정 이벤트 유형으로 캡처할 수 있습니다.
데이터 업데이트가 충돌하는 것을 최소화하거나 완전히 피해야 합니다.
발생하는 이벤트를 기록하거나, 재생하여 시스템 상태를 복원하거나, 변경 내용을 롤백하거나, 기록 및 감사 로그를 유지하려고 합니다. 예를 들어 태스크가 여러 단계로 구성된 경우 업데이트를 되돌리는 작업을 실행한 다음 일부 단계를 재생하여 데이터를 일관된 상태로 되돌려야 할 수 있습니다.
애플리케이션은 이미 이벤트를 작업의 자연스러운 기능으로 사용하고 있으며 이벤트 소싱에는 추가 개발 또는 구현 작업이 거의 필요하지 않습니다.
이러한 작업을 적용하는 데 필요한 작업에서 데이터를 입력하거나 업데이트하는 프로세스를 분리해야 합니다. 이 변경 내용은 UI 성능을 향상시키거나 이벤트가 발생할 때 작동하는 다른 수신기에 이벤트를 배포하는 것일 수 있습니다. 예를 들어 급여 시스템을 비용 제출 웹 사이트와 통합할 수 있습니다. 웹 사이트와 급여 시스템 모두 웹 사이트에서 업데이트된 데이터에 대한 응답으로 이벤트 저장소가 발생시키는 이벤트를 사용합니다.
요구 사항이 변경되거나 CQRS를 사용하고 데이터를 노출하는 읽기 모델 또는 뷰를 조정해야 하는 경우 구체화된 모델 및 엔터티 데이터의 형식을 유연하게 변경하려고 합니다.
CQRS를 사용하면 읽기 모델이 업데이트되는 동안 최종 일관성이 수용 가능하거나, 이벤트 스트림에서 엔터티와 데이터를 리하드레이션할 때 수용 가능한 수준의 성능 저하가 발생할 수 있습니다.
이 패턴은 다음과 같은 경우에 적합하지 않을 수 있습니다.
시스템에는 상태의 감사 가능성, 재생 또는 기록 재구성이 필요하지 않은 간단한 CRUD 작업이 있습니다. 유일한 요구 사항이 현재 상태 읽기 및 쓰기인 경우 이벤트 저장소의 운영 오버헤드는 정당화되지 않습니다.
프로토타입, MVP(최소 실행 가능한 제품) 또는 시스템에는 예상 수명이 짧습니다. 이벤트 디자인, 스키마 진화 전략 및 프로젝션 인프라에 대한 선행 투자는 이러한 시나리오에서 거의 수익을 내지 않습니다.
시스템에서는 데이터 보기에 대한 일관성 및 실시간 업데이트가 필요합니다. 이벤트 저장소와 프로젝션 간의 최종 일관성은 이벤트 소싱에 내재되어 있습니다.
데이터가 대부분 정적이거나 조회 테이블 또는 카탈로그와 같은 참조용인 도메인입니다. 이러한 유형의 데이터는 자주 변경되지 않으며 변경 기록의 이점을 얻지 못합니다.
Teams는 이벤트 기반 아키텍처에 대한 경험이 없습니다. 이벤트 소싱은 시스템을 테스트, 디버그 및 운영하는 방법을 변경합니다. 기본 지식없이 채택하면 반대로 비용이 많이 드는 안티패턴의 위험이 증가합니다.
팁 (조언)
이벤트 소싱은 전체 시스템에 대해 모두 적용할 필요는 없습니다. 결제 원장 또는 주문 처리 파이프라인과 같이 가장 많은 혜택을 주는 시스템 부분에 선택적으로 적용합니다. 사용자 프로필 관리 또는 애플리케이션 구성과 같이 복잡성이 정당화되지 않는 경우 기존 CRUD를 파트에 사용합니다.
워크로드 디자인
워크로드 디자인에서 이벤트 소싱 패턴을 사용하여 Azure Well-Architected Framework 핵심 요소 다루는 목표와 원칙을 해결하는 방법을 평가합니다. 다음 표에서는 이 패턴이 각 핵심 요소의 목표를 지원하는 방법에 대한 지침을 제공합니다.
| 핵심 요소 | 이 패턴으로 핵심 목표를 지원하는 방법 |
|---|---|
| 안정성 설계 결정을 통해 워크로드가 오작동에 대한 복원력을 높일 수 있으며 오류가 발생한 후 완전히 작동하는 상태로 복구 되도록 할 수 있습니다. | 이 패턴은 복잡한 비즈니스 프로세스의 변경 기록을 캡처하기 때문에 상태 저장소를 복구해야 하는 경우 상태 재구성을 용이하게 할 수 있습니다. - 데이터 분할 - RE:09 재해 복구 |
| 성능 효율성은 크기 조정, 데이터 및 코드의 최적화를 통해 워크로드가 수요를 효율적으로 충족 하는 데 도움이 됩니다. | 일반적으로 CQRS, 적절한 도메인 디자인 및 전략적 스냅샷과 결합된 이 패턴은 원자성 추가 전용 작업 및 쓰기 및 읽기에 대한 데이터베이스 잠금 방지로 인해 워크로드 성능을 향상시킬 수 있습니다. - PE:08 데이터 성능 |
이 패턴이 하나의 기둥 내에서 절충을 도입하는 경우, 이를 다른 기둥의 목표와 비교해서 고려해 보세요.
예시
컨퍼런스 관리 시스템은 완료된 컨퍼런스 예약 수를 추적해야 합니다. 이 숫자를 추적하여 잠재적 참석자가 예약을 시도할 때 사용 가능한 좌석을 확인할 수 있습니다. 시스템은 적어도 두 가지 방법으로 컨퍼런스의 총 예약 수를 저장할 수 있습니다.
시스템은 총 예약 수에 대한 정보를 예약 정보를 보유하는 데이터베이스에 별도의 엔터티로 저장할 수 있습니다. 참석자가 예약을 하거나 취소하면 시스템에서 이 수를 늘리거나 줄입니다. 이 방법은 이론적으로는 간단하지만 많은 참석자가 짧은 기간 동안 좌석을 예약하려고 하면 확장성 문제가 발생할 수 있습니다. 예를 들어 이 서지는 일반적으로 예약 기간이 종료되기 전 마지막 날에 발생합니다.
시스템은 이벤트 저장소에서 개최되는 이벤트로 예약 및 취소에 대한 정보를 저장할 수 있습니다. 이러한 이벤트를 재생하여 사용 가능한 좌석 수를 계산합니다. 이 방법은 이벤트의 불변성으로 인해 확장성이 향상될 수 있습니다. 시스템은 이벤트 저장소에서 데이터를 읽거나 이벤트 저장소에 데이터를 추가해야 합니다. 예약 및 취소에 대한 이벤트 정보를 수정하지 않습니다.
다음 다이어그램에서는 이벤트 소싱을 사용하여 회의 관리 시스템의 좌석 예약 하위 시스템을 구현하는 방법을 보여 줍니다.
이 아키텍처의 Visio 파일을 다운로드합니다.
워크플로
다음 워크플로는 이전 다이어그램에 해당합니다.
UI는 두 명의 참석자에 대한 좌석을 예약하는 명령을 실행합니다. 별도의 명령 처리기가 명령을 처리합니다. 명령 처리기는 UI에서 분리되고 명령으로 게시된 요청을 처리하는 논리의 한 조각입니다.
시스템은 예약 및 취소를 설명하는 이벤트를 재생하여 회의의 모든 예약에 대한 정보를 포함하는 엔터티를 생성합니다. 이 엔터티는 호출
SeatAvailability되며 엔터티의 데이터를 쿼리하고 수정하기 위한 메서드를 노출하는 도메인 모델 내에 포함됩니다.팁 (조언)
엔터티의 현재 상태를 얻기 위해 이벤트의 전체 목록을 재생할 필요가 없도록 스냅샷과 같은 최적화를 고려합니다. 스냅샷은 메모리에 있는 엔터티의 캐시된 복사본도 유지 관리합니다.
명령 처리기는 도메인 모델이 예약을 위해 노출하는 메서드를 호출합니다.
SeatAvailability엔터티는 예약된 좌석 수를 포함하는 이벤트를 발생시킵니다. 다음에 엔터티가 이벤트를 적용할 때, 모든 예약을 사용하여 남은 좌석 수를 계산합니다.시스템이 이벤트 저장소의 이벤트 목록에 새 이벤트를 추가합니다.
사용자가 좌석을 취소하는 경우 시스템은 유사한 프로세스를 따르지만 명령 처리기는 좌석 취소 이벤트를 생성하고 이벤트 저장소에 추가하는 명령을 실행합니다.
시스템은 이벤트 저장소를 사용하여 회의 예약 및 취소에 대한 전체 기록 또는 감사 내역을 제공할 수 있습니다. 이벤트 저장소의 이벤트는 신뢰할 수 있는 기록입니다. 시스템이 이벤트를 쉽게 재생하고 상태를 특정 시점으로 복원할 수 있으므로 다른 방법으로 엔터티를 유지할 필요가 없습니다.
다음 단계:
- CQRS 패턴: CQRS 구현에 대한 영구 정보 원본을 제공하는 쓰기 저장소는 일반적으로 이벤트 소싱 패턴의 구현을 기반으로 합니다. 이 패턴은 별도의 인터페이스를 사용하여 데이터를 업데이트하는 작업과 애플리케이션의 데이터를 읽는 작업을 분리합니다.
커뮤니티 리소스
개체 관계형 임피댄스 불일치입니다.
이벤트 소싱, 마틴 파울러가 쓴: 2005년에 발표된 이 패턴의 원래 설명은 기본 어휘를 만들었습니다.
CQRS 문서(PDF), Greg Young: 두 패턴을 공식화한 실무자의 이벤트 소싱 및 CQRS에 대한 최종 리소스입니다.
관련 리소스
다음 패턴 및 지침은 이 패턴을 구현할 때도 관련이 있을 수 있습니다.
구체화된 뷰 패턴: 이벤트 소싱 시스템에서 사용하는 데이터 저장소는 일반적으로 효율적인 쿼리에 적합하지 않습니다. 대신 일반적인 방법은 정기적으로 또는 데이터가 변경될 때 데이터의 미리 채워진 뷰를 생성하는 것입니다.
보상 트랜잭션 패턴: 시스템은 이벤트 소싱 저장소의 기존 데이터를 업데이트하지 않습니다. 대신 엔터티의 상태를 새 값으로 전환하는 새 항목을 추가합니다. 변경을 되돌리기 위해 이전 변경 내용은 되돌릴 수 없으므로 보상 항목을 사용합니다. 보상 트랜잭션 패턴 문서에서는 이전 작업에서 수행한 작업을 실행 취소하는 방법을 설명합니다.
마이크로 서비스에 대한 도메인 분석: DDD(도메인 기반 디자인)를 사용하는 시스템에서 Eventstream을 소유하는 엔터티는 일반적으로 명령을 수신하고, 비즈니스 규칙을 적용하고, 이벤트를 내보내는 일관성 경계인 집계입니다.