🐾 개념 정리
서비스
서비스는 어떤 기능이 구현되어 단독 배포가 가능한 소프트웨어 컴포넌트입니다. 서비스는 클라이언트가 자신이 서비스하는 기능에 접근할 수 있도록 커맨드, 쿼리, 이벤트로 구성된 API를 제공합니다.
서비스 작업은 크게 커맨드(command, 명령/CUD)와 쿼리(query, 조회/R)로 나뉩니다.
ex) createOrder() 커맨드는 주문 생성 후 데이터를 업데이트하며, findOrderById() 쿼리는 데이터를 조회합니다.
서비스는 클라이언트가 소비하는 OrderCreated 같은 이벤트를 발행하기도 합니다.
서비스 API는 내부 구현 상세를 캡슐화합니다. 모놀리스와 달리 개발자는 API를 우회하여 서비스에 접근하는 코드를 작성할 수 없으므로(무슨 뜻이지?!) 마이크로서비스 아키텍처에서 애플리케이션 모듈성은 보장됩니다.
각각의 마이크로서비스는 자체 아키텍처를 갖고 있기 때문에 기술 스택을 독자적으로 구축할 수 있지만, 대부분 헥사고날 아키텍처를 취합니다. API는 서비스에 구현된 비즈니스 로직과 소통하는 어댑터를 이용하여 구현합니다.
느슨한 결합(loose coupling)
느슨하게 결합된 서비스는 마이크로 서비스 아키텍처의 주요 특성 중 하나입니다.
서비스는 구현 코드를 감싼 API를 통해서만 상호 작용하므로 클라이언트에 영향을 끼치지 않고 서비스 내부 구현 코드를 바꿀 수 있습니다.
느슨하게 결합된 서비스는 유지보수성, 테스트성을 높이고 애플리케이션 개발 시간을 단축하는 효과가 있습니다.
무엇보다 개발자가 서비스를 이해하고, 변경하고, 테스트하기가 더 쉽습니다.
서비스는 느슨하게 결합되고 API를 통해서만 동작하기 때문에 서비스가 직접 DB와 통신하는 일은 불가능합니다.
또한, 클래스 필드 같은 서비스의 영속적인 데이터는 반드시 private으로 유지해야 합니다.
이렇게 해야 개발자가 자신이 맡은 서비스의 DB 스키마를 변경할 때 다른 서비스 개발자와 조율하느라 시간을 허비하지 않을 수 있다고 하네요!!
서비스가 DB 테이블을 서로 공유하지 않기 때문에 런타임 격리(runtime isolation)도 향상됩니다. 어떤 서비스가 DB 락을 획득하여 다른 서비스를 블로킹하는 일 자체가 불가능하죠.
물론 DB를 공유하지 않기 때문에 여러 서비스에 걸쳐 데이터를 쿼리하고 일관성을 유지하는 일은 더 복잡해지는 단점이 있습니다.
IPC
프로세스 간 통신(Inter-Process Communication, IPC)이란 프로세스들 사이에 서로 데이터를 주고받는 행위 또는 그에 대한 방법이나 경로를 뜻합니다.
서비스 인스턴스는 여러 머신에서 실행되는 프로세스 형태이므로 반드시 IPC를 통해 상호 작용해야 합니다. 따라서 IPC는 모놀리식 아키텍처보다 마이크로서비스 아키텍처에서 차지하는 비중이 더 크다.
IPC 기술은 옵션이 다양합니다. 요즘은 JSON 데이터를 주고받는 REST가 대세이지만, 모든 경우에 항상 좋다고 할수는 없으므로 여러 가지 옵션을 잘 검토해야 합니다.
다다음 글에서는 REST, 메시징 등 다양한 IPC 옵션과 그 트레이드오프에 대해 설명하겠습니다.
🐾 서비스의 규모는 별로 중요하지 않다.
마이크로서비스라는 용어는 '마이크로(micro)'라는 단어가 제일 먼저 귀에 꽂히는 것이 문제입니다.
왠지 서비스를 아주 조그맣게 만들어야 할 것 같은 느낌이 들기 때문이죠.
미니서비스, 나노서비스 등 크기를 바탕으로 만든 용어들도 그렇지만, 사실 크기가 중요한 것은 아닙니다.
크기보다는 작은 팀이 가장 짧은 시간에, 다른 팀과 협동하는 부분은 최소로 하여 개발 가능한 서비스를 설계해야 합니다.
이론적으로는 한 팀이 한 서비스를 맡을 수도 있는데, 이런 경우라면 마이크로서비스가 'micro'하다고 볼 수 없겠죠.
반대로 대규모 팀을 꾸려야 하거나 서비스를 테스트하는 시간이 너무 오래 걸리면 팀과 서비스를 분할해야 합니다.
다른 서비스의 변경분 때문에 내가 맡은 서비스도 계속 바꾸어야 한다거나, 내 서비스 때문에 다른 서비스가 바뀌어야 한다면 서비스가 느슨하게 결합되지 않았다는 반증입니다. 아니면 분산 모놀리스를 구축했기 때문에 그럴 수도 있습니다.
마이크로서비스 아키텍처는 작고 느슨하게 결합된 서비스로 애플리케이션을 구성하기 때문에 유지보수성, 테스트성, 배포성 등 개발 단계의 품질 속성이 개선됩니다. 또한, 조직 차원에서 소프트웨어을 더 빨리 개발할 수 있고, 애플리케이션 확장성도 향상됩니다.
마이크로서비스 아키텍처의 핵심은 애플리케이션이 서비스를 어떻게 식별하고 서로 어떻게 협동시킬지 결정하는 것입니다.
🐾 마이크로서비스 아키텍처 정의
마이크로서비스 아키텍처를 어떻게 정의해야 할까요?
일단 도메인 전문가가 문서로 정리한 요건들과 기존 애플리케이션이 출발점이겠죠.
사실 아키텍처를 정의하는 일은 과학보다는 예술에 가깝다고 하는군요.... 이제 애플리케이션 아키텍처를 정의하는 3단계 프로세스를 설명하겠습니다.
이는 누구나 기계적으로 따라할 수 있는 과정은 아니라서 여러 차례 되풀이해야할 수도 있고, 창의성이 필요한 부분도 있습니다.
1단계: 시스템 작업 식별
애플리케이션은 사용자의 요청을 처리하기 위해 존재합니다. 따라서, 아키텍처를 정의하는 1단계는 애플리케이션 요건을 핵심 요청으로 추출하는 것입니다.
여기서 '요청'은 REST, 메시징 같은 특정 IPC 기술이 아닌 좀 더 추상적인 관념으로 바라본 시스템 작업입니다.
시스템 작업은 애플리케이션이 처리하는 요청을 추상화한 것입니다.
데이터를 업데이트하는 커맨드나 데이터를 조회하는 쿼리가 모두 해당되죠.
각 커맨드의 동작은 추상적인 도메인 모델 관점에서 정의되며, 이 또한 기능 요건에서 도출됩니다.
결국 시스템 작업은 여러 서비스가 서로 협동하는 방식을 표현한 아키텍처 시나리오가 됩니다.
2단계: 서비스 식별
2단계는 어떻게 여러 서비스로 분해할지 결정하는 것입니다. 여기에서는 여러 가지 전략을 선택할 수 있습니다.
비즈니스 아키텍처 시각에서 비즈니스 능력에 따라 서비스를 정의할 수도 있고, DDD의 하위 도메인별로 서비스를 구성하는 전략도 가능합니다. 어떤 전략을 구사하든 최종 결과는 기술 개념이 아닌 비즈니스 개념 중심으로 이루어진 서비스들입니다.
상세한 부가 설명은 다음 글에 이어질 예정이라 간단하게 설명하고 넘어가도록 하겠습니다.
우선 비즈니스 능력이란 비즈니스가 가치를 생산하기 위해 하는 일을 말하며, 업종마다 다릅니다.
가령 온라인 쇼핑몰이라면 주문 관리, 재고 관리, 선적 등의 능력이 있을 것입니다.
DDD는 객체 지향 도메인 모델 중심의 복잡한 소프트웨어 애플리케이션을 구축하는 방법입니다. 도메인 내부에서 문제 해결이 가능한 형태로 도메인을 모델링하는 기법이죠. DDD에는 마이크로서비스 아키텍처에 적용하면 정말 유용한 하위 도메인(sub-domain) 과 경계 컨텍스트(bounded context)라는 개념이 있습니다.
3단계: 서비스 API 및 협동 정의
3단계는 서비스별로 API를 정의하는 일입니다. 이를 위해 먼저 1단계에서 식별된 시스템 작업을 각 서비스에 배정해야 합니다. 완전히 혼자만의 작업이 구현된 서비스도 있겠지만, 다른 서비스와 협동할 수밖에 없는 작업이 구현된 서비스도 있을 것입니다. 이때 여러 서비스가 협동하는 방식을 결정해야 하는데, 대부분 서비스에 추가 지원 작업을 두는 형태가 될 것입니다. API 구현 시 사용할 IPC도 정해야 합니다.
서비스 분해 과정에는 장애물이 많습니다.
첫째, 네트워크 지연(network latency)입니다. 서비스 간 왕복이 너무 잦아 실제로 분해할 수 없는 경우도 있습니다.
둘째, 서비스 간 동기 통신으로 인해 가용성이 떨어지는 문제입니다. 해결책은 자기 완비형 서비스(self-contained service) 개념이며 자세한 내용은 다음에 설명하도록 하겠습니다.
셋째, 여러 서비스에 걸쳐 데이터 일관성을 지키는 요건입니다. 이 문제는 보통 사가로 해결합니다.
넷째, 애플리케이션 도처에 숨어 있는 만능 클래스입니다. 이런 클래스는 DDD 개념을 활용하면 어렵지 않게 제거할 수 있습니다.
출처: 모놀리식 지옥에서 벗어나자! 마이크로서비스 패턴