본문 바로가기
개발/Node.js, TypeScript

[NestJS] 마이크로서비스와 하이브리드 애플리케이션 구성하기

by mixxeo(믹서) 2025. 2. 2.

마이크로서비스(Microservice) 아키텍처란?

마이크로서비스 아키텍처는 하나의 애플리케이션을 여러개의 독립적으로 배포 가능한 작은 서비스로 나누고, 이들 사이의 결합을 느슨하게 유지하는 소프트웨어 아키텍처 방식입니다.

기존의 모놀리식(Monolithic) 아키텍처와 비교했을 때, 서비스별 코드의 규모가 작고 개별적으로 배포할 수 있다는 점이 큰 장점입니다. 이러한 특성 덕분에 서비스 확장성과 유지보수에 용이하며, 최근에는 대규모 애플리케이션을 운영하는 많은 조직에서 마이크로서비스 아키텍처를 도입하는 것을 볼 수 있습니다.

이번 글에서는 NestJS에서 마이크로서비스 아키텍처를 구성하는 방법을 알아보고, 각 마이크로서비스에서 API 서버와 내부 서비스간 통신을 함께 운영하는 하이브리드 애플리케이션(Hybrid Application) 방식에 대해 살펴보려고 합니다.

 

 

 


NestJS 마이크로서비스

공식문서에서 NestJS의 마이크로서비스가 다양한 Transport 레이어의 프로토콜을 활용할 수 있는 애플리케이션이라고 소개하고있습니다. 일반적인 모놀리식 애플리케이션은 클라이언트-서버 간의 통신을 HTTP 기반의 요청-응답 방식으로 수행합니다. 반면, 마이크로서비스는 다양한 TCP 프로토콜(TCP, RabbitMQ, Kafka, gRPC 등)을 지원하여 분산 시스템을 운영할 수 있도록 합니다.

 

📌 마이크로서비스 구성하기 - 서버 측(Order Service)

1.  마이크로서비스 생성

`@nestjs/microservices` 패키지를 활용하면 NestJS 애플리케이션을 마이크로서비스 형태로 운영할 수 있습니다.

$ npm i --save @nestjs/microservices

 

`main.ts`의 bootstrap 함수에서 `NestApplication` 인스턴스가 아닌 `NestMicroservice` 인스턴스를 생성합니다.

// order-service
// main.ts

import { NestFactory } from '@nestjs/core';
import { Transport, MicroserviceOptions } from '@nestjs/microservices';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.createMicroservice<MicroserviceOptions>(
    AppModule,
    {
      transport: Transport.TCP,
    },
  );
  await app.listen();
}
bootstrap();

 

2.  메시지 패턴 - 응답 핸들러

NestJS 마이크로서비스는 메시지 기반 통신과 이벤트 기반 통신을 모두 지원하지만, 이 글에서는 메시지 기반 통신만 다루겠습니다.

요청-응답 메시지 패턴은 리시버 서비스가 메시지를 수신했음이 보장되어야할 때 유용합니다.

컨트롤러(Controller) 클래스에서 API 핸들러를 정의하듯이 `@nestjs/microservices` 패키지의 `@MessagePattern` 데코레이터를 사용해 메시지 응답 핸들러를 정의할 수 있습니다.

// order-service
// order.controller.ts

import { Controller } from '@nestjs/common';
import { MessagePattern } from '@nestjs/microservices';

@Controller()
export class OrderController {
	// ...

  @MessagePattern({ cmd: 'order' })
  async getUserOrders(data: GetOrderDto): Promise<OrderResponseDto> {
    // ...
    return orderService.getUserOrders();
  }
}

`order()`라는 메시지 핸들러는 `{ cmd: ‘order’ }` 패턴의 메시지가 수신되었을때 이를 처리합니다. 클라이언트측 서비스에서 전송한 데이터를 `data` 파라미터로 받을 수 있고, 메서드의 반환값이 응답값으로 전달됩니다.

 

 

📌 마이크로서비스 구성하기 - 클라이언트 측(User Service)

1. 다른 마이크로서비스와의 통신 모듈 설정

클라이언트 측 마이크로서비스는 `ClientProxy` 클래스를 활용해 다른 마이크로서비스에 메시지를 전송할 수 있습니다.

 

✅ 기본적인 방식 - `ClientsModule.register()`

