[번역] 최신 기술 – Event Sourcing 처음 적용하기

원문: https://msdn.microsoft.com/magazine/mt422577

뭐든 큰 변화없이 언제나 뻔하다고 생각하면 어느새 신경도 쓰지 않게 됩니다. 데이터 저장소를 생각할 때 우리는 당연히 데이터의 현재 상태가 저장되어 있겠거니 합니다. 보험, 금융과 같이 큰 규모의 프로젝트는 모든 이력을 정확히 추적하고 기록해야하지만, 그렇지 않은 대부분의 어플리케이션과 웹사이트는 현재 상태만 저장해도 충분합니다.

이처럼 현재 상태를 저장하는 방식은, 시스템의 현재 상태를 스냅샷으로 찍어서 보존한다고 표현할 수 있습니다. 데이터는 보통 관계형데이터베이스에 저장합니다. 이렇게만 해도 새 트랜젝션을 만들고 과거의 트랜젝션 결과를 가져올 수 있습니다. 여기까지가 지난 수 십년간의 ‘뻔한 것’이었습니다.

오늘날 비즈니스는 따라오기 벅찰 정도로 빠르게 변하고 있습니다. 그래서 비즈니스와 도메인에서 일어나는 이벤트를 정확하게 추적해야 하는 경우도 많아졌습니다. 이벤트소싱(ES; Event Sourcing)은 스토리지 설계와 데이터를 저장하고 가져오는 방식에 영향을 주는 패턴입니다. 또한, 도메인에서 비즈니스 이벤트를 저장하고 보는 수준에 그치지 않고 데이터 프로젝션까지 즉시 만들 수 있는 패턴입니다.

이벤트소싱은 비즈니스를 기록하고 살펴보기에 똑똑하고 멋진 방법입니다. 데이터 저장 모델로는 꽤 새로운 이론이며 관계형 모델이 처음 등장했을 때만큼 참신합니다. 최근 등장한 NoSQL 보다 큰 변화를 줄 수도 있습니다. 물론 이벤트소싱은 현재 활발히 사용 중인 관계형이나 NoSQL을 대체하는게 목적은 아니며, 여러분은 이벤트소싱을 이들 둘의 상위 개념으로 구현할 수 있습니다. 이벤트소싱은 특정 시점의 상태로만 취급했던 데이터를 이벤트 단위로 다룹니다. 이벤트소싱을 사용할 수록 우리는 데이터에 대한 시각도 새로워질 것입니다.

이벤트소싱을 적용하면 무엇이 좋은가?

현재 상태만 저장하는 저장 모델에서 한 단계 발전한 형태로 갱신 이력을 추적하는 모델이 있습니다. 서점관리 프로그램을 생각해봅시다. 책마다 설명 속성이 있고 여러분은 속성 수정 권한이 있습니다. 이 때 속성에 대한 수정 이력을 보존해야할까요?

요구사항은 상황마다 다르겠지만, 이번 예시에서는 이러한 변경내역 추적이 중요한 기능이라고 합시다. 어떻게 구현할 수 있을까요? 한가지 방법은, 현재 상태를 저장하는 테이블 하나와 변경 내역을 저장하는 별도의 테이블로 구성하는 것입니다. 업데이트할 때마다 하나의 레코드가 추가되며 업데이트 기록에는 변경한 컬럼과 변경한 내용을 저장합니다.

다른 방법으로 시도해볼까요? 하나의 테이블에 하나의 책에 대해서도 여러 개의 레코드를 기록합니다. 각 레코드는 그림 1과 같이 타임스탬프와 현재 상태를 순서대로 저장합니다.

Multiple Records Hold Entity History
그림 1 엔티티 변경 내역을 여러 개의 레코드로 저장하는 형태

위와 같이 구성하면 현재 상태를 가져오기 위한 별도의 API를 만들어야 합니다. 단순히 레코드ID로 쿼리해서는 최신 상태를 가져올 수 없고 타임스탬프 상의 최신이거나 업데이트카운트가 가장 큰 값을 가져오도록 만들어야 합니다. 입력한 데이터 엔티티에 대한 모든 이벤트는 하나의 흐름으로 표현할 수 있는데, 이처럼 이벤트를 흐름으로 표현하는 것이 이벤트소싱의 핵심입니다. 그러므로 이벤트를 원활하게 추적하는 시스템을 구현하고 싶다면 이벤트소싱이 정답입니다.

