우아한형제들 - 회원시스템 이벤트기반 아키텍처 구축하기
본 글은 우아한테크, 권용근 - 회원시스템 이벤트기반 아키텍처 구축하기를 정리한 글입니다.
목차
배달의민족 마이크로서비스 여행기
들어가기에 앞서 김영한 - 배달의민족 마이크로서비스 여행기를 간략히 소개합니다.
- 2015년 초기 배달의민족은 모놀리식 아키텍처로 구성됨
- 2017년 비즈니스의 폭발적 성장으로 대장애의 시대가 열림
- 2019년 마이크로서비스(이벤트기반 아키텍처)로 전환 완료, 시스템 안정화
위와 같이 많은 기업들이 마이크로서비스(이벤트기반 아키텍처)로 전환하고 소개해서 이는 익숙하지만 하나의 시스템에서 이벤트기반 아키텍처는 생소합니다. 본 영상에서는 배달의민족이 하나의 시스템안에서 이벤트기반 아키텍처를 어떻게 사용하는 소개합니다.
무엇을 이벤트로 발행할 것인가?
- 왜 마이크로서비스와 이벤트기반 아키텍처는 같이 언급될까?
- 마이크로서비스는 느슨하게 결합된 서비스의 모임으로 구조화하는 서비스 지향 아키텍처(SOA)
- 이벤트기반 아키텍처는 이를 도움
- 느슨한 결합을 이벤트기반 아키텍처로 어떻게 만족시킬수 있을까?
- 회원과 가족계정으로 예시를 보임
- 회원 도메인과 가족계정 도메인이 같은 서비스에 존재
- 회원의 본인인증 초기화시 가족계정에서 탈퇴되어야하는 정책이 존재함
- 트래픽 증가로 가족계정 도메인을 별도의 서비스로 분리
- HTTP호출로 여전히 강한 결합
- 비동기 요청으로 변경
- 스레드 레벨 의존은 분리했지만 인증해제시 후속작업 필요하다는 사항이 존재해 여전히 강한 결합
- 요청 행위를 메시징 시스템으로 처리
- 가족계정 탈퇴를 메시징으로 요청
- 물리적 의존은 제거되었지만 논리적 의존 관계가 존재, 강한 결합
- 메시징 시스템을 사용하였지만 비동기요청일뿐
- 도메인 이벤트를 메시징 시스템으로 처리
- 느슨한 결합
- 회원 도메인과 가족계정 도메인이 같은 서비스에 존재
- 이벤트 발행시에는 달성하려는 목적인 아닌 도메인 이벤트 그 자체인것을 알수 있음
이벤트 발행과 구독
- 회원시스템이 가진 문제를 해결하기 위해 3가지 이벤트 종류와 3가지 구독자 계층으로 정리
- 어플리케이션 이벤트
- 내부 이벤트
- 외부 이벤트
1. 어플리케이션 이벤트, 첫 번째 구독자 계층
- 하나의 어플리케이션 내에서 다루는 이벤트
- 스프링의 어플리케이션 이벤트
- 분산 비동기를 다룰수 있는 이벤트 버스 제공
- 트랜잭션 제어 가능
- 단일 어플리케이션에서 사용하기 충분함
- 단일 어플리케이션 내에서 이벤트를 다루는 이유
- 느슨한 결합이 외부 시스템과 뿐만 아니라 내부에서도 필요하기 때문
- 예로 도메인행위와 주 관심사가 먼 메시징 시스템으로 내부 이벤트를 발행하는 행위
2. 내부 이벤트, 두 번째 구독자 계층
- 첫 번째 구독자 계층이 발행하는 이벤트가 내부 이벤트
- AWS SNS와 AWS SQS
- 1:N 구조로 사용가능
- 메시지 유실에 대한 강한 신뢰 확보가능
- 발행된 내부 이벤트는 시스템 내의 이벤트 처리기(두 번째 구독자 계층)가 구독
- 첫 번째 구독자 계층이 어플리케이션 내에서 해결해야하는 비관심사를 처리했다면, 두 번째 구독자 계층이 이외 모든 도메인내 비관심사를 처리
비관심사 분리
- 도메인 행위가 수행될 때 함께 수행되어야하는 부가정책들이 존재함, 이들은 도메인의 주 행위에 대한 응집을 방해
- 위의 예시처럼 부가정책들이 주행위와 함께존재하면 안됨
- 이를 내부 이벤트로 처리
- 위와 같이 내부 이벤트를 발행하고 두 번째 구독자 계층에서 처리
- 분리된 비관심사는 각 구현이 되어 강한응집, 높은 재사용성 확보 가능
어플리케이션 이벤트 vs 내부 이벤트
- 비관심사 처리를 어플리케이션 이벤트로 처리할 수 있는거 아닌가?
- TRADE OFF
- 어플리케이션 이벤트 - 주요 행위와 강한 정합성 보장이 필요한 작업
- 주요 행위와 트랜잭션 공유
- 주요 행위와 성능을 고유
- 내부 이벤트 - 주요 행위와 강한 정합성 보장이 필요하지 않은 작업
- 주요 행위와 트랜잭션 분리
- 주요 행위와 성능 분리
3. 외부 이벤트
- 두 번째 구독자 계층이 발행하는 이벤트가 외부 이벤트
- 외부 이벤트는 마이스로서비스에서 타서비스(세 번째 구독자 계층)들이 구독하는 이벤트
열린 내부이벤트, 닫힌 외부이벤트
- 타서비스(세 번째 구독자 계층)들이 내부 이벤트를 구독하면 안되는가?
- 두 이벤트를 분리함으로써 내부에는 열린, 외부에는 닫힌 이벤트를 제공가능
- 열린 내부이벤트
- 내부 이벤트를 발행하고 구독하는 하나의 서비스는 같이 배포 될 수 있기 때문에 이벤트 페이로드를 변경가능
- 이를 열려있다고 함
- 닫힌 외부이벤트
- 페이로드를 변경하면 구독중인 타서비스 수정 필요, 이는 많은 비용이듬 (사실상 거의 불가능한 경우도 많음)
- 이를 닫혀있다고 함
이벤트 일반화
- 닫혀있는 외부 이벤트를위한 가장안전하고 유연한 고정된 형태의 이벤트 필요
- 언제(시간)
- 누가(식별자)
- 무엇을 하여(행위)
- 어떤 변화가(속성)
- 구독자는 데이터가 더 필요할 수 있지만 구독자의 행위를 기대하지 않아야 느슨한 결합
- ZERO-PAYLOAD
- 이벤트 순서 보장문제 해결로 주로 소개됨
- 외부 시스템에 대한 페이로드 의존도 제거가능
이벤트 저장소 구축
- AWS SNS, AWS SQS 구간은 매우높은 신뢰성 정책을 제공중
- SQS역시 높은 신뢰성과 데드레터큐(DLQ; Dead Letter Queue) 전략으로 내부 이벤트 처리 및 외부 이벤트 생성 가능
- 문제는 내부 이벤트 발행시점
- HTTP를 사용해서 발행 (AWS SNS 발행시점)
- 도메인 행위는 수행되었는데 이벤트가 발행되어있지 않을수 있음
- 이를 해결하기 위해 이벤트 저장 필요
이벤트 저장 시점
- 도메인행위와 이벤트처리를 별도 트랜잭션으로 처리, 즉 이벤트 발행을 보장할 수 없음
- 이 보장을 이벤트 저장소를 통해 만족시킴
- SNS 발행에 실패해도 이벤트 저장소에 저장이 보장되기 때문에 재발행가능
- 저장소 선택시 도메인 저장소와 이벤트 저장소와 트랜잭션을 묶을수 있어야함
- 다중 데이터베이스 분산 트랜잭션 구현필요
- 그러나 굉장히 어려운일
- 스프링의
ChainedTransactionManager
는 Deprecated 되었음 - 여기서는 이벤트 저장소를 별도로 사용하지 않고 도메인 저장소를 사용함
- 이벤트 유실을 낮은 확률이라도 허용할 수 없기때문
- 이를 Transactional Outbox Pattern 라고함
- 로컬 트랜잭션으로 데이터베이스 저장, 이벤트 발행으로 정합성 보장
이벤트 유실 문제 해결
- 이벤트 저장소에 발행여부 추가
- 발행여부 - published
- 발행일시 - published_at
- 첫 번째 구독자 계층, 이벤트 발행
- 두 번째 구독자 계층, 이벤트 발행 확인
- 주기적으로 실행되는 별도 배치로 일정주기동안 발행되지 않은 이벤트를 재발행
- 결론적으로 이벤트를 유실하지 않고 자동 재발행되는 시스템을 구축함
기록 테이블 통합
- 회원 시스템은 사용자의 행동을 기록, 각종 사용자의 로그 데이터를 기록함
- 이 데이터를 이벤트 저장소를 통해 통합할 수 있다는 것을 알게됨
- 다양한 형태로 기록되던 사용자 로그를 이벤트 저장소로 통합
Reference
- https://www.baeldung.com/spring-events
- https://docs.spring.io/spring-framework/reference/testing/testcontext-framework/application-events.html
- https://aws.amazon.com/ko/what-is/dead-letter-queue/
- https://microservices.io/patterns/data/transactional-outbox.html