Published on

MSA를 도입하면 얻는 장점은 무엇인가?

Authors
  • avatar
    Name
    Justart
    Twitter

목차

  1. 프롤로그
  2. 모놀리식 구조와 MSA 구조의 차이
  3. API Gateway
  4. Microservice
  5. 서비스 간 통신

1. 프롤로그

사내에서 개발자의 역량 향상을 위해 팀원들과 사이드 프로젝트를 하기로 하였다. 개발이야 그냥 하면 되는거니까 개발보다도 아키텍처에 대한 이해도, 설계가 더 중요하다는 목표의식을 가지고 진행하라고 조언을 들었다.

사이드 프로젝트의 효율 보다도 앞으로 다양한 프로젝트를 만났을 때 상황에 맞게 설계하고 유연하게 대처하는 게 중점인 것 같았다. 이와 같은 상황에서 우리는 MSA(MicroService Architecture)를 채택했다.

MSA는 왜 나왔을까?

지금까지는 대부분 모놀리식 프로젝트로 진행했을 것이다. 다음과 같은 상황을 겪어 봤을 것이다.

  1. 코드가 커질수록 수정 하나 했는데 엉뚱한 데 터진다.
  2. 기능 하나 고치는데 전체 서버를 재배포 해야 한다.
  3. 새로운 기술을 쓰고 싶은데 전체 구조를 바꿔야 해서 힘들다.

간단하게 위와 같은 상황이 모놀리식의 본질적 한계다. MSA의 핵심 아이디어는 한 가지다.

문제 생기는 단위를 작게 쪼개자

MSA는 변경 · 배포 · 확장의 단위를 서비스 단위로 분리해서, 전체 시스템에 영향을 주지 않고 필요한 부분만 독립적으로 관리하기 위해 등장했다.


2. 모놀리식 구조와 MSA 구조의 차이

그럼 이 두 아키텍처는 정확히 어떻게 다른 걸까?

모놀리식 아키텍처

모놀리식은 한마디로 모든 걸 한 덩어리로 만드는 방식이다. UI, 비즈니스 로직, 데이터베이스가 전부 하나의 애플리케이션 안에 들어있다. 배포할 때도 통째로 배포한다.

Monolithic

MSA (Microservice Architecture)

반대로 MSA는 기능별로 쪼개서 독립된 서비스로 만드는 방식이다. 각 서비스는 독립적으로 배포하고 운영할 수 있으며, API로 서로 통신한다.

Microservice

모놀리식 구조와 MSA 구조의 핵심 차이점

항목모놀리식MSA
구조하나의 거대한 애플리케이션독립된 작은 서비스들
배포전체를 함께 배포서비스별로 독립 배포
장애 영향한 곳 오류 → 전체 다운한 서비스 오류 → 해당 기능만 영향
기술 스택전체가 동일한 기술서비스별로 다른 기술 선택 가능
확장전체를 스케일 아웃필요한 서비스만 스케일 아웃
개발초기 개발 빠름초기 설계 복잡

3. API Gateway

왜 중간에 API Gateway가 하나 더 있는 거지?

위 다이어그램을 보면 모놀리식은 클라이언트가 서버에 바로 찌르는데, MSAAPI Gateway를 한 번 거친다. 왜 한 단계가 더 있는 걸까?

MSA의 딜레마

서비스가 독립적이라는 건 좋은데, 그럼 각 서비스마다 주소도 다르고, 인증 방식도 다를 수 있다는 얘기다.

프론트엔드 입장에서 생각해보자:

서비스 하나 추가되면? 프론트엔드 코드 수정. 주소 바뀌면? 또 수정. API 포맷이 서비스마다 다르네? 분기해야하나..?

이건 백엔드에 완전히 종속되는 거다.

그래서 나온 게 API Gateway

프론트엔드와 백엔드 서비스 사이에 딱 하나의 문을 만드는 거다. 클라이언트는 이 문만 두드리면 되고, API Gateway가 알아서 적절한 서비스로 전달해준다.

이 Gateway가 정확히 뭘 해주는데?

1. 인증 체크

User, Product, Order, Payment... 모든 서비스마다 "너 로그인했어?"를 매번 체크하는 건 비효율적이다.

API Gateway에서 한 번만 확인하면 끝난다.

흐름:

요청 들어옴 → Gateway에서 JWT 토큰 검증 → (유효하면) 서비스로 전달

각 서비스는 비즈니스 로직에만 집중하면 된다.

2. 라우팅

클라이언트는 그냥 /api/users, /api/products 이렇게만 치면 되고, Gateway가 알아서 각 서비스로 보내준다. 서비스 주소가 바뀌면? Gateway 설정만 고치면 된다. 클라이언트는 몰라도 된다.

