본문 바로가기

스터디/etc

마이크로서비스 도입 이렇게 한다.

마이크로서비스 도입 이렇게 한다.  책 스터디를 진행하며, 제가 맡은 1장 내용을 발표하기 위해 준비했던 글입니다.


모놀리스

마이크로서비스에 대해 이야기하기전에 모놀리스를 먼저 이해하고 가자.

모놀리스란? 배포 단위를 말하는 것이다. 시스템의 모든 기능을 함께 배포해야 할때 이를 모놀리스라고 한다.

모놀리스 유형에는 세 가지가 존재한다.

1. 단일 프로세스 모놀리스

단일 프로세스 모놀리스는 모놀리스하면 가장 일반적으로 떠오르는 유형이다.

단일 프로세스 모놀리스의 하위 집합으로 모듈식 모놀리스도 있다.

모듈식 모놀리스는 모듈 경계가 잘 정의되어있어 병렬 작업을 많이 수행할 수 있는데도 배포 고려사항은 훨씬 단순해지기에 탁월한 선택이 될 수 있다.

하지만 향후 서비스가 커지고 모놀리스를 끌어내고 싶을 땐 데이터베이스를 분리하는 과정에서 중대한 문제에 직면한다.

2. 분산 모놀리스

분산 모놀리스는 여러 서비스로 구성되는 시스템이지만 어떤 이유로든 전체 시스템을 함께 배포해야할 때 분산 모놀리스라고 정의한다.

예를 들어 여러 서비스로 구성된 시스템에서 인증 로직을 각자의 서비스에서 구현하고 있다면, 인증 정책이 추가될 때 마다 인증 서비스만 배포하는 것이 아니라 전체 시스템을 함께 배포해야하는 한다. 이러한 환경을 분산 모놀리스라고 한다.

분산 모놀리스는 결합도가 매우 높은 아키텍쳐를 만들어내며 지역적인 범위에서의 변경사항이 외부까지 영향을 미쳐 시스템의 다른 부분을 망가뜨린다.

3. 외부 블랙박스 시스템

분해하기를 원하는 몇몇 외부 소프트웨어를 외부 블랙박스 시스템 유형의 모놀리스로 간주할 수 있다.

다른 사람이 개발했고 우리는 코드를 변경할 수 없는 급여 시스템, HR(인사)시스템을 예로 들 수 있다.

모놀리스의 장,단점

장점

  • 훨씬 간단한 배포 방식과 그로인한 위험성 감소
  • 훨씬 더 간단한 개발자 워크플로우
  • 모니터링, 문제 해결, 전 구간 테스트 같은 활동 단순화
  • 코드 재사용 단순화 등 모든 선택이 단순해진다.

단점

  • 결합도의 위험에 취약하다.
  • 동일한 코드를 변경하길 원하는 개발자가 생기거나 한 기능을 다른 시점에 밀어넣고 싶어하는 팀들이 많아지는 등 혼란이 생긴다. (이를 배포 경합이라 부른다.)
  • 어떤 팀이 무엇을 소유하며 어떤 팀이 의사결정을 해야하는지에 대한 혼란이 발생한다.
  • 마이크로서비스라고해서 이 문제를 완벽히 피하진 않지만 마이크로 서비스가 유연성이 훨씬 뛰어나다.

모놀리스를 피해야 할 무언가로, 본질적으로 문제가 있는 것으로 바라보지 말자.

모놀리스 아키텍처는 선택이며 모든 상황에서 마이크로서비스가 올바른 선택인 것은 아니다.

3장에서 모놀리스와 마이크로서비스 사의의 절충안을 살펴보고 상황에 맞는 방식을 선택하는 법을 배울 예정이다.


마이크로서비스란 ?

마이크로서비스란 SOA의 한 유형이며 비즈니스 도메인을 중심으로 모델링된 독립적으로 배포가능한 서비스이다.

