기록이 힘이다.

[도메인 주도 마이크로서비스 개발] 2. MSA의 이해 - DDD 본문

IT서적/도메인 주도 설계로 시작하는 마이크로서비스 개발

[도메인 주도 마이크로서비스 개발] 2. MSA의 이해 - DDD

dev22 2023. 8. 9. 13:38
728x90

리액티브 선언: 현대 애플리케이션이 갖춰야 할 바람직한 속성들 

 

아키텍처를 정의하는 과정은 시스템 구축을 위한 여러 가지 비기능 요건(성능, 가용성, 보안, 유지보수성, 확장성 등)을 만족하는 다양한 해결 방법을 찾는 과정이다. 다른 말로 표현하면 여러 문제 영역에 대한 해결책을 찾는 과정이다. 

 

사람들은 기기에 포함된 애플리케이션이 요청에 즉각 응답하고 항상 가동되길 기대한다. 

reactive의 사전적 의미는 '반응을 보이는'이다. 이는 다양한 상황에 따라 빠르고 적절하게 반응하는 시스템을 의미한다. 급변하는 상황에 적응할 수 있는 시스템을 요구하는 것이다. 

 

리액티브 시스템이 신뢰성 있는 응답을 빠르게 제공하고 부분적 장애가 빨리 복구되고 수요 증가에 탄력적으로 대응하기 위해서는 시스템 자체가 변화와 확장에 언제든지 대응할 수 있는 아키텍처 유연성을 갖추는 것이 필수다. 

 

강 결합에서 느슨한 결합의 아키텍처로의 변화 

 

특정 벤더 중심의 아키텍처는 검증된 유명 제품군을 사용한다는 점에서 품질이 보장된다고 생각할 수 있는 반면, 특정 벤더에 의존한다는 점에서 특정 기술에 락인되어 쉽게 변경하거나 확장하지 못한다는 단점이 있다. 

클라우드 환경하에서 사용되는 오픈소스 또는 오픈소스를 기반으로 한 상용 제품들이 이전의 유명 벤더의 제품군 만큼이나 품질이 높아지고 다양한 기능을 지원하면서 서로 다른 오픈소스 제품 간에도 충반한 호환성을 제공하기 때문이다. 

 

이러한 모습은 강 결합 위주의 모노리스 아키텍처가 유연하고 느슨하게 결합되는 마이크로서비스 기반 아키텍처로 변화되고 있음을 보여주기도 한다. 

 

MSA 구성요소 및 MSA 패턴

 

저명한 소프트웨어 아키텍트인 크리스 리처드슨은 마이크로서비스 관련 기술을 설명하는 자신의 매체에서 이러한 마이크로서비스 아키텍처 패턴을 인프라 패턴, 애플리케이션 인프라 패턴, 애플리케이션 패턴 등으로 분류해서 정의했다. 

 

인프라가 구축돼야 하고, 그 위에 미들웨어가 올라가고, 미들웨어 위에서 애플리케이션이 동작해야 한다. 따라서 인프라, 미들웨어 영역을 대신하고 있는 플랫폼, 애플리케이션 순으로 살펴보자. 

 

패턴 유형 설명
인프라 구성요소 마이크로서비스를 지탱하는 하부구조 인프라를 구축하는 데 필요한 구성요소
플랫폼 패턴 인프라 위에서 마이크로서비스의 운영과 관리를 지원하는 플랫폼 차원의 패턴
애플리케이션 패턴 마이크로서비스 애플리케이션을 구성하는 데 필요한 패턴

 

인프라 구성요소

퍼블릭 클라우드와 베어 메탈, 프라이빗 클라우드 환경 

이제는 세계적인 플랫폼 사업자들이 자동화된 Iaas, Paas, Saas 서비스를 통해 쉽고 편하게 이용할 수 있게 제공한다. 

 

VM과 컨테이너 

도커 컨테이너의 이점 : 이식성, 신속성, 재사용성

 

마이크로서비스 같이 독립적이고 배포되고 수정되기 위한 환경은 가상 머신보다는 컨테이너가 더 적절하다. 왜냐하면 가변적이고 유연한 속성을 컨테이너가 쉽고 빠르게 지원할 수 있기 때문이다. 

 

가장 많이 사용하는 가상 머신 제품군으로는 AWS EC2, 애저 VM 등이 있고, 컨테이너 기술로는 도커가 자리잡고 있다. 

 

컨테이너 오케스트레이션