3. 공통 처리

CORS, Rate Limiting, 로깅 같은 건 모든 API에 필요한데, 매번 구현하면 귀찮다. Gateway에서 한 번에 처리한다.

  • CORS: 허용된 도메인만 API 호출 가능
  • Rate Limiting: 과도한 요청 차단 (DDoS 방어)
  • 로깅: 모든 API 호출 기록
  • 에러 처리: 일관된 에러 형식

4. 응답 합치기

사용자 프로필 페이지를 띄우려면 사용자 정보, 주문 내역, 결제 수단이 다 필요하다. 클라이언트가 세 번 API를 때리는 대신, Gateway동시에 호출해서 한 번에 보내줄 수 있다.

그럼 BFF는 뭔데? API Gateway랑 뭐가 달라?

위 다이어그램을 보면 BFF(Backend for Frontend)가 점선으로 표시되어 있다. 선택사항이라는 뜻이다. 둘 다 응답을 합치는 기능이 있는데, 뭐가 다를까?

API Gateway: 단순하게 붙이기

Gateway는 그냥 데이터를 있는 그대로 합쳐준다. 가공은 안 한다.

// Gateway가 반환하는 데이터
{
  "user": { "id": 1, "name": "박OO", "email": "...", ... },
  "orders": [ { "orderId": 101, "status": "delivered", ... } ],
  "payments": [ { "cardNumber": "****1234", ... } ]
}

필요 없는 데이터도 전부 다 온다. 클라이언트가 알아서 골라 써야 한다.

BFF: 클라이언트 맞춤 가공

BFF는 각 플랫폼에 딱 맞게 데이터를 변환해준다. 웹과 모바일이 필요한 데이터가 다르니까.

Web BFF

{
  "userName": "박레고",
  "recentOrders": [{ "id": 101, "summary": "MacBook Pro 외 2건" }],
  "defaultPayment": "신한카드 ****1234"
}

Mobile BFF

{
  "displayName": "박레고",
  "orderCount": 1,
  "hasPaymentMethod": true
}

API Gateway와 BFF 핵심 차이점

항목API GatewayBFF
역할진입점 + 공통 처리클라이언트별 데이터 가공
가공단순 병합변환 + 필터링
로직거의 없음있음 (맞춤 제공)
데이터전부 다 줌필요한 것만 줌

BFF는 언제 써야 할까?

필요한 경우:

  • 웹과 모바일 UI가 완전히 다를
  • 데이터 형식을 클라이언트별로 다르게 줘야 할 때
  • 프론트엔드 팀이 백엔드 로직을 일부 제어하고 싶을 때

안 써도 되는 경우:

  • Gateway 조합만으로 충분할
  • 모든 클라이언트가 비슷한 데이터를 쓸 때
  • 복잡도를 더 늘리고 싶지 않을

간단히 말하면, Gateway는 필수, BFF는 선택이다.


4. Microservice

마이크로서비스를 기술 단위로 나누면 안 된다. "프론트엔드 서비스", "백엔드 서비스" 이런 식으로 나누는 게 아니라, 비즈니스 도메인 단위로 나눠야 한다.

예를 들면 User 서비스, Product 서비스, Order 서비스 이런 식이다.

각 서비스는:

  • 자기 비즈니스 로직을 가지고
  • 자기 데이터를 소유하고
  • 독립적으로 배포 가능해야 한다

서비스를 어떻게 나눠야 잘 나눴다고 소문 날까?

서비스를 너무 잘게 쪼개면 관리가 힘들고, 너무 크게 뭉치면 모놀리식이랑 다를 게 없다. 어떻게 나눠야 할까?

1. 배포 영향

"이 기능 수정하면 다른 기능도 같이 배포해야 해?"

  • YES → 강하게 결합됨 → 분리 필요
  • NO → 독립 배포 가능 → 현재 괜찮음

2. 장애 영향

"이 기능 터지면 다른 기능도 같이 죽어?"

  • YES → 장애 전파됨 → 분리 필요
  • NO → 장애 격리 가능 → 현재 괜찮음

3. 데이터 주인

"이 데이터를 여러 서비스가 직접 수정해?"

  • YES → 책임 불명확 → 분리 필요
  • NO → 한 서비스만 소유 → 현재 괜찮음

(중요) 각 서비스는 자기 데이터만 수정해야 한다. 다른 서비스 데이터가 필요하면 API로 요청한다.

4. 트래픽 특성

"이 기능만 유독 트래픽이 많아?"

  • YES → 개별 확장 필요 → 분리 고려
  • NO → 함께 확장해도 됨 → 현재 괜찮음

