본문 바로가기
카테고리 없음

도메인 경계 재설계부터 데이터/CI 분할 자동화까지

by frontier12 2025. 5. 28.


대규모 모놀리스를 실전에서 쪼개는 법:


1. 왜 단순 마이크로서비스 분리로는 실패하는가

현업에서 모놀리스를 마이크로서비스로 나누는 작업은 “리팩터링”이 아니라
시스템 전체를 해체 후 재조립하는 외과적 절차에 가깝습니다.

실패하는 전형적인 시나리오:
• 경계 없이 Controller/Service만 분리해 호출 관계가 엉키는 경우
• 공용 DB를 공유한 채 API만 분리한 ‘분산 모놀리스’
• 도메인과 무관한 팀 단위 분리 → 조직 구조와 아키텍처가 충돌

따라서 도메인 중심 분할, 데이터 소유권 정리, 인프라 자동화가 반드시 병행되어야 합니다.



2. 도메인 경계 기반 코드 자동 분할 구조

2.1 폴더 구조 재설계 예시 (NestJS 기준)

apps/
  user-service/
    src/
      user/
      auth/
      main.ts
  order-service/
    src/
      order/
      payment/
      main.ts
libs/
  shared/
    logging/
    config/

• apps/: 독립 배포 가능한 서비스 단위
• libs/: 공통 유틸(단, DB나 서비스 레이어는 공유 금지)

2.2 Nx를 활용한 경계 강제 (monorepo용)

nx.json

{
  "projects": {
    "user-service": {
      "tags": ["scope:user"]
    },
    "order-service": {
      "tags": ["scope:order"]
    },
    "shared-logger": {
      "tags": ["type:shared"]
    }
  }
}

• lint rules를 통해 scope:user는 scope:order 호출 금지하도록 설정 가능
• 서비스 간 호출은 반드시 API 호출(gRPC/REST) 또는 Event 기반 통신만 허용



3. 데이터베이스 스키마 분리 전략

3.1 PostgreSQL 내 Schema 단위 분리

-- user-service migration
CREATE SCHEMA user;
CREATE TABLE user.accounts (
  id UUID PRIMARY KEY,
  name TEXT,
  email TEXT UNIQUE
);

-- order-service migration
CREATE SCHEMA order;
CREATE TABLE order.orders (
  id UUID PRIMARY KEY,
  user_id UUID,
  amount INTEGER
);

• DB는 하나, 하지만 서비스 간 테이블 명확히 분리
• Flyway/Liquibase로 schema별 migration 디렉토리 구분

3.2 서비스별 독립 DB 구성 + 동기화
• Debezium + Kafka: 변경된 데이터를 이벤트로 추출해 동기화
• Materialized View: 필요한 데이터만 복제하여 읽기 전용 사용

{
  "connector.class": "io.debezium.connector.postgresql.PostgresConnector",
  "database.hostname": "user-db",
  "database.server.name": "user-service",
  "table.include.list": "public.accounts"
}




4. CI/CD 파이프라인 자동 분리 및 매트릭스 구성

4.1 GitHub Actions 매트릭스 전략

jobs:
  deploy-microservices:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        service: [user-service, order-service]
    steps:
      - name: Checkout
        uses: actions/checkout@v2
      - name: Deploy ${{ matrix.service }}
        run: ./scripts/deploy.sh ${{ matrix.service }}

• 서비스별 별도 빌드/배포 논리 적용
• ./scripts/deploy.sh 내부에서 helm/kustomize로 k8s 배포



5. 트래픽 경계와 서비스 간 통신

5.1 gRPC 통신 예시 (NestJS)

user-service.proto

service UserService {
  rpc GetUser (GetUserRequest) returns (User);
}

order-service 호출 코드

const client = this.client.getService<UserService>('UserService');
const user = await client.getUser({ id: 'abc' }).toPromise();

• 서비스 간 강한 스키마 기반 통신 가능
• REST 대비 데이터 정합성·버전 관리 유리



6. 배포 경계: Helm + Kustomize로 서비스 분리

deploy/
  base/
    deployment.yaml
    service.yaml
  overlays/
    user/
      kustomization.yaml
    order/
      kustomization.yaml

# kustomization.yaml (user)
bases:
  - ../../base
patchesStrategicMerge:
  - deployment-user.yaml

• 공통 템플릿은 base로 관리, 각 서비스는 overlay로 확장
• 서비스 단위로 rollout, rollback 가능



7. 모니터링 및 Alert 자동 분리

Prometheus relabeling 규칙

- source_labels: [__meta_kubernetes_service_label_app]
  regex: user-service
  action: keep

Grafana 대시보드 자동 생성 (jsonnet or mixin)

local grafana = import 'grafonnet/grafana.libsonnet';

grafana.dashboard.new('User Service')
  .addPanel(
    grafana.panel.timeseries.new('Login Error Rate')
    .addTarget(
      grafana.prometheus.target.new('rate(login_errors_total[5m])')
    )
  )




결론: 모놀리스 분리는 자동화로 설계되지 않으면 절반의 성공에 그친다
• 코드만 나눈 서비스는 결국 다시 얽히고, DB를 공유하면 장애도 공유됩니다.
• 진정한 분리는 도메인, 데이터, 배포, 통신, 모니터링 전 과정을 자동화했을 때 완성됩니다.
• Terraform, GitHub Actions, Debezium, gRPC, Helm, Jsonnet 등을 활용해
“마이크로서비스 전환을 자동화 가능한 인프라 수준의 절차”로 승격해야 합니다.

당신의 시스템이 아직 단일 빌드, 단일 DB, 단일 배포라면
**분산 시스템이 아니라 분산된 위험(monolith of pain)**일 수 있습니다.
분리는 선택이 아니라 생존 전략입니다.
지금 자동화 기반으로 리디자인하지 않으면, 1년 내 확장 한계에 부딪히게 될 것입니다.