기존의 개념 중 이벤트소싱과 관련있어 보이지만 다른 개념도 있습니다. 이벤트소싱이 로깅이나 감시 기능과 유사하다고 생각할 수 있지만, 로깅은 예외 상황이나 프로파일링까지 고려한다는 점에서 다릅니다. 이벤트소싱은 비즈니스 이벤트에 대해서만 다룹니다. 그러므로, 로깅 기능을 구성할 때처럼 여러 도메인과 구조를 관통하는 공통된 역할을 콤포넌트화 하는 작업과도 다릅니다. 이처럼 공통 부분을 정의하는 과정을 Aspect-orient 소프트웨어에서는 공통의 관심사(cross-cutting concern; 횡단관심사라고도 함)라고 하는데 이와는 다른 의미입니다. 이벤트소싱은 데이터를 어떤 구조로 설계하고 저장하는지에 대한 내용으로 봐야 합니다.

이벤트소싱이란

이벤트소싱은 이벤트를 데이터 소스로 간주합니다. 수 십년간 개발자들은 이벤트에 대해서 그다지 중요하게 생각하지 않았습니다. 어쩌면 그런 이유로 이벤트소싱이 주목받지 못했는지도 모릅니다. 이벤트소싱이 딱히 쓸모있지 않다고 생각해도 괜찮습니다. 아직 필요하지 않을 뿐입니다.

이벤트소싱은 도메인 전문가가 이벤트를 순서대로 추적하고 싶을 때 특히 유용합니다. 소극적으로 사용한다면, 워크플로우를 표현하거나 비즈니스 로직을 일원화할 때도 유용합니다. 다만 이처럼 소극적으로 사용할 경우에는 이벤트를 보존할 필요성도 적고 이벤트를 최우선 순위로 취급하지도 않습니다. 이 정도가 요즘 흔히 사용하는 이벤트소싱 시나리오입니다. 이 글에서는 이벤트를 데이터 소스로 사용하도록 하겠습니다.  이벤트소스를 도입하려면 저장소에서 두 가지를 고려해야 합니다. 보존과 쿼리입니다. 여기서 보존이라함은 세가지 핵심 작업과 관련되어 있습니다. 바로 삽입/갱신/삭제입니다. 이벤트소싱 시나리오에서의 삽입은 현재 상태만 보존하는 통상의 시스템과 다를 바 없습니다. 요청을 받으면 새 이벤트로 저장합니다. 이벤트에는 GUID와 같은 고유식별자를 함께 기록하며, 그 외에 해당 이벤트의 타입 이름과 코드, 타임스탬프, 기타 정보도 저장합니다.

이벤트소싱에서 갱신 작업은 삽입의 다른 표현일 뿐입니다. 어떤 프로퍼티가 변경되었고 새 값은 무엇인지, 관련된 비즈니스 도메인이 무엇인지, 그 외에 변경 사유 등을 기록합니다. 갱신이 한 번 일어난 저장소의 데이터는 그림 2와 같습니다.

A New Record Indicates Update to Entity with ID #1
그림 2 Entity ID #1에 대해 갱신이 일어났음

이와 마찬가지로 삭제 작업은 해당 엔티티를 삭제했다는 정보를 ‘삽입’합니다.

갱신 작업은 쿼리할 때 새로운 고민거리를 줍니다. 갱신하기 전에 갱신할 대상이 이미 있는지, 현재 상태가 어떤지는 어떻게 알 수 있을까요? 먼저 간단한 쿼리 레이어를 하나 만들어서 ID를 조회하고, 그 다음 현재의 값에 기반하여 새 값으로 갱신하는 이벤트를 삽입해야 합니다.

예를 들면, Created 이벤트를 먼저 가져온 후 그 내용에 맞추어 새 데이터를 추가하는 방법입니다. 현재 상태의 값은 해당 ID에 대한 모든 이벤트를 조회한 후 처음부터 짚어나가면 구할 수 있습니다. 이런 방법을 ‘이벤트 리플레이’라고 합니다. 하지만 단순히 모든 이벤트를 재생해서 상태를 재구성하는 방법으로는 성능에 큰 문제가 발생합니다. 은행 계좌의 현재 잔액을 알려면 수 년 전의 계좌 개설일부터 현재까지의 모든 거래를 가져와야 할테니까요. 그리 좋은 방법은 아닙니다.