네트워크를 통해 서로 통신하며 우리가 직면할 수 있는 문제를 해결하기 위한 선택지를 제공한다. 또한 기술에 중립적이라는 장점도 있다.

독립적인 배포 가능성

다른 서비스를 변경하지 않고 하나의 서비스만 변경하고 배포할 수 있는 개념이다. 이렇게 할 수 있다 뿐 아니라, 실제로 시스템 내에서 배포를 관리하는 방법이라는 사실이 중요하다.

독립적인 배포를 위해선 서비스가 느슨하게 결합되어 있어야 하는데 데이터베이스 공유가 특히 문제가 되는 경우가 많다.

안정적이고 느슨하게 결합된 서비스가 필요하다면 먼저 서비스 경계를 찾는게 중요하다.

비즈니스 도메인을 중심으로 하는 모델링

비즈니스 도메인을 중심으로 하는 모델링은 한 기능을 출시하기 위해 두 서비스를 변경하고 각자 배포를 조정하는 작업은 모놀리스에서 변경하는 것보다 더 많은 작업이 필요하고 번거로운데 이러한 경우를 최소로 줄이게끔 해주는 방법

시스템을 설계하는 모든 조직은 불가피하게 조직의 커뮤니케이션 구조를 본떠 시스템 구조를 만들어 낼 것이다. - 멜빈 콘웨이

핵심 역량 측면에서 사람들을 그룹으로 묶은 3계층(웹 UI, 백엔드, 데이터베이스) 아키텍쳐가 콘웨이 법칙이 적용되는 좋은 예다. 핵심 역량을 기준으로 팀을 꾸리다보니 3계층 아키텍쳐는 가장 흔한 아키텍처가 되었다고 한다.

3계층 아키텍처는 기술 관점에서는 응집력이 높지만 비즈니스 기능 관점에서는 응집력이 낮다.

비즈니스 기능 변경을 쉽게 변경할 수 있게 만드려면 기술 관점에서의 응집력보다는 비즈니스 기능 응집력을 선택할 필요가 있다.

비즈니스 도메인 시스템은 변화를 쉽게 만드므로 팀을 쉽게 조직화할 수 있다.

데이터 소유권

어떤 서비스가 다른 서비스에서 보유한 데이터에 접근하고 싶다면 데이터 소유권을 가진 서비스에 데이터를 요청해야 한다.

이때 해당 서비스는 외부에 무엇을 공유하고 무엇을 숨길지 결정할 수 있고 그로인해 변경 가능성이 있는 내부 구현 세부사항을 좀 더 변경 가능성이 없는 외부와의 공개 계약 서비스로 매핑함으로써 안정적인 인터페이스를 보장할 수 있다.

독립적인 배포 가능성을 원한다면 각 서비스간의 안정적인 인터페이스를 구축하자. 그렇지 않으면 특정 서비스의 인터페이스가 변경되면 이를 사용하는 다른 서비스도 변경해야하는 연쇄효과가 발생한다.

이렇게 서비스를 뒷밤침하는 데이터베이스를 숨기는 방법도 비즈니스 도메인을 중심으로하는 모델링과 마찬가지로 결합도를 낮춘다.

마이크로서비스 장단점

장점

  • 독립적인 배포로 인한 시스템 확장성과 견고성 개선
  • 기술에 중립적이기에 다양한 기술 선택이 가능하며 각기 다른 기술의 올바른 조합을 찾을 수도 있다.
  • 서비스의 병렬 개발 작업이 가능해, 개발자가 자신의 문제에만 집중할 수 있다.
  • 문제를 어떻게 해결할지에 대한 더 많은 선택지를 열어주는 유연성을 제공한다.

