티스토리 뷰
부트캠프 팀 프로젝트를 시작하면서 팀원들에게 MSA를 처음부터 설명해야 할 일이 생겼다. 원래 노션에 적어서 공유했었는데 티스토리로 옮기는 게 낫겠다 싶어서 옮긴다. 교과서 개념보다는 "왜 이렇게 하는가"를 실감할 수 있도록, 넷플릭스와 쇼핑몰 예시를 중심으로 정리한 온보딩 문서다. 틀린 부분이 있다면 댓글로 알려주세요 🙏
1. 왜 마이크로서비스 아키텍처를 사용하는지?
먼저 아키텍처를 설명하기 전에 모놀리식(monolithic) 구조를 이해해야 한다.
모놀리식은 말 그대로 하나의 거대한 프로그램이다.
예를 들어 쇼핑몰을 만든다고 생각해보자.
쇼핑몰에는 이런 기능들이 있다.
회원가입
로그인
상품 조회
상품 구매
결제
리뷰 작성
추천 시스템
모놀리식 구조에서는 이 모든 기능이 하나의 서버 코드 안에 들어있다.
예를 들어
shopping-mall-server
user
product
order
payment
review
recommendation
이 모든 코드가 한 프로젝트 안에 들어있다.
그리고 서버는 하나다.
shopping-mall-server
이 서버가 모든 요청을 처리한다.
2. 모놀리식 구조의 문제
처음에는 이 구조가 굉장히 편하다.
왜냐면
- 개발하기 쉽다
- 배포하기 쉽다
- 테스트하기 쉽다
하지만 서비스가 커지면 문제가 생긴다.
문제 1 — 코드가 너무 커짐
예를 들어 넷플릭스 같은 서비스라고 생각해봅시다
로그인
영상추천
영상스트리밍
영상평가
댓글
프로필관리
구독결제
이 모든 기능이 하나의 서버에 들어있다면?
코드가 수십만 줄이 된다. 그러면 어떤 일이 생기냐면
한 기능 수정하면 → 전체 서버 다시 배포하는 과정을 거쳐야 한다.
문제 2 — 장애 전파
예를 들어 개발 실습을 할 때를 생각해 보면,
어느 한 파일에서만 에러가 발생해도 서버가 아예 실행되지 않는 경우가 있다.
즉, 하나의 특정 기능에서 발생한 오류지만 애플리케이션이 하나로 묶여 있기 때문에
한 파일 오류 → 애플리케이션 실행 실패 → 서버 실행 불가
결과적으로 전체 서비스가 동작하지 않게 된다.
모놀리식 구조에서도 이와 비슷하게 하나의 기능에서 발생한 문제가 전체 서비스 장애로 이어질 수 있다.
그래서 나온 것이
Microservice Architecture (MSA)
마이크로서비스의 핵심 원칙
1. 단일 책임 원칙 (Single Responsibility)
하나의 서비스는 하나의 기능만 책임진다.
예시)
User Service → 회원 관리
Product Service → 상품 관리
Order Service → 주문 관리
이렇게 나눈다.
2. 느슨한 결합 (Loose Coupling)
각 마이크로서비스들끼리는 최대한 독립적으로 동작해야 한다.
예를 들어 Order Service는 User Service 내부 동작 코드를 알 필요가 없다. 단지 userId만 알면 된다.
모놀리식(강한 결합)
모놀리식에서는 Order 테이블과 User 테이블이 같은 DB 안에 있다. DB를 직접 들여다본다 (강한 결합) 그래서 SQL을 짤 때 그냥 JOIN을 걸어버리면 된다
예시) SELECT * FROM orders o JOIN users u ON o.user_id = u.id
문제 발생: 어느 날 유저 팀에서 "우리 유저 ID 시스템을 숫자(Long)에서 UUID(문자열)로 바꿀게요" 하고 DB 스키마를 변경하면?
결과: 주문 조회 쿼리에서 JOIN 에러가 터진다. 주문 서비스 코드 곳곳에 박혀있는 SQL이나 ORM 모델을 다 찾아다니며 수정해야 할 수도 있다.
MSA(느슨한 결합)
주문 서비스는 유저가 누군지 상세히 알 필요가 없다. 그냥 누가 주문한 건지.. userId만 알고있으면 된다.
[동기 방식: OpenFeign 활용]
- 사용자가 '치킨'을 주문한다.
- Order Service: 주문 생성함. 근데 이 유저(ID: 123)가 진짜 있는 애인지 확인 필요. 응답올때까지 기다림
- Order Service: (Feign 사용) userService.exists(123) 호출.
- User Service: 해당 유저 활동중인 유저 맞다고 order service에게 응답해줌
- Order Service: 주문 완료 처리.
- 비동기방식도 궁금하다면
- Order Service: "주문 완료! (메시지: userId: 123이 2만원 결제함을 카프카에 던짐)"
- User Service: 카프카를 지켜보고 있다가 메시지를 줍는다. "오, 123번 유저가 결제했네? 포인트 200원 적립해줘야지."
- Order Service: 포인트가 적립됐는지 말았는지 신경 안 쓰고 다음 주문 받으러 간다.
- [비동기 방식: Kafka 활용]
3. 높은 응집도 (High Cohesion)
하나의 서비스 안의 코드들은 서로 관련된 기능이어야 한다.
예를 들어 User Service안에는
회원가입 로그인 프로필 수정
이런 것만 있어야 한다. 여기에 갑자기 상품결제 이런 게 들어있으면 안 된다.
왜 강한 응집력이 중요할까?
만약 하나의 서비스가 여러 기능을 담당하면
User + Order + Payment
이렇게 되면 결국 다시 모놀리식이 된다.
그러면 서비스 분리가 의미 없어진다.
즉 MSA로 쪼갤 때 경계를 잘못 나누면 결국 모놀리식이랑 다를 게 없어진다
마이크로서비스 안에서도 관련 기능끼리만 묶어라 = 높은 응집도를 유지해라!
3. 도메인이란?
여기서 중요한 개념이 도메인(domain) 이다.
도메인은 쉽게 말하면
비즈니스 영역이다.
예를 들어 쇼핑몰에서는 이런 도메인이 있다.
사용자 도메인
상품 도메인
주문 도메인
결제 도메인
넷플릭스라면
사용자 도메인
영상 도메인
추천 도메인
구독 도메인
MSA 설계는 보통 2단계로 생각한다.
1단계 : Domain (비즈니스 영역 나누기)
2단계 : Service (기능 단위로 나누기)
예를 들어 쇼핑몰이면
1. 도메인
User Domain //유저 관리
Product Domain //상품관리
Order Domain //주문 관리
Payment Domain //결제 관리
여기까지는 비즈니스 영역이다.
그래서 보통 강의에서는
User Service
Product Service
Order Service
이런식으로 도메인 = 서비스처럼 단순화해서 설명한다.
하지만 실제 MSA에서는 도메인 안에 여러 마이크로서비스가 있다.
User Domain
2개의 마이크로 서비스가 존재하도록 설계할 수 있다.
auth-service → 로그인 / JWT
user-service → 프로필 CRUD
이해해야 하는 건 서비스 개수가 아니라 왜 나눴는가이다.
Auth와 User의 담당 기능?
- Auth는 인증 로직만 담당한다소셜 로그인: 구글, 카카오 로그인 시 해당 플랫폼의 Access Token을 검증하고 우리 서비스용 토큰으로 바꿔줌.권한 부여(Authorization): "이 유저는 USER인가 ADMIN인가?" 와 같은 역할 정보를 토큰에 심어줌.
- 검증(Validation): 이 토큰이 변조가 됐는지 안 됐는지, 만료가 됐는지 안 됐는지 확인.
- 토큰 관리: JWT 발급, 리프레시 토큰 저장 및 갱신.
- User는 프로필 관리만 담당한다
- 예를들어
- 개인정보: 이름, 이메일, 전화번호, 생년월일 수정.
- 상태 관리: 휴면 계정 여부, 탈퇴 처리, 회원 등급(VIP, GOLD).
- 부가 정보: 배송지 목록, 프로필 이미지 경로, 선호 카테고리 설정. 등등등
- 예를들어
- 회원가입 그 이후의 사용자의 상태와 정보를 관리한다.
왜 분리하는지?
→ 프로필 수정은 자주 일어나지 않지만, '인증'은 모든 API 호출마다 일어난다. 인증 로직이 복잡해지거나 트래픽이 몰려도 프로필 관리 로직은 영향을 받지 않아야 한다.
인증 로직은 여러 서비스에서 공통으로 사용될 수 있기 때문에 독립 서비스로 분리했다.
즉, 한 개의 도메인 안에 여러 개의 마이크로 서비스가 포함되어있는 구조이다.
4. 도메인 안에서 마이크로서비스 나누기
예를 들어 넷플릭스의 Video Domain을 보자.
이 도메인 안에서도 여러 마이크로서비스가 있다. 일단 3개의 마이크로 서비스가 있다고 가정하자. 실제로 어떻게 구성되어 있는지는 저도 잘 모른다
- Video Storage Service - 영상 저장
- Video Streaming Service - 영상을 사용자에게 보여줌
- Video History Service - 시청기록을 저장. 예를들면 사용자가 '더 글로리'를 15분 20초까지 봤다는 것을 기록
1. Video Storage Service (영상 저장)
- 역할: 영상 원본 파일의 물리적 관리. 영상파일을 저장하고있고 요청받으면 반환한다
- 데이터 관리:
- Cloud Storage (S3 등): 실제 기가바이트(GB) 단위의 고용량 영상 파일 보관한다(이건 DBMS가 아님..)
- DB에 영상을 직접 넣으면 DB가 터지기 때문에, "정보는 DB에, 실물은 스토리지에" 분리해서 관리한다
2. Video Streaming Service (영상을 사용자에게 전달)
- 역할: 사용자의 요청을 받고, 영상을 끊김 없이 전송(Streaming).
- 데이터 관리: 자체적인 비즈니스 로직만 가짐. 영상 실물은 Storage Service에 요청해서 가져옴.
3. Video History Service (시청 기록 담당)
- 역할: 유저별로 어떤 영상을 어디까지 봤는지(offset) 기록 및 조회.
- 데이터 관리: userId, videoId, lastWatchedTime 같은 실시간 로그성 데이터 저장.
요청 흐름
시청기록 서비스가 없다 치고 스트리밍 서비스랑 스토리지 서비스만 통신할때
사용자 브라우저(url에 접속) ex) http://netflex/video
↓
Video Streaming Service
(사용자 요청 받았음, 이 마이크로서비스 안엔 비디오가 없기 때문에 실제 영상 파일을 저장하고 있는 마이크로서비스인 Video Storage Service에게 영상 달라고 요청 보냄)
↓
Video Storage Service
(Video Streaming Service에게 요청받은 비디오를 찾아서 Video Streaming Service에게 반환해줌)
↓
Video Streaming Service
(반환받은 비디오를 사용자 브라우저에 내보냄)
↓
사용자 브라우저
즉 스트리밍 서비스 → 스토리지 서비스로 요청을 전달한다.
스트리밍-히스토리-스토리지 간 통신
[Phase 1: 영상 재생 시작]
사용자 브라우저: http://netflix/video/play?id=더글로리 접속
Streaming Service: 요청 수신.
Streaming Service → History Service (동기 호출 - Feign): "이 유저가 이 영상을 어디까지 봤지?"
History Service: "15분 20초까지 봤어." (반환)
Streaming Service → Storage Service (동기 호출 - Feign): "더 글로리 영상 파일, 15분 20초 지점부터 데이터 좀 줘."
Storage Service: DB에서 경로 찾고 S3에서 해당 구간 데이터 추출해서 Streaming Service로 전달.
Streaming Service: 사용자 브라우저로 영상 스트리밍 시작.
[Phase 2: 영상 시청 중 및 종료]
사용자 브라우저: 5분 동안 영상을 더 봄.
Streaming Service: 사용자가 영상을 끄면, 실시간 시청 정보를 수집.
Streaming Service → History Service (비동기 호출 - Kafka): "이 유저 이제 20분 20초까지 봤으니까 기록 업데이트해 둬!"
왜 카프카(비동기)인가요?
-> 기록 업데이트가 0.1초 늦어진다고 영상이 안 나오는 건 아니니까 스트리밍 서비스는 메시지만 던지고 바로 다음 일을 하러 간다.
History Service: 카프카에서 메시지를 읽어 DB에 저장.
5. 서비스 간 통신이 필요한 이유
마이크로서비스는 기능을 쪼개놓았기 때문에, 하나의 비즈니스 로직(예: 주문)을 완성하려면 여러 서비스가 서로 협력해야 한다. 이때 가장 중요한 것이 통신 방식의 선택이다.
동기 통신 (Synchronous): 지금 당장 답장해야함
동기 통신은 요청을 보낸 쪽이 응답이 올 때까지 아무것도 하지 않고 기다리는 방식이다. 우리가 흔히 쓰는 HTTP REST API 호출이 대표적이다.
- 특징: 설계가 직관적이고, 결과값을 즉시 받아서 다음 로직을 처리할 수 있다.
- 사용 도구: OpenFeign(강의에서 사용한 것), gRPC, RestTemplate 등. (특히 Spring Cloud에서는 인터페이스만으로 HTTP 호출이 가능한 OpenFeign을 주로 사용한다.)
- 언제 쓰는가?
- 결과가 즉시 확인되어야 할 때: "재고가 있는가?", "결제가 성공했는가?", "비밀번호가 맞는가?"처럼 다음 단계로 넘어가기 위해 반드시 '예/아니오' 혹은 데이터가 필요한 경우에 사용한다.
주의할 점 (장애 전파): 동기 통신은 호출한 서비스가 응답을 줄 때까지 연결을 유지한다. 만약 결제 서비스가 느려지면 주문 서비스도 같이 느려지고, 결국 전체 시스템이 마비될 수 있다. 이를 '강한 결합'으로 인한 장애 전파라고 한다.
비동기 통신 (Asynchronous): 일단 메시지 던질게 확인 부탁 나는 마저 할일하러 갈게
비동기 통신은 요청을 보내놓고 응답을 기다리지 않는다. 대신 메시지 큐(Message Queue)라는 중간 전달자에게 메시지를 던져두고 자기 할 일을 하러 간다.
- 특징: 서비스 간의 의존성이 낮다(느슨한 결합). 메시지를 받는 쪽이 잠시 죽어있어도 메시지 큐에 데이터가 보관되므로 나중에 처리할 수 있다.
- 사용 도구: Kafka, RabbitMQ, AWS SQS 등.
- Kafka: 대용량 로그나 실시간 데이터 처리에 강점이 있다. 엄밀히는 메시지 브로커가 아니라 분산 이벤트 스트리밍 플랫폼으로, 메시지를 소비한 후에도 일정 기간 보관(영속성)하고 필요하면 재처리할 수 있다는 점에서 RabbitMQ와 차이가 있다.
- RabbitMQ: 메시지의 정확한 전달과 라우팅이 중요할 때 사용한다.
- 언제 쓰는가?
- 즉시 응답이 필요 없을 때: "주문 완료 이메일 발송", "추천 알고리즘 업데이트", "로그 기록" 등은 주문 성공 화면을 띄우는 것보다 1~2초 늦게 처리되어도 큰 문제가 없다.
동기 통신
예를 들어 쇼핑몰에서 주문을 한다고 했을 때
사용자가 결제하기 버튼 클릭
Order Service → Product Service // 주문 서비스가 상품 서비스에게 재고 있는지 체크요청 보낸다. 응답이 올 때까지 기다리고 있다.
Product Service는 "재고 있음" 이런 응답을 보내야 다음 단계 진행할 수 있다
그래서 흐름은
사용자
↓
Order Service
↓
Product Service (재고 확인)
↓
Payment Service (결제)
↓
주문 완료
- Order Service → Product Service: "재고 있어?" (응답 대기)
- Order Service → Payment Service: "2만원 결제해줘." (응답 대기)
- 결과: "결제 성공! 주문 번호 123번입니다." 화면 표시.
비동기 통신
주문은 이미 성공했다. 이제 뒤에서 조용히 처리해도 되는 일들이다.
하지만 동시에 해야 하는 것들
- Order Service: "123번 주문 성공함!" 메시지를 큐에 던짐.
- Email Service: (큐에서 메시지 읽음) "ㅇㅇ님, 치킨 주문 완료되었습니다." 메일 발송.
- Recommendation Service: (큐에서 메시지 읽음) "이 유저는 치킨을 좋아하네? 다음에 콜라 추천해줘야지." 데이터 업데이트.
6. 인프라와 배포: 쪼개진 서버들을 어떻게 운영할까?
마이크로서비스는 서버가 여러 개라서 로컬 테스트가 어렵다. 로컬에서 테스트하려면 터미널을 마이크로서비스의 개수만큼 여러개 띄워서 server start를 해야한다. 그래서 일단 각 서버들을 도커 컨테이너로 띄워서 관리하기 위해 도커를 사용해서 패킹한다.
로컬 테스트와 Docker
각 서비스를 도커 컨테이너로 만든다. docker-compose를 사용하면 12개의 서비스와 데이터베이스, 메시지 큐(Kafka)까지 명령어 한 줄로 내 컴퓨터에서 똑같이 띄워볼 수 있다. 사실 도커 컴포즈는 여러 도커 컨테이너를 한 파일에서 관리하고 한개의 명령어로 도커 컨테이너를 띄우기 위해서 존재한다. 도커 컴포즈가 없다면 각 서비스마다 또 터미널을 열어서 도커파일을 실행시켜줘야한다.
Kubernetes (K8s)
컨테이너가 100개, 1000개로 늘어나면 사람이 관리할 수 없다. 이때 쿠버네티스를 쓴다. 쿠버네티스는 다음과 같은 기능이 있다.
- 자동 복구(Self-healing): 특정 서비스 컨테이너가 죽으면 쿠버네티스가 알아서 다시 살려낸다.
- 오토 스케일링: 갑자기 트래픽이 몰리면 서버 대수를 자동으로 늘려준다.
7. 인프라의 표준: Docker와 Containerization
마이크로서비스 아키텍처에서 각 서비스는 독립적인 실행 환경을 보장받아야 한다. 이를 가능하게 하는 것이 바로 도커(Docker)이다.
도커 이미지와 컨테이너의 관계
도커의 동작 원리는 객체지향 프로그래밍의 Class와 Instance 관계와 매우 흡사하다.
- Docker File: 도커 이미지를 만들기 위한 설계도이다. 베이스 OS, 런타임(Java, Python), 종속성 설치 명령어가 적혀 있다.
- Docker Image (Class): 도커 파일을 빌드한 결과물이다. 실행 가능한 패키지 상태이며, 그 자체로는 정적인 파일이다. 마치 바탕화면의 실행 아이콘과 같다.
- Docker Container (Instance): 도커 이미지를 실행(Run)시킨 상태이다. 아이콘을 더블클릭하여 프로세스가 메모리에 올라가는 것과 같다. 컨테이너가 생성되는 순간, 이미지에 정의된 종속성들이 격리된 환경에서 설치되고 애플리케이션 실행 환경이 완벽하게 구성된다.
왜 도커 컨테이너로 패킹하는가?
우리가 보통 GitHub에서 프로젝트를 클론 받으면 보통 다음과 같은 과정을 거친다
- 환경에 맞는 언어/런타임 설치 (Java 17, Node 18 등)
- 의존성 라이브러리 설치 (npm install, gradle build)
- 로컬 DB 설정 및 환경 변수 세팅
하지만 심사위원이나 다른 팀원의 컴퓨터에 이 환경이 갖춰져 있지 않다면 실행조차 되지 않는다. 도커 컨테이너는 이 모든 과정을 패키징하여 격리된 환경에서 실행하기 때문에, 로컬 시스템에 무엇이 깔려 있든 상관없이 "어디서나 동일하게 동작함"을 보장한다.
이미지 관리와 클라우드 배포
코드를 GitHub에 올려 공유하듯이, 빌드된 도커 이미지는 Docker Hub라는 중앙 저장소에 푸시하여 관리한다. 이렇게 저장된 이미지는 어디서든 다운로드(Pull)하여 실행할 수 있다.
- 로컬 실행: 내 컴퓨터에서 개발 및 테스트 용도로 컨테이너를 띄운다.
- 클라우드 배포: AWS EC2 같은 가상 서버에 도커를 설치하고 컨테이너를 띄우거나, 클라우드 전용 컨테이너 서비스에 올려 실제 서비스를 운영한다.
8. 컨테이너 오케스트레이션: Kubernetes (K8s)
서비스가 12개로 쪼개지면 관리해야 할 컨테이너도 12개 이상이 된다. 만약 트래픽이 몰려 특정 서비스의 컨테이너를 10개로 늘려야 한다면?(→ 똑같은 컨테이너가 10개로 늘어나는 거임. 쌍둥이 컨테이너임. 스케일아웃) 혹은 컨테이너 하나가 오류로 죽어버린다면?
사람이 일일이 접속해서 docker run을 다시 할 수는 없다. 그래서 수많은 컨테이너를 지휘하고 관리하는 오케스트레이션(Orchestration) 도구가 등장한 것이다.
왜 쿠버네티스인가?
컨테이너 관리 도구에는 Docker Swarm, Nomad 등 여러 가지가 있지만, 현재는 쿠버네티스(Kubernetes)가 업계 표준으로 자리 잡았다. 넷플릭스처럼 수만 개의 컨테이너를 운영하는 기업들도 쿠버네티스를 통해 시스템을 관리한다.
- 자동 배포 및 복구: 컨테이너가 죽으면 쿠버네티스가 이를 감지하고 즉시 새 컨테이너를 띄워 서비스 가용성을 유지한다.
- 오토 스케일링: 부하가 심해지면 컨테이너 개수를 자동으로 늘리고, 한가해지면 다시 줄여 비용을 절감한다.
- 트래픽 로드 밸런싱: 수많은 컨테이너로 들어오는 요청을 골고루 분산시켜 준다.
9. CI/CD: 코드에서 배포까지의 자동화 파이프라인
결국 MSA의 완성은 자동화에 있다. 개발자가 코드를 수정하면 사용자가 바로 쓸 수 있을 때까지의 과정은 다음과 같다.
- Code Push: 개발자가 코드를 GitHub에 푸시한다.
- CI (Jenkins/GitHub Actions): 코드를 감지하여 테스트를 수행하고, 성공하면 Docker Image를 빌드한다.
- Registry Push: 생성된 이미지를 Docker Hub(또는 ECR 등 이미지 레지스트리)에 업로드한다.
- CD (ArgoCD 등): 새 이미지를 감지해 쿠버네티스 클러스터에 자동 배포(Rolling Update)한다.
※ 쿠버네티스(AWS EKS)는 CD 도구가 실제로 배포를 수행하는 실행 플랫폼이다.
참고 (GitOps 방식): GitHub Actions로 이미지를 빌드한 뒤, 쿠버네티스 매니페스트(배포 설정 파일)가 담긴 별도 레포지토리를 업데이트하면 ArgoCD가 이를 감지하여 EKS 클러스터에 자동 동기화하는 방식을 GitOps라고 한다. 코드 변경과 배포 설정을 Git으로 일원화해서 관리할 수 있다는 장점이 있다.
10. 기술적 자유도: "서비스마다 가장 잘 맞는 옷을 입히다"
모놀리식 구조에서는 프로젝트 전체가 하나의 언어(예: Java/Spring)와 하나의 DB(예: MySQL)에 묶여 있었다. 하지만 MSA는 서비스가 컨테이너 단위로 완전히 격리되어 있기 때문에 각 마이크로서비스마다 '최적의 기술 스택'을 다르게 가져갈 수 있다.
1) 폴리글랏 프로그래밍 (Polyglot Programming)
모든 기능을 스프링으로만 만들 필요가 없다. 서비스의 특성에 따라 언어를 골라 쓸 수 있다. 예를들어)
- Auth Service: 보안과 속도가 중요하니까 Go 언어로 가볍고 빠르게 구축.
- Recommendation Service: 데이터 분석과 AI 라이브러리가 풍부한 Python(FastAPI)으로 구축.
- Order Service: 복잡한 비즈니스 로직과 트랜잭션 관리가 검증된 Java(Spring Boot)로 구축.
2) 서비스마다 독립된 DB (Database per Service)
MSA의 철칙 중 하나는 "자기 DB는 자기만 본다"이다. 심지어 DB의 종류조차 서비스 특성에 맞게 다를 수 있다.
- User Service: 관계가 명확한 사용자 정보는 MySQL(RDBMS)에 저장.
- History Service: 초당 수만 건의 시청 로그가 쌓이므로 쓰기 속도가 엄청나게 빠른 Cassandra나 MongoDB(NoSQL)를 사용.
- Ranking Service: 실시간 순위를 빠르게 계산해야 하므로 메모리 기반의 Redis를 메인 DB로 사용.
왜 DB를 공유하면 안 되나요? 만약 12개 서비스가 하나의 MySQL을 공유하면, 한 서비스의 쿼리가 DB 부하를 일으킬 때 나머지 11개 서비스가 동시에 멈춘다. 또한, 한 팀에서 테이블 스키마를 바꾸면 모든 팀의 코드를 수정해야 하는 '강한 결합' 상태가 되어 MSA의 의미가 사라진다.
※ 참고: 실습 환경에서는 리소스 제약상 하나의 MySQL 컨테이너 안에서 서비스별로 스키마(Schema)를 분리하는 방식으로 타협하기도 한다. 이는 완전한 DB per Service 원칙의 구현은 아니지만, 서비스 간 데이터 격리 개념을 실습하기 위한 현실적인 접근이다.
3) 멀티 클라우드 (Multi-Cloud Strategy)
심지어 서비스가 돌아가는 인프라(Cloud)조차 섞어서 쓸 수 있다.
- Streaming Service: 전용 네트워크 인프라가 잘 되어 있는 AWS에 배치.
- Data Analytics Service: 빅데이터 분석 도구(BigQuery 등)가 강력한 GCP에 배치.
- Admin Service: 사내 망과의 연결이 편한 Azure나 NHN Cloud에 배치.
이렇게 구성해도 각 서비스는 표준화된 통신 규약(HTTP/REST, gRPC)과 쿠버네티스라는 공통 플랫폼 위에서 돌아가기 때문에, 서로 어느 클라우드에 있는지 상관없이 대화할 수 있다.
마무리하며
마이크로서비스 아키텍처는 단순히 '서버를 쪼개는 것'이 아니다.
- 1 서비스 = 1 책임을 지키며,
- 상황에 맞게 동기(Feign)와 비동기(Kafka) 통신을 섞어 쓰고,
- Docker와 Kubernetes를 통해 효율적으로 운영하는 종합적인 시스템 설계 방식이다.
이 문서는 팀원 온보딩용으로 작성했기 때문에 일부 개념을 의도적으로 단순화했다. 실제 프로덕션 환경에서는 서비스 메시(Istio), 분산 트레이싱(Jaeger), API Gateway 보안 등 더 많은 레이어가 존재한다. 다음 글에서는 실제 프로젝트에서 MSA를 어떻게 적용했는지 다뤄볼 예정이다. 아마도....된다면...