이벤트 소싱 패턴

예약

데이터의 현재 상태만 도메인에 저장하는 대신 추가 전용 저장소를 사용하여 해당 데이터에 수행된 전체 작업을 기록합니다. 저장소는 레코드 시스템 역할을 하며, 도메인 개체를 구체화하는 데 사용될 수 있습니다. 이렇게 하면 데이터 모델과 비즈니스 도메인을 동기화할 필요가 없고 성능, 확장성 및 응답성이 향상되므로 복잡한 도메인의 태스크를 간소화할 수 있습니다. 또한 트랜잭션 데이터에 일관성을 제공하고 보정 작업에 사용할 수 있는 전체 감사 추적 및 기록을 유지할 수 있습니다.

컨텍스트 및 문제점

대부분의 애플리케이션은 데이터를 사용하며, 일반적인 접근 방법은 사용자가 데이터로 작업할 때 애플리케이션이 데이터의 현재 상태를 업데이트하여 유지 관리하는 것입니다. 예를 들어 기존의 CRUD(만들기, 읽기, 업데이트 및 삭제) 모델에서 일반적인 데이터 프로세스는 저장소에서 데이터를 읽고 수정한 다음, 주로 데이터를 잠그는 트랜잭션을 사용하여 데이터의 현재 상태를 새 값으로 업데이트하는 것입니다.

CRUD 접근 방법에는 다음과 같은 몇 가지 제한 사항이 있습니다.

  • CRUD 시스템은 데이터 저장소에 대해 직접 업데이트 작업을 수행합니다. 이러한 작업으로 성능과 응답이 느려질 수 있으며 필요한 처리 오버헤드로 인해 확장성을 제한할 수 있습니다.

  • 많은 동시 사용자가 있는 공동 작업 도메인에서 데이터 업데이트 충돌은 대체로 업데이트 작업이 단일 데이터 항목에서 수행되기 때문에 발생합니다.

  • 별도의 로그에 각 작업의 세부 정보를 기록하는 다른 감사 메커니즘이 없으면 기록이 손실됩니다.

해결 방법

이벤트 소싱 패턴은 각각 추가 전용 저장소에 기록되는 일련의 이벤트에 의해 구동되는 데이터 작업 처리 방법을 정의합니다. 애플리케이션 코드가 데이터에서 수행된 각 작업을 명확하게 설명하는 일련의 이벤트를 이벤트 저장소로 보내며, 이벤트는 여기에 저장됩니다. 각 이벤트는 데이터에 대한 변경 집합(예: AddedItemToOrder)을 나타냅니다.

이벤트는 데이터의 현재 상태에 대한 레코드 시스템(권한 있는 데이터 원본) 역할을 하는 이벤트 저장소에 저장됩니다. 이벤트 저장소는 일반적으로 이 이벤트를 게시하여 소비자에게 알리고 필요한 경우 처리할 수 있도록 합니다. 예를 들어 소비자는 이벤트의 작업을 다른 시스템에 적용하는 태스크를 시작하거나 작업을 완료하는 데 필요한 기타 관련 작업을 수행할 수 있습니다. 이벤트를 생성하는 애플리케이션 코드와 이벤트를 구독하는 시스템은 분리됩니다.

이벤트 저장소에서 게시된 이벤트는 일반적으로 애플리케이션의 작업이 엔터티를 변경할 때 엔터티의 구체화된 뷰를 유지 관리하고 외부 시스템과 통합하는 데 사용됩니다. 예를 들어 시스템에서 UI 부분을 채우는 데 사용되는 모든 고객 주문의 구체화된 뷰를 유지 관리할 수 있습니다. 애플리케이션은 새 주문을 추가하고, 주문에 품목을 추가하거나 제거하고, 배송 정보를 추가합니다. 이러한 변경 내용을 설명하는 이벤트를 처리하고 구체화된 뷰를 업데이트하는 데 사용할 수 있습니다.