단점

  • 네트워크를 거치는 통신이기에 즉각적이지 않다. 때문에 시스템 동작을 예측하기 어렵고 일관성 있는 데이터를 찾기 어렵다.
  • 이러한 문제로 트랜잭션 같은 단일 프로세스 모놀리스로 해결 가능한 단순한 활동은 더 어려워진다. 시스템이 복잡해지면 다른 종류의 기술을 도입하는 대가로 트랜잭션과 안전성을 포기해야한다.
  • 네트워크 호출은 실패할 수 있으며 언젠가는 실패한다.

사용자 인터페이스

서버쪽 마이크로서비스에 집중하다보면 UI는 모놀리스인 경우가 많다. 새로운 기능을 더 신속하게 배포하기를 원한다면 UI도 분해해야 한다. 이는 3장에서 다른다.

규모

마이크로서비스는 얼마나 커야할까?에 대한 고민이다.

저자가 생각하는 이상적인 규모는 '가능한 작은 인터페이스를 유지하는 것'이다.

궁긍적으로 규모는 맥락에 크게 의존한다. 15년 동안 한 시스템에서 작업한 사람들은 10만 line의 코드규모의 시스템도 매우 이해하기 쉬울 것이고 프로젝트를 처음 접하는 사람은 너무 크다고 느낄 것이다.

규모를 고민하기 보단 2가지 사항에 집중하자.

  1. 우리는 얼마나 많은 마이크로서비스를 다룰 수 있는가?
  2. 마이크로서비스를 최대로 활용하기 위한 경계를 정의하는 방법은 무엇일까?

서비스가 많아질수록 시스템의 복잡성이 증가하고 이에 대처하기 위한 새로운 기술을 배우고 채택해야하기 때문에 마이크로서비스 아키텍처로 가는 점진적인 마이그레이션을 추천한다.

소유자

이 그림처럼 제품 소유자와 배포팀이 따로있는 전통적인 IT 조직에서 발생하는 기능 장애는 매우 많고 다양하다.

이보다는 이 그림처럼 제품소유자가 배포 팀으로서 직접 작업하고 중앙 IT 기능을 이용해서 고객 중심 배포 팀을 지원하는 게 좋다.

마이크로서비스 아키텍처를 통해 이런 변화는 훨씬 더 쉬워진다. 여러 팀에서 공유되는 서비스를 축소하는 것은 배포 경합을 최소화하기 위한 핵심이다.


결합도와 응집력

구조는 응집력이 높고 결합도가 낮을 때 안정적이다. - 레리 콘스탄틴

  • 응집력: 함께 바뀌고 함께 머무는 코드
  • 결합도: 하나의 변경에 함께 변경해야 하는 정도

구현 결합도

A가 B에 결합되어 B가 변경될 때 A가 변경된다.

가장 위험한 형태의 결합도이지만 가장 쉽게 결합도를 낮출 수 있다.

구현 결합도의 가장 일반적인 예로 데이터베이스를 공유하는 형태이다.

이 그림같은 방식이면 추천서비스와 주문서비스가 강하게 결합한다. 주문 서비스의 DB에 조금의 변경사항만 있어도 추천서비스가 영향을 받게 된다.

이 그림처럼 구현 세부사항을 감추는 것이 더 바람직한 방법이다.

추천서비스가 주문서비스 API를 사용해 필요한 정보를 가져오는 형태이다. 주문서비스가 제공하는 API 스펙만 유지하면 주문서비스의 변경에 추천서비스는 영향을 받지 않을 수 있다.

하지만 이런 방법도 추천서비스가 대량으로 API를 호출해야 할 경우 주문서비스 서버에 부담을 줄 수 있다.

이 그림처럼 추천서비스가 대량으로 주문서비스 데이터에 접근하기 위한 외부용 주문 데이터베이스를 공개하는 방법을 사용할 수 있다.

추천서비스는 이제 주문서비스에서 공개한 주문 데이터베이스에 접근해서 정보를 가져온다.

이와 같은 방식도 주문서비스의 내부 구현방식에 추천서비스는 영향을 받지 않는다. 필요에 따라 공개된 데이터 모델을 개선하면 된다.

