API Gateway와 서비스 간 통신
학습 목표
- API Gateway의 역할과 필요성
- 동기 vs 비동기 통신 선택 기준
- Service Mesh와 사이드카 패턴
- 실전 통신 패턴 구현
API Gateway란?
클라이언트와 백엔드 마이크로서비스 사이의 **단일 진입점(Single Entry Point)**입니다.
문제 상황 (Gateway 없이)
┌─────────┐
│ Mobile │──┐
│ App │ │
└─────────┘ │ ┌──────────┐
├───→│ Auth │
┌─────────┐ │ │ Service │
│ Web │──┤ └──────────┘
│ App │ │
└─────────┘ │ ┌──────────┐
├───→│ Product │
│ │ Service │
│ └──────────┘
│
│ ┌──────────┐
└───→│ Order │
│ Service │
└──────────┘
문제점:
- 클라이언트가 여러 서비스 호출 필요
- 각 서비스마다 인증 처리
- 서비스 주소 변경 시 클라이언트도 변경
- CORS 정책 관리 어려움
해결책: API Gateway
graph TD
Mobile["Mobile App"]
Web["Web App"]
Gateway["API Gateway<br/>라우팅, 인증, Rate Limiting"]
Auth["Auth Service"]
Product["Product Service"]
Order["Order Service"]
Mobile --> Gateway
Web --> Gateway
Gateway --> Auth
Gateway --> Product
Gateway --> Order
style Mobile fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px
style Web fill:#e3f2fd,stroke:#1565c0,stroke-width:2px
style Gateway fill:#fff3e0,stroke:#ef6c00,stroke-width:3px
style Auth fill:#fce4ec,stroke:#c2185b,stroke-width:2px
style Product fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px
style Order fill:#e0f2f1,stroke:#00695c,stroke-width:2px
API Gateway의 주요 기능
1. 라우팅
// Kong Gateway 설정 예제
{
"routes": [
{
"path": "/api/products",
"service": "product-service"
},
{
"path": "/api/orders",
"service": "order-service"
}
]
}
2. 인증/인가
// Gateway에서 JWT 검증
async function authenticate(request) {
const token = request.headers.authorization?.split(' ')[1];
if (!token) {
throw new UnauthorizedError();
}
const decoded = jwt.verify(token, JWT_SECRET);
// 사용자 정보를 헤더에 추가
request.headers['X-User-Id'] = decoded.userId;
request.headers['X-User-Role'] = decoded.role;
return next();
}
3. Rate Limiting
// 분당 100 요청 제한
{
"plugins": [{
"name": "rate-limiting",
"config": {
"minute": 100,
"policy": "local"
}
}]
}
4. 응답 집계 (Response Aggregation)
// 여러 서비스 호출 결과 합치기
async function getProductDetails(productId) {
const [product, reviews, inventory] = await Promise.all([
productService.getProduct(productId),
reviewService.getReviews(productId),
inventoryService.getStock(productId),
]);
return {
...product,
reviews,
stock: inventory.quantity,
};
}
동기 vs 비동기 통신
동기 통신 (REST/gRPC)
사용 시기:
- 즉시 응답 필요
- 간단한 CRUD 작업
- 클라이언트가 결과를 대기해야 함
예제: REST API
// Order Service가 Product Service 호출
async function createOrder(customerId, productId, quantity) {
// 1. 상품 정보 조회 (동기)
const product = await fetch(
`http://product-service/api/products/${productId}`
).then(res => res.json());
if (product.stock < quantity) {
throw new InsufficientStockError();
}
// 2. 주문 생성
const order = await Order.create({
customerId,
productId,
quantity,
totalAmount: product.price * quantity,
});
return order;
}
장점:
- 구현 간단
- 즉시 응답
- 디버깅 쉬움
단점:
- 서비스 간 강한 결합
- 장애 전파
- 성능 병목
비동기 통신 (메시지 큐)
사용 시기:
- 즉시 응답 불필요
- 장시간 작업
- 서비스 간 느슨한 결합 필요
- 이벤트 기반 아키텍처
예제: 이벤트 기반
// 주문 생성 시 이벤트 발행
async function createOrder(orderData) {
const order = await Order.create(orderData);
// 이벤트 발행 (비동기)
await eventBus.publish('order.created', {
orderId: order.id,
customerId: order.customerId,
items: order.items,
totalAmount: order.totalAmount,
});
return order;
}
// Payment Service가 이벤트 구독
eventBus.subscribe('order.created', async (event) => {
await processPayment(event.orderId, event.totalAmount);
// 결제 완료 이벤트 발행
await eventBus.publish('payment.completed', {
orderId: event.orderId,
});
});
// Inventory Service가 이벤트 구독
eventBus.subscribe('order.created', async (event) => {
await decreaseStock(event.items);
});
// Notification Service가 이벤트 구독
eventBus.subscribe('order.created', async (event) => {
await sendEmailToCustomer(event.customerId, event.orderId);
});
장점:
- 서비스 간 느슨한 결합
- 확장성 우수
- 장애 격리
단점:
- 복잡도 증가
- 디버깅 어려움
- 최종 일관성
통신 패턴 Best Practice
1. Saga 패턴 (분산 트랜잭션)
주문 → 결제 → 재고 차감이 하나의 트랜잭션처럼 동작
// Choreography 방식 (이벤트 기반)
class OrderSaga {
async execute(orderData) {
// 1. 주문 생성
const order = await createOrder(orderData);
emit('order.created', order);
// 2. 결제 (Payment Service가 처리)
// 3. 재고 차감 (Inventory Service가 처리)
// 4. 배송 준비 (Delivery Service가 처리)
}
// 보상 트랜잭션 (실패 시)
async compensate(orderId) {
emit('order.cancelled', { orderId });
// 각 서비스가 보상 트랜잭션 실행
}
}
2. Circuit Breaker 패턴
서비스 장애 시 빠른 실패
class CircuitBreaker {
private failureCount = 0;
private state = 'CLOSED'; // CLOSED, OPEN, HALF_OPEN
async call(serviceCall) {
if (this.state === 'OPEN') {
throw new ServiceUnavailableError();
}
try {
const result = await serviceCall();
this.onSuccess();
return result;
} catch (error) {
this.onFailure();
throw error;
}
}
private onFailure() {
this.failureCount++;
if (this.failureCount >= THRESHOLD) {
this.state = 'OPEN';
setTimeout(() => this.state = 'HALF_OPEN', TIMEOUT);
}
}
private onSuccess() {
this.failureCount = 0;
this.state = 'CLOSED';
}
}
3. Retry with Exponential Backoff
async function retryWithBackoff(fn, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
return await fn();
} catch (error) {
if (i === maxRetries - 1) throw error;
const delay = Math.pow(2, i) * 1000; // 1s, 2s, 4s
await sleep(delay);
}
}
}
// 사용
const product = await retryWithBackoff(() =>
productService.getProduct(productId)
);
Service Mesh
서비스 간 통신을 관리하는 인프라 계층
┌─────────┐ ┌─────────┐
│Service A│◄───────►│Service B│
│ ┌─────┐ │ │ ┌─────┐ │
│ │Proxy│ │ │ │Proxy│ │
│ └──┬──┘ │ │ └──┬──┘ │
└────┼────┘ └────┼────┘
│ │
└──────┬────────────┘
│
┌─────▼─────┐
│ Control │
│ Plane │
│ (Istio) │
└───────────┘
기능:
- 자동 로드 밸런싱
- 서비스 디스커버리
- 트래픽 관리
- 보안 (mTLS)
- 모니터링
다음 강의
다음 강의에서는 배포 전략과 CI/CD 파이프라인을 배웁니다.
핵심 정리
- API Gateway는 마이크로서비스의 단일 진입점
- 동기 통신은 간단하지만 결합도 높음
- 비동기 통신은 복잡하지만 확장성 좋음
- Saga, Circuit Breaker 등 패턴으로 안정성 확보
- Service Mesh로 통신 관리 자동화