언제든지 애플리케이션에서 이벤트 기록을 읽을 수 있습니다. 그런 다음, 이 기록을 통해 해당 엔터티와 관련된 모든 이벤트를 재생하고 사용하여 엔터티의 현재 상태를 구체화할 수 있습니다. 이 프로세스는 요청을 처리할 때 도메인 개체를 구체화하기 위해 필요에 따라 발생할 수 있습니다. 그렇지 않은 경우, 엔터티 상태를 구체화된 뷰로 저장하여 프레젠테이션 레이어를 지원하도록 예약된 태스크를 통해 프로세스가 발생합니다.

이 그림은 구체화된 뷰 만들기, 외부 애플리케이션 및 시스템과 이벤트 통합, 이벤트를 재생하여 특정 엔터티의 현재 상태 프로젝션 만들기 등의 몇 가지 이벤트 스트림 사용 옵션을 포함하여 패턴 개요를 보여 줍니다.

이벤트 소싱 패턴의 개요 및 예

이벤트 소싱 패턴에는 다음과 같은 장점이 있습니다.

  • 이벤트를 변경할 수 없으며 추가 전용 작업을 사용하여 저장할 수 있습니다. 이벤트를 시작한 사용자 인터페이스, 워크플로 또는 프로세스가 계속 진행될 수 있고, 이벤트를 처리하는 태스크를 백그라운드에서 실행할 수 있습니다. 이 프로세스는 트랜잭션 처리 중에 경합이 없다는 사실과 함께 특히 애플리케이션의 프레젠테이션 수준 또는 사용자 인터페이스에서 성능과 확장성을 크게 향상시킬 수 있습니다.

  • 이벤트는 이벤트가 나타내는 작업을 설명하는 데 필요한 관련 데이터와 함께 수행된 일부 작업을 설명하는 단순 개체입니다. 이벤트는 데이터 저장소를 직접 업데이트하지 않습니다. 단순히 적절한 시간에 처리하기 위해 기록됩니다. 이벤트를 사용하면 구현 및 관리를 간소화할 수 있습니다.

  • 이벤트는 일반적으로 도메인 전문가에게 의미가 있는 반면, 개체 관계형 임피던스 불일치로 인해 복잡한 데이터베이스 테이블을 이해하기 어려울 수 있습니다. 테이블은 발생한 이벤트가 아니라 시스템의 현재 상태를 나타내는 인공 구문입니다.

  • 이벤트 소싱을 사용하면 데이터 저장소의 개체를 직접 업데이트하지 않아도 되기 때문에 동시 업데이트로 인한 충돌을 방지할 수 있습니다. 그러나 도메인 모델이 상태 불일치를 초래할 수 있는 요청으로부터 보호되도록 디자인해야 합니다.

  • 추가 전용 이벤트 스토리지에서 제공하는 감사 추적을 사용하면 데이터 스토리지에 대해 수행된 작업을 모니터할 수 있습니다. 언제든지 이벤트를 재생하여 현재 상태를 구체화된 뷰 또는 프로젝션으로 다시 생성하고, 시스템 테스트 및 디버깅을 지원할 수 있습니다. 또한 보상 이벤트를 사용하여 변경을 취소해야 하는 요구 사항에는 취소된 변경 기록이 제공됩니다. 모델이 현재 상태를 저장한 경우에는 이 기능을 사용할 수 없습니다. 이벤트 목록을 사용하여 애플리케이션 성능을 분석하고 사용자 동작 추세를 검색할 수도 있습니다. 또는 기타 유용한 사업 정보를 가져올 수 있습니다.

  • 이벤트 저장소는 이벤트를 발생하고, 태스크는 이러한 이벤트에 대한 응답으로 작업을 수행합니다. 이렇게 태스크와 이벤트를 분리하면 유연성과 확장성이 제공됩니다. 태스크는 이벤트 유형과 이벤트 데이터를 알고 있지만 이벤트를 트리거한 작업에 대해서는 알 수 없습니다. 또한 여러 태스크가 각 이벤트를 처리할 수 있습니다. 이 경우 이벤트 저장소에서 발생하는 새 이벤트만 수신 대기하는 다른 서비스 및 시스템과 쉽게 통합할 수 있습니다. 그러나 이벤트 소싱 이벤트는 대체로 매우 낮은 수준이며, 대신 특정 통합 이벤트를 생성해야 할 수도 있습니다.