`@Module` 데코레이터에서 `ClientsModule` 를 활용하여 통신할 다른 마이크로서비스 트랜스포터를 추가합니다.

`name` 은 injection token역할을 하며, 마이크로서비스와의 통신이 필요한 서비스에서 `ClientProxy` 인스턴스를 주입할 때 사용합니다.

💡 마이크로서비스 트랜스포터(Transporter)란?
- 마이크로서비스간 메시지를 주고받는 전송 계층 (Transport Layer)
- NestJS는 `Transport` enum을 제공함
- TCP 트랜스포터의 경우, 기본 host와 port는 `localhost:3000`으로 설정됨
// user-service
// order-service.module.ts

import { Module } from '@nestjs/common';
import { ClientsModule } from '@nestjs/microservices';

@Module({
  imports: [
    ClientsModule.register([
      { name: 'ORDER_SERVICE', transport: Transport.TCP },
    ]),
  ],
})
export class OrderServiceModule {}

`ClientsModule`은 NestJS의 내장 모듈이므로, `ClientsModule.register()`를 호출하면 NestJS가 자동으로 DI 컨테이너에 `ClientProxy` 인스턴스를 등록해줍니다.

 

 

✅ 동적 설정 - `ClientProxyFactory.create()`

`ConfigService` 와같은 다른 서비스에서 트랜스포터 설정값을 동적으로 가져와야 하는 경우 커스텀 프로바이더(Custom Provider)를 등록할 수 있습니다. `useFactory`를 사용해 `ClientProxy` 인스턴스를 동적으로 생성합니다.

// user-service
// order-service.module.ts

import { Module } from '@nestjs/common';
import { ClientProxyFactory } from '@nestjs/microservices';

import { ConfigService } from '..';

@Module({
  providers: [
    {
      provide: 'ORDER_SERVICE',
      useFactory: (configService: ConfigService) => {
		    const orderServiceOptions = configService.get('orderServiceOptions');
		    
		    return ClientProxyFactory.create({
			    options: {
				    host: orderServiceOptions.host,
				    port: orderServiceOptions.port,
			    },
		    });
      },
      inject: [ConfigService],
    }
  ],
  exports: ['ORDER_SERVICE'],
})
export class OrderServiceModule {}

 

2. 메시지 패턴 - 요청 메시지 전송(핸들러 호출)

모듈에서 `ClientProxy` 인스턴스를 등록했다면, 이제 서비스에서 `ClientProxy`를 주입받아 마이크로서비스에 메시지를 전송할 수 있습니다. `send()` 메서드는 마이크로서비스의 특정 핸들러(`@MessagePattern`)를 호출하는 역할을 합니다.

  • 첫 번째 인자로 `@MessagePattern` 데코레이터에 정의한 메시지 패턴(`{ cmd: ‘order’ }`)을 전달하고
  • 두 번째 인자로 전송할 데이터(`user`)를 전달하면 됩니다.
// user-service
// order.service.ts

import { Inject, Injectable } from '@nestjs/common';
import { ClientProxy } from '@nestjs/microservices';

@Injectable()
export class OrderService {
  constructor(
    @Inject('ORDER_SERVICE') private readonly client: ClientProxy
  ) {}

  getUserOrders(user: User): Observable<Orders> {
    return this.client.send({ cmd: 'order' }, user);
  }
}

이제 `order()` 를 호출하면, `ORDER_SERVICE` 마이크로서비스의 `@MessagePattern({ cmd: ‘order’ })` 핸들러가 실행되고 `Observable`을 응답으로 반환하게 됩니다.

 

 

 


NestJS 하이브리드 애플리케이션

이제 각 마이크로서비스 애플리케이션이 외부에서는 HTTP 기반의 API 서버로 동작하면서, 동시에 마이크로서비스 간에는 TCP 기반의 내부 서버로 통신할 수 있도록 설정하는 방식을 알아보겠습니다.

이를 위해서는 NestJS의 하이브리드 애플리케이션 개념을 활용해야 합니다. 하이브리드 애플리케이션이란, 하나의 애플리케이션이 둘 이상의 소스로부터 요청을 처리할 수 있는 구조를 의미합니다.

  • 예를 들어, HTTP 서버와 마이크로서비스 리스너(TCP, Redis, Kafka 등)를 결합하거나
  • 여러 개의 마이크로서비스 리스너를 하나의 애플리케이션에서 실행할 수도 있습니다.

