전체 목록
설계Medium#049

마이크로서비스 아키텍처와 모놀리식 아키텍처의 장단점을 비교해주세요.

#설계#MSA#아키텍처#시스템설계

답변 포인트

확장성, 복잡성, 팀 독립성, 운영 비용을 생각해보세요.

정답 및 해설

빠른 요약

모놀리식: 단순한 개발/배포/디버깅, 단일 코드베이스. 단점: 확장 시 전체 배포, 기술 스택 고정, 한 부분 장애가 전체에 영향.

소프트웨어 아키텍처를 설계할 때 가장 근본적인 결정 중 하나가 모놀리식(Monolithic)과 마이크로서비스(Microservices) 중 무엇을 선택할지입니다. 모놀리식은 하나의 단일 애플리케이션으로 모든 기능을 구성하는 방식이고, 마이크로서비스는 각 기능을 독립적으로 배포 가능한 작은 서비스들로 분리하는 방식입니다. 두 접근법 모두 장단점이 명확하여 팀 규모, 서비스 규모, 기술 성숙도에 따라 적합한 선택이 달라집니다.

모놀리식 아키텍처

구조

설계
[ ]

    (UI Layer)             

    (Business Logic Layer)    
                              
                                
                                
                                

    (Data Access Layer)          

                            

           (  )
     [  ]

장점

설계
1.   
   -  IDE/   
   -    (npm start )
   -   (  )

2.  
   -  ,   
   - CI/CD   

3.  
   -      (  )
   -    ( DB)

4.  
   -   
   -   mock 

5.  
   -      

단점

TypeScript
// 모놀리식의 문제점 예시
// 하나의 기능 변경이 전체에 영향을 미침

// 결제 모듈의 버그가 전체 서비스 다운으로 이어질 수 있음
class PaymentService {
  processPayment(order: Order) {
    // 이 함수에 버그가 생기면 전체 앱에 영향
    throw new Error('결제 시스템 오류')
  }
}

// 배포 시 전체 애플리케이션 재시작 필요
// 결제 코드 한 줄 수정해도 전체 서비스 배포 필요
설계
:
1.  
   -     
   -     (/CPU )

2.   
   -   /  
   -    

3.  
   -      

4.  
   -     
   -      

5.  
   -   "빅볼 오브 머드" 
   -   

마이크로서비스 아키텍처

구조

설계
[ ]


   
[API Gateway]
   
      
                            
                      
                                              
 [DB]        [DB]        [DB]        [DB]     
      
                                               
         [  (Kafka/RabbitMQ)]

서비스 간 통신 방식

TypeScript
// 동기 통신 - REST API
// 주문 서비스에서 사용자 정보 조회
class OrderService {
  async createOrder(userId: string, items: Item[]) {
    // 사용자 서비스 HTTP 호출
    const user = await fetch(`http://user-service/api/users/${userId}`)
      .then(res => res.json())

    // 결제 서비스 HTTP 호출
    const payment = await fetch('http://payment-service/api/payments', {
      method: 'POST',
      body: JSON.stringify({ userId, amount: this.calculateTotal(items) })
    }).then(res => res.json())

    return { orderId: Date.now(), user, payment, items }
  }
}

// 비동기 통신 - 메시지 브로커 (Kafka)
// 주문 완료 이벤트 발행
class OrderService {
  async completeOrder(orderId: string) {
    await this.orderRepository.complete(orderId)

    // 이벤트 발행 - 다른 서비스가 구독
    await this.kafka.publish('order.completed', {
      orderId,
      userId: order.userId,
      amount: order.totalAmount,
      timestamp: new Date()
    })
  }
}

// 알림 서비스에서 이벤트 구독
class NotificationService {
  @KafkaSubscribe('order.completed')
  async onOrderCompleted(event: OrderCompletedEvent) {
    await this.emailSender.send(event.userId, '주문이 완료되었습니다.')
    await this.smsSender.send(event.userId, '주문 완료')
  }
}

장점

설계
1.  /
   -    
   -      ( )

2.   
   -  : Node.js
   -  : Java ()
   -  : Python
   -    DB  

3.  
   -          (Circuit Breaker  )

4.  
   -      
   - Conway's Law:   =  

5. 
   -     

단점

TypeScript
// 마이크로서비스의 복잡성 예시