이벤트 소싱은 일반적으로 이벤트에 대한 응답으로 데이터 관리 태스크를 수행하고 저장된 이벤트에서 뷰를 구체화하여 CQRS 패턴과 결합됩니다.

문제 및 고려 사항

이 패턴을 구현할 방법을 결정할 때 다음 사항을 고려하세요.

구체화된 뷰를 만들거나 이벤트를 재생하여 데이터 프로젝션을 생성할 때만 시스템이 일치합니다. 요청을 처리하는 결과로 애플리케이션이 이벤트 저장소에 이벤트를 추가하는 과정과 이벤트를 게시하고 이벤트 소비자가 이벤트를 처리하는 과정 사이에 약간의 지연이 있습니다. 이 기간 동안 엔터티의 추가 변경 내용을 설명하는 새 이벤트가 이벤트 저장소에 도착했을 수 있습니다. 시스템은 이러한 시나리오에서 최종 일관성을 고려하도록 설계되어야 합니다.

참고 항목

결과적 일관성에 대한 자세한 내용은 데이터 일관성 입문서를 참조하세요.

이벤트 저장소는 영구적인 정보 소스이므로 이벤트 데이터가 업데이트되면 안 됩니다. 변경 내용을 취소하기 위해 엔터티를 업데이트하는 유일한 방법은 이벤트 저장소에 보정 이벤트를 추가하는 것입니다. 지속형 이벤트의 형식(데이터 아님)을 변경해야 하는 경우, 마이그레이션 중에 저장소의 기존 이벤트를 새 버전과 결합하기 어려울 수 있습니다. 새 형식을 준수하도록 모든 이벤트를 반복해서 변경하거나 새 형식을 사용하는 새 이벤트를 추가해야 할 수도 있습니다. 이벤트 스키마의 각 버전에 버전 스탬프를 사용하여 이전 이벤트 형식과 새 이벤트 형식을 둘 다 유지 관리하는 것이 좋습니다.

다중 스레드 애플리케이션과 여러 애플리케이션 인스턴스가 이벤트 저장소에 이벤트를 저장할 수 있습니다. 이벤트 저장소의 이벤트 일관성은 특정 엔터티에 영향을 주는 이벤트 순서와 마찬가지로 중요합니다(엔터티에 대한 변경 순서는 현재 상태에 영향을 줌). 모든 이벤트에 타임스탬프를 추가하면 문제를 방지할 수 있습니다. 또 다른 일반적인 방법은 요청에서 발생하는 각 이벤트에 증분 식별자를 주석으로 추가하는 것입니다. 두 작업이 동일한 엔터티에 대한 이벤트를 동시에 추가하려고 하면 이벤트 저장소에서 기존 엔터티 식별자 및 이벤트 식별자와 일치하는 이벤트를 거부할 수 있습니다.

정보를 얻기 위해 이벤트를 읽는 표준 방법이나 SQL 쿼리와 같은 기존 메커니즘은 없습니다. 추출할 수 있는 데이터는 이벤트 식별자를 기준으로 사용하는 이벤트 스트림뿐입니다. 이벤트 ID는 일반적으로 개별 엔터티에 매핑됩니다. 엔터티의 현재 상태는 관련된 모든 이벤트를 해당 엔터티의 원래 상태에 대해 재생해야만 확인할 수 있습니다.

