Eternity's Chit-Chat

aeternum.egloos.com



진화적인 설계-3.ACCOUNTING 패턴 3부 Evolutionary Design

DOMAIN EVENT

DOMAIN EVENT의 목적은 외부에서 발생한 흥미로운 사건에 대한 정보를 시스템에 전달하는 것이다. DOMAIN EVENT의 핵심은 이미 발생한 과거의 사건을 표현한다는 점이다. 2010년 8월 26일에 3,000,000원이 입금되었다는 것은 실제로 발생한 과거의 사건을 표현하는 것이다.

이처럼 과거에 실제로 발생한 사건을 표현하는 DOMAIN EVENT의 특징은 시스템 구현에 있어 한 가지 제약사항을 추가한다. DOMAIN EVENT는 과거의 기록이자 역사의 자취이기 때문에 이미 처리된 DOMAIN EVENT의 정보는 수정하거나 삭제할 수 없다. 과거에 일어난 사건을 표현하는 DOMAIN EVENT를 수정하거나 삭제한다는 것은 과거의 사실을 조작하거나 부정하는 것이 된다. 따라서 EVENT SOURCING 패턴을 지원하기 위해 DOMAIN EVENT에 부과되는 가장 기본적인 제약은 상태를 바꿀 수 없다는 것이다. 즉, DOMAIN EVENT는 불변(immutable)이다. 

DOMAIN EVENT의 상태를 변경하지 못하기 때문에 발생하는 가장 큰 문제는 오류의 수정 과정이 복잡해진다는 것이다. 사람의 상호작용을 포함하는 대부분의 비즈니스에서는 사람의 착오나 오류로 인해 잘못된 값이 저장될 가능성이 높기 때문에 시간이 흐른 뒤에 잘못된 데이터를 정정하거나 삭제해야 하는 경우가 적지 않다.
그러나 오류가 발생했다고 하더라도 불변 상태라는 제약을 가지는 DOMAIN EVENT를 수정하거나 삭제할 수는 없다. 잘못된 데이터가 전달되었다는 것 역시 과거에 실제로 발생한 사건을 표현한 것이기 때문이다. 잘못된 데이터를 가진 DOMAIN EVENT를 수정하거나 삭제하는 것은 잘못된 데이터가 전달되고 처리되었다는 과거의 사실을 부정하는 것이 된다. 따라서 DOMAIN EVENT는 수정, 삭제가 불가능하고 오직 추가만이 가능하다. DOMAIN EVENT의 이러한 특징으로 인해 DOMAIN EVENT를 저장하는 이벤트 로그(Event Log)는 추가만 가능한 저장소(additive only storage)로 사용된다.

2010년 8월 26일의 입금액이 3,000,000원이 아니라 4,000,000원이었다는 사실을 알게 되었다고 가정하자. 이미 처리된 DOMAIN EVENT를 수정하거나 삭제할 수 없으므로 3,000,000원에 대한 DOMAIN EVENT는 변경하지 않은 채 입금액을 4,000,000원으로 정정할 수 있는 방법이 필요하다.
DOMAIN EVENT의 추가만 가능하다는 전제 하에 애플리케이션의 오류를 수정할 수 있는 유일한 방법은 오류 수정을 위한 새로운 DOMAIN EVENT를 추가하는 것이다. 새로운 DOMAIN EVENT를 이용해서 기존의 오류를 무효화시킨 후 정상적인 값으로 시스템의 상태를 변경시키는 것이다. 기존의 오류를 ‘무효화’시킨다는 의미는 경우에 따라 다양한 의미를 지니지만 일반적으로 값이 수치나 금액으로 표현되는 경우 기존의 값을 0으로 소급시키는 것을 의미한다.

<표 4>는 오류 정정을 목적으로 추가된 새로운 DOMAIN EVENT를 이용해 2010년 8월 26일의 ‘증감액(원)’ 항목에 -3,000,000원을 더하여 입금액을 0원으로 소급시킨 후 입금액을 4,000,000원으로 변경시킨 경우의 계좌 상태를 표현한 것이다. 처음에는 2010년 8월 26일의 입금액이 3,000,000원이었으나 오류 수정 후 4,000,000원으로 변경되었다는 이력이 고스란히 담겨져 있다는 점에 주목하라(수정을 위해 DOMAIN EVENT를 사용하는 것을 RETROACTIVE EVENT 패턴이라고 하고, 원래의 금액을 0으로 소급시킨 후 정상적인 값을 추가해서 오류를 수정하는 것을 REVERSAL ADJUSTMEMT 패턴이라고 한다).

