[1편] MSA 이벤트 발행 - 이론

이지민·2023년 2월 16일
1

MSA

목록 보기
1/3
post-thumbnail

왜 이벤트를 발행해야 하나?

MSA의 특징중에 하나는 Database per Service이다. 즉, 서비스 각각이 자체 DB를 가지고 있다는 것이다. 이를 통해, 서비스들은 서로 느슨한 결합을 유지할 수 있게 된다. 또한 다른 서비스가 DB 락을 획득하여 서비스를 블로킹하는 일을 방지할 수도 있다.
하지만 서비스 별로 DB를 가지고 있다보니 트랜잭션 관리나 반정규화된 데이터들의 동기 처리가 어렵다. 따라서 MSA에서는 최종 일관성을 보장하는 방법을 택하게 된다.

최종 일관성 (Eventual Consistency) 이란?
데이터 항목에 새로운 업데이트가 없으면 궁극적으로 해당 항목에 대한 모든 접근들은 마지막으로 업데이트된 값을 반환하는 것
즉, 일시적으로 데이터의 합이 맞지 않을 수 있지만, 언젠가 동기화가 되면 모든 클라이언트가 동일한 데이터를 받을 수 있게 된는 것이다.

그렇다면 어떻게 최종 일관성을 보장할 수 있을까? MSA에서는 어떤 서비스에서 변경이 일어나면 그 변경을 다른 서비스에게 알려 최종 일관성을 보장하도록 하는데, 이때 변경을 다른 서비스에게 알리기 위해 이벤트 (Event Driven)를 활용하게 된다. 이번 글 (article)에서는 서비스에서 어떻게 이벤트를 발행하는 지에 대해 정리하고자 한다.

예제 소개

강의 관리 플랫폼

강의 관리 플랫폼은 강사 (Teacher)가 자신의 강의 (Course)를 올리고 학생 (Student)들이 강의를 수강 (Class)할 수 있는 중계 플랫폼이다. 이 글에서는 강의를 등록, 수정, 삭제 등을 할 수 있는 강의 서비스 (Course Service)의 이벤트를 발행하는 과정을 살펴보고자 한다.

강의 서비스 이벤트 도출

위 그림은 MSA Easy를 활용하여 강의 서비스에 대해 이벤트 스토밍을 한 결과이다. 그 결과 도출된 이벤트는 강의가 등록됨 (CourseCreatedEvent), 강의정보가 변경됨 (CourseUpdatedEvent), 강의가 게시됨 (CoursePostedEvent), 강의 게시 중단됨 (CourseUnpostedEvent), 강의 삭제됨 (CourseDeletedEvent) 총 다섯가지이다.

이벤트 활용

이벤트를 발행하는 방법에 알아보기 전에 이렇게 발행된 이벤트는 어떻게 사용될 수 있는지를 먼저 알아보고자 한다. 우선 이벤트에 연결된 정책 (Policy)을 실행하는데 사용할 수 있다. 예를 들어, 강의가 게시가 되면 강의 홍보를 위해 마켓팅 서비스를 통해 학생들에게 홍보 메일을 보낸다고 하자. 이때 학생들에게 홍보 메일을 보내는 것은 강의가 게시됐다는 이벤트에 대한 정책이다. 이를 구현하기 위해 강의 서비스는 강의가 게시됨 (CoursePostedEvent) 이벤트를 발행하고 이 이벤트를 구독하고 있는 마켓팅 서비스는 새로운 강의가 게시 됐음을 알게되어 학생들에게 홍보 메일을 보낸다.
또 CQRS를 패턴을 사용할 때 최종 일관성을 보장하기 위해 사용한다. 커맨드로 인한 비지니 객체에 대한 변경을 쿼리 모델에 전달할 때 이벤트를 활용할 수 있다. 예를 들면, 강의가 게시되면 (커맨드) 학생들이 수강 신청하는 페이지에 게시된 강의가 리스트에 보여야한다 (쿼리). 이를 위해, 강의를 게시하는 커맨드 작업에 수행할 때 강의가 게시됨 (CoursePostedEvent) 이벤트를 발행하여쿼리 모델도 변경을 반영할 수 있게 하는 것이다.

[그림] CQRS

이벤트 발행하기