앞에서 마이크로서비스 애플리케이션을 생성할 때는 `createMicroservice()` 메서드를 사용했습니다. 이 방식은 하나의 독립적인 마이크로서비스 애플리케이션을 생성하는 역할을 합니다. 따라서 여러 개의 서버(HTTP + 마이크로서비스)를 동시에 실행할 수는 없습니다.

 

 

📌 HTTP 서버 + TCP 마이크로서비스 리스너

여러 개의 서버를 하나의 NestJS 애플리케이션에서 실행하려면, `connectMicroservice()` 메서드를 사용해야 합니다. 이를 통해 기존 `NestApplication` 인스턴스에 여러 개의 `NestMicroservice` 인스턴스를 추가할 수 있습니다.

// order-service
// main.ts

import { NestFactory } from '@nestjs/core';
import { Transport, MicroserviceOptions } from '@nestjs/microservices';
import { AppModule } from './app.module';

async function bootstrap() {
  // HTTP API 서버 생성
  const app = await NestFactory.create(AppModule);
  
  //  TCP 기반 마이크로서비스 추가
  const microservice = app.connectMicroservice<MicroserviceOptions>({
	  transport: Transport.TCP,
  });

  await app.startAllMicroservices(); // 마이크로서비스 실행
  await app.listen(3001); // HTTP 서버 실행
}
bootstrap();

이제 NestJS 애플리케이션이 HTTP 서버와 TCP 마이크로서비스를 동시에 실행합니다.

 

 

📌 Controller 예시

예를 들어, 컨트롤러 클래스는 이런 식으로 API 엔드포인트와 메시지 핸들러를 함께 구성할 수 있습니다.

// order-service
// order.controller.ts

import { Controller } from '@nestjs/common';
import { MessagePattern } from '@nestjs/microservices';

@Controller()
export class OrderController {
	@ApiPost('order')
	async order(@Body body: OrderDto) {
	  // ...
	}

  @MessagePattern({ cmd: 'order' })
  async getUserOrders(data: GetOrderDto): Promise<OrderResponseDto> {
    // ...
    return orderService.getUserOrders();
  }
}

 

📌 `createMicroservice()` vs `connectMicroservice()`

`createMicroservice()`와 `connectMicroservice()` 메서드에 대해 조금 더 자세히 알아보기 위해 NestJS의 일부 코드를 참고했습니다.

둘 다 NestJS에서 마이크로서비스를 생성하고 연결하는 역할을 하는 메서드입니다. 하지만 사용 목적에 조금 차이가 있어 필요에 따라 구분해서 사용해야합니다.

 

✅ `createMicroservice()`

// nest/packages/core/nest-factory.ts

  public async createMicroservice<T extends object>(
    moduleCls: IEntryNestModule,
    options?: NestMicroserviceOptions & T,
  ): Promise<INestMicroservice> {
	  // ...
	  const container = new NestContainer(applicationConfig, options);
		// ...
	  
	  await this.initialize(
      moduleCls,
      container,
      graphInspector,
      applicationConfig,
      options,
    );
    return this.createNestInstance<INestMicroservice>(
      new NestMicroservice(
        container,
        options,
        graphInspector,
        applicationConfig,
      ),
    );
  }
  • 새로운 `NestContainer` 생성
  • 새로운 DI 컨테이너를 만들고 의존성 관리 준비를 합니다.
  • 애플리케이션 초기화 설정 수행
  • 마이크로서비스를 설정하고 필요한 의존성을 주입합니다.
  • `NestMicroservice` 인스턴스 생성 및 반환
  • 새로운 마이크로서비스 인스턴스를 생성하고 반환합니다.

새로운 `NestContainer`를 생성하여 다른 애플리케이션 분리된 독립적인 마이크로서비스 인스턴스를 생성합니다. 따라서 단독 실행되는 마이크로서비스 애플리케이션 만들 때 사용합니다.

 

 

✅ `connectMicroservice()`