<표 4> 새로운 DOMAIN EVENT를 이용해서 2010년 8월 26일의 입금을 수정

지금까지의 이야기를 통해 DOMAIN EVENT가 가지는 두 가지 특징을 알 수 있다.

첫째, DOMAIN EVENT는 외부에서 발생한 사건을 기술하는 ‘소스 데이터(Source Data)’와 이벤트의 처리 결과를 기술하는 ‘처리 데이터(Processing Data)’의 두 가지 부분으로 구성된다. 소스 데이터가 ‘어떤 사건이 발생했는가’를 기록한다며 처리 데이터는 ‘처리 결과가 무엇인가’를 기록한다. <표 4>에서 소스 데이터에는 실제 입금과 출금 사건을 표현하는 ‘타입’, ‘금액’, ‘적요’, ‘거래일자’ 항목이 포함되고, 처리 데이터에는 소스 데이터를 처리한 결과인 ‘증감액(원)’ 항목이 포함된다. 처리 데이터를 DOMAIN EVENT의 일부로 포함시키는 이유는 각각의 DOMAIN EVENT 에 의한 처리 결과를 추적 가능하도록 상호 연결시킴으로써 오류 수정을 용이하게 하기 위해서이다.

처리 데이터가 DOMAIN EVENT를 구성하는 속성이라고 하더라도 DOMAIN EVENT의 소스 데이터와는 쉽게 구분할 수 있도록 독립적인 객체 그룹으로 분리하는 것이 좋다. 두 데이터의 분리를 통해 ‘무엇이 상태 변경을 야기했는가’와 ‘어떤 상태가 변경되었는가’를 명확하게 표현할 수 있다.

<그림 3> 불변 소스 데이터와 가변 처리 데이터를 포함하는 DOMAIN EVENT

ACCOUNTING 패턴의 경우 DOMAIN EVENT의 ‘처리 데이터’를 표현하는 것은 ACCOUNTING ENTRY이다. ACCOUNTING 패턴은 EVENT SOURCING 패턴의 한 예로 ‘이벤트(Event)’를 통해서만 시스템의 상태 변경이 가능하며, ‘이벤트(Event)’를 처리하여 얻어진 ‘회계 항목(Accounting Entry)’를 추가해서 시스템의 상태를 변경할 수 있는 패턴의 집합이라는 점을 상기하라. 따라서 ACCOUNTING 패턴의 문맥에서 <그림 3>의 Processing Data는 ACCOUNTING ENTRY로 대체할 수 있다.

<그림 4> DOMAIN EVENT와 ACCOUNTING ENTRY의 관계

둘째, DOMAIN EVENT의 소스 데이터에는 두 가지 종류의 시간(timepoint)이 포함되어 있다. 첫 번째 시간은 DOMAIN EVENT가 표현하는 외부의 사건이 실제로 발생한 시점을 가리킨다. 두 번째 시간은 우리가 사건이 발생했다는 사실을 알게 된 시점으로 일반적으로 DOMAIN EVENT가 시스템에 통지된(결과적으로 영속성 저장소인 이벤트 로그에 기록된) 시간을 가리킨다. 전자를 ‘실제 시간(actual time)’이라고 하고 후자를 ‘기록 시간(record time)’이라고 한다.

‘실제 시간’과 ‘기록 시간’의 차이를 이해하기 위해 다시 계좌 관리 예제를 살펴 보자. <표 5>와 같이 2010년 8월 26일에 계좌로 3,000,000원이 입금되었다고 가정하자. 이때 2010년 8월 26일은 계좌로 금액이 입금된 실제 시간을 의미하므로 이 시간은 ‘실제 시간’이 된다.
<표 5> 거래일자 항목은 ‘실제 시간(actual time)’을 의미한다 

실제 시간과 기록 시간은 동일한 시간이 아닐 수 있다. (현실성은 떨어지지만)입금된 정보를 수작업으로 처리한 후 다음 날 계좌 관리 시스템으로 일괄 전송하는 은행 시스템이 있다고 가정하자. <표 5>에서 2010년 8월 26일 09시 04분 07초에 이루어진 입금 정보가 계좌 관리 시스템에 통지된 시간이 2010년 8월 27일 06시 30분 20초라고 하면, ‘실제 시간’은 2010년 8월 26일 09시 04분 07초가 되고 ‘기록 시간’은 2010년 8월 27일 06시 30분 20초가 된다. 따라서 <표 6>과 같이 계좌 변경 내역에 ‘기록 시간’을 의미하는 ‘통지일자’ 항목을 추가할 수 있다.