서비스 인터페이스를 정의할 때는 '외부에서 내부로'라는 사고 방식이 유용하다. 해당 서비스를 사용하는 입장에서 필요한 게 무엇인지 정의하고 구현을 시작하는 방식이다.

이러한 방식으로 서비스 인터페이스를 제공하면 컨슈머가 사용하기 쉬운 서비스를 제공할 수 있다는 장점 외에도 외부 제공 인터페이스와 내부 구현을 분리하는 과정에도 도움이 된다.

시간적 결합도

시간적 결합도는 주로 분산 환경에서 동기식 호출 시 실행시간에 발생하는 문제이다.

메시지가 전송되는 시점과 메시지가 처리되는 방식이 시간과 관련 되어 있는 경우 시간적 결합도가 존재한다고 한다.

연산을 수행하기 위해 동기식 호출을 사용하는 창고, 주문, 고객 3가지 서비스는 시간적으로 결합되어 있는 것이다.

이 문제는 다양한 방법으로 해소할 수 있다.

  1. 캐싱 사용 → 몇몇 경우에 시간 결합도를 피할 수 있다.
  2. 메시지 브로커 같은 서비스(SNS?)를 사용한 비동기 전송 → 미리 보내놓고 여유가 생긴 시점에 해당 메시지를 처리함

배포 결합도

한 개의 모듈의 변경에 다른 모듈도 함께 배포를 해야하면 배포 결합도가 있는 것이다.

배포에는 위험이 따르기에 배포 결합도가 있는 모듈 중 더 큰 프로세스를 독립적으로 배포가능한 마이크로서비스로 분해한다면 배포 범위를 줄임으로써 배포의 위험을 낮출 수 있다.

릴리스 규모가 작을수록 위험 부담도 줄어든다. 잘못될 것이 적기 때문이다. 또한 변경을 줄였기에 문제를 찾아내 해결하기가 쉬워진다. 릴리스 규모를 줄이는 방법을 찾는 것은 지속적인 배포의 핵심이며 출시도 쉽고 안전하며 빠른 피드백을 얻을 수 있다.

도메인 결합도

말그대로 도메인간의 결합도를 말하며 기본적으로 여러 서비스로 구성된 시스템은 서비스간에 상호작용이 있어야만 동작하고 이것이 마이크로서비스 아키텍처에서 도메인 결합도의 결과이다.

이러한 도메인 결합도 또한 서비스간 최소한의 정보를 공유하며 줄여야한다. 매우 광범위한 정보를 공유한다면 접근 제어가 필요한 정보(신용카드 세부정보 등)에 문제가 생기는 등 여러가지 부작용이 생기기도 하고 강한 결합으로 인해 변화에 유연성이 떨어진다.

도메인 결합도의 예시이다. 주문서비스는 주문에 관한 모든 세부사항을 창고서비스로 전송하고 창고서비스는 고객ID로 고객서비스에서 고객의 정보를 가져온다.

이 상황에서 전체 주문을 창고와 공유하고 있는 것은 바람직하지 않다. 창고는 포장할 물품과 배송지에 대한 정보만 필요하기 때문이다.

주문에 모든 세부사항을 전송하기 보다는 창고서비스가 요구하는 정보만 포함한 새로운 도메인 개념을 생각해서 해결하는 것이 바람직하다.

필요한 경우에는 창고서비스가 고객정보를 포함한 모든 정보를 다 물품 선택 명령에서 제공받고 고객서비스를 아예 모르게하는 방식으로 결합도를 줄일 수도 있다. 주문서비스에서는 창고서비스로 가기 전 이미 고객서비스와 상호작용을 하기 때문에 문제가 없다. 이 방식이 작동하려면 주문서비스에서 주문 처리 과정 중 창고서비스 API를 호출해야 한다.

