Event Sourcing이란?
일반적인 애플리케이션은 데이터베이스에 ‘현재 상태’만 저장합니다. 예를 들어 사용자의 잔액이 10000원이라면, DB 테이블에는 그냥 10000이라는 숫자 하나만 남습니다. 이전에 얼마를 입금했고 얼마를 출금했는지는 알 수 없죠. Event Sourcing(이벤트 소싱)은 이런 방식과 반대로, 상태 자체를 저장하는 대신 상태를 변화시킨 ‘이벤트’를 순서대로 모두 저장하는 설계 패턴입니다.
즉 ‘잔액 10000원’이라는 결과값 대신 ‘5000원 입금됨’, ‘3000원 출금됨’, ‘8000원 입금됨’ 같은 이벤트들을 시간 순서대로 쌓아두고, 필요할 때 이 이벤트들을 처음부터 순서대로 재생(replay)해서 현재 상태를 계산해내는 방식입니다.
동작 원리: 통장 거래내역처럼 생각하기
Event Sourcing을 이해하는 가장 쉬운 비유는 은행 통장입니다. 통장 앱을 열면 현재 잔액만 딱 보여주는 게 아니라, 언제 얼마가 입금되고 출금됐는지 거래내역이 전부 남아있죠. 은행은 실제로 매 순간의 잔액을 따로 저장하는 게 아니라, 모든 거래(이벤트)를 기록해두고 필요할 때 이를 합산해서 잔액을 보여주는 구조에 가깝습니다.
코드로 단순화하면 다음과 같습니다.
AccountCreated– 계좌 생성됨MoneyDeposited(5000)– 5000원 입금됨MoneyWithdrawn(3000)– 3000원 출금됨
이 이벤트들을 순서대로 적용하면 현재 잔액은 0 + 5000 - 3000 = 2000원이 됩니다. 매번 이렇게 처음부터 계산하면 느리기 때문에, 실무에서는 특정 시점의 상태를 ‘스냅샷’으로 캐싱해두고 그 이후 이벤트만 재생하는 최적화를 함께 사용합니다.
왜 상태 대신 이벤트를 저장할까?
가장 큰 이유는 ‘이력 추적’입니다. 일반적인 CRUD 방식은 UPDATE 쿼리가 실행되는 순간 이전 값이 사라집니다. 하지만 금융, 재고관리, 주문 시스템처럼 ‘왜 이 값으로 바뀌었는지’가 중요한 도메인에서는 과거 상태와 변경 이유를 모두 보존해야 할 때가 많습니다.
Event Sourcing을 쓰면 다음과 같은 이점이 생깁니다.
- 모든 변경 이력이 자동으로 감사 로그(audit log) 역할을 함
- 버그가 발생했을 때 특정 시점의 상태를 그대로 재현 가능
- 동일한 이벤트로 여러 개의 읽기 모델(read model)을 만들 수 있어 CQRS 패턴과 궁합이 좋음
물론 단점도 있습니다. 이벤트가 쌓일수록 재생 비용이 커지고, 이벤트 스키마가 바뀌면 과거 이벤트를 어떻게 해석할지 설계가 까다로워집니다. 그래서 모든 시스템에 무조건 적용하기보다는, 이력 관리와 감사가 특히 중요한 도메인에 선택적으로 도입하는 것이 일반적입니다.