마이크로서비스패턴[6] — 6장
6장 비즈니스 로직 개발: 이벤트 소싱
1장 모놀리식 지옥에서 벗어나라
2장 분해 전략
3장 프로세스 간 통신
4장 트랜잭션 관리: 사가
5장 비즈니스 로직 설계
6장 비즈니스 로직 개발: 이벤트 소싱
7장 마이크로서비스 쿼리 구현
8장 외부 API 패턴
9장 마이크로서비스 테스트 1부
10장 마이크로서비스 테스트 2부
11장 프로덕션 레디 서비스 개발
12장 마이크로서비스 배포
13장 마이크로서비스로 리팩터링
이벤트소싱이란 어플리케이션의 모든 상태 변화를 이벤트 순서에 따라 저장하는 기법이다.
이전의 이벤트 발행 로직은 비즈니스 로직에 추가되어 있기 때문에 간에 누락되는 경우에도 이벤트는 그대로 진행이 되는 구조였다. 하지만 이벤트 소싱 기법을 활용하면 애그리거트가 생성/수정될 때마다 무조건 이벤트를 발행해서 프로그래밍 오류를 제거할 수 있게 된다.
이번 장에서는 이벤스 소싱의 장단점과 구현방법에 대해 얘기한다.
6. 이벤트 소싱 응용 비즈니스 로직 개발
이벤트 소싱은 애그리거트를 이벤트 형태로 저장하고, 이를 사용하는 서비스는 이벤트 스토어를 구독하는 형태로 애그리거트를 로드할 때 이벤트 스토어의 이벤트를 재연하는 방법으로 사용한다.
기존 영속화의 문제점
- 객체 — 관계 임피던스의 부정합
OOP와 RDB간 기본 철학이 다름 - 애그리거트 이력의 부재
애그리거트가 업데이트되면 이전상태는 사라짐. 이력을 남기려면 개발자가 직접 코드를 구현해야 하고, 비즈니스 로직과 동기화해야하는 코드를 중복 생성하게 됨 - 감사 로깅을 구현하기가 번거롭다
여러 로깅을 종류별로 구현하기에 번거롭고, 비즈니스 로직은 계속 변경되기 때문에 버그가 발생할 가능성이 높다. - 이벤트 발행 로직이 비즈니스에 추가된다
ORM은 어플리케이션 수준의 콜백은 존재하지만, DB 트랜잭션의 일부로 메시지를 발행하는 기능은 존재하지 않는다. 결국 이벤트 생성 로직이 필요한데, 비즈니스 로직과 동기화되지 않을 가능성이 있다.
6.1. 이벤트 소싱
기존의 영속화는 애그리거트를 테이블에, 필드를 컬럼에, 인스턴스를 각 로우에 매핑했지만, 이벤트 소싱은 애그리거트를 DB에 있는 이벤트 저장소에 일련의 이벤트로 저장한다.
+----------+----------------+-------------+-----------+------------+
| EVENT_ID | EVENT_TYPE | ENTITY_TYPE | ENTITY_ID | EVENT_DATA |
+----------+----------------+-------------+-----------+------------+
| 1002 | ORDER_CREATED | ORDER | 1001 | {...} |
| 1003 | ORDER_APPROVED | ORDER | 1001 | {...} |
| 1004 | ORDER_SHIPPED | ORDER | 1001 | {...} |
+----------+----------------+-------------+-----------+------------+
사용 순서는 아래와 같다.
1. 애그리거트의 이벤트를 로드
2. 기본 생성자를 호출하여 애그리거트 인스턴스 생성
3. 이벤트를 하나씩 순회하며 apply()를 호출
동시 업데이트: 낙관적 잠금
이벤트 스토어는 이벤트와 함께 전달된 버전 정보를 각 애그리거트 인스턴스마다 두고, 어플리케이션이 이벤트를 삽입할 때 이벤트 저장소가 버전 변경 여부를 체크하는 방식으로 DB의 동시 업데이트를 제한한다.
이벤트 발행 : 폴링
이벤트를 EVENTS 테이블에 저장한다고 가정할 때, 이벤트 발생순서와 커밋 시점의 차이로 인해 순서가 뒤죽박죽이 될 수도 있다. 이런 문제점은 이벤트 발행 여부 컬럼을 추가함으로써 추적할 수 있다.
SELECT * FROM EVENTS WHERE PUBLISHED = 0 ORDER BY EVENT_ID;
UPDATE EVENTS SET PUBLISHED = 1 WHERE EVENT_ID = {eventId};
스냅샷을 통한 성능 개선
발행되는 이벤트 수가 많은 경우, 주기적으로 애그리거트 상태의 스냅샷을 저장하고, 물리적인 조회 대상을 줄인 다음 이벤트를 조회하여 성능을 개선하는 방법도 있다.
이벤트 소싱의 장점
- 확실하게 이벤트를 발행
- 애그리거트 이력의 보존
- O/R 임피던스의 불일치를 방지
- 데이터 타임머신의 역할
이벤트 소싱의 단점
- 초기 학습시간 필요
- 복잡한 메시징 기반 어플리케이션
- 이벤트 발전이 까다로움
- 데이터 삭제가 어려움
- 이벤트 저장소 쿼리가 어려움 => CQRS 필요
6.2. 이벤트 스토어 구현
이벤트 저장소는 DB와 메시지 브로커를 합한 것이다.
RDBMS에 이벤트를 저장할 수도 있지만, 성능/확장성이 유용한 전용 이벤트 저장소를 두는 방법이 있다고 한다.
제품
- Event Store(https://github.com/EventStore/EventStore)
- Lagom (https://www.lagomframework.com/)
- Axon (https://axoniq.io/)
- Eventuate (https://eventuate.io/)
책에서는 저자가 만든 이벤츄에이트를 예시로 설명한다.
6.3. 사가와 이벤트 소싱을 접목
여러 서비스에 걸쳐 데이터 일관성을 유지하려면 서비스가 사가를 시작하거나, 사가에 참여해야 할 경우가 많다. 따라서 이벤트 소싱 기반의 비즈니스 로직을 연계해야 한다.
사가에 필요한 단계들과 액션
- 사가 생성 : 사가를 시작한 서비스는 원자적으로 애그리거트를 생성/수정하고 사가 오케스트레이터를 생성해야 한다.
ex) createdOrder()는 Order애그리거트와 CreateOrderSaga를 생성해야함
- 사가 오케스트레이션 : 사가 오케스트레이터는 응답을 소비하고, 자신의 상태를 업데이트 한 후 커맨드 메시지를 전송해야 한다.
- 사가 참여자 : 구독하는 메시지를 소비하고, 애그리거트를 생성/수정하고 다시 응답 메시지를 전송해야 한다.
이벤트 소싱의 개념은 익숙하지 않은 개념이다.
굳이 이렇게 복잡하게 사용해야하나라는 생각을 할 수도 있는데, 사가를 통해서 메시지를 발행/구독하더라도 메시지 자체를 관리하는 방법을 고민하게 될테고 그걸 해결하기 위한 방법이자 기존 사가의 단점을 보완하기 위해 필요한 기술이라 생각하면 된다.
예전 스프링캠프 2017에서 발표된 자료를 보면 이해하기 더 용이하다.
https://github.com/jaceshim/springcamp2017/blob/master/springcamp2017_implementing_es_cqrs.pdf