그래서 이와 같이 모든 이벤트를 가져와야 하는 문제를 해결하는 방법 중 하나로 스냅샷을 만드는게 있습니다. 스냅샷은 특정 시점의 상태를 저장한 레코드입니다. 스냅샷을 만들면 적어도 모든 이벤트를 리플레이할 필요는 없습니다.

구현에 있어서, 이벤트소싱은 특정 기술이나 제품이 아닙니다. 그러므로 관계형 데이터베이스를 사용하든 NoSQL을 사용하든 상관 없습니다. 그 대신, 이벤트소싱을 소프트웨어 콤포넌트 개념으로 본다면 ‘이벤트 저장소(event store)’를 구현한다고 말할 수 있겠습니다. 이벤트 저장소는 이벤트 로그를 구현하는 작업과 별반 다를 바 없습니다. 그러므로 최소한의 기능만 충족한다면 데이터베이스가 제공하는 API를 이용하여 직접 만들어도 무방합니다.

이벤트 저장소는 두 가지 전제를 가지고 있습니다. 먼저, 추가만 가능하고 갱신은 없습니다. 삭제 또한 삭제 표식을 추가할 뿐 이벤트를 지우지 않습니다. 둘째, 요청하는 이벤트 ID에 맞게 이벤트 스트림을 반환할 수 있어야 합니다. 이 두가지 기능만 있으면 이벤트 저장소의 기본 요건은 충족됩니다.

이벤스 저장소 구현 시 고려사항

앞서 말했듯이, 이벤트 저장소는 기본 기능만 충족하면 특정 기술과 상관없이 구현할 수 있습니다. 데이터를 보존하는 부분은 보통 관계형 데이터베이스나 NoSQL을 사용합니다. 관계형데이터베이스로 구현한다면 한 이벤트마다 하나의 레코드를 가지도록 하고 하나의 테이블은 하나의 엔티티 타입을 가지도록 구현하는 식입니다.

이벤트는 다양한 포멧이 있습니다. 예를 들어, 모든 이벤트에 공통된 속성도 있지만 그렇지 않을 수도 있습니다. 물론 이벤트가 최대한 공통된 속성을 가질 수록 구현하기에 좋습니다. 그렇게 하기 어렵다면 행 단위(row)로 레코드를 쌓지 않고 SQL Server 2014에 추가된 기능인 Column Store Index를 이용하여 열 단위, 즉 컬럼 하나씩 쌓도록 테이블을 구성할 수도 있습니다. 또 다른 방법이라면, 이벤트를 JSON 오브젝트로 만든 후 이를 문자열로 직렬화하여 하나의 문자열 컬럼에 넣을 수도 있습니다.

NoSQL에서는 다양한 속성을 담은 하나의 레코드를 “도큐먼트”라는 단위로 저장합니다. 일부 NoSQL 제품은 이러한 도큐먼트 저장에 특화되어있습니다. 개발자 입장에서는 클래스를 만들고, 값을 채우고, 그대로 저장하면 끝나므로 아주 간편합니다. 보통은 각 이벤트의 유형 별로 클래스를 만들어서 저장하도록 구현합니다.

진행 중인 프로젝트

이벤트소싱은 아직 구조적으로 성숙한 단계는 아닙니다. 무엇 하나 표준화된 규약이 없기 때문에 이벤트 저장소와 이를 이용한 개발 경험은 계속 발전해나갈 여지가 많습니다. 그러므로 이벤트소싱 솔루션은 직접 만들어도 좋습니다. 이 섹션에서는 이벤트 저장소를 좀 더 구조적으로 만들고 편리하게 다룰 수 있는 도구를 몇가지 소개하겠습니다.

이벤트소싱에 최적화된 이벤트 저장소를 사용하면 이벤트 기록과 읽기 작업에만 집중할 수 있어서 보다 효율적인 개발이 가능합니다. NEventStore (neventstore.org) 프로젝트는 이런 시도 중 하나입니다. 간단히 이벤트를 기록하고 다시 읽을 수 있고, 가장 중요한 점이라면 특정 저장소에 의존하지 않고 이를 선택할 수 있다는 점입니다. 아래 예시는 저장소로 SQL을 사용하고 있습니다.

