이벤트 소싱 패턴

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

컨텍스트 및 문제점

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

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

  • CRUD 시스템은 데이터 저장소에서 직접 업데이트 작업을 수행하므로 성능 및 응답 속도가 저하되고 필요한 처리 오버헤드로 인해 확장성이 제한될 수 있습니다.

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

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

CRUD 접근 방식의 제한에 대한 자세한 내용은 CRUD, Only When You Can Afford It(CRUD, 사용 가능한 경우에만)을 참조하세요.

해결 방법

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

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

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

또한 언제든지 응용 프로그램이 이벤트 기록을 읽고 해당 엔터티와 관련된 모든 이벤트를 재생 및 이용하여 엔터티의 현재 상태를 구체화하는 데 사용할 수 있습니다. 이 작업은 프레젠테이션 레이어를 지원하기 위해 엔터티 상태를 구체화된 뷰로 저장할 수 있도록 예약된 태스크를 통해 또는 요청을 처리할 때 도메인 개체 구체화 요청 시 수행될 수 있습니다.

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

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

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

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

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

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

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

  • 추가 전용 이벤트 저장소에서 제공하는 감사 추적을 사용하면 데이터 저장소에 대해 수행된 작업을 모니터하고, 언제든지 이벤트를 재생하여 현재 상태를 구체화된 뷰 또는 프로젝션으로 재생성하고, 시스템 테스트 및 디버그를 지원할 수 있습니다. 또한 변경 내용을 취소하기 위해 보정 이벤트를 사용해야 하므로 모델이 현재 상태만 저장하는 경우와 달리 취소된 변경 기록이 제공됩니다. 이벤트 목록을 사용하여 응용 프로그램 성능을 분석하고 사용자 행동 경향을 검색하거나 기타 유용한 비즈니스 정보를 얻을 수도 있습니다.

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

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

문제 및 고려 사항

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

구체화된 뷰를 만들거나 이벤트를 재생하여 데이터 프로젝션을 생성할 때만 시스템이 일치합니다. 요청 처리의 결과로 응용 프로그램이 이벤트 저장소에 이벤트 추가, 이벤트 게시 및 이벤트 소비자의 이벤트 처리 사이에 약간의 지연이 있습니다. 이 기간 동안 엔터티의 추가 변경 내용을 설명하는 새 이벤트가 이벤트 저장소에 도착했을 수 있습니다.

참고

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

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

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

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

각 이벤트 스트림의 길이는 시스템 관리 및 업데이트에 영향을 줍니다. 스트림이 큰 경우 지정된 이벤트 수 등의 특정 간격으로 스냅숏을 만드는 것이 좋습니다. 스냅숏에서 해당 시점 이후에 발생한 이벤트를 재생하면 엔터티의 현재 상태를 확인할 수 있습니다. 데이터 스냅숏을 만드는 방법에 대한 자세한 내용은 Martin Fowler 엔터프라이즈 응용프로그램 아키텍처 웹 사이트의 스냅숏마스터-하위 스냅숏 복제를 참조하세요.

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

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

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

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

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

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

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

  • 이벤트 사용이 응용 프로그램 작업의 자연 기능이고 추가 개발 또는 구현 노력이 거의 필요하지 않은 경우.

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

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

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

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

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

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

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

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

회의 관리 시스템은 잠재적 참석자가 예약을 시도할 때 사용 가능한 좌석이 있는지 확인할 수 있도록 완료된 회의 예약 수를 추적해야 합니다. 다음과 같이 총 회의 예약 수를 둘 이상의 방법으로 저장할 수 있습니다.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  • 데이터 일관성 입문서. 개별 읽기 저장소 또는 구체화된 뷰와 함께 이벤트 소싱을 사용하는 경우 데이터 읽기가 즉시 일치하지 않고 결과적 일관성만 제공됩니다. 분산 데이터에 대한 일관성 유지와 관련된 문제를 요약합니다.

  • 데이터 분할 지침. 이벤트 소싱을 사용하여 확장성을 향상하고, 경합을 줄이고, 성능을 최적화하는 경우 데이터가 분할되는 경우가 많습니다. 데이터를 개별 파티션으로 나누는 방법과 발생할 수 있는 문제에 대해 설명합니다.