Featured image of post [OOAD] 17. 마이크로서비스 아키텍처와 OOAD

[OOAD] 17. 마이크로서비스 아키텍처와 OOAD

마이크로서비스 경계를 기술 계층(UI/로직/DB)이 아니라 바운디드 컨텍스트로 그으면, 분산된 진흙 덩어리를 피할 수 있습니다. 서비스 분해 전략과 서비스별 데이터 소유권 원칙, 도입 여부를 가르는 판단 기준까지 다룹니다.

17. 마이크로서비스 아키텍처와 OOAD

14장에서 정의한 바운디드 컨텍스트는 “모델의 경계"였고, 17장은 그 경계를 배포 단위로 실현하는 마이크로서비스 아키텍처를 다룹니다. 마이크로서비스는 유행하는 기술 스타일이 아니라, 조직이 커질 때 바운디드 컨텍스트 경계를 물리적으로도 분리해 팀의 자율성을 확보하는 선택지 중 하나입니다.

학습 목표

  • 마이크로서비스 경계를 기술 계층이 아니라 바운디드 컨텍스트/비즈니스 역량 기준으로 나눠야 하는 이유를 설명할 수 있다.
  • “데이터베이스는 서비스마다 하나"라는 원칙이 지키는 것과, 이를 어겼을 때 생기는 문제를 설명할 수 있다.
  • 마이크로서비스 도입이 정당화되는 조건과, 오히려 손해가 되는 조건을 구분할 수 있다.

서비스 경계를 어떻게 그을 것인가

마이크로서비스를 처음 도입할 때 흔히 저지르는 실수는 계층(레이어)을 서비스 경계로 오해하는 것입니다. “UI 서비스”, “비즈니스 로직 서비스”, “DB 서비스"처럼 10장에서 다룬 계층을 그대로 네트워크로 쪼개면, 요청 하나를 처리하는 데 서비스 3개를 순서대로 호출해야 하고 서비스 간 결합도는 오히려 모놀리식보다 높아집니다.

Chris Richardson은 『Microservices Patterns』(2018)에서 서비스 분해 전략으로 **비즈니스 역량 기준 분해(Decompose by Business Capability)**와 **하위 도메인 기준 분해(Decompose by Subdomain)**를 제시합니다. 두 전략 모두 핵심은 같습니다. 서비스를 “레이어"가 아니라 14장에서 정의한 바운디드 컨텍스트 기준으로 나눈다는 것입니다. “주문 컨텍스트”, “재고 컨텍스트”, “정산 컨텍스트"가 각각 하나의 서비스가 되고, 각 서비스는 자신의 컨텍스트 안에서는 스스로 계층(UI-비즈니스-DB)을 온전히 갖춥니다.

flowchart TB
  subgraph OrderSvc["Order Service"]
    OrderAPI["API"] --> OrderDomain["Domain"] --> OrderDB[("Order DB")]
  end
  subgraph InventorySvc["Inventory Service"]
    InvAPI["API"] --> InvDomain["Domain"] --> InvDB[("Inventory DB")]
  end
  OrderSvc -->|"이벤트/API 호출"| InventorySvc

데이터 소유권: 서비스마다 자신의 데이터베이스를 가진다

마이크로서비스 아키텍처의 가장 엄격한 원칙 중 하나는 각 서비스가 자신의 데이터베이스를 독점적으로 소유하고, 다른 서비스는 그 데이터에 직접 접근할 수 없다는 것입니다. 여러 서비스가 하나의 공유 DB를 바라보면, 스키마를 바꾸려 할 때 다른 팀의 서비스가 함께 영향을 받아 배포를 독립적으로 할 수 없게 되고, 이는 마이크로서비스로 나눈 목적(팀의 독립적 배포) 자체를 무너뜨립니다.

이 원칙은 16장에서 다룬 애그리거트 경계와 정확히 대응합니다. 애그리거트가 “하나의 트랜잭션에서 일관돼야 하는 최소 단위"였다면, 마이크로서비스 경계는 그 애그리거트/컨텍스트를 넘어서는 트랜잭션을 애초에 만들지 않도록 강제합니다. 주문 서비스가 재고 데이터를 직접 SELECT하는 대신, 재고 서비스가 제공하는 API나 이벤트를 통해서만 정보를 얻어야 합니다. 이 통신 방식은 19장에서 다룰 이벤트 기반 아키텍처와 자연스럽게 연결됩니다.

분산 시스템이 가져오는 새로운 문제

