1. 개요
Microservice Architecture(MSA)는 서비스간 느슨한 결합을 통해 더 빠르게 개발하고 더 빠르게 배포하여 end user의 요구사항을 보다 유연하게 제공할 수 있는 아키텍처 스타일이다.
다만, MSA의 일반적인 원칙은 Database per Service이다.
각각의 Microservice는 각각의 Database를 가지도록 설계되기 때문에 여러개의 서비스 사이에서 데이터의 정합성을 보장하는 일은 쉬운일이 아니다.
1.1. 분산 데이터 처리 이슈
* 데이터 정합성을 유지하기 위해 필요한 방안
- 대기 또는 실패 시 재호출
- Batch 작업 등으로 실패건 일괄 취소
- 보상 트랜잭션 처리
1.2. Eventuate 소개
Microservice 아키텍처에 내재되어 있는 분산 데이터 관리 문제를 해결하여 비즈니스 논리에 집중 할 수 있도록 개발된 오픈소스 기반 프레임워크.
개발 및 배포는 Chris Richardson이 만든 eventuate.io를 통해 이루어지며 라이선스는 Apache 라이선스 방식임
1.3. 요약
Microservice 아키텍처 설계를 위해 Database를 분리하게 되면 필연적으로 각 Database사이에 데이터의 정합성에 대한 이슈가 발생하게 된다.
2 Phase Commit을 통한 데이터 정합성 처리를 할수 있으나 RESTFul, Message Queue 등을 통해 각각의 Microservice 간에 전송되는 데이터는 현실적으로 트랜잭션을 관리하기 어렵다.
Eventuate F/W를 통해 이 문제를 해결하는 방법에 대해 소개한다.
2. Eventuate F/W 이란?
Eventuate F/W이란 앞서 설명했듯이 분산 데이터 관리 문제를 해결하여 비즈니스 논리에 집중 할 수 있도록 개발된 오픈소스 기반 프레임워크이다.
Eventuate™ 은 Eventuate Tram 과 Eventuate Local로 이루어저 있으며 다시 Eventuate Tram은 Core와 Tram-SAGA로 구분되어 진다.
- Eventuate Tram: 전통적인 JPA/JDBC 기반 마이크로 서비스를 위한 프레임워크, 비즈니스 로직 재구성 없이 Spring 프레임워크 기반 마이크로서비스에 Eventuate Tram을 쉽게 적용가능
- Eventuate Tram-core: Transactional Outbox 패턴을 사용하여 messages/events를 데이터베이스 트랜잭션의 일부로 전송한다.
- Eventuate Tram-SAGA: Eventuate Tram을 기반으로 오케스트레이션 기반 sagas를 제공한다. - Eventuate Local: 이벤트소싱(Event Sourcing) 기반 프레임워크로 현재 데이터의 상태를 관리하는것이 아니라 상태 변경 이벤트를 저장하여 관리하도록 일련의 과정을 지원한다.
본 포스팅에서는 Eventuate Tram 중 Eventuate Tram-core에 대해 집중적으로 다룬다.
2.1. Eventuate Tram 아키텍처
- Publisher Service: 비즈니스 트랜잭션을 생성하는 서비스, 이 서비스가 실행 시 messages/events를 OUTBOX 테이블에 저장한다.
- Customer Service: Publisher Service에서 생성한 messages/events를 실제 사용하는 서비스
- OUTBOX 테이블: 동일한 Database 트랜잭션으로 F/W이 지정한 테이블(MESSAGE)을 사용
- Eventuate CDC Service: 별도 Spring 기반의 모듈로 OUTBOX테이블을 log Tailing 혹은 JDBC Polling 하여 messages/events를 감지하고 이를 Message Broker(e.g. Kafka)에 전송
- Reader: CDC의 컴포넌트로 DBMS의 로그 파일 tailing이나 'SELECT'를 이용한 주기적인 Polling을 통해 messages/events를 감지
- pipeline: Meessage 발생을 위해 Reader가 실행시킴
- Offset Store: 로그 파일 tailing 시 현재 위치를 기록하기 위해 사용됨
- publisher: pipeline에 의해 이용되며 Message Broker(e.g. Kafka)에게 messages/events를 발생함
- Zookeeper: 클러스터링 구축 시 리더 선출을 위해 사용
2.2. Eventuate Tram 예제 프로그램 구조
3. 프레임워크 적용
토이 프로젝트에서 이를 쉽게 사용하기 위해 필요한 구성 환경을 하나의 프로젝트로 구현해볼 생각이다.
3.1. Eventuate Tram Starter 프로젝트 생성
우선 쉽게 Eventuate Tram을 적용하기 위해 spring-boot-autoconfigure를 기반으로 Eventuate Tram Starter 프로젝트를 생성하여 사용한다.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.rara</groupId>
<artifactId>rara-eventuate</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>rara-eventuate</name>
<description>eventuate Starter project for Spring Boot</description>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
<!-- Eventuate Tram Core -->
<dependency>
<groupId>io.eventuate.tram.core</groupId>
<artifactId>eventuate-tram-spring-jdbc-kafka</artifactId>
<version>0.24.0.RELEASE</version>
</dependency>
<dependency>
<groupId>io.eventuate.tram.core</groupId>
<artifactId>eventuate-tram-spring-events</artifactId>
<version>0.24.0.RELEASE</version>
</dependency>
</dependencies>
<repositories>
<repository>
<id>SpringLibM</id>
<url>https://repo.spring.io/libs-milestone/</url>
</repository>
<repository>
<id>central</id>
<url>https://repo1.maven.org/maven2/</url>
</repository>
</repositories>
</project>
- eventuate-tram-spring-jdbc-kafka와 eventuate-tram-spring-event 0.24.0.RELEASE을 사용한다. (당시 Stable한 버전 사용)
- eventuate-tram-spring-jdbc-kafka은 내부적으로 다시 아래 의존성들을 참조함
- eventuate-tram-spring-producer-jdbc
- eventuate-tram-spring-consumer-common
- eventuate-tram-spring-consumer-jdbc
- eventuate-tram-spring-consumer-kafka
추가로 필요한 클래스들을 Import 하기 위해 내부적으로 AutoConfiguration용 클래스를 생성한다.
@Configuration
@Import(TramJdbcKafkaConfiguration.class)
public class EventuateCoreAutoConfiguration {
}
- TramJdbcKafkaConfiguration(eventuate-tram-spring-jdbc-kafka)은 다시 내부적으로 아래의 두개의 클래스를 다시 Import함
- TramMessageProducerJdbcConfiguration(eventuate-tram-spring-producer-jdbc)
- EventuateTramKafkaMessageConsumerConfiguration(eventuate-tram-spring-consumer-kafka) - TramMessageProducerJdbcConfiguration는 MessageProducer의 구현체를 Bean으로 생성시켜주는데 이를 통해 JDBC기반으로 동일한 트랜잭션 상 OUTBOX 테이블인 Message 테이블에 JDBC Template 형태로 Insert문을 실행시킬수 있다. (실제 비즈니스 로직상에는 MessageProducer.send() 만 호출하면 됨)
- EventuateTramKafkaMessageConsumerConfiguration는 내부적으로 TramConsumerCommonConfiguration를 다시 Import하면서 MessageConsumer의 구현체 Bean을 생성시켜줌
도메인이벤트를 사용하기 위해 아래와 같이 필요한 클래스를 Import한다.
@Configuration
@Import({TramEventsPublisherConfiguration.class})
public class EventuateEventAutoConfiguration {
}
추가로 resources/META-INF 하위에 spring.factories를 생성하고 다음을 추가한다.
#Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.rara.eventuate.config.EventuateCoreAutoConfiguration,\
com.rara.eventuate.config.EventuateEventAutoConfiguration