-
Uber DOMA: 마이크로서비스 관리 방법론이코에코(Eco²)/Foundations 2025. 12. 21. 08:58
원문: Introducing Domain-Oriented Microservice Architecture - Uber Engineering (2020)
들어가며

2018에서 2020년, Uber는 2,200개 이상의 마이크로서비스를 운영하고 있었다. 그리고 위 포스팅에서 그 혼란과 해결책을 공유했다.
마이크로서비스는 모놀리스의 문제를 해결하기 위해 도입됐지만, 규모가 커지면서 새로운 종류의 복잡성을 만들어냈다.
Uber의 DOMA(Domain-Oriented Microservice Architecture)는 이 복잡성을 관리하기 위한 아키텍처 원칙이다.
DOMA는 마이크로서비스를 부정하는 게 아닌 마이크로서비스를 조직화하는 방법론이다.
Uber의 여정: 모놀리스에서 카오스까지
2012년: 모놀리스 시절
Uber는 단일 Python 애플리케이션으로 시작했다.
초기에는 이 구조가 잘 작동했다. 팀이 작고, 코드베이스가 작고, 비즈니스 로직이 단순했기 때문이다.2014-2018년: 마이크로서비스 폭발
회사가 성장하면서 모놀리스는 한계에 부딪혔다:
- 배포에 몇 시간이 걸림
- 한 팀의 변경이 다른 팀에 영향
- 새 기능 추가가 점점 어려워짐
해결책으로 마이크로서비스를 도입했고, 서비스 수가 폭발적으로 증가했다.
2018년: 새로운 문제의 시작
┌─────────────────────────────────────────────────────────────┐ │ 마이크로서비스 복잡성의 기하급수적 증가 │ ├─────────────────────────────────────────────────────────────┤ │ │ │ 서비스 수와 잠재적 연결 수: │ │ │ │ N개 서비스 → N × (N-1) / 2 개의 잠재적 연결 │ │ │ │ 10개 → 45개 연결 (관리 가능) │ │ 100개 → 4,950개 연결 (어려움) │ │ 500개 → 124,750개 연결 (카오스) │ │ 2,200개 → 2,418,900개 연결 (??????) │ │ │ │ "서비스가 늘어날수록 복잡성은 선형이 아니라 │ │ 제곱에 비례하여 증가한다" │ │ │ └─────────────────────────────────────────────────────────────┘
마이크로서비스의 진짜 문제들
1. 시스템을 이해할 수 없다
2,200개 서비스 중에서 내가 수정해야 할 서비스는 어디에 있을까?
이 서비스는 어떤 다른 서비스에 의존하고 있을까? 내가 이 코드를 바꾸면 어디에 영향을 미칠까?
이런 질문에 답하기가 점점 어려워졌다. 새로 입사한 엔지니어가 시스템 전체를 파악하는 것은 불가능에 가까워졌다.2. 정보가 사일로에 갇힌다
각 팀이 자신의 서비스만 알고, 전체 그림을 아는 사람이 없다:
- A팀: "우리는 결제 서비스만 담당해요"
- B팀: "우리는 알림 서비스만 알아요"
- C팀: "우리는 CNI만 알아요"
- 누구도: "결제 후 알림이 어떻게 연결되는지"를 책임지지 않음
3. 변경의 파급 효과를 예측할 수 없다
하나의 서비스를 수정하면, 그것에 의존하는 수십 개의 서비스에 영향을 미칠 수 있다.
하지만 그 의존성을 모두 파악하기가 어렵다.4. 중복 기능이 생긴다
팀들이 독립적으로 움직이다 보니, 비슷한 기능을 여러 팀이 각자 만드는 일이 발생한다:
- A팀: 자체 인증 로직 구현
- B팀: 또 다른 인증 로직 구현
- 결과: 유지보수 비용 2배, 보안 취약점 2배
5. 온보딩이 끝없이 어려워진다
새 팀원에게 "우리 시스템은..."이라고 설명하려면 어디서부터 시작해야 할까? 2,200개 서비스를 다 설명할 수는 없다.
DOMA의 핵심 원칙
Uber는 이 문제들을 해결하기 위해 4가지 핵심 원칙을 세웠다.
원칙 1: Domain (도메인)
핵심 아이디어: 관련 있는 마이크로서비스들을 논리적 그룹으로 묶는다.
┌─────────────────────────────────────────────────────────────┐ │ 도메인의 개념 │ ├─────────────────────────────────────────────────────────────┤ │ │ │ Before: 2,200개의 개별 서비스 │ │ ┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐ │ │ │ S │ S │ S │ S │ S │ S │ S │ S │ S │ S │ S │ S │ ... │ │ └───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘ │ │ (서비스 하나하나가 개별 단위) │ │ │ │ After: 도메인으로 그룹화 │ │ ┌─────────────────┐ ┌─────────────────┐ │ │ │ Rides 도메인 │ │ Eats 도메인 │ │ │ │ ┌───┬───┬───┐ │ │ ┌───┬───┬───┐ │ │ │ │ │ S │ S │ S │ │ │ │ S │ S │ S │ │ │ │ │ └───┴───┴───┘ │ │ └───┴───┴───┘ │ │ │ └─────────────────┘ └─────────────────┘ │ │ │ │ 도메인 = 하나의 비즈니스 capability를 나타내는 서비스 집합 │ │ │ └─────────────────────────────────────────────────────────────┘도메인의 특징:
- 하나의 비즈니스 개념을 대표 (예: "라이드", "배달", "결제")
- 하나의 팀이 소유하고 책임짐
- 내부 서비스들은 외부에서 직접 접근 불가 (캡슐화)
실제 예시 (Uber):
- Rides 도메인: 매칭, 가격 책정, 배차 서비스
- Eats 도메인: 주문, 배달, 레스토랑 관리 서비스
- Maps 도메인: 라우팅, ETA 계산, 지오코딩 서비스
- Payments 도메인: 결제 처리, 정산, 프로모션 서비스
원칙 2: Layer (레이어)
핵심 아이디어: 도메인들을 수직적 계층으로 조직화하고, 의존 방향을 규칙화한다.
┌─────────────────────────────────────────────────────────────┐ │ 레이어 계층 구조 │ ├─────────────────────────────────────────────────────────────┤ │ │ │ Layer 2: Edge / Product │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ Rider App Driver App Merchant App │ │ │ │ (최종 사용자 facing 제품) │ │ │ └──────────────────────┬──────────────────────────────┘ │ │ │ 의존 │ │ ▼ │ │ Layer 1: Business Logic │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ Rides Domain Eats Domain Maps Domain │ │ │ │ (핵심 비즈니스 로직) │ │ │ └──────────────────────┬──────────────────────────────┘ │ │ │ 의존 │ │ ▼ │ │ Layer 0: Infrastructure │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ Auth Storage Messaging Observability │ │ │ │ (공통 인프라 서비스) │ │ │ └─────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────┘핵심 규칙: 아래 레이어는 위 레이어에 의존하면 안 된다.
- ✅ Rider App → Rides Domain → Auth (위에서 아래로)
- ❌ Auth → Rides Domain (아래에서 위로) 금지!
이 규칙이 왜 중요할까?
- 순환 의존 방지: A→B→C→A 같은 순환이 생기면 시스템이 엉킴
- 계층별 안정성: 아래 레이어일수록 안정적이고 변경이 적음
- 테스트 용이성: 아래 레이어를 모킹하면 위 레이어 테스트 가능
원칙 3: Gateway (게이트웨이)
핵심 아이디어: 각 도메인은 단일 진입점(Gateway)을 통해서만 접근 가능하다.
┌─────────────────────────────────────────────────────────────┐ │ Gateway 패턴 │ ├─────────────────────────────────────────────────────────────┤ │ │ │ 외부 (다른 도메인, 제품) │ │ │ │ │ │ 오직 Gateway를 통해서만 접근 │ │ ▼ │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ Rides Domain Gateway │ │ │ │ │ │ │ │ 역할: │ │ │ │ • 외부 요청 수신 │ │ │ │ • 내부 서비스로 라우팅 │ │ │ │ • API 버전 관리 │ │ │ │ • 인터페이스 안정성 보장 │ │ │ └──────────────────────┬──────────────────────────────┘ │ │ │ │ │ ┌────────────────┼────────────────┐ │ │ ▼ ▼ ▼ │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │ Matching │ │ Pricing │ │ Dispatch │ │ │ │ Service │ │ Service │ │ Service │ │ │ └──────────┘ └──────────┘ └──────────┘ │ │ ↑ ↑ ↑ │ │ └──────────────┴──────────────┘ │ │ 도메인 내부 서비스 직접 접근 금지! │ │ │ └─────────────────────────────────────────────────────────────┘Gateway가 제공하는 이점:
- 캡슐화: 도메인 내부 구현을 숨김
- 리팩토링 자유: 내부 서비스를 마음대로 재구성해도 외부 영향 없음
- 계약 안정성: Gateway API만 안정적이면 됨
- 중앙화된 정책: 인증, 로깅, 모니터링을 한 곳에서
원칙 4: Extension (확장)
핵심 아이디어: 도메인의 핵심 로직을 수정하지 않고 기능을 확장할 수 있는 플러그인 포인트를 제공한다.
┌─────────────────────────────────────────────────────────────┐ │ Extension 패턴 │ ├─────────────────────────────────────────────────────────────┤ │ │ │ Rides 도메인 (Core) │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ │ │ │ │ 라이드 요청 처리 플로우: │ │ │ │ │ │ │ │ 요청 수신 │ │ │ │ │ │ │ │ │ ▼ │ │ │ │ [Extension Point: pre_ride_hooks] ◀── 여기 확장! │ │ │ │ │ │ │ │ │ ▼ │ │ │ │ 드라이버 매칭 │ │ │ │ │ │ │ │ │ ▼ │ │ │ │ 가격 계산 │ │ │ │ │ │ │ │ │ ▼ │ │ │ │ [Extension Point: price_modifiers] ◀── 여기도 확장! │ │ │ │ │ │ │ │ │ ▼ │ │ │ │ 라이드 생성 │ │ │ │ │ │ │ └─────────────────────────────────────────────────────┘ │ │ │ │ 등록된 Extensions: │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ │ Safety │ │ Promo │ │ Rewards │ │ │ │ Extension │ │ Extension │ │ Extension │ │ │ │ │ │ │ │ │ │ │ │ pre_ride: │ │ price_mod: │ │ post_ride: │ │ │ │ 안전 체크 │ │ 할인 적용 │ │ 포인트 적립 │ │ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │ │ └─────────────────────────────────────────────────────────────┘Extension이 필요한 이유:
"라이드 시작 전에 안전 체크를 추가하고 싶어요" → Rides 코드를 직접 수정?- ❌ Rides 코드 직접 수정: Rides 팀 승인 필요, 배포 충돌, 코드 복잡해짐
- ✅ Extension 등록: Safety 팀이 독립적으로 개발/배포, Rides 코드 변경 없음
도메인 간 통신 원칙
비동기를 기본으로
DOMA에서 도메인 간 통신은 가능하면 비동기(Async)를 권장한다.
┌─────────────────────────────────────────────────────────────┐ │ 도메인 간 통신 방식 비교 │ ├─────────────────────────────────────────────────────────────┤ │ │ │ 동기 통신 (Synchronous): │ │ ┌─────────────┐ 요청 ┌─────────────┐ │ │ │ Domain A │ ──────▶│ Domain B │ │ │ │ │◀────── │ │ │ │ └─────────────┘ 응답 └─────────────┘ │ │ │ │ 문제점: │ │ • A가 B의 응답을 기다리는 동안 블로킹 │ │ • B가 죽으면 A도 영향 받음 (장애 전파) │ │ • A와 B가 강하게 결합됨 │ │ │ │ 비동기 통신 (Asynchronous): │ │ ┌─────────────┐ ┌───────┐ ┌─────────────┐ │ │ │ Domain A │──────▶│ MQ │──────▶│ Domain B │ │ │ └─────────────┘ 이벤트 └───────┘ └─────────────┘ │ │ │ │ │ └── 응답 기다리지 않고 바로 다음 작업 │ │ │ │ 장점: │ │ • A는 메시지만 보내고 바로 다음 작업 │ │ • B가 죽어도 A는 영향 없음 (MQ가 버퍼링) │ │ • 느슨한 결합 │ │ │ └─────────────────────────────────────────────────────────────┘언제 동기 통신을 쓰나?
모든 통신을 비동기로 할 수는 없다. 동기 통신이 필요한 경우:
즉각적인 응답이 비즈니스 요구사항 실시간 가격 조회, 잔액 확인 트랜잭션 일관성 필요 결제 승인 후 주문 확정 사용자가 결과를 기다림 로그인 인증
DOMA가 해결하는 문제들
Before vs After
시스템 이해 2,200개 서비스 각각 파악 필요 도메인 단위로 파악 (훨씬 적은 수) 소유권 서비스마다 다른 팀 도메인 = 팀 (명확한 책임) 변경 영향 예측 불가 Gateway가 변경 격리 온보딩 어디서 시작? 도메인부터 학습 기능 추가 여러 서비스 수정 Extension 등록 구체적인 효과
- 인지 부하 감소: "우리 팀은 Rides 도메인을 담당해요" - 범위가 명확
- 독립적 개발: 도메인 내부는 자유롭게 변경, 외부 영향 없음
- 장애 격리: Gateway가 도메인의 방화벽 역할
- 확장 유연성: Extension으로 기능 추가가 쉬워짐
DOMA 도입 시 주의점
도메인 경계 정하기가 가장 어렵다
잘못된 도메인 경계는 오히려 문제를 악화시킨다:
- 너무 작은 도메인: 서비스 1-2개짜리 도메인 → 오버헤드만 증가
- 너무 큰 도메인: 서비스 100개짜리 도메인 → 그냥 작은 모놀리스
- 잘못된 경계: 강하게 결합된 서비스들을 다른 도메인에 배치 → 도메인 간 통신 폭증
좋은 도메인 경계의 특징:
- 하나의 비즈니스 capability를 대표
- 팀 구조와 일치
- 도메인 내부 통신 >> 도메인 간 통신
점진적 도입이 핵심
모두가 하루아침에 DOMA를 적용할 수는 없다.
(이코에코는 태생이 AI/Cloud Native, 도메인 분리로 출발해 베이스라인이 마련된 상태다.)
- 가장 문제가 심한 영역부터 도메인화
- Gateway 먼저 도입하여 경계 명확화
- 점진적으로 도메인 확대
- Extension 시스템은 나중에
핵심 개념 정리
Domain 관련 서비스들의 논리적 그룹 인지 부하 감소, 명확한 소유권 Layer 수직적 의존성 계층화 순환 의존 방지, 안정성 확보 Gateway 도메인의 단일 진입점 캡슐화, 리팩토링 자유 Extension 플러그인 방식 확장 코어 수정 없이 기능 추가
더 읽을 자료
- Uber's Fulfillment Platform - DOMA 실제 적용 사례
- Building Microservices, 2nd Edition - Sam Newman
- Team Topologies - 팀 구조와 아키텍처의 관계
# workloads/namespaces/base/namespaces.yaml 에서 발췌 apiVersion: v1 kind: Namespace metadata: name: scan labels: istio-injection: enabled app.kubernetes.io/part-of: ecoeco-backend # 제품군 tier: business-logic # 레이어 role: api # 역할 domain: scan # 도메인라벨 체계:
라벨 값 설명 app.kubernetes.io/part-ofecoeco-backend,ecoeco-platform제품군 구분 tierbusiness-logic,data,observability,infrastructure,integrationDOMA Layer roleapi,database,cache,messaging,metrics,dashboards세부 역할 domainauth,scan,character,my,chat,location,image비즈니스 도메인 2. 레이어 구조 (실제 코드 기반)
┌─────────────────────────────────────────────────────────────────────────┐ │ Eco² 레이어 아키텍처 │ ├─────────────────────────────────────────────────────────────────────────┤ │ │ │ Layer 2: Product (Edge) │ │ ┌───────────────────────────────────────────────────────────────────┐ │ │ │ Mobile Web App (React/PWA) ─────────▶ api.growbin.app │ │ │ └───────────────────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ═══════════════════════════════════════════════════════════════════ │ │ │ Istio Ingress Gateway (eco2-gateway) │ │ │ │ └─▶ AuthorizationPolicy → ext-authz (인증) │ │ │ │ └─▶ VirtualService (라우팅) │ │ │ ═══════════════════════════════════════════════════════════════════ │ │ │ │ │ Layer 1: Business Logic (tier=business-logic) │ │ ┌───────────────────────────────────────────────────────────────────┐ │ │ │ │ │ │ │ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ │ │ │ │ AI Domain │ │ User Domain │ │ Info Domain │ │ │ │ │ │ (ns: scan, │ │ (ns: auth, │ │ (ns: location, │ │ │ │ │ │ chat) │ │ my, character)│ │ image) │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ • scan-api │ │ • auth-api │ │ • location-api │ │ │ │ │ │ • chat-api │ │ • my-api │ │ • image-api │ │ │ │ │ │ │ │ • my-grpc │ │ │ │ │ │ │ │ │ │ • character-api│ │ │ │ │ │ │ │ │ │ • character-grpc│ │ │ │ │ │ │ └────────┬────────┘ └────────┬────────┘ └─────────────────┘ │ │ │ │ │ │ │ │ │ │ │ gRPC (50051) │ gRPC (50052) │ │ │ │ └───────────────────┘ │ │ │ │ scan → character → my (도메인 간 gRPC 통신) │ │ │ │ │ │ │ └───────────────────────────────────────────────────────────────────┘ │ │ │ │ │ Layer 0: Infrastructure │ │ ┌───────────────────────────────────────────────────────────────────┐ │ │ │ │ │ │ │ Platform (tier=infrastructure) │ │ │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ │ │ │ ext-authz │ │ Istio │ │ ArgoCD │ │ │ │ │ │ (ns: auth) │ │ (ns: istio- │ │ (ns: argocd)│ │ │ │ │ │ JWT 검증 │ │ system) │ │ GitOps │ │ │ │ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │ │ │ │ │ │ │ Data (tier=data) Integration (tier=integration) │ │ │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ │ │ │ PostgreSQL │ │ Redis │ │ RabbitMQ │ │ │ │ │ │ (ns:postgres)│ │ (ns: redis) │ │ (ns:rabbitmq)│ │ │ │ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │ │ │ │ │ │ │ Observability (tier=observability) │ │ │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ │ │ │ Prometheus │ │ Grafana │ │ EFK Stack │ │ │ │ │ │ (ns:prometh)│ │ (ns:grafana)│ │ (ns:logging)│ │ │ │ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │ │ │ │ │ │ └───────────────────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────────┘3. Gateway 패턴 구현 (Istio 기반)
Eco²는 Istio Service Mesh로 Gateway 패턴을 구현하고 있다.
┌─────────────────────────────────────────────────────────────────────────┐ │ Eco² Gateway 아키텍처 (Istio) │ ├─────────────────────────────────────────────────────────────────────────┤ │ │ │ 클라이언트 요청 │ │ │ │ │ ▼ │ │ ┌─────────────────────────────────────────────────────────────────┐ │ │ │ ALB (AWS Load Balancer) │ │ │ │ • HTTPS 종료 (ACM Certificate) │ │ │ │ • api.growbin.app → Istio Ingress Gateway │ │ │ └─────────────────────────────┬───────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────────────────────────────────────────────────────────┐ │ │ │ Istio Ingress Gateway (eco2-gateway) │ │ │ │ • namespace: istio-system │ │ │ │ • 모든 외부 트래픽의 단일 진입점 │ │ │ └─────────────────────────────┬───────────────────────────────────┘ │ │ │ │ │ ┌───────────────────┴───────────────────┐ │ │ ▼ ▼ │ │ ┌──────────────────────┐ ┌───────────────────────┐ │ │ │ AuthorizationPolicy │ │ VirtualService │ │ │ │ (ext-authz-policy) │ │ (도메인별 라우팅) │ │ │ │ │ │ │ │ │ │ • /api/v1/* 검사 │ │ • /api/v1/auth → auth │ │ │ │ • OAuth 콜백 우회 │ │ • /api/v1/scan → scan │ │ │ │ • 헬스체크 우회 │ │ • /api/v1/user → my │ │ │ └──────────┬───────────┘ │ • /api/v1/chat → chat │ │ │ │ └───────────────────────┘ │ │ ▼ │ │ ┌──────────────────────┐ │ │ │ ext-authz │ │ │ │ (Go, gRPC) │ │ │ │ │ │ │ │ 역할: │ │ │ │ ✅ JWT 검증 (RS256) │ │ │ │ ✅ 블랙리스트 조회 │ │ │ │ ❌ 라우팅 (담당 X) │ │ │ └──────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────────┘실제 AuthorizationPolicy 설정:
# workloads/routing/gateway/base/authorization-policy.yaml apiVersion: security.istio.io/v1beta1 kind: AuthorizationPolicy metadata: name: ext-authz-policy namespace: istio-system spec: selector: matchLabels: istio: ingressgateway action: CUSTOM provider: name: eco2-ext-authz rules: - to: - operation: hosts: [api.dev.growbin.app, api.growbin.app] paths: [/api/v1/*] notPaths: # OAuth 콜백, JWKS, 헬스체크는 인증 우회 - /api/v1/auth/kakao/callback - /api/v1/auth/.well-known/jwks.json - /api/v1/*/health4. 도메인 간 gRPC 통신 (실제 코드)
┌─────────────────────────────────────────────────────────────────────────┐ │ 도메인 간 gRPC 통신 흐름 │ ├─────────────────────────────────────────────────────────────────────────┤ │ │ │ [1] scan → character (보상 평가) │ │ │ │ ┌────────────┐ gRPC (50051) ┌────────────────┐ │ │ │ scan-api │ ─────────────────────▶ │ character-grpc │ │ │ │ │ │ │ │ │ │ CharacterGrpcClient: │ CharacterServicer: │ │ │ • CircuitBreaker │ • GetCharacterReward() │ │ │ • Retry (Exp Backoff) │ │ │ │ │ • Timeout │ │ │ │ └────────────┘ └────────────────┘ │ │ │ │ [2] character → my (캐릭터 지급 동기화) │ │ │ │ ┌────────────────┐ gRPC (50052) ┌────────────┐ │ │ │ character-grpc │ ────────────────▶ │ my-grpc │ │ │ │ │ │ │ │ │ │ MyUserCharacterClient: │ UserCharacterServicer: │ │ │ • CircuitBreaker │ • GrantCharacter() │ │ │ • Retry (Exp Backoff) │ │ │ │ │ • Timeout │ │ │ │ └────────────────┘ └────────────────┘ │ │ │ │ NetworkPolicy로 격리: │ │ • allow-scan-to-character-grpc (ns: character) │ │ • allow-character-to-my-grpc (ns: my) │ │ │ └─────────────────────────────────────────────────────────────────────────┘gRPC 클라이언트 구현 특징 (실제 코드):
# domains/scan/core/grpc_client.py class CharacterGrpcClient: def __init__(self, settings: "Settings") -> None: # Circuit Breaker로 장애 전파 방지 self._circuit_breaker = CircuitBreaker( name="character-grpc-client", fail_max=settings.grpc_circuit_fail_max, # 연속 실패 5회 timeout_duration=settings.grpc_circuit_timeout_duration, # 30초 ) async def _call_with_retry(self, call_func, log_ctx: dict): """Exponential backoff + jitter 재시도""" for attempt in range(self.max_retries + 1): try: return await call_func() except grpc.aio.AioRpcError as e: if e.code() in RETRYABLE_STATUS_CODES: delay = min(self.retry_base_delay * (2**attempt), self.retry_max_delay) delay *= (0.75 + random.random() * 0.5) # jitter await asyncio.sleep(delay)5. NetworkPolicy 격리 (실제 설정)
# workloads/network-policies/base/allow-scan-to-character-grpc.yaml apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: allow-scan-to-character-grpc namespace: character spec: podSelector: matchLabels: app: character-grpc policyTypes: - Ingress ingress: - from: - namespaceSelector: matchLabels: kubernetes.io/metadata.name: scan # scan 네임스페이스만 허용 ports: - protocol: TCP port: 500516. DOMA 원칙 적용 현황 평가
원칙 현재 상태 구현 방식 개선 여지 Domain ✅ 적용됨 네임스페이스 + domain라벨로 명시적 경계- Layer ✅ 적용됨 tier라벨로 계층 구분, nodeSelector로 배치 격리- Gateway ✅ 부분 적용 Istio Gateway + ext-authz로 인증 통합 도메인별 Gateway는 없음 Extension ❌ 미적용 Character 보상 로직이 하드코딩 Evaluator 인터페이스로 확장 가능성 있음 7. 개선 로드맵 (DOMA 관점)
P1 gRPC → MQ 전환 동기 gRPC 호출 RabbitMQ 비동기 통신 P1 ext-authz 로컬 캐시 매번 Redis 조회 MQ로 캐시 동기화 P2 도메인 Gateway 없음 도메인별 진입점 통일 P3 Extension 시스템 보상 로직 하드코딩 플러그인 아키텍처 gRPC → MQ 전환 대상:
현재 통신 특성 전환 방식 scan → characterFire-and-forget 가능 RabbitMQ Point-to-Point character → mybest-effort 동기화 RabbitMQ + at-least-once my → character(캐릭터 조회)읽기 캐싱 필요 로컬 캐시 + MQ 동기화 '이코에코(Eco²) > Foundations' 카테고리의 다른 글
Domain-Driven Design: Aggregate와 트랜잭션 경계 (0) 2025.12.21 CQRS: Command와 Query의 책임 분리 (0) 2025.12.21 Enterprise Integration Patterns: 메시징 시스템의 설계 원칙 (2) 2025.12.21 Event Sourcing: 상태(state)에서 이벤트로 (1) 2025.12.21 The Log: 분산 시스템을 이해하는 가장 중요한 개념 (0) 2025.12.21