컨테이너가 많아지면 그에 따라 컨테이너의 자동 배치 및 복제, 장애 복구, 확장 및 축소, 컨테이너 간 통신, 로드 밸런싱 등의 컨테이너 관리를 위한 기능이 필요해진다. 

컨테이너 오케스트레이션 도구로는 도커 스윔, 아파치 메소스 등이 있다. 구글이 자사의 도커 컨테이너 관리 노하우를 CNCF 재단에 제공해서 공개한 쿠버네티스가 큰 인기를 끌고 있다. 

컨테이너 배포의 기본 단위에 해당하는 파드, 디플로이먼트, 레플리라셋 정보를 확인하고 설정할 수 있다. 쿠버네티스는 다음과 같은 주요 기능을 제공한다. 

자동화된 자원 배정, 셀프 치유, 수평 확장

 

그 밖의 다양한 클라우드 인프라 서비스

 

애플리케이션 형태를 MSA가 아닌 모노리스 시스템으로 구축하기 위해서는 가상 머신 클라우드 제품군인 AWS EC2, Azure VM을 사용할 수도 있고 모노리스 시스템이 아니라 MSA 시스템으로 간다고 해도 쿠버네티스는 아니지만 동일하게 컨테이너 기반인 AWS Elastic Beanstalk나 ECS,  Azure Web App, Google App Engine 등의 Paas를 고려할 수도 있다. 

 

마이크로서비스 운영과 관리를 위한 플랫폼 패턴

 

개발 지원 환경: 데브옵스 인프라 구성 

여기서는 협의의 의미로 개발과 운영을 병행 가능하게끔 높은 품질로 소프트웨어를 빠르게 개발하도록 지원하는 빌드, 테스트, 배포를 위한 자동화 환경을 말한다. 

 

CI(지속적 통합)은 애자일 방법론 중 켄트 벡이 만든 XP의 주요 프랙티스로 시작됐으며, 오랜 시간이 걸리는 빌드를 매일 자동화해서 수행한다면 개발 생산성이나 소스코드 품질이 높아진다는 경험에서 출발했다. 

자동으로 통합 및 테스트하고 그 결과를 리포트로 기록하는 활동을 CI라고 하고, 실행 환경에 내보내는 활동을 CD라 한다. 

경험으로 획득한 지혜: 마이크로서비스 관리/운영 패턴

스프링 부트와 스프링 클라우드를 이용하면 마이크로서비스 애플리케이션의 운영 환경을 쉽게 구축할 수 있다. 

 

스프링 클라우드: 스프링 부트 + 넷플릭스 OSS

다양한 서비스의 등록 및 탐색을 위한 서비스 레지스트리, 서비스 디스커버리 패턴

 

서비스 디스커버리 패턴은 클라이언트가 여러 개의 마이크로서비스를 호출하기 위해서는 최적 경로를 찾아주는 라우팅 기능과 적절한 부하 분산을 위한 로드 밸런싱 기능이 제공돼야 한다. 넷플릭스의 OSS로 예를 들면 라우팅 기능은 줄(Zuul)이, 로드 밸런싱은 리본(Ribbon)이 담당한다. 

서비스 레지스트리 패턴은 백엔드 마이크로서비스 서비스의 명칭과 유동적인 IP 정보를 매핑해서 보관할 저장소가 필요하다 넷플릭스 OSS의 유레카(Eureka)가 그 기능을 담당한다. 

 

서비스 단일 진입을 위한 API 게이트웨이 패턴

 

다양한 클라이언트가 다양한 서비스에 접근하기 위해서는 단일 진입점을 만들어 놓으면 여러모로 효율적이다. 다른 유형의 클라이언트에게 서로 다른 API 조합을 제공할 수도 있고, 각 서비스에 접근할 때 필요한 인증/인가 기능을 한 번에 처리할 수도 있다. 또 정상적으로 동작하던 서비스에 문제가 생겨 서비스 요청에 대한 응답 지연이 발생하면 정상적인 다른 서비스로 요청 경로를 변경하는 기능이 작동되게 할 수도 있다. 

 

API 게이트웨이 패턴은 스프링 클라우드의 스프링 API 게이트웨이 서비스라는 제품으로 구현할 수 있다. 

 

BFF 패턴

 

다양한 클라이언트를 위해서 특화된 처리를 위한 API 조합이나 처리가 필요. API 게이트웨이와 같은 진입점을 하나로 두지 않고 프런트엔드의 유형에 따라 각각 두는 패턴이다. Backend For Frontend 프런트를 위한 백엔드

외부 구성 저장소 패턴 

 