각 이벤트 스트림의 길이는 시스템 관리 및 업데이트에 영향을 줍니다. 스트림이 큰 경우 지정된 이벤트 수 등의 특정 간격으로 스냅샷을 만드는 것이 좋습니다. 스냅샷에서 해당 시점 이후에 발생한 이벤트를 재생하면 엔터티의 현재 상태를 확인할 수 있습니다. 데이터 스냅샷 만들기에 대한 자세한 내용은 기본-하위 스냅샷 복제를 참조하세요.

이벤트 소싱이 데이터 업데이트의 충돌 가능성을 최소화하더라도 애플리케이션이 결과적 일관성과 트랜잭션 부족에서 발생하는 불일치를 처리할 수 있어야 합니다. 예를 들어 고객이 해당 품목을 주문하는 동안 재고 감소를 알리는 이벤트가 데이터 저장소에 도착할 수 있습니다. 이 경우 고객에게 안내하거나 이월 주문을 만들어 두 작업을 조정해야 하는 요구 사항이 발생합니다.

이벤트 게시는 최소한 한 번일 수 있으므로 이벤트 소비자가 멱등적이어야 합니다. 이벤트가 두 번 이상 처리되는 경우 이벤트에 설명된 업데이트를 다시 적용하면 안 됩니다. 소비자의 여러 인스턴스는 완료된 총 주문 수와 같은 엔터티의 속성을 유지 관리하고 집계할 수 있습니다. 주문 완료 이벤트가 발생하면 한 인스턴스만 집계가 증가해야 합니다. 이 결과가 이벤트 소싱의 주요 특성은 아니지만 일반적인 구현 결정입니다.

선택한 이벤트 스토리지는 애플리케이션에서 생성된 이벤트 부하를 지원해야 합니다.
하나의 이벤트를 처리할 때는 무한 루프가 발생할 수 있으므로 하나 이상의 새 이벤트를 만드는 시나리오를 염두에 두어야 합니다.

이 패턴을 사용해야 하는 경우

다음 시나리오에서 이 패턴을 사용하세요.

  • 데이터에 의도, 목적 또는 이유를 캡처하려는 경우. 예를 들어 고객 엔터티의 변경 내용을 이사, 계정 폐쇄 또는 사망과 같은 일련의 특정 이벤트 유형으로 캡처할 수 있습니다.

  • 데이터 업데이트 충돌 발생을 최소화하거나 완전히 방지하는 것이 중요한 경우.

  • 발생하는 이벤트를 기록하거나, 재생하여 시스템 상태를 복원하거나, 변경 내용을 롤백하거나, 기록 및 감사 로그를 저장하려는 경우. 예를 들어 태스크에 여러 단계가 필요한 경우 업데이트를 되돌리는 작업을 실행한 후 일부 단계를 재생하여 데이터를 일관된 상태로 되돌려야 할 수도 있습니다.

  • 이벤트를 사용하는 경우. 이는 작동하는 애플리케이션의 기본 기능이며 별도의 개발 또는 구현 작업이 거의 필요하지 않습니다.

  • 데이터 입력 또는 업데이트 프로세스와 이러한 작업을 적용하는 데 필요한 태스크를 분리해야 하는 경우. 이러한 변경은 UI 성능을 높이거나 이벤트 발생 시 작업을 수행하는 다른 수신기에 이벤트를 분배하기 위한 것일 수 있습니다. 예를 들어 급여 시스템을 비용 제출 웹 사이트와 통합할 수 있습니다. 웹 사이트의 데이터 업데이트에 대한 응답으로 이벤트 저장소에서 발생하는 이벤트가 웹 사이트와 급여 시스템 모두에서 사용됩니다.

  • 요구 사항 변경에 따라 구체화된 모델 및 엔터티 데이터의 형식을 유연하게 변경하기 원하거나 CQRS와 함께 사용할 때 읽기 모델 또는 데이터를 표시하는 뷰를 조정해야 하는 경우.

  • CQRS와 함께 사용할 때 읽기 모델이 업데이트되는 동안 최종 일관성이 허용되거나 이벤트 스트림에서 엔터티와 데이터를 리하이드레이션할 때의 성능 영향이 허용되는 경우.

