상호 작용 스타일
-
스타일
일대일 : 각 클라이언트 요청은 한 서비스가 처리
일대다: 각 클라이언트 요청을 여러 서비스가 협동 처리
-
일대일 상호작용
요청/응답 (동기)
비동기 요청 응답
단방향 알림
-
일대다 상호작용
발행/구독
발행/비동기 응답
마이크로 서비스 API 정의
API와 인터페이스의 중요성
- 소프트웨어 개발의 핵심 요소
- 모듈 간 통신을 정의하고 구현 세부사항을 숨김
모놀리식 vs 마이크로서비스 아키텍처의 API
- 모놀리식: 프로그래밍 언어 수준의 인터페이스
- 마이크로서비스: 서비스 간 약속으로서의 API 인터페이스 처럼 중요함
마이크로 서비스 api 특징
- 작업(이름, 매개변수, 반환형)과 이벤트로 구성
- 런타임 시 호환성 문제 발생 가능성 (클라이언트와 서비스는 같이 컴파일 되지않기 때문)
API 정의의 중요성
- IDL(인터페이스 정의 언어) 사용
- API 우선 접근 방식 권장 (클라이언트와 소통하며)
API 설계 시 고려사항
- IPC 선택에 따른 API 내용 결정
- API의 진화와 관련 이슈 고려
( API는 메시지 채널, 메시지 타입, 메시지 포맷으로 정의. HTTP로 통신하는 API는 URL, HTTP 동사, 요청/응답 포맷으로 구성)
- 마이크로서비스 아키텍처에서 API 설계와 정의는 매우 중요,
- API 우선 접근 방식을 통해 더 나은 서비스를 구축할 수 있음.
- API는 시간에 따라 진화하므로 이를 관리하는 방법을 고려
API 발전시키기
API의 변화
- 새 기능 추가, 기존 기능 변경 삭제
- 모놀리식 vs 마이크로서비스 애플리케이션에서의 API 변경 차이점
마이크로서비스에서 API 변경의 어려움
- 클라이언트를 다른 팀이 개발
- 모든 클라이언트 강제 업그레이드 불가능
- 서비스의 신구 버전 동시 실행
시맨틱 버저닝 (Semantic Versioning)
- API 버저닝을 위한 지침
- MAJOR.MINOR.PATCH 형식
- 각 번호 증가 규칙 설명
API 버전 번호 적용
- REST API: URL 경로에 메이저 버전 포함
- 메시징 기반 서비스: 메시지에 버전 번호 포함
- 마이크로서비스 환경에서 API 변경은 복잡하고 어려운 과제이며, 이를 관리하기 위해 시맨틱 버저닝과 같은 체계적인 접근 방식이 필요
- API를 일관된 규칙에 따라 버저닝하고 발전시키는 것이 중요
API 변경 유형
- 하위 호환되는 소규모 변경
- 하위 호환되지 않는 중대한 대규모 변경
하위 호환되는 변경
- 옵션 속성 추가, 새 작업 추가 등
- 견고성 원칙 준수 필요
중대한 대규모 변경 관리
- 일정 기간 동안 신구 버전 API 동시 지원
- HTTP의 경우 URL에 버전 번호 포함 또는 MIME 타입 활용
( Multipurpose Internet Mail Extensions의 약자로, 파일의 형식을 나타내는 인터넷 표준)
3 .MIME: HTTP 프로토콜에서 Content-Type 헤더로 사용
4. 구조: 'type/subtype' 형식으로 구성
5. application/json: JSON 데이터 같은..
메시지 포맷
-
텍스트 포맷 (JSON, XML)
장점: 사람이 읽기 쉬움, 자기 서술적
단점: 메시지가 길고 파싱 오버헤드
-
이진 포맷 (프로토콜 버퍼, 아브로)
장점: 효율적, IDL 제공
단점: 사람이 읽기 어려움
동기 RPI 패턴 응용 통신
RPI (Remote Procedure Invocation)
- 클라이언트가 서비스에 요청을 보내고 응답을 받는 IPC
- 블로킹과 논블로킹 클라이언트 모두 존재
- 프록시 인터페이스를 통해 통신 프로토콜 캡슐화
REST (Representational State Transfer)
- HTTP 기반의 IPC
- 리소스 중심의 아키텍처
- HTTP 동사(GET, POST, PUT 등)를 사용하여 리소스 조작
- URL로 리소스 참조
REST의 특징
- 확장성, 인터페이스 일반화, 독립적 배포 등을 강조
- 많은 API가 RESTful이라고 주장하지만 실제로는 그렇지 않을 수 있음
- RPI와 REST는 마이크로서비스 간 통신을 위한 중요한 패턴
- 특히 REST는 현재 API 개발의 주류이지만, 진정한 RESTful API를 구현하는 것은 쉽지 않음
- 개발자들은 REST의 원칙을 제대로 이해하고 적용해야 함
RPI / IPC ?
- IPC (Inter-Process Communication):
- 프로세스 간 통신
- 컴퓨터에서 실행 중인 서로 다른 프로그램들이 정보를 주고받는 방법. 마치 사람들이 대화를 나누는 것처럼, 프로그램들도 서로 "대화"를 할 수 있어야 하는데, 이를 가능하게 하는 방법을 IPC
- RPI (Remote Procedure Invocation)
-
원격 프로시저 호출
-
다른 컴퓨터에 있는 프로그램의 기능을 마치 내 컴퓨터에 있는 것처럼 사용하는 방법
-
예를 들어, 여러분이 친구에게 전화해서 어떤 일을 부탁하고 그 결과를 받는 것과 비슷
-
여러분의 컴퓨터가 네트워크를 통해 다른 컴퓨터에 있는 프로그램에 "전화를 걸어" 어떤 작업을 수행하도록 요청하고, 그 결과를 받아오는 것이 RPI
REST 성숙도 모델 (레너드 리처드슨의 모델)
- 레벨 0: 단일 URL 끝점, HTTP POST로만 통신
- 레벨 1: 리소스 개념 도입, 여전히 POST만 사용
- 레벨 2: HTTP 동사 활용 (GET, POST, PUT 등)
- 레벨 3: HATEOAS 원칙 적용, 리소스 간 링크 제공
REST API와 IDL (Interface Definition Language)
- 과거 REST에는 IDL이 없었음
- 개발자 커뮤니티의 요구로 IDL 필요성 대두
- 스웨거 프로젝트를 통해 오픈 API 명세가 REST IDL로 발전
- 스웨거: REST API 개발/문서화 도구 세트, 클라이언트 스텁과 서버 스켈레톤 생성 기능 포함
- REST API의 성숙도는 다양한 레벨로 구분될 수 있으며,
- 높은 레벨의 REST API는 더 유연하고 강력한 기능을 제공
- 또한, REST API도 명확한 정의와 문서화가 필요하며, 이를 위해 IDL과 같은 도구의 사용이 중요해짐
REST API의 문제점
- 여러 리소스를 한 번에 가져오기 어려움
- HTTP 동사에 복잡한 작업을 매핑하기 어려움
해결 방안
- 쿼리 매개변수를 사용한 확장 (예: ?expand=consumer)
- 하위 리소스 정의 (예: GET /orders/order-id-1345?expand=consumer 주문 소비자 한꺼번에 조회 )
- 시나리오가 복잡해지면 효율이 떨어지고 구현 시간이 많이 소요되는 문제
- REST API는 데이터를 수정할 때 대개 PUT 동사를 쓰지만, 가령 주문 데이터만 하더라도 이 데이터를 업데이트하는 경로는 주문 취소/변경 등 다양
- 주문 취소 끝점 POST /orders/{orderId}/cancel 등.. 으로 해결
- GraphQL, Netflix Falcor 등의 대체 기술 사용
REST의 장점
- 단순하고 익숙함
- 테스트 용이성
- 요청/응답 스타일 통신 지원
- 방화벽 친화적
- 중간 브로커 불필요
REST의 단점
- 요청/응답 스타일 통신만 지원
- 가용성 문제 ( 가용성이란? )
- 클라이언트/서비스가 직접 통신하기 때문에 교환이 일어나는 동안 양쪽 다 실행 중이어야 함
- 시스템이나 서비스가 필요할 때 정상적으로 작동하고 접근 가능한 상태에 있는 시간의 비율
- 사용자가 서비스를 이용하고자 할 때 실제로 이용 가능한 정도
- 시스템이 장애 없이 지속적으로 운영되는 능력
- 클라이언트가 서비스 위치를 알아야 함
- 다중 리소스 조회 및 업데이트의 어려움
- 다중 업데이트 작업을 HTTP 동사에 매핑하기 어려울 때가 많음
- REST API는 널리 사용되는 표준이지만 복잡한 시나리오에서 한계.
- 이러한 한계를 극복하기 위해 다양한 해결 방안과 대체 기술들이 등장하고 있으며,
- 개발자들은 이러한 옵션들을 고려하여 적절한 API 설계를 해야 함
동기 RPI 패턴: gRPC
- 다양한 언어로 클라이언트/서버 작성 가능한 프레임워크
- 이진 메시지 기반 프로토콜
- 프로토콜 버퍼 IDL로 API 정의
- HTTP는 한정된 동사만 지원하기 때문에 다양한 업데이트 작업을 지원하는 REST API를 설계하기가 쉽지 않아 등장한 기술
gRPC의 특징
- 서비스와 요청/응답 메시지 정의로 구성
- 단순 요청/응답 및 스트리밍 RPC 지원
- 프로토콜 버퍼 메시지 포맷 사용
gRPC API 예시
gRPC의 장점
- 다양한 업데이트 작업이 포함된 API를 설계
- 큰 메시지를 교환할 때 콤팩트하고 효율적인 IPC
- 양방향 스트리밍 지원
- 다양한 언어 간 연동 가능
gRPC의 단점
- 자바스크립트 클라이언트 구현 복잡성
- 구형 방화벽의 HTTP/2 지원 문제
- gRPC는 REST의 한계를 극복하고 더 효율적이고 유연한 API 설계를 가능하게 하는 대안 기술
- 그러나 REST와 마찬가지로 동기 통신 메커니즘이기 때문에 부분 실패 문제는 여전히 해결해야 할 과제
부분 실패 처리: 회로 차단기 패턴
부분 실패의 문제
- 분산 시스템에서 서비스 간 동기 호출 시 발생 가능
- 시스템 전체의 중단으로 이어질 위험
회로 차단기 패턴
- 연속 실패 횟수가 임계치 초과 시 호출을 즉시 거부하는 RPI 프록시
견고한 RPI 프록시 설계 방법
- 네트워크 타임아웃 설정
- 미처리 요청 개수 제한
- 회로 차단기 패턴 적용
- 성공/실패 요청 개수를 지켜보다가 에러율이 주어진 임계치를 초과하면 그 이후 시도는 바로 실패 처리
- 타임아웃 시간 이후 클라이언트가 재시도해서 성공하면 회로 차단기는 닫힙니다.
- 넷플릭스 히스트릭스(Netflix Hystrix)23는 이와 같이 다양한 패턴이 구현된 오픈 소스 라이브러리
불능 서비스 복구 방법
- 에러 반환
- 대체 값(기본값, 캐시된 응답) 반환
API 게이트웨이의 역할
- 여러 서비스 호출 결과 조합
- 부분 실패 시 적절한 대응 전략 실행
- 분산 시스템에서 부분 실패는 피할 수 없는 문제이며, 이를 효과적으로 처리하기 위해서는 견고한 RPI 프록시 설계와 적절한 복구 전략이 필요
- 서비스 디스커버리 메커니즘의 중요성도 강조
서비스 디스커버리
서비스 디스커버리의 필요성
- 클라우드 기반 마이크로서비스의 동적인 네트워크 위치
- 자동 확장, 실패, 업그레이드로 인한 서비스 인스턴스 변화
- 서비스 인스턴스마다 네트워크 위치가 동적 배정됨
- 서비스 인스턴스는 자동 확장, 실패, 업그레이드 등 여러 가지 사유로 계속 달라지므로 클라이언트 코드는 서비스 디스커버리를 사용할 수밖에 없음
서비스 디스커버리 메커니즘
- 서비스 레지스트리를 이용한 네트워크 위치 관리
- 두 가지 구현 방법: 직접 상호작용 vs 배포 인프라 이용
- 핵심은 애플리케이션 서비스 인스턴스의 네트워크 위치를 DB화한 서비스 레지스트리(service registry)입니다.
- 서비스 인스턴스가 시작/종료할 때마다 서비스 레지스트리가 업데이트됩니다.
- 클라이언트가 서비스를 호출하면 우선 서비스 디스커버리가 서비스 레지스트리에서 가용 서비스 인스턴스 목록을 가져오고, 그중 한 서비스로 요청을 라우팅합니다.
애플리케이션 수준의 서비스 디스커버리
- 자가 등록 패턴: 서비스가 자신을 레지스트리에 등록
- 클라이언트 쪽 디스커버리 패턴: 클라이언트가 레지스트리 조회 후 부하 분산
- 서비스 클라이언트는 이 서비스 레지스트리로부터 전체 서비스 인스턴스 목록을 가져와 그중 한 인스턴스로 요청을 라우팅합니다.
- 예: 넷플릭스의 유레카, 피보탈의 스프링 클라우드
- 도커나 쿠버네티스 등 최신 배포 플랫폼에는 대부분 서비스 레지스트리, 서비스 디스커버리 메커니즘이 탑재되어 있습니다
- 배포 플랫폼이 제공하는 서비스 레지스트리와 디스커버리 메커니즘
- 서드파티 등록 패턴: 등록기가 서비스 등록 대행
- 서버 쪽 디스커버리 패턴: 요청 라우터가 서비스 레지스트리 조회 및 부하 분산
- 서비스 디스커버리는 동적인 마이크로서비스 환경에서 필수적
- 애플리케이션 수준과 플랫폼 수준의 구현 방식이 있습니다.
- 각 방식의 장단점을 고려하여 선택해야 하지만, 가능하다면 플랫폼에서 제공하는 서비스 디스커버리를 사용하는 것이 권장됩니다.
비동기 메시징 패턴 응용 통신
비동기 메시징 패턴 소개 (메시징은 서비스가 메시지를 서로 비동기적으로 주고받는 통신 방식)
- 서비스 간 비동기적 통신 방식
- 메시지 브로커 사용 또는 브로커리스 아키텍처 가능
- 클라이언트는 응답을 기다리지 않고 비블로킹 방식으로 동작
메시징 모델의 구성 요소
- 메시지: 헤더(메타데이터)와 본문(실제 데이터)으로 구성
- 메시지 종류: 문서, 커맨드, 이벤트
- 문서 : 데이터만 포함된 제네릭한 메시지(예: 커맨드에 대한 응답). 메시지를 어떻게 해석할지는 수신자가 결정
- 커맨드 : RPC(원격 프로시저 호출) 요청과 동등한 메시지. 호출할 작업과 전달할 매개변수가 지정되어 있습니다.
- 이벤트 : 송신자에게 어떤 사건이 발생했음을 알리는 메시지. 이벤트는 대부분 Order, Customer 같은 도메인 객체의 상태 변화를 나타내는 도메인 이벤트
- 메시지 채널: 송신자와 수신자 간 메시지 교환 통로
메시징 아키텍처
1.송신 포트 인터페이스 -> 메시지 송신자 어댑터 -> 메시지 채널 -> 메시지 핸들러 어댑터 -> 수신 포트 인터페이스
2. 채널 종류: 점대점 채널, 발행-구독 채널
-
점대점 : 채널: 채널을 읽는 컨슈머 중 딱 하나만 지정하여 메시지를 전달합니다. 앞서 설명한 일대일 상호 작용 스타일의 서비스가 이 채널을 사용합니다(예: 커맨드 메시지).
-
발행 - 구독 : 채널34: 같은 채널을 바라보는 모든 컨슈머에 메시지를 전달합니다. 앞서 설명한 일대다 상호 작용 스타일의 서비스가 이 채널을 사용합니다(예: 이벤트 메시지).
-
비동기 메시징 패턴이 마이크로서비스 아키텍처에서 유연하고 효율적인 통신 방식을 제공.
-
이 패턴은 서비스 간 느슨한 결합을 가능하게 하고, 다양한 상호 작용 스타일을 지원
메시징을 통한 다양한 상호 작용 스타일 구현 가능성
요청/응답 및 비동기 요청/응답
- 둘 다 클라이언트가 요청을 보내고 서비스가 응답
- 요청/응답: 즉시 응답 기대
- 비동기 요청/응답: 즉시 응답을 기대하지 않음
- 메시징은 기본적으로 비동기적이나 블로킹 구현 가능
비동기 요청/응답 구현
- 클라이언트: 커맨드 메시지를 점대점 채널로 전송
- 서비스: 응답 메시지를 클라이언트의 점대점 채널로 전송
- MessageId와 CorrelationId를 통한 요청-응답 매칭
단방향 알림
- 클라이언트가 점대점 채널로 메시지 전송
- 서비스는 응답하지 않음
발행/구독
- 여러 컨슈머가 읽는 채널에 메시지 발행
- 도메인 이벤트 발행에 주로 사용
발행/비동기 응답
- 발행/구독과 요청/응답의 조합
- 클라이언트: 응답 채널 정보가 포함된 메시지 발행
- 컨슈머: CorrelationId가 포함된 응답 메시지 전송
- 메시징 시스템에서 구현 가능한 다양한 통신 패턴을 설명
- 각 패턴의 특징과 구현 방식을 소개하여, 개발자들이 상황에 맞는 적절한 통신 방식을 선택하자
메시징 기반 서비스의 API 명세 작성
- 메시지 채널명, 교환되는 메시지 타입과 포맷 명시
- JSON, XML, 프로토콜 버퍼 등 표준 포맷 사용
- 비동기 API는 클라이언트 호출 작업과 서비스 발행 이벤트로 구성
- 비동기 작업 문서화: 요청/비동기 응답 스타일과 단방향 알림 스타일
- 발행 이벤트 문서화: 이벤트 채널, 발행 이벤트 메시지 타입과 포맷
브로커리스 메시징
- 서비스 간 직접 메시지 교환 (예: ZeroMQ)
- 장점: 낮은 네트워크 트래픽, 짧은 지연 시간, SPOF 없음, 낮은 운영 복잡도
- 단점: 서비스 디스커버리 필요, 낮은 가용성, 복잡한 메커니즘 구현 어려움
브로커 기반 메시징 (보통 이 방법 권장)
- 중앙 집중식 메시지 교환 (메시지 브로커 사용)
- 장점: 송신자가 수신자 위치 몰라도 됨, 메시지 버퍼링 가능
- 주요 제품: ActiveMQ, RabbitMQ, Apache Kafka, AWS Kinesis, AWS SQS
메시지 브로커 선택 시 고려사항
- 프로그래밍 언어 지원
- 메시징 표준 지원 (AMQP, STOMP 등)
- 메시지 순서 유지
- 전달 보장
- 메시지 영속화
- 내구성 (접속 중단 시 메시지 처리)
- 확장성
- 지연 시간
- 경쟁사 컨슈머 지원
브로커 선택의 트레이드오프
- 짧은 지연 시간 vs 메시지 순서 유지/전달 보장
- 메모리 저장 vs 디스크 저장
- 애플리케이션 요구사항에 따른 최적 선택 필요
- 애플리케이션 내 다른 부분에 다른 메시징 요건 가능성
메시징 순서 유지 및 확장성의 중요성 강조
- 메시징 시스템 구현 시 API 명세 작성 방법, 브로커 사용 여부 결정, 적절한 메시지 브로커 선택의 중요성을 강조
- 각 선택에 따른 장단점을 이해하고, 애플리케이션의 요구사항에 맞는 최적의 솔루션을 선택해야 함
메시지 브로커로 메시지 채널 구현
- 메시지 브로커마다 채널 구현 방식이 다름
- JMS: 큐(점대점), 토픽(발행-구독)
- AMQP (RabbitMQ): 익스체인지+큐(점대점), 팬아웃 익스체인지+개별 큐(발행-구독)
- Apache Kafka: 토픽(점대점, 발행-구독)
- AWS Kinesis: 스트림(점대점, 발행-구독)
- AWS SQS: 큐(점대점만 지원)
브로커 기반 메시징의 장단점
- 장점:
- 느슨한 결합: 클라이언트가 서비스 인스턴스 위치를 몰라도 됨
- 메시지 버퍼링: 수신자 처리 가능할 때까지 메시지 저장
- 유연한 통신: 다양한 상호 작용 스타일 지원
- 명시적 IPC: 원격 서비스 호출의 특성을 명확히 인식
- 단점:
- 성능 병목 가능성 -> 다행히 요즘 메시지 브로커는 대부분 확장이 잘 되도록 설계되었습니다.
- 단일 장애점 가능성 -> 다행히 요즘 브로커는 대부분 고가용성이 보장되도록 설계
- 운영 복잡도 증가 -> : 메시징 시스템 역시 설치, 구성, 운영해야 할 시스템 컴포넌트입니다.
수신자 경합과 메시지 순서 유지
- 문제: 다수의 수신자로 인한 메시지 순서 꼬임 가능성
- 해결책: 샤딩된 채널 사용
a) 채널을 여러 샤드로 구성
b) 송신자가 메시지 헤더에 샤드 키 지정
c) 메시지 브로커가 수신자 인스턴스를 그룹화하고 샤드 배정
- 아파치 카프카, AWS 키네시스 등 요즘 메시지 브로커는 샤딩된(sharded, 파티셔닝된(partitioned)) 채널을 이용
중복 메시지 처리
1 .문제: 메시지 브로커의 '적어도 한 번 전달' 보장으로 인한 중복 가능성
-
해결 방법:
a) 멱등한 메시지 핸들러 작성
동일 입력에 대해 부수 효과 없는 처리
실제로는 구현이 어려운 경우가 많음
b) 메시지 추적과 중복 제거
메시지 ID를 DB에 기록하여 처리 여부 추적
전용 테이블 사용 또는 애플리케이션 테이블에 기록
-
메시징 시스템 구현 시 고려해야 할 여러 측면들을 상세히 설명
-
특히 메시지 채널 구현 방식, 브로커 사용의 장단점, 메시지 순서 유지 방법, 중복 메시지 처리 방법 등을 다룸
트랜잭셔널 메시징의 필요성
- DB 업데이트와 메시지 전송을 원자적으로 수행해야 함
- 분산 트랜잭션의 한계로 인해 새로운 방법 필요
트랜잭셔널 아웃박스 패턴
- OUTBOX 테이블을 임시 메시지 큐로 사용
- DB 트랜잭션의 일부로 메시지를 OUTBOX에 삽입
- 메시지 릴레이가 OUTBOX에서 메시지를 읽어 발행
메시지 발행 방법
a) 폴링 발행기 패턴
OUTBOX 테이블을 주기적으로 폴링
미발행 메시지를 조회하여 발행
장점: 단순함
단점: DB 부하, 확장성 제한
- DB 폴링은 규모가 작을 경우 쓸 수 있는 단순한 방법입니다. 하지만 DB를 자주 폴링하면 비용이 유발되고 NoSQL DB는 쿼리 능력에 따라 사용 가능 여부가 결정
b) 트랜잭션 로그 테일링 패턴
- 랜잭션 로그 마이너는 트랜잭션 로그 항목을 읽고, 삽입된 메시지에 대응되는 각 로그 항목을 메시지로 전환하여 메시지 브로커에 발행합니다. RDBMS의 OUTBOX 테이블에 출력된 메시지 또는 NoSQL DB에 레코드로 추가된 메시지를 이런 식으로 발행할 수 있습니다.
DB 트랜잭션 로그를 읽어 변경사항 발행
예:
- Debezium( DB 변경분을 아파치 카프카 메시지 브로커에 발행하는 오픈 소스 프로젝트)
- LinkedIn Databus
- DynamoDB Streams
- 이벤추에이트 트램(Eventuate Tram) (MYSQL 빈로그(binlog) 프로토콜, Postgres(포스트그레스) WAL(Write-Ahead Logging), 폴링을 응용해서 OUTBOX 테이블의 변경분을 읽어 아파치 카프카로 발행)
장점: 효율적, 확장성 좋음,
단점: 구현 복잡
- 트랜잭션 로그를 읽어 변경분을 하나씩 메시지로 메시지 브로커에 발행
메시징 라이브러리/프레임워크의 필요성
- 서비스가 메시지를 주고받으려면 라이브러리가 필요합니다. 메시지 브로커에도 클라이언트 라이브러리가 있지만 직접 사용하면 다음과 같은 문제 있음
- 메시지 브로커 API에 메시지를 발행하는 비즈니스 로직이 클라이언트 라이브러리와 결합
- 메시지 브로커의 클라이언트 라이브러리는 대부분 저수준이고 메시지를 주고받는 코드가 꽤 긴 편
- 메시지 브로커의 클라이언트 라이브러리는 기본적인 메시지 소통 수단일 뿐, 고수준의 상호 작용 스타일은 지원하지 않습니다.
- 고수준 추상화 및 편의 기능 제공 필요
Eventuate Tram 프레임워크 소개
- 트랜잭셔널 메시징 지원
- 중복 메시지 감지 기능
- 기본 메시징 API: MessageProducer, MessageConsumer
- 도메인 이벤트 발행/구독 API
- 커맨드/응답 메시징 API
Eventuate Tram의 주요 기능
- 트랜잭셔널 메시징
- 도메인 이벤트 처리
- 커맨드/응답 패턴 지원
- 저수준 및 고수준 API 제공
- 마이크로서비스 아키텍처에서 트랜잭셔널 메시징의 중요성과 이를 구현하기 위한 다양한 패턴 및 도구를 소개
- Eventuate Tram 프레임워크를 통해 이러한 패턴들을 쉽게 구현할 수 있음
기초 메시징 API
- 기초 메시징 API는 MessageProducer, MessageConsumer 두 인터페이스로 구성됩니다. 프로듀서 서비스는 다음 코드처럼 MessageProducer 인터페이스를 통해 메시지를 메시지 채널에 발행
MessageProducer 인터페이스: 메시지 발행
javaCopyMessageProducer messageProducer = ...;
String channel = ...;
String payload = ...;
messageProducer.send(destination, MessageBuilder.withPayload(payload).build())
- 컨슈머 서비스는 MessageConsumer 인터페이스를 통해 메시지를 구독합니다.
MessageConsumer 인터페이스: 메시지 구독
javaCopyMessageConsumer messageConsumer;
messageConsumer.subscribe(subscriberId, Collections.singleton(destination), message -> { ... })
- 이 두 인터페이스는 비동기 요청/응답 및 도메인 이벤트 발행에 관한 핵심 고수준 API입니다.
도메인 이벤트 발행/구독
- DomainEventPublisher 인터페이스: 도메인 이벤트 발행
javaCopyDomainEventPublisher domainEventPublisher;
String accountId = ...;
DomainEvent domainEvent = new AccountDebited(...);
domainEventPublisher.publish("Account", accountId, Collections.singletonList(domainEvent));
DomainEventDispatcher 클래스: 도메인 이벤트 소비
javaCopyDomainEventHandlers domainEventHandlers = DomainEventHandlersBuilder
.forAggregateType("Order")
.onEvent(AccountDebited.class, domainEvent -> { ... })
.build();
new DomainEventDispatcher("eventDispatcherId", domainEventHandlers, messageConsumer);
커맨드/응답 메시징
- CommandProducer 인터페이스: 커맨드 메시지 전송
javaCopyCommandProducer commandProducer = ...;
Map<String, String> extraMessageHeaders = Collections.emptyMap();
String commandId = commandProducer.send("CustomerCommandChannel", new DoSomethingCommand(), "ReplyToChannel", extraMessageHeaders);
CommandDispatcher 클래스: 커맨드 메시지 소비 및 처리
javaCopyCommandHandlers commandHandlers = CommandHandlersBuilder
.fromChannel(commandChannel)
.onMessage(DoSomethingCommand.class, (command) -> {
... ;
return withSuccess();
})
.build();
CommandDispatcher dispatcher = new CommandDispatcher("subscribeId", commandHandlers, messageConsumer, messageProducer);
Eventuate Tram 프레임워크의 주요 특징
- 자바 애플리케이션용 트랜잭셔널 메시징 기본 지원
- 트랜잭션이 걸린 상태에서 메시지 송수신 가능한 저수준 API 제공
- 도메인 이벤트 발행/소비 및 커맨드 전송/처리를 위한 고수준 API 제공
- Eventuate Tram 프레임워크가 제공하는 다양한 수준의 메시징 API를 소개하고, 이를 통해 마이크로서비스 간의 효율적인 통신을 구현할 수 있다는 것
비동기 메시징으로 가용성 개선
비동기 메시징의 필요성
- 동기 통신(예: REST)은 서비스 간 의존성으로 인해 전체 시스템 가용성 저하
- 수학적으로 시스템 가용성 = 각 서비스 가용성의 곱
- 비동기 메시징으로 가용성 개선 가능
동기 통신의 문제점
- 예: 주문 생성 API에서 소비자 서비스, 음식점 서비스 동기 호출
- 모든 서비스가 동시에 가용해야 함
- 한 서비스라도 다운되면 전체 기능 불가
동기 상호 작용 제거 방법
a. 비동기 상호 작용 스타일 사용
- 클라이언트와 서비스 간 메시징 채널을 통한 비동기 통신
- 블로킹 없는 통신으로 탄력성 향상
b. 데이터 복제
- 필요한 데이터의 레플리카를 각 서비스에서 유지
- 이벤트 구독을 통한 데이터 동기화
- 장점: 타 서비스 의존성 감소, 단점: 대용량 데이터 복제 시 비효율적
c. 응답 반환 후 마무리
- 로컬 데이터로 요청 검증
- DB 업데이트(OUTBOX 테이블에 메시지 삽입)
- 클라이언트에 즉시 응답
- 이후 비동기적으로 타 서비스와 통신
비동기 처리 예시 (주문 생성)
- 주문을 PENDING 상태로 생성
- 클라이언트에 즉시 응답
- 소비자 서비스, 음식점 서비스에 비동기 검증 요청
- 검증 결과에 따라 주문 상태 업데이트
비동기 처리의 장단점
- 장점:
- 서비스 간 느슨한 결합
- 개별 서비스 장애 시에도 전체 시스템 운영 가능
- 확장성 및 탄력성 향상
- 단점:
-
클라이언트 코드 복잡성 증가
-
최종 일관성으로 인한 추가 로직 필요 (폴링 또는 알림)
-
마이크로서비스 아키텍처에서 비동기 메시징을 통해 서비스 간 결합도를 낮추고 전체 시스템의 가용성과 탄력성을 높일 수 있다
-
이는 동기 통신의 한계를 극복하고 더 안정적인 시스템을 구축하는 데 중요한 방법