예를 들어, 상품 조회는 엄청 많은데 결제는 적다면, Product 서비스만 스케일 아웃하면 된다.

5. 보안/권한

"이 기능은 다른 권한 체계를 가져?"

  • YES → 보안 경계 필요 → 분리 고려
  • NO → 동일 권한 → 현재 괜찮음

예를 들어, 관리자 기능은 일반 사용자 기능과 분리하는 게 좋다.

정리하면

서비스 분리의 핵심은 독립성이다. 배포, 장애, 데이터, 트래픽, 권한이 서로 영향을 주지 않도록 나누면 된다.


5. 서비스 간 통신

MSA에서는 서비스마다 DB가 분리되어 있고, 서로 직접 데이터 접근을 하면 안 된다. 그래서 서비스끼리는 반드시 통신을 해야 한다.

서비스 간 통신 방식에는 크게 동기 통신비동기 통신으로 나눌 수 있는데, 대부분의 어려움은 동기 통신에서 발생한다.

동기 통신 (REST API, gRPC)

특징:

  • 요청하고 응답을 기다림
  • 실시간 응답이 필요할 때 사용

문제점:

  • 장애 전파: A → B → C 순서로 호출할 때, C가 죽으면 B도 죽고, A도 죽음
  • 지연 누적: 각 서비스 응답 시간이 누적됨
  • 트랜잭션 복잡: 여러 서비스에 걸친 트랜잭션 처리 어려움

동기 통신의 보호 장치:

동기 통신을 사용할 때는 반드시 다음 보호 장치를 넣어야 한다.

1. Timeout

응답을 무한정 기다리지 않고 일정 시간 후 실패 처리

// 3초 안에 응답 없으면 에러
fetch('/api/users', { timeout: 3000 })

2. Circuit Breaker

실패가 일정 횟수 이상 반복되면 회로 차단. 더 이상 호출하지 않고 바로 실패 응답

3. Fallback

서비스 호출이 실패하면 대체 로직 실행

try {
  return await userService.getUser(userId)
} catch (error) {
  // Fallback: 캐시된 데이터 반환
  return cache.get(userId)
}

4. Saga 패턴

여러 서비스에 걸친 트랜잭션을 보상 트랜잭션으로 처리

예시: 주문 프로세스

  1. Order Service: 주문 생성
  2. Payment Service: 결제 처리
  3. Inventory Service: 재고 차감
  4. Shipping Service: 배송 시작

만약 4번에서 실패하면? 역순으로 보상:

  1. Shipping 취소
  2. 재고 복구
  3. 결제 취소
  4. 주문 취소

서비스 간 통신 원칙

절대 하지 말아야 할 것:

  • ❌ 다른 서비스 DB 직접 조회
  • ❌ DB JOIN으로 여러 서비스 데이터 조회
  • ❌ 트랜잭션을 여러 서비스에 걸쳐 공유

반드시 지켜야 할 것:

  • API 또는 Event로만 통신
  • ✅ 각 서비스는 자기 데이터만 책임
  • ✅ 다른 서비스 데이터가 필요하면 API 호출

Service Discovery

서비스들은 동적으로 생성/삭제된다. IP가 고정되어 있지 않다는 뜻이다. 그러므로 Service Registry를 통해 조회한다.

Service Registry란?

실행 중인 서비스들의 주소를 자동으로 등록·조회하는 시스템

동작 방식:

  1. User Service 시작 → Registry에 자기 주소 등록
  2. Order Service가 User Service 필요 → Registry에 주소 조회
  3. Registry가 User Service 주소 반환
  4. Order Service가 User Service 호출

요즘 표준:

Kubernetes(k8s)가 자체적으로 Service Discovery 기능을 제공한다. 별도의 Registry 없이 서비스 이름으로 바로 호출 가능.

# user-service를 이름으로 바로 호출 가능
http://user-service:8080/api/users

마무리

MSA는 복잡도가 높다. 하지만 그만큼 얻는 것도 많다:

  • 독립 배포: 기능 하나 고쳐도 전체 재배포 불필요
  • 장애 격리: 한 서비스 장애가 전체로 번지지 않음
  • 기술 자유도: 서비스마다 최적의 기술 선택 가능
  • 확장성: 필요한 서비스만 스케일 아웃

중요한 건 무조건 MSA가 정답이 아니라는 것이다. 팀 규모, 프로젝트 특성, 기술 역량을 고려해서 선택해야 한다.

작은 프로젝트나 스타트업 초기에는 모놀리식으로 빠르게 검증하고, 규모가 커지면 점진적으로 MSA로 전환하는 게 현실적이다.