이 글은 한빛미디어 Microservices Up & Running 책을 보고 작성한 글임을 알려드립니다.
1. 마이크로서비스의 경계가 중요한 이유와 중요한 시기, 경계를 찾는 법
성공적인 마이크로서비스 시스템을 구축하는 과정에서 어려운 측면 중 하나는 적절한 마이크로서비스 경계를 식별하는 것입니다. 큰 코드베이스를 더 작고 간단하고 느슨하게 결합된 부분으로 나누면 유지 보수성이 향상됩니다. 하지만 경계를 어떻게 설정할지에 대한 고민이 필요합니다.
마이크로는 국제단위계에서 100만 분의 1미터를 의미합니다. 그렇다면 서비스에서 의미하는 마이크로는 무슨 의미일까요? 예를 들어 처음에는 다음과 같은 방법으로 접근합니다.
- 크기 기반 경계 설정
마이크로서비스에 대한 기준을 500줄로 정의한다.
이렇게 단순한 접근 방법은 실제로 실행 되었습니다. 역사적으로 소스코드라인(SLOC)은 노력과 복잡성의 척도로 사용되었지만, 복잡성에 대해서는 좋은 방법은 아닙니다.
- 함수 단위 경계 설정
함수 단위로 경계를 나눈다. aws 람다 등을 사용, 서버리스의 생산성과 광범위한 채택을 기반으로 한다.
위와 같은 방법을 마이크로서비스의 경계를 나누는 팀들이 있습니다. 하지만 이러한 결정은 다음과 같은 문제가 있습니다.
- 기술적 요구를 기반으로 경계를 그리는 것은 안티 패턴이다.
저번 글에서 제임스 루이스와 마틴 파울러가 마이크로서비스에 대한 정의를 한것을 언급했습니다. 그들의 말에 따르면, 마이크로서비스는 기술적 요구가 아닌 ‘비즈니스 기능을 중심으로 구성’되어야 합니다. 이러한 접근 방식은 서버리스 함수의 경계와 일치하지 않습니다.
- 너무 일찍 세분화되는 문제 마이크로서비스 프로젝트가 초기에 폭발적인 수준으로 세분화되면 높은 복잡성이 문제가 됩니다. 이는 개발 진행 속도를 저해할 수 있습니다. 또한 작은 서버리스 함수로 분리하는 것은 조정 비용 최소화 측면에서 효율적이지 않습니다.
요약하자면 마이크로서비스의 경계 기준은 크기 기반이나 함수 기반 접근 방식을 피해야합니다. 앞서 말한 것을 맹목적으로 피하는 것이 아닌 프로젝트의 너무 많은 서비스를 만들어 조기 최적화 하는 방식을 피해야합니다.
2. 도메인 주도 설계와 마이크로서비스 경계
마이크로서비스 아키텍처 구축(한빛미디어, 2017)을 쓴 샘 뉴먼은 마이크로서비스 설계 모범 사례를 위한 몇가지 규칙을 소개했습니다.
-
느슨한 결합
- 서비스는 서로를 인식하지 않고 독립적이어야 한다.
- 서비스 간 빈번한 통신은 잠재적인 성능 문제외에도 구성 요소의 긴밀한 결합으로 이어질 수 있다.
- 서비스 간 다양한 유형의 런타임 호출을 제한해야 한다.
-
높은 응집력
- 서비스에 포함된 기능들은 관련성이 높아야한다.
- 응집력 높은 서비스는 조정 비용 최소화 접근 방식에서 목표를 손상시키는 것을 예방한다.
-
비즈니스 기능과 연결
- 비즈니스 경계와 서비스 경계를 일치 시키면 느슨한 결합과 높은 응집력의 설계 요건을 만족 시킬 수 있다.
- 서브시스템이 제공하는 비즈니스 기능의 모음은 지속성이 높다.
위와 같은 설계 원칙은 매우 유용한 것으로 입증되었으며, 마이크로서비스 실무자들 사이에서 널리 채택되었습니다. 하지만 이것은 높은 수준의 의욕만 넘친 원칙이며 서비스의 크기에 대한 지침을 제공하지 않습니다. 이에 따라 더욱 실용적인 방법론을 찾기 위해서 사람들은 도메인 주도 설계(DDD)로 눈을 돌렸습니다.
3. 제한된 콘텍스트
도메인 주도 설계(위키북스, 2011)을 작성한 에릭 에반스는 복잡한 시스템을 분석할 때 전체 시스템을 대표하는 단일 통합 도메인 모델을 찾는 것을 피해야한다고 이야기 했습니다.
대규모 프로젝트에서는 여러 모델이 공존하고 있으며, 이는 많은 경우에 잘 작동한다. 서로 다른 모델은 각각의 콘텍스트 내에 존재한다.
기본적으로 복잡한 시스템이 여러 도메인의 모음이라는 것을 주장하고, 제한된 콘텍스트(bounded context) 라는 개념을 도입했습니다. 모델은 특정한 콘텍스트에서 완전한 의미를 갖습니다. 또한 경계를 갖는 콘텍스트를 제한된 콘텍스트라고 합니다. 제한된 콘텍스트는 모델의 경계를 결정하고, 한 개의 제한된 콘텍스트는 논리적으로 한 개의 모델을 갖습니다.
ex) 주문 도메인 -> 주문(제한된 콘텍스트), 결재 계산(제한된 콘텍스트)
제한된 콘텍스트는 각 모델의 적용 가능성의 범위를 정의한다.
제한된 콘텍스트를 정의한 후 에반스는 유비쿼터스 언어의 개념을 확립하여 제한된 콘텍스트의 최적화된 경계를 식별하는 공식을 제공했습니다. 유비쿼터스 언어란 비즈니스 요구사항과 구현 고려 사항 사이의 균형을 이루고, 협업하기 위한 공통 언어 또는 어휘를 뜻합니다. 여기서 서로 다른 경계의 콘텍스트에서 다른 의미를 전달할 수 있음을 알고 있어야 합니다. 즉, 동일한 단어가 서로 다른 맥락에서 다른 의미로 사용될 수 있다는 뜻입니다. DDD에 따르면 어떤 용어의 의미를 바꾸는 것을 관찰하면 콘텍스트의 경계를 식별할 수 있다고 합니다.
Tip: 유비쿼터스 언어와 관련된 어휘를 식별하기 위해 잘 작성된 잡 스토리의 핵심 명사를 사용하는 방법을 적극 권장한다.
4. 콘텍스트 매핑
DDD는 시스템에 공존하는 여러 독립 모델을 설계합니다. 하위 도메인들은 서로 통신할 수 있습니다. 훨씬 큰 시스템에서 다양한 도메인의 표현과 이들이 서로 협업하는 방식을 콘텍스트 맵이라고 합니다. 또 이것을 식별하고 설명하는 작업을 콘텍스트 매핑이라고 합니다. DDD는 제한된 콘텍스트를 매핑할 때 몇 가지 주요 유형의 상호작용을 식별합니다.
그 중 가장 기본적인 유형은 공유 커널입니다. 두 제한된 콘텍스트가 같은 모델을 공유하는 경우,두 도메인이 대체로 독립적으로 개발되고 각 도메인의 일부 하위 집합이 우연히 겹치는 경우 발생합니다. 마이크로서비스 관점에서 이는 초기에 높은 수준의
조정을 요구할 수 있습니다. 따라서 한 팀은 오너나 큐레이터로 다른 한 팀은 기여자로 지정하는 것이 좋습니다. 또는 업스트림-다운스트림의 관계를 맺는 것이
대안이 될 수 있습니다. 업스트림은 공급자 역할을 하고 다운스트림은 해당 기능의 소비자가 됩니다.
조정 및 결합 유형에 따라 업스트림-다운스트림 매핑은 여러 형태로 도입될 수 있습니다.
- 고객-공급자: 업스트림은 다운스트림에게 기능을 제공한다.
- 순응주의자: 업스트림은 다운스트림의 요구사항을 명시적으로 고려하지 않는다. 따라서 업스트림이 제공하는 기능이 변경되면, 다운스트림은 이를 지속적으로 변경사항을 따라야 한다.
- 부패 방지 계층: 다운스트림은 업스트림 인터페이스의 변경 사항으로부터 자신을 보호하기 위해 다운스트림과 업스트림의 유비쿼터스 언어 사이에 부패 방지 계층이라 불리는 번역 계층을 만든다.
- 오픈 호스트 서비스: 업스트림은 다운스트림이 자신의 기능을 사용하고 있음을 알고 있는 경우 현재와 미래의 고객 요구사항을 조정하는 대신에 고객이 채택할 표준 인터페이스를 정의하고 공개한다.