마이크로서비스가 사용하는 자원의 설정 정보를 쉽고 일관되게 변경 가능하도록 관리할 필요가 있다. 각 마이크로서비스의 외부 환경 설정 정보를 공동으로 저장하는 백업 저장소다. 쿠버네티스 - 컨피그맵

인증/ 인가 패턴

 

중앙 집중식 세션 관리 

세션 저정소로 보통 레디스나 멤캐시드를 사용한다. 

 

클라이언트 토큰

사용자의 브라우저에 저장된다. JWT(RFC7519)

 

장애 및 실패 처리를 위한 서킷 브레이커 패턴

 

시스템 과부하나 특정 서비스에 문제가 생겼을 때 자연스럽게 다른 정상적인 서비스로 요청 흐름이 변경되게 해야 한다. 전기회로 차단기와 비슷하다고 해서 서킷 브레이커 패턴. 

중앙화된 로그 집계 패턴

ELK 스택은 ElasticSearch(분산형 검색.분석 엔진), Logstash(로그 집합기), Kibana(시각화)라는 세 가지 오픈소스 프로젝트를 기반으로 데이터 분석환경을 구성한 것이다. 

각 서비스의 인스턴스 로그를 집계해서 중앙에서 집중 관리할 수 있으며, 손쉽게 특정 로그를 검색 및 분석할 수 있다. 또한 특정 메시지가 로그에 나타나거나 특정 예외가 발생할 때 운영자나 개발자에게 직접 통보하게 할 수도 있다. 

 

 서비스 메시 패턴

대표적 구현체인 구글의 이스티오(IStio)를 나타낸다. 애플리케이션이 배포되는 컨테이너에 완전히 격리되어 별도의 컨테이너로 배포되는 사이드카 패턴을 적용해서 서비스 디스커버리, 라우팅, 로드 밸런싱, 로깅, 모니터링, 보안, 트레이싱 등의 기능을 제공한다. 

기본적으로 이스티오는 쿠버네티스에 탑재되어 이러한 서비스 메시 기능을 지원한다. 

 

애플리케이션 패턴

UI 컴포지트 패턴 또는 마이크로 프런트엔드 

 

하나의 기능을 변경했을 때 이를 제공하는 마이크로 프런트엔드와 백엔드를 구성하는 마이크로서비스가 모두 변경되고 배포된다. 

 

마이크로서비스 통신 패턴

 

동기 통신 방식 

클라이언트에서 서버 측에 존재하는 마이크로서비스 REST API를 호출할 때 사용되는 기본 통신 방법, 다양한 클라이언트 채널 연계나 라우팅 및 로드 밸런싱을 원활하게 하기 위한 방법으로 중간에 API 게이트웨이를 둘 수 있다. 

동기 방식은 요청하면 바로 응답이 오는 방식을 말한다. 

서비스가 다른 서비스를 호출해서 얻은 정보를 이용해 기능을 제공한다는 의미는 해당 서비스 간의 의존관계가 높다는 것을 위미한다. 비즈니스 기능 처리를 어렵게 만든다. 

 

비동기 통신 방식

동기 호출처럼 응답을 기다리지 않는다 메시지를 보낸 다음 응답을 기다리지 않고 다음 일을 처리한다. 동기식처럼 완결성을 보장할 수는 없다 이를 보장하기 위한 매커니즘이 필요한데, 보통 아파치 카프카(Apache Kafka), 레빗엠 큐(RabbitMQ) 같은 메시지 브로커를 활용한다. 

이러한 메커니즘에서는 메시지를 보내는 생산자와 메시지를 가져다가 처리하는 소비자가 서로 직접 접속하지 않고 메시지 브로커에 연결된다. 클라우드 벤더에서 완전관리형으로 제공하는 AWS의 SQS나 SNS, Azure Event Hub 등도 많이 사용된다. 

 

저장소 분리 패턴

 

각 마이크로서비스는 각자의 비즈니스를 처리하기 위한 데이터를 직접 소유해야 한다는 것을 말한다. 그렇기 때문에 자신이 소유한 데이터는 다른 서비스에 직접 노출하지 않고 각자가 공개한 API를 통해서만 접근할 수 있다(정보 은닉). 또한 저장소가 격리돼 있기 때문에 각 저장소를 자율적으로 선택할 수 있다(폴리그랏 저장소). 궁극적으로 이 같은 제약이 데이터를 통한 변경의 파급 효과(영향도)를 줄여 서비스를 독립적으로 만든다. 

 

