API 게이트웨이 패턴 구현
API 게이트웨이는 마이크로서비스 아키텍처에서 중요한 역할을 하는 컴포넌트입니다.
클라이언트와 백엔드 서비스 사이의 단일 진입점 역할을 하며, 라우팅, 프로토콜 변환, 요청 집계, 인증 등 다양한 기능을 제공합니다.
NestJS를 사용하여 효과적인 API 게이트웨이를 구현할 수 있습니다.
API 게이트웨이의 개념과 역할
API 게이트웨이는 다음과 같은 주요 역할을 수행합니다.
- 라우팅 및 프록시
- 인증 및 권한 부여
- 요청/응답 변환
- 로드 밸런싱
- 서킷 브레이커
- 로깅 및 모니터링
NestJS로 API 게이트웨이 구현하기
- 의존성 설치
npm install @nestjs/microservices @nestjs/axios
- 게이트웨이 모듈 설정
import { Module } from '@nestjs/common';
import { ClientsModule, Transport } from '@nestjs/microservices';
import { ApiGatewayController } from './api-gateway.controller';
@Module({
imports: [
ClientsModule.register([
{ name: 'USER_SERVICE', transport: Transport.TCP },
{ name: 'PRODUCT_SERVICE', transport: Transport.TCP },
]),
],
controllers: [ApiGatewayController],
})
export class ApiGatewayModule {}
- 라우팅 및 프록시 구현
import { Controller, All, Param, Req, Inject } from '@nestjs/common';
import { ClientProxy } from '@nestjs/microservices';
import { Request } from 'express';
@Controller('api')
export class ApiGatewayController {
constructor(
@Inject('USER_SERVICE') private userService: ClientProxy,
@Inject('PRODUCT_SERVICE') private productService: ClientProxy,
) {}
@All('users/:path')
handleUserRequests(@Param('path') path: string, @Req() req: Request) {
const pattern = { cmd: path };
return this.userService.send(pattern, req.body);
}
@All('products/:path')
handleProductRequests(@Param('path') path: string, @Req() req: Request) {
const pattern = { cmd: path };
return this.productService.send(pattern, req.body);
}
}
인증 및 권한 부여
API 게이트웨이 수준에서 인증을 구현하면 백엔드 서비스의 보안을 일관되게 관리할 수 있습니다.
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
import { JwtService } from '@nestjs/jwt';
@Injectable()
export class AuthMiddleware implements NestMiddleware {
constructor(private jwtService: JwtService) {}
use(req: Request, res: Response, next: NextFunction) {
const token = req.headers['authorization']?.split(' ')[1];
if (token) {
try {
const decoded = this.jwtService.verify(token);
req['user'] = decoded;
} catch (error) {
// 토큰 검증 실패
}
}
next();
}
}
요청 집계 및 응답 변환
여러 마이크로서비스의 데이터를 조합하여 클라이언트에 응답할 수 있습니다.
@Get('user-with-orders/:userId')
async getUserWithOrders(@Param('userId') userId: string) {
const [user, orders] = await Promise.all([
this.userService.send({ cmd: 'get-user' }, { userId }).toPromise(),
this.orderService.send({ cmd: 'get-user-orders' }, { userId }).toPromise(),
]);
return { ...user, orders };
}
로드 밸런싱 및 서킷 브레이커
NestJS의 @nestjs/microservices
모듈은 기본적인 로드 밸런싱을 제공합니다.
서킷 브레이커는 @nestjs/circuit-breaker
패키지를 사용하여 구현할 수 있습니다.
import { CircuitBreaker } from '@nestjs/circuit-breaker';
@CircuitBreaker({ threshold: 5, duration: 10000 })
@Get('products')
getProducts() {
return this.productService.send({ cmd: 'get-products' }, {});
}
API 버전 관리
URL 기반 버전 관리 예시
@Controller('api/v1')
export class ApiV1Controller {
// V1 API 엔드포인트
}
@Controller('api/v2')
export class ApiV2Controller {
// V2 API 엔드포인트
}
모니터링, 로깅, 분산 추적
Prometheus와 OpenTelemetry를 사용한 모니터링 및 추적 구현
import { PrometheusController } from '@willsoto/nestjs-prometheus';
import { OpenTelemetryModule } from '@metinseylan/nestjs-opentelemetry';
@Module({
imports: [
PrometheusModule.register(),
OpenTelemetryModule.forRoot(),
],
controllers: [PrometheusController],
})
export class ApiGatewayModule {}
성능 최적화 및 캐싱
Redis를 사용한 캐싱 구현
import { CacheModule, CacheInterceptor } from '@nestjs/cache-manager';
import { redisStore } from 'cache-manager-redis-store';
@Module({
imports: [
CacheModule.register({
store: redisStore,
host: 'localhost',
port: 6379,
}),
],
})
export class ApiGatewayModule {}
@UseInterceptors(CacheInterceptor)
@Get('products')
getProducts() {
// 이 응답은 캐시됩니다
}
Best Practices 및 주의사항
- 보안 강화 : HTTPS 사용, 적절한 CORS 설정
- 에러 처리 : 일관된 에러 응답 포맷 사용
- 속도 제한 : 과도한 요청으로부터 서비스 보호
- 문서화 : Swagger 등을 사용한 API 문서 자동화
- 테스트 : 단위 테스트 및 통합 테스트 구현
- 로깅 : 구조화된 로깅으로 문제 해결 용이성 확보
- 확장성 : 컨테이너화 및 오케스트레이션 도구 활용
- 모니터링 : 핵심 메트릭 정의 및 지속적 관찰
NestJS를 사용한 API 게이트웨이 구현은 마이크로서비스 아키텍처의 복잡성을 효과적으로 관리할 수 있게 해줍니다.
라우팅, 인증, 요청 집계 등의 기능을 중앙화하여 백엔드 서비스의 복잡성을 줄이고, 클라이언트와의 상호작용을 단순화할 수 있습니다.