var store = Wireup.Init()
  .UsingSqlPersistence("connection")
  .InitializeStorageEngine()
  .UsingJsonSerialization()
  .Build();
var stream = store.CreateStream(aggregateId);
stream.Add(new EventMessage { Body = eventToSave });
stream.CommitChanges(aggregateId);
위의 예시는 이벤트를 기록할 때이며, 이벤트를 읽을 때는 스트림을 열고 커밋된 이벤트 컬렉션을 한 흐름으로 볼 수 있습니다.
또 다른 프로젝트로 Event Store (geteventstore.com) 프로젝트가 있습니다. .NET과 HTTP API를 제공하며, 이 API로 이벤트를 취합하고 하나의 스트림으로 관리할 수 있습니다. 이벤트 스트림을 가지고 크게 세 가지의 작업을 할 수 있습니다. (1) 이벤트 쓰기 (2) 가장 최근의 이벤트 또는 이벤트의 특정 구간 읽기 (3) 갱신할 때 받아보기가 가능합니다.
받아보기(subscription) 기능은 기본적으로 스트림에 이벤트를 추가할 때마다 콜백 함수를 호출하지만 그 방식은 세가지로 나뉩니다.
  • Volatile: 설정 시점 이전의 이벤트는 무시합니다. 새로 입력한 이벤트부터 받습니다.
  • Catch-up: 정해진 시작 지점부터 이벤트를 받습니다. 이미 입력된 이벤트도 받을 수 있습니다.
  • Persistent: 하나의 이벤트를 여러 곳에서 받을 수 있습니다. 여러 곳에서 받을 경우에도 최소 한 번(at-least-once)을 보장하며 순서에 상관없이 여러번 받을 수도 있습니다.

(역자 주: 더 자세한 정보는 Event Store 기술문서를 참고해주세요.)

정리

이벤트소싱은 이벤트를 어플리케이션의 데이터소스로 사용합니다. 어플리케이션을 만들 때 데이터의 마지막 상태만 다루는게 아닌 비즈니스 이벤트의 흐름을 기준으로 설계하고 개발할 수 있습니다. 저장하는 이벤트 데이터는 아주 저수준의 정보이므로 현재 상태를 알기 위해서는 별도의 투사(projection) 과정을 거쳐야 합니다. 투사란, 이벤트 리플레이를 하면서 특정 시점에 대한 데이터의 상태값을 만드는 일련의 처리 과정을 말합니다. 이벤트를 이용하면 어떤 형태로든 다양한 형식의 투사 결과를 만들 수 있습니다. 그리하여, 현재의 상태값 또한 다양한 형식으로 맞춰서 구할 수 있습니다.


Dino Esposito “Microsoft .NET: Architecting Applications for the Enterprise” (Microsoft Press, 2014), “Programming ASP.NET MVC 5” (Microsoft Press, 2014)의 공동저자입니다. JetBrains에서 .NET과 안드로이드 분야의 기술 에반젤리스트이며, 세계 곳곳의 여러 행사에서 연사로 활동하고 있습니다. Esposito의 소프트웨어에 대한 비전은 다음 링크에서 볼 수 있습니다.  software2cents.wordpress.com, 트위터: @despos.

이 문서의 리뷰를 한 Microsoft 기술 전문가 Jon Arne Saeteras에게 감사드립니다.

이 문서를 번역한 김영재 교육서비스 바로풀기의 개발사 Bapul의 CTO로서 기술로 교육에 새로운 시각을 주기 위해 열심히 개발하고 있습니다.

Advertisements

One thought on “[번역] 최신 기술 – Event Sourcing 처음 적용하기

답글 남기기

아래 항목을 채우거나 오른쪽 아이콘 중 하나를 클릭하여 로그 인 하세요:

WordPress.com 로고

WordPress.com의 계정을 사용하여 댓글을 남깁니다. 로그아웃 / 변경 )

Twitter 사진

Twitter의 계정을 사용하여 댓글을 남깁니다. 로그아웃 / 변경 )

Facebook 사진

Facebook의 계정을 사용하여 댓글을 남깁니다. 로그아웃 / 변경 )

Google+ photo

Google+의 계정을 사용하여 댓글을 남깁니다. 로그아웃 / 변경 )

%s에 연결하는 중