[마이크로서비스패턴] 6장 비즈니스 로직 개발:이벤트 소싱
6장. 비즈니스 로직 개발: 이벤트 소싱
6장 핵심내용
- 이벤트 소싱 패턴 응용 비즈니스 로직 개발
- 이벤트 저장소 구현
- 사가와 이벤트 소싱 기반의 비즈니스 로직 연계
- 이벤트 소싱을 응용한 사가 오케스트레이터 구현
1.이벤트 소싱 응용 비즈니스 로직 개발
이벤트 소싱 : 상태 변화를 나타내는 일련의 도메인 이벤트로 애그리거트를 저장
장점 : 이력 보존 -> 감사/통제 용도, 도메인 이벤트 확실하게 발행
단점 : 학습 곡선, 이벤트 저장소 쿼리 어려움(CQRS 패턴 적용 필요)
기존 영속화 문제점
1) 객체 관계 임피던스 부정합
테이블 형태의 관계형 스키마와 리치 도메인 모델 간의 간극.
2) 애그리거트 이력 없음
현재 애그리거트의 상태만 저장. 애그리거트 이력을 남기는 것은 개발자가 직접 구현해야 함.
3) 감사 로깅 구현하기 어려움. 오류 자주 발생.
어느 사용자가 애그리거트를 변경했는지 감사 로그를 남겨 추적.
감사는 보안/통제 + 사용자 액션 이력 자체가 중요한 경우도 있음.
감사 로깅 코드, 비즈니스 로직의 계속된 분화로 버그 가능성 높아짐.
4) 이벤트 발행 로직이 비즈니스 로직에 추가됨
도메인 이벤트 발행을 지원하지 않는 기존 영속화 한계점.
개발자가 직접 이벤트 생성 로직 추가 필요. -> 비즈니스 로직 동기화 되지 않는 위험성 존재
이벤트 소싱
이벤트 위주 비즈니스 로직 구현, 애그리거트를 DB에 일련의 이벤트로 저장하는 방식.
애그리거트를 DB에 있는 이벤트 저장소에 일련의 이벤트로 저장
- eg) Order 애그리거트를 EVENTS 테이블의 여러 로우로 저장 => 각 로우가 주문 생성됨, 주문 승인됨, 주문 배달됨 등의 도메인 이벤트
- 출처 : https://learn.microsoft.com/ko-kr/azure/architecture/patterns/event-sourcing
순서
- 애그리거트 생성/수정 시 애플리케이션은 애그리거트가 발생시킨 이벤트를 EVENTS 테이블에 삽입
- 애그리거트 로드 시 이벤트 저장소에서 이벤트 가져와 재연
- 애그리거트의 이벤트를 로드
- 기본 생성자를 호출하여 애그리거트 인스턴스 생성
- 이벤트를 하나씩 순회하며 apply() 호출
Eventuate 클라이언트 프레임워크 사례
Class aggregateClass = ...;
Aggregate aggregate = aggregateClass.newInstance();
for (Event event : events) {
aggregate = aggregate.applyEvent(event);
}
// 애그리거트 사용
이벤트 소싱에서의 이벤트는 곧 상태 변화
를 의미한다. 이벤트는 필수 데이터 또는 여러 데이터를 포함시켜 강화할 수 있음.
이벤트 소싱에서 이벤트에 포함되는 내용은 주로 애그리거트에 의해 결정됨. 애그리거트 상태가 변경될 때 마다 반드시 이벤트 발생시켜야 함.
+ 상태 전이를 위해 필요한 데이터를 이벤트가 갖고 있어야함.(단순 상태 값 변경 ~ 객체 추가/삭제 등)
비즈니스 로직 : process(<T extends Command> command)
& apply(<T extends Event> event)
로 변경
이벤트 저장소 : 낙관적 잠금 기법 통해 동시 업데이트 처리.
- 이벤트에 딸려온 버전 정보를 각 애그리거트 인스턴스마다 두고, 애플리케이션이 이벤트 삽입할 때 이벤트 저장소가 버전 변경 여부 체크
- 이벤트 번호를 버전 번호로 사용 가능. 또는, 이벤트 저장소에서 명시적으로 버전 번호 관리.
이벤트를 OUTBOX 테이블에 저장 후 꺼내어 사용한 후 삭제하는 이벤트 발행 방식과 달리, 이벤트 소싱은 EVENTS 테이블에 영구 저장
이벤트 발행의 두 가지 방식
- 3장 내용 중 메시지 확실하게 발행하는 방법
- 폴링
- 영구적으로 이벤트가 저장되기 때문에 어느 이벤트가 새 이벤트인지 분간이 필요
- 트랜잭션이 이벤트를 발생시키는 순서와 다르게 커밋할 수 있는 문제 존재 => EVENT 테이블에
이벤트 발행 여부
추적 컬럼 추가 1. 트랜잭션 로그 테일링 - 트랜잭션 로그 통해 EVENTS 테이블에 삽입된 이벤트를 읽어 메시지 브로커에 발행
성능 개선 : 스냅샷 활용
이벤트 수가 계속해서 증가하기 때문에 주기적으로 애그리거트 상태의 스냅샷을 저장.
Class aggregateClass = ...;
Snapshot snapshot = ...;
Aggregate aggregate = recreateFromSnapshot(aggregateClass, snapshot);
for (Event event : events) {
aggregate = aggregate.applyEvent(event);
}
// 애그리거트 사용
멱등한 메시지 처리
메시지 브로커가 동일 메시지 여러 번 전송할 수도 있기 때문에, 메시지 컨슈머는 멱등하게 개발해야 함. 비즈니스 로직이 애그리거트를 생성/수정하는 로컬 ACID 트랜잭션의 일부로 처리한 메시지 ID를 PROCESSED_MESSAGES
테이블에 기록. -> 해당 테이블에 메시지 ID 존재 시 중복 메세지기 때문에 솎아냄.
도메인 이벤트
이벤트 소싱 방식은 이벤트 영구 저장으로 언제든 재구성 가능. but, 이벤트 구조 변경에 따른 문제 발생.
이벤트 소싱 프레임워크가 이벤트 저장소에서 이벤트를 로드할 때 바꾸어 줌. : 업캐스터(upcaster)
이벤트 소싱 장점
- 도메인 이벤트 확실하게 발행
- 애그리거트 이력 보존
- O/R 임피던스 불일치 문제 대부분 방지
- 개발자에게 타임 머신 제공
이벤트 소싱 단점
- 학습 곡선
- 복잡한 구조
- 이벤트 개량 어려움
- 데이터 삭제 어려움 : 보통 삭제 플래그를 활용한 소프트 삭제. 개인 정보 보안 문제는 암호화 ID, UUID를 활용한 가명화 통해 해결.
- 이벤트 저장소 쿼리 어려움 : 관계형 DB의 경우 쿼리가 복잡해짐. NoSQL의 경우 기본키 검색만 지원해 CQRS로 해결.
2.이벤트 저장소 구현
이벤트 저장소 : DB + 메시지 브로커 핵심 : 구독기가 모든 이벤트를 순서대로 처리하도록 보장하는 것
이벤트 스토어 프레임 워크 : 이벤트 스토어, 라곰, 액손, 이벤추에이트
3.사가 & 이벤트 소싱 접목
코레오그래피 사가 : 쉽게 구현 할 수 있으나, 애그리거트 상태 변화 없어도 무조건 이벤트를 발행해야 한다는 단점이 있음.
오케스트레이션 사가 생성
RDBMS 이벤트 저장소 사용하는 경우 하나의 트랜잭션으로 이벤트 저장소 업데이트 + 사가 오케스트레이터 생성 작업 처리 가능.
NoSQL의 경우, 이벤트 저장소에 이벤트를 저장 -> 이벤트 핸들러에서 사가를 생성하는 방식으로 처리. 다만 주의해야할 점은 중복 이벤트 처리 필요.
이벤트의 사가ID를 추출해서 중복 이벤트에 대한 처리를 해줌. 1) 애그리거트 ID를 사가 ID로 쓰거나 2) 이벤트 ID를 사가 ID로 쓰는 방식이 있음.
이벤트 소싱 기반 사가 참여자 구현
- 커맨드 메시지 멱등 처리 : 메시지 ID 기록 후 처리 여부 확인
- 응답 메시지 원자적 전송
사가 오케스트레이터 구현 : 이벤트 소싱
- 이벤트 소싱으로 사가 오케스트레이터 저장
- 커맨드 메시지 확실하게 전송
- 응답을 꼭 한 번만 처리