[NestJS] Microservices (Kafka, NATS, RabbitMQ, gRPC, TCP)
정의
NestJS Microservices 는 HTTP 대신 message broker (Kafka, RabbitMQ, NATS, Redis) 나 gRPC / TCP 로 통신하는 서비스를 같은 프로그래밍 모델로 작성하는 기능입니다. Controller 대신 Message pattern handler 를 정의하고, ClientProxy 로 다른 서비스에 요청합니다.
지원 Transport
- TCP (기본): 단순 socket
- Redis (pub/sub): 이벤트 유연
- NATS: 가벼움, cloud native
- MQTT: IoT
- RabbitMQ (AMQP): 성숙, queue + routing
- Kafka: 이벤트 스트리밍, 대규모
- gRPC: HTTP/2 + Protocol Buffers, RPC
- Custom: 자체 구현
Microservice 앱 생성
// main.ts
import { NestFactory } from '@nestjs/core';
import { Transport } from '@nestjs/microservices';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.createMicroservice(AppModule, {
transport: Transport.KAFKA,
options: {
client: {
brokers: ['localhost:9092'],
clientId: 'my-service',
},
consumer: {
groupId: 'my-consumer-group',
},
},
});
await app.listen();
}
bootstrap();
Hybrid (HTTP + Microservice)
한 앱이 HTTP 도 서빙:
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.connectMicroservice({
transport: Transport.KAFKA,
options: {...},
});
await app.startAllMicroservices();
await app.listen(3000); // HTTP
}
Message Pattern (Request-Response)
Controller 에서:
import { Controller } from '@nestjs/common';
import { MessagePattern, Payload } from '@nestjs/microservices';
@Controller()
export class UsersController {
@MessagePattern({cmd: 'get_user'})
async getUser(@Payload() data: {id: number}) {
return this.usersService.findOne(data.id);
}
@MessagePattern({cmd: 'create_user'})
async createUser(@Payload() dto: CreateUserDto) {
return this.usersService.create(dto);
}
}
Broker 에서 {cmd: 'get_user'} 패턴 매칭.
Event Pattern (Fire-and-forget)
응답 없음, side effect 만:
@EventPattern('user_created')
async handleUserCreated(@Payload() data: UserCreatedEvent) {
await this.emailService.sendWelcome(data.email);
}
Client (다른 서비스 호출)
Module
import { ClientsModule, Transport } from '@nestjs/microservices';
@Module({
imports: [
ClientsModule.register([
{
name: 'USERS_SERVICE',
transport: Transport.KAFKA,
options: {
client: {brokers: ['localhost:9092']},
consumer: {groupId: 'orders-consumer'},
},
},
]),
],
})
export class OrdersModule {}
사용
import { ClientKafka, ClientProxy } from '@nestjs/microservices';
@Injectable()
export class OrdersService {
constructor(
@Inject('USERS_SERVICE') private usersClient: ClientKafka,
) {}
async onModuleInit() {
// Kafka 는 subscribe 명시적으로
this.usersClient.subscribeToResponseOf('get_user');
await this.usersClient.connect();
}
async getUser(id: number) {
// Request-response (RxJS Observable 반환)
return this.usersClient.send({cmd: 'get_user'}, {id}).toPromise();
}
async notifyUserCreated(user: User) {
// Event (fire-and-forget)
this.usersClient.emit('user_created', user);
}
}
Kafka 심화
Producer 설정
{
transport: Transport.KAFKA,
options: {
client: {
brokers: ['localhost:9092'],
clientId: 'producer-1',
},
producer: {
allowAutoTopicCreation: true,
transactionalId: 'my-txn', // exactly-once
},
consumer: {
groupId: 'my-group',
allowAutoTopicCreation: true,
sessionTimeout: 30000,
heartbeatInterval: 3000,
},
subscribe: {
fromBeginning: false,
},
},
}
커스텀 partition
this.kafka.emit('user_created', {
key: user.id.toString(), // partition key
value: user,
headers: {'x-source': 'nest'},
});
Exactly-once
Producer 트랜잭션 + consumer offset commit 관리. 자세한 것은 Kafka 문서.
RabbitMQ 심화
설정
{
transport: Transport.RMQ,
options: {
urls: ['amqp://localhost:5672'],
queue: 'user_queue',
queueOptions: {
durable: true,
},
noAck: false, // manual ack
prefetchCount: 10, // per-worker
},
}
Manual Ack
@MessagePattern('process_order')
async processOrder(@Payload() data: any, @Ctx() ctx: RmqContext) {
const channel = ctx.getChannelRef();
const originalMsg = ctx.getMessage();
try {
await this.processInternal(data);
channel.ack(originalMsg);
} catch (error) {
channel.nack(originalMsg, false, false); // DLQ 로 (false = requeue X)
}
}
Dead Letter Exchange
queueOptions: {
durable: true,
arguments: {
'x-dead-letter-exchange': 'dlx',
'x-dead-letter-routing-key': 'user_dlq',
'x-message-ttl': 60000,
},
}
gRPC
Proto 파일
// users.proto
syntax = "proto3";
package users;
service UsersService {
rpc GetUser (GetUserRequest) returns (User);
rpc StreamUsers (Empty) returns (stream User);
}
message GetUserRequest {
int32 id = 1;
}
message User {
int32 id = 1;
string email = 2;
string name = 3;
}
message Empty {}
Server
// main.ts
const app = await NestFactory.createMicroservice<MicroserviceOptions>(
AppModule,
{
transport: Transport.GRPC,
options: {
package: 'users',
protoPath: join(__dirname, 'users.proto'),
url: '0.0.0.0:5000',
},
},
);
await app.listen();
Controller
import { GrpcMethod, GrpcStreamMethod } from '@nestjs/microservices';
import { Observable } from 'rxjs';
@Controller()
export class UsersController {
@GrpcMethod('UsersService', 'GetUser')
async getUser(data: {id: number}): Promise<User> {
return this.usersService.findOne(data.id);
}
@GrpcStreamMethod('UsersService', 'StreamUsers')
streamUsers(): Observable<User> {
return this.usersService.stream();
}
}
Client
import { ClientGrpc, GrpcMethod, Transport } from '@nestjs/microservices';
@Injectable()
export class OrdersService implements OnModuleInit {
private usersService: UsersServiceClient;
constructor(@Inject('USERS_PACKAGE') private client: ClientGrpc) {}
onModuleInit() {
this.usersService = this.client.getService<UsersServiceClient>('UsersService');
}
async getUser(id: number) {
return firstValueFrom(this.usersService.getUser({id}));
}
}
Message Serialization
기본 JSON. Custom serializer / deserializer:
class CustomSerializer implements Serializer {
serialize(value: any): any {
return {data: value, timestamp: Date.now()};
}
}
Retry / DLQ
프레임워크 레벨 지원 제한적. Consumer 코드에서:
@MessagePattern('process')
async process(@Payload() data: any, @Ctx() ctx: RmqContext) {
const retryCount = data.retry || 0;
try {
await this.handleMessage(data);
ctx.getChannelRef().ack(ctx.getMessage());
} catch (err) {
if (retryCount < 3) {
// republish with retry count
} else {
// DLQ
ctx.getChannelRef().nack(ctx.getMessage(), false, false);
}
}
}
v11 신규
- Status / Unwrap / On methods: transporter 상태
- NATS queue per handler
- NATS gracefull shutdown
- DI container 로부터 microservice options
Interceptor / Guard / Pipe
같은 개념 적용:
@UseGuards(AuthGuard)
@UseInterceptors(LoggingInterceptor)
@MessagePattern('secure_op')
async secureOp(@Payload() data: any) { ... }
Guard 안:
canActivate(ctx: ExecutionContext): boolean {
if (ctx.getType() === 'rpc') {
const rpcCtx = ctx.switchToRpc();
// ...
}
}
함정
WARNING
Kafka subscribe 를 잊으면 응답 못 받음. subscribeToResponseOf('pattern') + connect().
CAUTION
Message ordering. Kafka 는 partition 안에서만 순서. RabbitMQ 는 queue 안에서 순서.
WARNING
Exactly-once vs at-least-once. 대부분 broker 는 at-least-once. Idempotent handler 필수.
IMPORTANT
Timeout 관리. Request-response 는 client 가 timeout 명시. 응답 안 오면 hang.
CAUTION
Serialization. 큰 payload 는 broker 부담. Reference (S3 URL) 만 전달하는 편이 나은 경우.
관련 위키
- NestJS - 상위 개요
- Controllers - MessagePattern 대상
- Modules - ClientsModule
- Providers - ClientProxy 주입
- Config - broker URL
- Deployment
- Testing
- Exception Filters
- FastAPI Background Tasks - 대비 (Celery/Arq)
이 글의 용어 (9개)
- [FastAPI] Background Tasksfastapi
- 정의 FastAPI 에서 응답 반환 후 실행하는 짧은 작업 은 , 장기/신뢰성 있는 작업 은 외부 task queue (Celery, Arq, RQ) 로 오프로드합니다. 두 카테…
- [Framework] NestJSframeworks
- 정의 NestJS 는 Kamil Myśliwiec 이 2017년 발표한 Node.js 서버측 프레임워크 입니다. TypeScript first, Angular 에서 영감받은 모듈…
- [NestJS] Config (Environment, Validation)frameworks
- 정의 NestJS Config 는 패키지로 환경변수를 관리합니다. 파일 로딩, TypeScript type-safe access, 검증 (Joi/Zod), 여러 환경 (dev/s…
- [NestJS] Controllersframeworks
- 정의 NestJS Controller 는 데코레이터가 붙은 클래스로, HTTP 요청을 라우팅하고 응답을 반환합니다. Method 별로 , , , , 데코레이터를 붙여 URL + …
- [NestJS] Deployment (Docker, Kubernetes, PM2)frameworks
- 정의 NestJS Deployment 는 프로덕션 환경에서 앱을 배포/운영하기 위한 절차입니다. Docker 이미지 빌드, Kubernetes 매니페스트, PM2 프로세스 관리,…
- [NestJS] Exception Filtersframeworks
- 정의 NestJS Exception Filter 는 애플리케이션에서 발생한 예외를 catch 하여 클라이언트에게 응답 형식을 통일합니다. 데코레이터로 어떤 예외를 처리할지 지정합…
- [NestJS] Modulesframeworks
- 정의 NestJS Module 은 데코레이터가 붙은 클래스로, 관련된 controller, provider, import/export 를 하나로 묶는 조직 단위입니다. Angul…
- [NestJS] Providers & Dependency Injectionframeworks
- 정의 NestJS Provider 는 데코레이터가 붙은 클래스 (또는 값/factory) 로, DI container 에 등록되어 다른 클래스에 주입됩니다. Service, Re…
- [NestJS] Testing (Jest, Supertest, e2e)frameworks
- 정의 NestJS Testing 은 Jest (기본) + Supertest 조합으로 unit test 와 e2e test 를 지원합니다. 의 로 module 을 격리된 형태로 만…
💬 댓글