이 블로그는 마이크로서비스패턴 (길벗) 책 내용을 스터디를 위해 정리 한 내용 입니다.
책구매는 바로가기 에서 구매 가능합니다.
이 장은 이벤트 소싱의 작동 원리를 살펴보고, 비즈니스로직을 작성하는 방법에 대해 설명한다.
이벤트 소싱 응용 비즈니스 로직 개발
이벤트 소싱이란?
이벤트 소싱은 데이터를 저장하는 방식중의 하나로 발생한 이벤트를 저장하는 기법이다. 주로 비즈니스 로직을 구성하고, 애그리거트를 저장하는 기법이다. 이벤트는 애그리거트의 상태 변화를 나타낸다.
이벤트의 최종 결과값이 아니라, 이벤트로 발생된 모든 상태를 저장한다. 이벤트의 모든 상태를 저장하기때문에 조회가 쉽지 않아 CQRS 패턴을 적용해야 한다.
기존 영속화의 문제점
기존 영속화는 클래스는 테이블, 필드는 컬럼, 인스턴스는 각 ROW에 매핑되어 관리되는 방법을 말한다. 개발 관리는 쉽지만 다음 문제점이 있다.
- 객체-관계 임피던스 부정합 (Object - Relational Impedance Mismatch)
객체(Object) 와 DB의 관계(Relational) 의 부정합이 발생한다. - 애그리거트 이력이 없다
기존 영속화는 현재 애그리거트 상태만 관리하여, 이전 상태를 추적, 관리 하기 어렵다. - 검사로깅은 구현하기 힘들고 오류도 자주 발생한다.
사용자의 행동 이력을 체크할 수 없다. (감사)
감사로깅 코드를 작성하여 관리 할수 있지만 구현이 어렵고, 로직이 발전하면서 분화하기때문에 버그의 가능성이 높다. - 이벤트 발행 로직이 비즈니스 로직에 추가된다
도메인 이벤트를 발생하지 않는다.
이벤트 소싱 개요
이벤트 소싱은 이벤트 위주로 비즈니스 로직을 구현하고, 애그리거트를 이벤트로 저장하는 기법이다.
이벤트를 이용하여 애그리거트를 저장
애그리거트를 DB에 있는 이벤트 저장소에 이벤트 그대로 저장한다.
예를 들어, 주문의 경우 ORDER 테이블에 주문 그대로 저장하는게 아니라, 주문 애그리거트를 events 테이블에 여러 로우로 저장한다.
조회시에는, 이벤트 저장소에서 이벤트를 모두 가져와 이벤트 순서대로 하나씩 적용해 최종 상태를 만들어낸다.
- 애그리거트 이벤트 로드
- 기본 생성자를 호출해 애그리거트 인스턴스 생성
- 이벤트를 하나씩 순회하여 apply() 호출
이벤트는 곧 상태변화
도메인 이벤트는 애그리거트의 변화를 컨슘머에게 알리는 장치이다. 애그리거트ID만 포함시켜 발행자를 재 조회하게 할수도 있고, 모든 데이터를 포함해 전달 할 수도 있다.
전달 받은 이벤트는 상태를 변화시킬수 있는 데이터를 포함해야 하고, 수신받은 애그리거트는 현재의 상태를 수신받은 데이터를 이용해 변화 시킨다.
애그리거트 메서드의 관심사는 오직 이벤트
비즈니스 로직은 애그리거트 루트에 있는 커맨드 메서드를 호출해 처리한다. 이벤트 소싱에서 커맨드 메서드는 직접 필드를 업데이트 하지 않고, 이벤트를 발생시킨다. 커맨드 메서드에서 발생한 이벤트는 각 이벤트 처리 메서들을 이용해 DB에 저장되며, 애그리거트에 적용되어 상태를 업데이트 한다.
커맨트 메서드 (process) : 요청 받은 객체를 매개변수로 받아 어떻게 변경해야 할지 결정한다. 그리고, 애그리거트 상태는 바꾸지 않고, 상태 변경을 나타내는 이벤트 목록을 반환한다.
이벤트 메서드 (apply) : 각자 정해진 이벤트 타입을 매개변수로 받아 애그리거트를 업데이트 한다.
이벤트 소싱 기반의 Order 애그리거트
동시 업데이트: 낙관적잠금
여러 요청이 동일한 애그리거트를 동시에 업데이트 하는 일은 자주 발생한다. 때문에 낙관적 잠금을 이용해 방어한다.
애그리거트 루트에 version 컬럼이 있는 테이블에 매핑하고, 애그리거트가 업데이트 될때마다 버젼을 증가 시킨다.
두 트랜잭션이 발생했을때, 읽는 시점과 쓰는 시점에 다른 트랜잭션은 실패하게 된다.
이벤트 소싱과 이벤트 발행
이벤트 소싱은 애그리거트를 여러 이벤트로 저장하며, 이 이벤트를 하나씩 읽어 애그리거트를 재구성한다.
이벤트 발행 : 폴링
이벤트 발해기가 이벤트를 발행 할때, 이벤트가 계속해서 발생을 했을경우, 이벤트는 누적순서대로 발행이 된다.
하지만 트랜잭션이 동시에 수행중에 첫번째 트랜잭션보다 두번째 발생한 이벤트 트랜잭션이 먼저 commit된다면, 이벤트 순서의 역전이 이루어져 이벤트가 발행 될 수 있다. 이때, 애그리서트 구성시 이벤트 역전에 의해 오류가 발생 할 수 있다.
이를 해결하는 방법은 event 테이블에 처리 완료 플래그를 삽입해, 처리되지 않은 이벤트만 가져와 처리 하는 방법이 있다.
(?) 해결 되지 않을 것으로 생각됨. 김지영
이벤트 발행 : 트랜잭션 로그 테일링
스냅샷으로 성능 개선
이벤트가 이벤트 저장소에 누적 저장이 될경우, 저장된 이벤트가 양이 적다면 문제가 없을지 모르나, 데이터가 점점 많아지면 이를 재구성하는데 리소스가 많이 발생 할 수 있다.
이를 해결하기 위해 중간중간 스냅샷 버젼을 저장한다. 애그리거트 재 구성시, 스냅샷이 존재 할경우, 스냅샷을 기준으로 해당 시점 이후의 이벤트만 가져와 재 구성 하면 된다.
멱등한 메시지 처리
메시지브로커가 이벤트를 중복으로 어려번 발행 할 수 있다. 때문에 멱등하게 개발 해야 한다.
RDBMS 이벤트 저장소 활용
메시지 ID를 PROCESSED_MESSAGES, 이벤트는 EVENTS 테이블에 한 트랜잭션으로 저장해, 중복 메시지 발생시 무시하게 처리한다.
NoSQL 이벤트 저장소 활용
메시지 컨슘머가 메시지 처리 도중 생성된 메시지 ID를 저장한다.
??? 단, 아무런 처리 결과가 없는 이벤트가 왔을경우, 또 재 전송됬을경우 문제가 있을수 있다. 때문에 아무런 처리가 없을 경우에도 이벤트를 발생시켜 저장시킨다. 이때, 컨슘머는 처리 하지 않게 구현한다. ???
도메인 이벤트 발전시키기
비즈니스로직이 발전하면서 이벤트의 스키마는 계속 변할 수 밖세 없다. 과거 저장된 이벤트들도 재구성시 영향을 받게 된다. 이런 문제점을 해결 해야 한다.
이벤트 스키마
애그리거트의 추가, 필드 추가 등은 하위호환성을 가지므로 이슈가 없다.
하지만, 애그리거트/이벤트/필드 추가, 개명, 삭제등은 하위호환성을 가지지 못한다.
업캐스팅을 통한 스키마 변화관리
이때, SQL의 경우 데이터를 기본값 혹은 신규 스키마에 맞게 마이그레이션 하면되지만, NoSQL 은 쉽지 않다. 이렇게 마이그레이션이 쉽지 않은 경우에는, 이벤트 소싱 프레임워크가 이벤트를 로드할때 새 스키마 구조로 바꾸는 방법이 있다.
이 구현체를 업캐스터(upcaster) 라고 한다.
이벤트 소싱의 장점
- 도메인이벤트를 확실하게 발행
애그리거트 상태가 바뀔때마다 확실하게 이벤트를 발행한다. 때문에 알림, 로깅, 분석, 모니터링등이 가능한다. - 애그리거트 이력 보존
애그리거트의 과거 생태를 임시 쿼리로 쉽게 조회 가능한다. - O/R 임피던스 불일치 문제를 거의 방지
- 개발자에게 타임머신을 제공
발생한 모든 이벤트를 저장하므로, 특정 시점의 데이터와 사용자의 행동을 추적 할 수 있다.
이벤트 소싱의 단점
- 새로운 프로그래밍 모델을 배우려면 시간이 걸린다.
새로운 구조의 프로그래밍이므로, 익숙해지는데 시간이 오래 걸린다. - 메시징 기반 애플리케이션은 복잡하다.
중복체크, ID 저장등 구현해야 하는 로직이 많으며 복잡하다. - 이벤트를 발전 시키기 어렵다.
로직이 발전하면서 이벤트 스키마가 변화하는데, 업캐스트등의 추가적인 개발이 필요하다. - 데이터를 삭제 하기 어렵다.
이벤트가 모두 저장되어 재구성 되므로, 이벤트 데이터의 삭제가 어렵다. - 이벤트 저장소를 쿼리하기 어렵다.
특정 데이터를 얻기 위해서 모든 이벤트를 가져와 재 구성해야 한다.
이벤트 저장소구현
이벤추에이트 로컬 이벤트 저장소의 작동원리
자바용 이벤추에이트 클라이언트 프레임워크
사가와 이벤트 소싱을 접목
여러 서비스에 걸쳐 데이터의 일관성을 유지하기 위해서는, 사가를 시작하거나, 사가에 참여 해야 한다. 이를 위해 사가와 이벤트 소싱 기잔의 비즈니스 로직을 연계 해야 한다.
코레오그래피 사가구현 : 이벤트 소싱
이벤트 소싱은 이벤트가 모든 로직을 주도하므로, 코레오그래피 사가를 구현하기에 알맞다. 애그리거트가 업데이트 되면 사가가 이벤트를 발생시키고, 각기 이벤트 핸들러들은 애그리거트를 업데이트 한다.
??? 단, 애그리거트 상태변화가 없어도 이벤트는 발생 시켜야 한다.????
오케스트레이션 사가 생성
사가 오케스트레이터 작성 : RDBMS 이벤트 저장소 사용 서비스
RDBMS 는 이벤트 저장소를 업데이트 하고, 오케스트레이터를 생성하는 작업을 한 트랜잭션으로 구현할 수 있다.
사가 오케스트레이터 작성 : NoSQL 이벤트 저장소 사용 서비스
NoSQL 은 이벤트 저장소에 저장하고나서 오케스트레이터를 생성하는 작업을 한 트랜잭션으로 구현할 수 없다. 때문에 애그리거트가 발생시킨 도메인 이벤트에 반응하여 사가 오케스트레이터를 생성하는 이벤트 핸들러가 필요하다.
주의할 점은 중복이벤트를 방지하기 위해서 사가 인스턴스를 정확히 하나만 생성 해야 한다.
방법은 애그리거트ID 나 이벤트ID 를 사가ID로 사용하는것이다. 이렇게 생성된 사가ID를 이용하여 이벤트 핸들러에서 이미 처리된 ID의 중복 처리를 방지 하면 된다.
이벤트 소싱 기반의 사가 참여자 구현
커맨드 메시지를 멱등하게 처리
중복된 메시지를 솎아 내야 한다. 이벤트 메시지ID를 기록하여, 처리전 확인하는 로직으로 처리한적이 있는지 체크 한다.
응답메시지를 원자적으로 전송 ???