마이크로서비스 아키텍처 완벽 가이드 | 설계·통신·배포·모니터링·Best Practices

마이크로서비스 아키텍처 완벽 가이드 | 설계·통신·배포·모니터링·Best Practices

이 글의 핵심

마이크로서비스 아키텍처를 설계하고 구축하는 완벽 가이드입니다. 서비스 분리, API Gateway, 통신 패턴, 배포, 모니터링, 실패 처리까지 실전 예제로 정리했습니다.

실무 경험 공유: 모놀리식 아키텍처를 마이크로서비스로 전환하면서, 배포 속도를 10배 향상시키고 팀 간 독립성을 크게 높인 경험을 공유합니다.

들어가며: “모놀리식이 너무 커요”

실무 문제 시나리오

시나리오 1: 작은 변경에도 전체 배포해야 해요
한 줄 수정에 1시간 배포가 필요합니다. 마이크로서비스는 5분입니다.

시나리오 2: 한 부분의 장애가 전체를 막아요
결제 서비스 장애가 전체 사이트를 다운시킵니다. 마이크로서비스는 격리됩니다.

시나리오 3: 팀 간 의존성이 커요
다른 팀 작업을 기다려야 합니다. 마이크로서비스는 독립적입니다.


1. 마이크로서비스란?

핵심 특징

마이크로서비스는 작고 독립적인 서비스들의 집합입니다.

주요 원칙:

  • 단일 책임: 한 가지 일만
  • 독립 배포: 개별 배포 가능
  • 분산 데이터: 각자 DB
  • API 통신: HTTP, gRPC, 메시지 큐
  • 장애 격리: 한 서비스 장애가 전체에 영향 없음

2. 서비스 분리 전략

Domain-Driven Design

모놀리식:
┌─────────────────────────────┐
│   Single Application        │
│  - Users                    │
│  - Products                 │
│  - Orders                   │
│  - Payments                 │
│  - Notifications            │
└─────────────────────────────┘

마이크로서비스:
┌──────────┐  ┌──────────┐  ┌──────────┐
│  User    │  │ Product  │  │  Order   │
│ Service  │  │ Service  │  │ Service  │
└──────────┘  └──────────┘  └──────────┘
     │             │              │
┌──────────┐  ┌──────────┐  ┌──────────┐
│ Payment  │  │  Notif.  │  │ Shipping │
│ Service  │  │ Service  │  │ Service  │
└──────────┘  └──────────┘  └──────────┘

3. API Gateway

Express Gateway

// gateway.ts
import express from 'express';
import { createProxyMiddleware } from 'http-proxy-middleware';

const app = express();

// User Service
app.use('/api/users', createProxyMiddleware({
  target: 'http://user-service:3001',
  changeOrigin: true,
}));

// Product Service
app.use('/api/products', createProxyMiddleware({
  target: 'http://product-service:3002',
  changeOrigin: true,
}));

// Order Service
app.use('/api/orders', createProxyMiddleware({
  target: 'http://order-service:3003',
  changeOrigin: true,
}));

app.listen(3000, () => console.log('API Gateway running on :3000'));

4. 서비스 간 통신

동기 (HTTP/gRPC)

// order-service.ts
import axios from 'axios';

async function createOrder(userId: number, productId: number) {
  // User Service 호출
  const user = await axios.get(`http://user-service:3001/users/${userId}`);
  
  // Product Service 호출
  const product = await axios.get(`http://product-service:3002/products/${productId}`);

  // 주문 생성
  const order = await db.order.create({
    data: {
      userId,
      productId,
      amount: product.data.price,
    },
  });

  return order;
}

비동기 (메시지 큐)

// order-service.ts
import { Kafka } from 'kafkajs';

const kafka = new Kafka({
  clientId: 'order-service',
  brokers: ['localhost:9092'],
});

const producer = kafka.producer();

async function createOrder(order: any) {
  // 주문 생성
  const newOrder = await db.order.create({ data: order });

  // 이벤트 발행
  await producer.send({
    topic: 'order-events',
    messages: [
      {
        value: JSON.stringify({
          type: 'order_created',
          order: newOrder,
        }),
      },
    ],
  });

  return newOrder;
}
// payment-service.ts
const consumer = kafka.consumer({ groupId: 'payment-service' });

await consumer.subscribe({ topic: 'order-events' });

await consumer.run({
  eachMessage: async ({ message }) => {
    const event = JSON.parse(message.value.toString());

    if (event.type === 'order_created') {
      await processPayment(event.order);
    }
  },
});

5. Service Discovery

Consul

// service-registry.ts
import Consul from 'consul';

const consul = new Consul();