여러 개의 분산된 서비스에 걸쳐 비즈니스 처리를 수행해야 하는 경우 비즈니스 정합성 및 데이터 일관성을 어떻게 보장할 것인가에 대한 문제다. 

 

분산 트랜잭션 처리 패턴

 

손쉽게 적용할 수 있는 한 가지 방법은 여러 개의 분산된 서비스를 하나의 일관된 트랜잭션으로 묶는 것이다. 

앞에서 언급한 것처럼 분산 트랜잭션 처리에서는 여러 서비스 간의 비즈니스 및 데이터 일관성을 유지할 필요가 있다. 분산 트랜잭션 처리를 위한 전통적인 방법으로 2단계 커밋 같은 기법이 있다. 그런데 이 방법은 각 서비스에 잠금이 걸려 발생하는 성능 문제 탓에 효율적인 방법이 아니다. 

특히 클라우드의 가장 큰 장애는 네트워크 장애, 특정 서비스가 처리되지 않을 경우 즉시 영향을 받기도 한다. 

 

마이크로서비스의 독립적인 분산 트랜잭션 처리를 지원하는 패턴이 바로 사가(Saga) 패턴이다. 

사가 패턴은 각 서비스의 로컬 트랜잭션을 순차적으로 처리하는 패턴이다. 다른 트랜잭션의 결과에 따라 롤백이 필요하다면 보상 트랜잭션을 사용한다. 

 

데이터 일관성에 대한 생각의 전환: 결과적 일관성

모든 애플리케이션에는 비즈니스 처리를 위한 규칙이 있고, 이러한 비즈니스 규칙을 만족하도록 데이터 일관성이 유지돼야 한다. 이전까지는 이 같은 데이터 일관성이 실시간으로 반드시 맞아야 한다는 생각이 일반적이었다. 

 

북미의 브랙 프라이데이 같은 대규모 쇼핑 이벤트를 생각해 보자. 수만 개의 주문이 발생하는데 결제 서비스에서 타사 외부 연동 장애가 발생해 더는 주문을 받을 수 없는 상황이 발생할 수도 있다. 따라서 이러한 상황을 고려했을 때 비즈니스 관점에서 보면 주문과 결제, 이메일 전송을 순차적으로 처리하기보다 무조건 먼저 주문을 많이 받아 놓는 것이 좋을 수 있다. 

결과적 일관성, 고가용성을 극대화한다. 

 

읽기와 쓰기 분리: CQRS 패턴

 

비즈니스에 대한 기존 관념을 조금만 바꾸면 가용성을 높일 수 있는 방법이 다양하다. 

명령 조회 책임 분리를 의미한다. CQRS는 기존의 일반적이었던 동일한 저장소에 데이터를 넣고 입력, 조회, 수정, 삭제를 모두 처리하는 방식에 도전하는 흥미로운 패러다임이다. 

일반적으로 사용자의 비즈니스 요청은 크게 시스템 상태를 변경하는 명령과 시스템의 상태를 조회하는 부분으로 나눌 수 있다. 조회 요청이 훨씬 많이 사용된다. 

이처럼 쓰기 전략과 읽기 전략을 각각 분리하면 쓰기 시스템의 부하를 줄이고 조회 대기 시간을 줄이는 등 엄청난 이점을 누릴 수 있다. 이러한 CQRS 방식을 이벤트 메시지 주도 아키텍처와 연계해서 살펴보자. 

DDD에서 나온 이야기가 다시 나오게 된다. 데이터 일관성 유지를 위해 필요한 것이 이벤트 주도 아키텍처다. 

 

API 조합과 CQRS

API 조합은 각 기능을 제공하는 마이크로서비스를 조합하는 사우이 마이크로서비스를 만들어 조합된 기능을 제공할 수 있다. 그렇지만 이러한 구조는 상위 서비스가 하위 서비스에 의존하는 결과를 가져온다. 

CQRS는 주문 이력 서비스를 제공하는 마이크로서비스가 독자적인 저장소를 갖도록 만든다. 또한 주문 이력의 세세한 원청 정보 보유 서비스도 독자적 저장소를 가지고 서비스를 제공하고 있다. 이 원천 정보를 보유한 여러 마이크로서비스는 자신의 서비스의 정보가 변경되는 시점에 변경 내역을 각자의 변경 이벤트로 발행한다.

조회용 마이크로서비스를 별도로 생성하고 다른 서비스로부터 비동기 이벤트로 일관성을 맞춤으로써 api 조합 방식의 단점인 직접적인 의존성을 줄일 수 있는 것이다.