// 분산 트랜잭션 관리 - Saga 패턴 필요
class OrderSaga {
  async executeOrderCreation(order: Order) {
    try {
      // 1. 재고 서비스에서 재고 확인/감소
      await this.inventoryService.reserve(order.items)

      // 2. 결제 서비스에서 결제 처리
      const payment = await this.paymentService.charge(order.amount)

      // 3. 주문 생성
      await this.orderRepository.create(order)

    } catch (error) {
      // 실패 시 보상 트랜잭션 (Compensating Transaction)
      await this.inventoryService.release(order.items)  // 재고 복구
      await this.paymentService.refund(payment.id)      // 결제 취소
      throw error
    }
  }
}

// 서비스 간 네트워크 오류 처리 - Circuit Breaker
class PaymentServiceClient {
  private circuitBreaker = new CircuitBreaker({
    failureThreshold: 5,  // 5번 실패 시 차단
    resetTimeout: 60000,  // 60초 후 재시도
  })

  async charge(amount: number) {
    return this.circuitBreaker.execute(async () => {
      const response = await fetch('http://payment-service/charge', {
        method: 'POST',
        body: JSON.stringify({ amount }),
        signal: AbortSignal.timeout(3000) // 3초 타임아웃
      })
      if (!response.ok) throw new Error('결제 서비스 오류')
      return response.json()
    })
  }
}
설계
:
1.   
   -  , ,   
   -   (Saga  )  

2.   
   -  , API Gateway,  
   -   / ( )
   -   (Kubernetes )

3.  
   -      (Docker Compose )
   - API   
   -   (Contract) 

4.  
   -    DB    
   -  (Eventual Consistency)  

마이크로서비스 필수 인프라

YAML
# Docker Compose로 로컬 마이크로서비스 환경
version: '3.8'
services:
  api-gateway:
    image: nginx
    ports:
      - "80:80"

  user-service:
    build: ./user-service
    environment:
      - DB_URL=postgres://user-db:5432/users
    depends_on:
      - user-db

  order-service:
    build: ./order-service
    environment:
      - DB_URL=mongo://order-db:27017/orders
      - KAFKA_URL=kafka:9092

  payment-service:
    build: ./payment-service
    environment:
      - DB_URL=postgres://payment-db:5432/payments

  kafka:
    image: confluentinc/cp-kafka
    ports:
      - "9092:9092"

  user-db:
    image: postgres
  order-db:
    image: mongo
  payment-db:
    image: postgres

선택 기준

설계
   :
   MVP 
   (10 )
    (  )
   
  DevOps/  

   :
   (   )
     (  )
   
    ( )
 DevOps  

  :
"마이크로서비스로 시작하지 말라. 모놀리스로 시작해서
시스템이 성숙하면 자연스럽게 분리하라."
(MonolithFirst )

중간 단계: 모듈러 모놀리스

TypeScript
// 모놀리스를 모듈로 잘 구조화하면 나중에 마이크로서비스로 분리 용이
// Next.js 풀스택 예시 - 도메인별 모듈 구조

// app/
// ├── (api)/
// │   ├── users/       → 나중에 user-service로 분리 가능
// │   ├── orders/      → 나중에 order-service로 분리 가능
// │   └── payments/    → 나중에 payment-service로 분리 가능
// ├── lib/
// │   ├── users/       (DB 접근, 비즈니스 로직)
// │   ├── orders/
// │   └── payments/
// └── components/

// 명확한 모듈 경계를 유지하면 마이크로서비스 전환이 쉬워짐
// lib/orders/service.ts
export class OrderService {
  // 외부에서는 이 서비스만 사용 (내부 구현 은닉)
  async createOrder(data: CreateOrderDto): Promise<Order> { /* ... */ }
}

// 다른 모듈에서 직접 DB 접근 금지 - 서비스를 통해서만
// ❌ import { db } from '@/lib/orders/db'  // 내부 구현 직접 접근
// ✅ import { OrderService } from '@/lib/orders/service'  // 공개 인터페이스만

정리 표

구분모놀리식마이크로서비스
개발 복잡도낮음높음
배포 단위전체 애플리케이션각 서비스 독립 배포
확장성전체 복제만 가능서비스별 독립 확장
장애 격리어려움 (장애 전파)가능 (Circuit Breaker)
기술 스택단일서비스별 자유 선택
데이터 관리단일 DB, 쉬운 트랜잭션분산 DB, 복잡한 트랜잭션
운영 인프라단순복잡 (쿠버네티스 등 필요)
팀 구조단일 팀에 적합다수 독립 팀에 적합
적합한 단계초기/소규모성장기/대규모
로컬 개발단순복잡 (Docker Compose 등)

관련 질문

같은 카테고리/태그 기준