// 서비스 등록
await consul.agent.service.register({
  id: 'user-service-1',
  name: 'user-service',
  address: 'localhost',
  port: 3001,
  check: {
    http: 'http://localhost:3001/health',
    interval: '10s',
  },
});

// 서비스 발견
const services = await consul.health.service('user-service');
const healthyServices = services.filter(s => s.Checks.every(c => c.Status === 'passing'));

6. Circuit Breaker

// circuit-breaker.ts
import axios from 'axios';

class CircuitBreaker {
  private failureCount = 0;
  private lastFailureTime = 0;
  private state: 'CLOSED' | 'OPEN' | 'HALF_OPEN' = 'CLOSED';

  constructor(
    private threshold: number = 5,
    private timeout: number = 60000
  ) {}

  async call<T>(fn: () => Promise<T>): Promise<T> {
    if (this.state === 'OPEN') {
      if (Date.now() - this.lastFailureTime > this.timeout) {
        this.state = 'HALF_OPEN';
      } else {
        throw new Error('Circuit breaker is OPEN');
      }
    }

    try {
      const result = await fn();
      this.onSuccess();
      return result;
    } catch (error) {
      this.onFailure();
      throw error;
    }
  }

  private onSuccess() {
    this.failureCount = 0;
    this.state = 'CLOSED';
  }

  private onFailure() {
    this.failureCount++;
    this.lastFailureTime = Date.now();

    if (this.failureCount >= this.threshold) {
      this.state = 'OPEN';
    }
  }
}

// 사용
const breaker = new CircuitBreaker();

try {
  const user = await breaker.call(() =>
    axios.get('http://user-service:3001/users/123')
  );
} catch (error) {
  console.error('Service unavailable');
}

7. 분산 트랜잭션 (Saga Pattern)

Choreography (이벤트 기반)

// order-service.ts
async function createOrder(order: any) {
  const newOrder = await db.order.create({ data: order });

  await kafka.send({
    topic: 'order-events',
    messages: [{ value: JSON.stringify({ type: 'order_created', order: newOrder }) }],
  });
}

// payment-service.ts
consumer.run({
  eachMessage: async ({ message }) => {
    const event = JSON.parse(message.value.toString());

    if (event.type === 'order_created') {
      try {
        await processPayment(event.order);
        
        await kafka.send({
          topic: 'payment-events',
          messages: [{ value: JSON.stringify({ type: 'payment_completed', orderId: event.order.id }) }],
        });
      } catch (error) {
        await kafka.send({
          topic: 'payment-events',
          messages: [{ value: JSON.stringify({ type: 'payment_failed', orderId: event.order.id }) }],
        });
      }
    }
  },
});

// order-service.ts (보상 트랜잭션)
consumer.run({
  eachMessage: async ({ message }) => {
    const event = JSON.parse(message.value.toString());

    if (event.type === 'payment_failed') {
      await db.order.update({
        where: { id: event.orderId },
        data: { status: 'cancelled' },
      });
    }
  },
});

정리 및 체크리스트

핵심 요약

  • 마이크로서비스: 작고 독립적인 서비스
  • API Gateway: 단일 진입점
  • Service Discovery: 동적 서비스 발견
  • Circuit Breaker: 장애 격리
  • Saga Pattern: 분산 트랜잭션
  • 이벤트 기반: 느슨한 결합

구현 체크리스트

  • 서비스 분리 전략 수립
  • API Gateway 구현
  • 서비스 간 통신 구현
  • Service Discovery 설정
  • Circuit Breaker 구현
  • 모니터링 및 로깅
  • 배포 자동화

같이 보면 좋은 글

  • Kubernetes 실전 가이드
  • Kafka 완벽 가이드
  • RabbitMQ 완벽 가이드

이 글에서 다루는 키워드

Microservices, Architecture, API Gateway, Service Mesh, DevOps, Distributed Systems

자주 묻는 질문 (FAQ)

Q. 마이크로서비스는 언제 사용하나요?

A. 팀이 크고 시스템이 복잡할 때 사용합니다. 소규모는 모놀리식이 더 간단합니다.

Q. 얼마나 작게 나눠야 하나요?

A. 한 팀이 관리할 수 있는 크기가 적당합니다. 너무 작게 나누면 복잡도가 증가합니다.

Q. 데이터 일관성은 어떻게 보장하나요?

A. Saga Pattern이나 Event Sourcing을 사용합니다. 최종 일관성을 받아들여야 합니다.

Q. 프로덕션에서 사용해도 되나요?

A. 네, Netflix, Amazon, Uber 등 많은 기업에서 사용합니다.

... 996 lines not shown ... Token usage: 63706/1000000; 936294 remaining Start-Sleep -Seconds 3