서비스를 네트워크로 분리하면 모놀리식에는 없던 문제가 생깁니다. 함수 호출은 실패하지 않는다고 가정할 수 있지만, 네트워크 호출은 타임아웃·부분 실패·순서 뒤바뀜이 항상 가능합니다. 08장에서 다룬 시퀀스 다이어그램의 “실패/지연을 먼저 모델링하라"는 조언은 마이크로서비스 환경에서 선택이 아니라 필수가 됩니다.

  • 부분 실패: 주문 서비스가 재고 서비스 호출에 실패했을 때 무엇을 해야 하는가(재시도? 실패 처리? 보상?)
  • 데이터 일관성: 여러 서비스에 걸친 작업은 단일 트랜잭션으로 묶을 수 없으므로, 최종 일관성(eventual consistency)을 받아들여야 함
  • 관측 가능성: 요청 하나가 여러 서비스를 거치므로, 분산 추적(distributed tracing)과 중앙화된 로깅 없이는 문제를 진단하기 어려움

흔한 오해: 마이크로서비스가 곧 좋은 아키텍처다

마이크로서비스는 조직이 커지고, 팀마다 독립적으로 배포해야 하는 실질적 압력이 있을 때 그 비용(운영 복잡도, 분산 시스템 문제, 네트워크 지연)을 정당화합니다. 팀 하나가 전체 시스템을 관리하는 초기 스타트업이 처음부터 서비스를 10개로 쪼개면, 배포 자율성이라는 이득 없이 분산 시스템의 비용만 떠안게 됩니다. 게다가 14장에서 언급했듯, 컨텍스트 경계를 명확히 정하지 않은 채 서비스만 물리적으로 나누면 서비스 간 호출이 뒤엉킨 “분산된 진흙 덩어리(Distributed Big Ball of Mud)“가 됩니다. 이는 모놀리식의 문제를 네트워크 지연이라는 비용까지 얹어 반복하는 결과입니다.

이런 이유로 많은 팀이 **모듈러 모놀리스(Modular Monolith)**를 절충안으로 택합니다. 하나의 배포 단위 안에서 바운디드 컨텍스트별로 모듈 경계(패키지/모듈 수준의 강한 캡슐화)를 엄격히 지키다가, 실제로 팀을 분리하고 독립 배포가 필요해지는 시점에 해당 모듈을 별도 서비스로 추출하는 방식입니다. 컨텍스트 경계가 이미 명확하다면 이 추출은 비교적 안전하게 이뤄집니다.

실무 체크리스트

  • 서비스 경계가 레이어(UI/로직/DB)가 아니라 바운디드 컨텍스트 기준으로 그어졌는가?
  • 서비스가 다른 서비스의 데이터베이스에 직접 쿼리를 던지고 있지 않은가?
  • 여러 서비스에 걸친 호출에서 타임아웃/재시도/보상 전략이 정의돼 있는가?
  • 지금 조직 구조와 배포 빈도가 마이크로서비스 도입 비용을 정당화하는가, 아니면 모듈러 모놀리스로도 충분한가?

연습 과제

기초(★☆☆)

  • 14장에서 그린 컨텍스트 맵을 그대로 서비스 경계 후보로 옮겨보고, 레이어 기준으로 나눴다면 어떻게 달랐을지 비교해보세요.

중급(★★☆)

  • 주문 서비스가 재고 서비스를 호출하는 시나리오에서, 재고 서비스가 3초간 응답하지 않을 때의 타임아웃/재시도 정책을 설계해보세요.

고급(★★★)

  • 현재 모놀리식으로 운영 중인(또는 가정한) 시스템을 모듈러 모놀리스로 먼저 재구성하는 계획을 세우고, 어느 모듈을 가장 먼저 별도 서비스로 추출할지 우선순위를 근거와 함께 정해보세요.

요약

  • 마이크로서비스 경계는 레이어가 아니라 바운디드 컨텍스트/비즈니스 역량 기준으로 긋는다.
  • 각 서비스는 자신의 데이터베이스를 독점 소유해야 애그리거트 일관성 경계가 네트워크를 넘어 깨지지 않는다.
  • 조직 규모와 독립 배포 압력이 비용을 정당화하지 않는다면 모듈러 모놀리스가 더 나은 절충안일 수 있다.

참고 문헌 및 출처(추천)

  • Chris Richardson, 『Microservices Patterns』(2018)
  • Sam Newman, 『Building Microservices』(2015/2021)
  • Melvin Conway, “How Do Committees Invent?"(1968) — Conway’s Law

다음 글