<표 6> 통지일자 항목은 ‘기록 시간(record time)’을 의미한다

실제 시간과 기록 시간 모두를 포함하는 <표 6>은 다음과 같은 두 가지 질문에 답할 수 있다. 

  • 실제로 입금된 시간은 언제인가? 
  • 입금 사실이 시스템에 통지된 시간은 언제인가?

이제 앞에서 살펴 본 DOMAIN EVENT의 오류 수정 예제로 돌아가 보자. 실무 담당자는 입금액이 3,000,000원이 아니라 4,000,000원이라는 사실을 실제 입금 일자로부터 일주일이 지난 2010년 9월 2일이 되어서야 발견했다고 가정하자. 실무 담당자는 2010년 9월 2일 14시 46분 17초에 입금액 수정 정보를 포함한 RETROACTIVE EVENT를 시스템에 통지함으로써 계좌 오류를 수정했다. 이 경우 계좌 거래 내역은 <표 7>과 같이 변경될 것이다.

<표 7> 입금액 수정 후의 거래 내역

<표 7>은 2010년 8월 26일의 입금에 대해 다음과 같은 추가적인 질문에도 답할 수 있다.

  • 2010년 8월 27일에는 8월 26일의 입금액을 얼마로 알고 있었는가?
  • 2010년 9월 3일에는 8월 26일의 입금액을 얼마로 알고 있는가?

실제 시간과 기록 시간은 시간의 축을 따라 값의 변경 내역을 추적할 수 있는 메커니즘을 제공한다. 두 가지 시간을 DOMAIN EVENT에 기록함으로써 과거의 특정 시점에 알고 있었던 시스템의 상태를 복구할 수 있다. 과거의 그 시점에 우리가 바라보던 시스템의 상태는 어떠했는가? 기록 시간과 실제 시간을 이용해서 과거의 상태를 재구성해 보면 이에 대한 답을 구할 수 있다.

DOMAIN EVENT를 저장하고 기록 시간과 실제 시간이라는 두 가지 시간 축을 따라감으로써 사건의 흐름과 변경 이력뿐만 아니라 특정 시점의 시스템 상태를 유추할 수 있는 능력을 얻게 된다. 이것이 DOMAIN EVENT 패턴이 EVENT SOURCING 패턴의 기반이 되는 이유다.

<그림 5> 실제 시간인 whenOccurred와 기록 시간인 whenNotice를 포함하는 DOMAIN EVENT

DOMAIN EVENT와 관련해서 언급할 필요가 있는 마지막 시간의 종류는 ‘처리 시간(process time)’이다. ‘처리 시간’은 실제로 DOMAIN EVENT를 처리한 시간으로 이 시간 역시 '발생 시간(actual time)'과 ‘기록 시간(record time)’과는 다를 수 있다.

‘기록 시간’은 DOMAIN EVENT가 시스템에 통지된 시간을 의미하며 결과적으로 영속성 저장소인 이벤트 로그에 DOMAIN EVENT가 저장되는 시간이다. ‘처리 시간’은 이벤트 로그에서 DOMAIN EVENT를 읽어 DOMAIN EVENT를 처리함으로써 시스템의 상태를 변경시킨 시간이다. DOMAIN EVENT가 이벤트 로그에 저장되지 않고 시스템에 통지되는 즉시 처리된다면 ‘기록 시간’과 ‘처리 시간’이 동일하겠지만 이벤트 프로세서에 의해 비동기적으로 처리되거나 배치(batch) 처리된다면 처리 시간과 기록 시간의 차이는 클 것이다. 이제 마지막 시간 축인 ‘처리일자’ 항목을 계좌 거래 내역에 추가할 수 있다.

<표 8> 처리 시간(processing time)인 처리일자 항목이 추가된 거래 내역

처리 시간은 외부에서 발생한 사건을 표현하는 항목이 아니므로 DOMAIN EVENT의 소스 데이터(source data)가 아니라 처리 데이터(processing data)에 포함된다는 사실에 주목하라. DOMAIN EVENT와 ACCOUNTING ENTRY 의 관점에서 처리 시간은 ACCOUNTING ENTRY의 속성이다.

<표 9> 이벤트(Event), 회계 항목(Accounting Entry), 최종상태로 표현된 계좌 거래 내역

3가지 종류의 시간 정보를 포함하는 DOMAIN EVENT와 ACCOUNTING ENTRY의 최종 모습은 <그림 6>과 같다.
<그림 6> 처리 시간인 whenBooked를 포함하는 ACCOUNTING ENTRY