또 다른 대안으로 주문 이벤트를 창고서비스가 소비하도록하는 방식이 있다. 이런 방식으로 종속성을 효과적으로 뒤집고 결합도를 줄일 수 있다.

API를 호출하는 방식과 이벤트 발생시키는 방식은 각각 고유한 장점이 있으며 해당 기능 처리 로직과 외부 서비스 기능의 상호작용에 따라 두가지 방법 중 한가지를 선택하면 된다.

고유한 장점은 참고자료 링크해놨습니다.

도메인 주도 설계

도메인 중심으로 서비스를 모델링했을 때 문제는 모델링한 모델을 구현하는 방법인데 여기서 도메인 주도 설계가 등장한다.

집계(aggregate)

집계는 다양한 정의가 존재한다. 저자는 집계를 주문, 송장 등 실제 도메인 개념에서 표현하는 모델로 사용한다고 한다.

하나의 마이크로서비스는 하나 이상의 다양한 집계의 수명 주기와 데이터 저장소를 처리할 것이다.

다른 서비스의 기능이 이와 같은 집계 중 하나를 변경하려는 경우, 해당 집계의 변경을 직접 요청하거나, 집계 자체를 시스템의 다른 구성 요소에 반응시켜서 상태 전환을 개시해야 한다.

결제서비스에서 송장의 상태 전환을 시작하기 위해 재무서비스에 요청을 직접 전송하는 예제이다.

여기서 중요한 점은 외부 시스템이 집계 내의 상태 변경을 요청하면 집계가 거절할 수도 있다는 사실이다.

집계는 다른 집계와 관련이 있을 수 있다. 이 그림에서처럼 주문 집계와 고객 집계가 관련되어 있지만 고객과 주문을 별도의 집계로 모델링하고 각각 다른 서비스에서 처리할 수 있다.

시스템을 집계로 분해하는 방식은 주관적일 수 있다. 시간이 지나서 성능 등의 이유로 집계를 재구성할 수도 있다.

하지만 처음 모델링할 때 구현에 관한 문제는 부차적인 것으로 생각하고 시스템 사용자의 멘탈 모델을 초기 설계 지침으로 삼아야한다.

비개발자 동료의 도움을 받아 도메인 모델을 구현하는데 참고하는 협업 방식인 '이벤트 스토밍'도 있다.

경계 컨텍스트

경계 컨텍스트는 인사 / 영업 / 마케팅처럼 일반적으로 조직 내부의 더 큰 조직적인 경계를 나타낸다.

경계 컨텍스트는 구현 세부사항을 숨긴다. 창고에서 어떤 지게차의 종류가 쓰이는지는 창고 직원 외의 누구의 관심사도 아니다.

구현관점에서는 하나 이상의 집계를 포함한다. 일부 집계들은 경계 컨택스트 외부로 공개될 수도 있고 내부에 숨겨져 있을 수도 있다. 또한 다른 경계 컨텍스트와 서로 관계가 있을 수도 있다.

경계 컨택스트가 서비스에 매핑될 때 이런 종속성은 서비스간 종속성이 된다.

집계와 경계 컨택스트를 마이크로서비스에 매핑

집계와 경계 컨텍스트 모두 서비스 경계가 될 수 있다. 마이크로서비스로 전환을 시작하면 경험이 적기 때문에 일단 서비스의 수를 줄이고 싶을 것이다. 이럴 땐 경계 컨텍스트를 중심으로 분할해야 할 것이다.

경험을 통해 얻은 확신으로 서비스를 더 작게 분해하기로 결정된다면 집계 경계를 중심으로 분할하는 방법을 모색하다.

집계 경계를 중심으로 서비스를 분할하는 방법 외에도 컨슈머에게 더 작은 크기로 API를 제공하는 방법이 존재한다. 서비스를 분할하는 방식은 많이 리소스가 들기 때문에 컨슈머에게 더 조밀한 수준의 API를 제공하는 방법이 나을지도 모른다.