이 패턴은 다음과 같은 경우에 유용하지 않을 수 있습니다.

  • 작거나 단순한 도메인, 비즈니스 논리가 거의 없거나 전혀 없는 시스템 또는 기존의 CRUD 데이터 관리 메커니즘에서 기본적으로 잘 작동하는 비도메인 시스템.

  • 데이터 뷰에 대한 일관성 및 실시간 업데이트가 필요한 시스템.

  • 감사 추적, 기록, 작업 롤백 및 재생 기능이 필요하지 않은 시스템.

  • 기본 데이터에 대한 업데이트 충돌이 거의 발생하지 않는 시스템. 예를 들어 데이터를 업데이트하지 않고 주로 추가하는 시스템입니다.

워크로드 디자인

설계자는 이벤트 소싱 패턴을 워크로드 디자인에 사용하여 Azure Well-Architected Framework 핵심 요소에서 다루는 목표와 원칙을 해결하는 방법을 평가해야 합니다. 예시:

핵심 요소 이 패턴이 핵심 목표를 지원하는 방법
안정성 디자인 결정은 워크로드가 오작동에 대한 복원력을 갖도록 하고 오류가 발생한 후 완전히 작동하는 상태로 복구 되도록 하는 데 도움이 됩니다. 복잡한 비즈니스 프로세스의 변경 기록을 캡처하기 때문에 상태 저장소를 복구해야 하는 경우 상태 재구성을 용이하게 할 수 있습니다.

- RE:06 데이터 분할
- RE:09 재해 복구
성능 효율성은 크기 조정, 데이터, 코드의 최적화를 통해 워크로드가 수요를 효율적으로 충족하는 데 도움이 됩니다. 일반적으로 CQRS와 결합된 이 패턴은 적절한 기본 디자인 및 전략적 스냅샷 통해 원자성 추가 전용 작업 및 쓰기 및 읽기에 대한 데이터베이스 잠금 방지로 인해 워크로드 성능을 향상시킬 수 있습니다.

- PE:08 데이터 성능

디자인 결정과 마찬가지로 이 패턴으로 도입될 수 있는 다른 핵심 요소의 목표에 대한 절충을 고려합니다.

예시

컨퍼런스 관리 시스템은 완료된 컨퍼런스 예약 수를 추적해야 합니다. 이를 통해 잠재적 참석자는 예약을 시도하면서 남은 자리가 있는지 확인할 수 있습니다. 다음과 같이 총 회의 예약 수를 둘 이상의 방법으로 저장할 수 있습니다.

  • 예약 정보를 보유하는 데이터베이스에 총 예약 수에 대한 정보를 별도의 엔터티로 저장할 수 있습니다. 예약이 완료되거나 취소되면 시스템이 이 개수를 적절하게 증분하거나 감소할 수 있습니다. 이 접근 방법은 이론상 간단하지만, 짧은 기간 동안 많은 참석자가 좌석을 예약하려고 시도할 경우 확장성 문제가 발생할 수 있습니다. 예를 들어 예약 기간 마감 전날 등에 발생합니다.

  • 시스템이 예약 및 취소 정보를 이벤트 저장소에 보류된 이벤트로 저장할 수 있습니다. 그런 다음 이 이벤트를 재생하여 사용 가능한 좌석 수를 계산할 수 있습니다. 이 접근 방법은 이벤트를 변경할 수 없기 때문에 더 확장성이 있습니다. 시스템이 이벤트 저장소에서 데이터를 읽거나 이벤트 저장소에 데이터를 추가할 수 있기만 하면 됩니다. 예약 및 취소에 대한 이벤트 정보는 수정되지 않습니다.