강의가 게시되는 과정을 살펴보자. 강사 (Teacher)는 강의 서비스에게 강의를 게시하겠다고 (postCourse) Rest Api를 통해 요청을 한다. 그러면 강의 서비스는 1) DB 어댑터를 통해 강의 posted 값을 1로 변경하고 2) 이벤트 발행 어댑터를 통해 강의가 게시됨 이벤트 (CoursePostedEvent)를 발행한다. 이때 유의해야 할점은 DB 업데이트와 이벤트 발행이 하나의 트랜잭션으로 이뤄져야한다는 것이다. 그 이유는 DB만 업데이트를 하고 메시지는 아직 전송하지 않았는데 서비스가 중단되면 시스템이 불안정해지는 문제가 발생할 수 있기 때문이다. 따라서 DB 업데이트와 이벤트 발행은 원자적으로 하나의 트랜잭션으로 실행되어야하고 이를 가능하게 하는 것이 바로 MSA 패턴이 바로 Transactional Outbox 패턴이다.

[그림] 강의 서비스 아키텍처

CREATE TABLE `course` (
  `course_id` varchar(36) NOT NULL,
  `name` varchar(45) NOT NULL,
  `thumbnail` varchar(45) NOT NULL,
  `description` varchar(45) NOT NULL,
  `teacher_id` varchar(45) NOT NULL,
  `registered_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `last_updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `posted` tinyint NOT NULL,
  PRIMARY KEY (`course_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3

[그림] 게시된 강의 예시 (posted의 값이 1로 변경됨)

Transactional Outbox Pattern

Reference: https://debezium.io/blog/2019/02/19/reliable-microservices-data-exchange-with-the-outbox-pattern/

[그림] Transactional Outbox Pattern
(출처: https://microservices.io/patterns/data/transactional-outbox.html)

Outbox 패턴은 OUTBOX 테이블을 임시 메시지 큐로 사용하는 방법이다. 비지니스 객체에 대한 CUD DB 트랜잭션의 일부로 OUTBOX 테이블에 메시지를 삽입한다. 이를 통해, 비지니스 객체에 대한 변경과 메시지를 임시 메시지 큐인 OUTBOX애 발행하는 과정이 하나의 트랜잭션으로 이뤄진다. 그리고 메시지 릴레이는 OUTBOX의 변경분을 읽어 메시지 브로커에 메시지를 발행한다.


CREATE TABLE `outbox` (
  `id` varchar(36) NOT NULL,
  `aggregatetype` varchar(255) NOT NULL,
  `aggregateid` varchar(255) NOT NULL,
  `type` varchar(255) NOT NULL,
  `payload` json NOT NULL,
  `timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;

[그림] OUTBOX 테이블

  • id: 메시지의 유니크한 아이디. 메시지 생성 시 자동으로 생성. 컨슈머 (Consumer) 쪽에에서 중복 메시지를 처리하는 용도로 활용할 수 있다.
  • aagregate_type: 이벤트와 관련된 aggregate root의 타입. 비지니스 객체의 CUD은 aggregate root를 통해서 이뤄진다. 따라서 어떤 비지니스 객체의 변경에 의해 발행된 이벤트인지를 알 수 있다. (예시, Course)
  • aggregate_id: aggregate root의 아이디, 이 예제에서는 강의 ID가 aggregate의 아이디가 된다.
  • type: 이벤트 타입 (예시, CoursePostedEvent)
  • payload: JSON구조의 이벤트 내용
  • timestamp: 이벤트 발행 시간

메시지 릴레이가 OUTBOX 테이블을 읽어와서 브로커에 메시지를 발행하는 방법에는 두 가지가 방법(폴링 발행기 패턴, 트랜잭션 로그 테일링 패턴이 있는데 이번 예제에서는 트랜잭션 로그 테일링 패턴을 사용한다. 트랜잭션 로그 테일링 패턴은 트랜잭션 로그 마이너로 DB 트랜잭션으로 생성되는 로그를 읽어 변경분을 하나씩 메시지로 메시지 브로커에 발행하는 방식이다. 이를 응용한 여러가지 방법이 있는데 이번 예제에서는 Debezium (디비지움) 오픈소스를 활용할 예정이다. 디비지움은 DB 변경분을 아파치 카프카 메시지 브로커에 발행하는 오픈 소스 프로젝트로 mongodb, mysql, postgres 등 여러 RDB에 대한 커넥터를 지원한다. 이번 예제에서는 mysql을 연동할 예정이다.

[그림] Transaction Log Tailing

다음 글
[2편] MSA 이벤트 발행 - 구현
다음 편에서는 강의 관리 플랫폼의 서비스 중 하나인 강의 서비스를 구현하고 강의 도메인 이벤트를 debezium을 이용하여 Outbox 패턴을 구현하여 transactional하게 발행하는 내용을 다룬다.

profile
개발하는 사람입니다.

0개의 댓글