// nest/packages/core/nest-application.ts

  public connectMicroservice<T extends object>(
    microserviceOptions: T,
    hybridAppOptions: NestHybridApplicationOptions = {},
  ): INestMicroservice {
    // ...
    
    const instance = new NestMicroservice(
      this.container,
      microserviceOptions,
      this.graphInspector,
      applicationConfig,
    );
    instance.registerListeners();
    instance.setIsInitialized(true);
    instance.setIsInitHookCalled(true);

    this.microservices.push(instance);
    return instance;
  }
  • 마이크로서비스 인스턴스 생성
  • 현재 애플리케이션(`this.container`)을 기반으로 새로운 마이크로서비스를 생성합니다.
  • 마이크로서비스 활성화
  • 마이크로서비스의 리스너를 등록하고 초기화 및 초기화 훅 호출 완료 상태로 설정합니다.
  • 마이크로서비스 리스트에 추가 후 반환
  • 현재 애플리케이션 새로운 마이크로서비스를 추가하고 해당 인스턴스를 반환합니다.

새로운 `NestContainer`를 만들지 않고 기존 애플리케이션 컨텍스트를 공유하는 마이크로서비스 인스턴스를 생성합니다. 그리고 기존 NestJS 애플리케이션에 마이크로서비스를 추가합니다. 따라서 HTTP 서버와 마이크로서비스 서버를 함께 실행하는 하이브리드 애플리케이션 만들 때 사용합니다.

 

 

📌 `startAllMicroservices()`

// nest/packages/core/nest-application.ts

  public async startAllMicroservices(): Promise<this> {
    this.assertNotInPreviewMode('startAllMicroservices');
    await Promise.all(this.microservices.map(msvc => msvc.listen()));
    return this;
  }

NestJS 애플리케이션에 연결된 모든 마이크로서비스를 실행(`listen()`)합니다.

 

 

📌  하이브리드 애플리케이션 bootstrap 순서

NestJS 서버를 실행하는데 기본적으로는 HTTP 서버와 마이크로서비스 실행 순서는 큰 영향을 주지 않습니다. 그러나 마이크로서비스를 먼저 실행하고(`startAllMicroservices()`) HTTP 서버를 실행(`listen()`) 하는 순서가 권장됩니다.

 

`listen()` → `startAllMicroservices()` 의 문제점

  • HTTP 서버가 먼저 실행된 후 마이크로서비스가 실행됩니다.
    • 클라이언트가 HTTP 요청을 보낼 때, 마이크로서비스가 아직 준비되지 않았을 가능성이 있습니다. 특정 API가 마이크로서비스를 호출해야 하는 경우(예를 들어, API가 메시지큐에 메시지를 보내는 경우), 요청이 실패할 수 있습니다.
  • 서버 시작 시 마이크로서비스를 호출해야 하는 경우 문제가 발생할 수 있습니다.
    • `onApplicationBootstrap()`이나 `onModuleInit()` 를 활용시, 마이크로서비스를 호출해야한다면 마이크로서비스가 준비되기 전 실행되어 오류가 발생할 수 있다고 합니다.(참고)

 

 

 


마무리

이번 글에서는 NestJS에서 마이크로서비스 아키텍처를 구성하는 방법과 하이브리드 애플리케이션을 활용하여 HTTP API 서버와 마이크로서비스 통신을 위한 내부 서버를 함께 운영하는 방식에 대해 알아보았습니다.

 

 

 


참고 자료

https://docs.nestjs.com/microservices/basics

 

Documentation | NestJS - A progressive Node.js framework

Nest is a framework for building efficient, scalable Node.js server-side applications. It uses progressive JavaScript, is built with TypeScript and combines elements of OOP (Object Oriented Programming), FP (Functional Programming), and FRP (Functional Rea

docs.nestjs.com

 

https://docs.nestjs.com/faq/hybrid-application

 

Documentation | NestJS - A progressive Node.js framework

Nest is a framework for building efficient, scalable Node.js server-side applications. It uses progressive JavaScript, is built with TypeScript and combines elements of OOP (Object Oriented Programming), FP (Functional Programming), and FRP (Functional Rea

docs.nestjs.com

https://github.com/nestjs/docs.nestjs.com/issues/2485

 

Update FAQ Hybrid application Docs to clarify bootstrap order · Issue #2485 · nestjs/docs.nestjs.com

Is there an existing issue that is already proposing this? I have searched the existing issues Is your feature request related to a problem? Please describe it When utilizing onApplicationBootstrap...

github.com

https://github.com/nestjs/nest

 

GitHub - nestjs/nest: A progressive Node.js framework for building efficient, scalable, and enterprise-grade server-side applica

A progressive Node.js framework for building efficient, scalable, and enterprise-grade server-side applications with TypeScript/JavaScript 🚀 - nestjs/nest

github.com