다음 다이어그램은 이벤트 소싱을 사용하여 회의 관리 시스템의 좌석 예약 하위 시스템을 구현할 수 있는 방법을 보여 줍니다.

이벤트 소싱을 사용하여 회의 관리 시스템에서 좌석 예약 정보 캡처

두 개의 좌석을 예약하기 위한 작업 시퀀스는 다음과 같습니다.

  1. 사용자 인터페이스가 두 명의 참석자를 위한 좌석을 예약하는 명령을 실행합니다. 명령은 별도의 명령 처리기에 의해 처리됩니다. 사용자 인터페이스에서 분리되고 명령으로 게시된 요청을 처리하는 논리 부분입니다.

  2. 예약 및 취소를 설명하는 이벤트를 쿼리하여 회의에 대한 모든 예약 정보를 포함하는 집계가 생성됩니다. 이 집계는 SeatAvailability라고 하며, 집계의 데이터를 쿼리하고 수정하기 위한 메서드를 노출하는 도메인 모델에 포함됩니다.

    고려할 몇 가지 최적화는 스냅샷 사용(집계의 현재 상태를 얻기 위해 전체 이벤트 목록을 쿼리 및 재생할 필요가 없음) 및 메모리에 캐시된 집계 사본 유지 관리입니다.

  3. 명령 처리기는 도메인 모델에서 노출된 메서드를 호출하여 예약을 수행합니다.

  4. SeatAvailability 집계는 예약된 좌석 수를 포함하는 이벤트를 기록합니다. 다음에 집계가 이벤트를 적용하면 모든 예약이 남아 있는 좌석 수를 계산하는 데 사용됩니다.

  5. 시스템이 이벤트 저장소의 이벤트 목록에 새 이벤트를 추가합니다.

사용자가 좌석을 취소하는 경우 명령 처리기가 좌석 취소 이벤트를 생성하고 이벤트 저장소에 추가하는 명령을 사용한다는 점을 제외하고 시스템이 유사한 프로세스를 따릅니다.

이벤트 저장소를 사용하면 확장성의 범위가 더 넓어질 뿐 아니라 컨퍼런스 예약 및 취소의 전체 기록 또는 감사 추적도 제공됩니다. 이벤트 저장소의 이벤트는 정확한 레코드입니다. 시스템이 쉽게 이벤트를 재생하고 상태를 특정 시점으로 복원할 수 있으므로 다른 방법으로 집계를 유지할 필요가 없습니다.

이 예제에 대한 정보는 이벤트 소싱 소개에서 확인할 수 있습니다.

다음 단계

이 패턴을 구현할 때 다음 패턴 및 지침도 관련이 있을 수 있습니다.

  • CQRS(명령 및 쿼리 책임 분리). CQRS 구현에 대한 영구적 정보 소스를 제공하는 쓰기 저장소는 종종 이벤트 소싱 패턴 구현을 기반으로 합니다. 개별 인터페이스를 사용하여 애플리케이션의 데이터를 읽는 작업과 데이터를 업데이트하는 작업을 분리하는 방법을 설명합니다.

  • 구체화된 뷰 패턴. 이벤트 소싱을 기반으로 하는 시스템에서 사용되는 데이터 저장소는 일반적으로 효율적인 쿼리에 적합하지 않습니다. 대신, 일반적인 접근 방법은 정기적인 간격으로 또는 데이터가 변경될 때 미리 채워진 데이터 뷰를 생성하는 것입니다.

  • 보상 트랜잭션 패턴. 이벤트 소싱 저장소의 기존 데이터는 업데이트되지 않습니다. 대신 엔터티 상태를 새 값으로 변환하는 새 항목이 추가됩니다. 이전 변경은 되돌릴 수 없으므로 변경을 되돌리기 위해서는 보정 항목이 사용됩니다. 이전 작업에서 수행한 작업을 실행 취소하는 방법을 설명합니다.