백엔드/NestJS

[NestJS]내장된 기능 활용해 마이크로서비스 구현하기 - 2. TCP

SparkIT 2024. 11. 3. 21:22
[NestJS]내장된 기능 활용해 마이크로서비스 구현하기 - 1. 개요
[NestJS]내장된 기능 활용해 마이크로서비스 구현하기 - 2. TCP
[NestJS]내장된기능활용해마이크로서비스구현하기 - 3. MQTT
[NestJS]내장된 기능 활용해 마이크로서비스 구현하기 - 4. Kafka

 

 

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

(기본적으로 저는 도커 컨테이너로 각 마이크로서비스를 구현하고 이들을 도커 컴포즈로 묶어서 실행하여 실습을 진행했습니다.)

 

전체 구조

실습은 위와 같은 구조를 이용했습니다. 먼저 HTTP 요청을 받아서 다른 마이크로서비스에 TCP 요청을 보낼 수 있는 A 서버를 만들었습니다. 또 A 서버로부터 TCP 요청을 받고 응답을 반환할 B 서버도 만들었습니다.

 


A 마이크로서비스 서버 (Client)

# main.ts

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

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  await app.listen(3000);
}
bootstrap();

해당 서버는 HTTP 프로토콜을 이용해야합니다. 또한 TCP 프로토콜은 요청만 보낼 것이고 외부에서 들어오는 TCP 요청은 받지 않을 것이므로 단순 HTTP 프로토콜 이용 방식으로 실행했습니다.

 

# app.module.ts

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ClientsModule, Transport } from '@nestjs/microservices';

@Module({
  imports: [
    ClientsModule.register([
      {
        name: 'MATH_SERVICE',
        transport: Transport.TCP,
        options: {
          host: 'B 서버 도커 컨테이너명',
          port: 'B 서버 TCP 통신에 사용할 포트',
        },
      },
    ]),
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule { }

imports에는 내가 TCP 요청을 보낼 때 관련된 정보를 기입합니다. name은 service코드에 주입할 때 사용할 이름을 설정하는 것이고 transport는 사용할 프로토콜을 지정하는 부분입니다. 그리고 options에는 요청을 보낼 마이크로서비스 서버의 정보를 기입하면 됩니다. 저는 위에서 언급했듯 도커 컨테이너를 활용해 환경을 구성했기 때문에 위와 같이 설정했습니다.

 

# app.controller.ts

import { Controller, Get, Query } from '@nestjs/common';
import { AppService } from './app.service';
import { Observable } from 'rxjs';

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get('sum')
  getSum(@Query('numbers') numbers: string): Observable<number> {
    return this.appService.accumulate();
  }
}

HTTP GET 메서드를 하나 간단하게 설정했습니다.

 

# app.service.ts

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

@Injectable()
export class AppService {
  constructor(
    @Inject('MATH_SERVICE') private client: ClientProxy,
  ) {}
  
  accumulate() {
    const pattern = { cmd: 'sum' };
    const payload = [1, 2, 3];
    return this.client.send<number>(pattern, payload);
  }
}

HTTP GET 요청을 받았을 때 실행될 실제 로직입니다. 요청은 patternpayload 두 가지로 구성됩니다. pattern은 어떻게 호출할지에 대한 내용이고 payload는 전달할 데이터입니다. 즉 요청을 받을 마이크로서비스에는 해당 pattern을 받을 때 실행되는 로직이 구현되어 있어야합니다.

 

 


B 마이크로서비스 서버

# main.ts

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

async function bootstrap() {
  const app = await NestFactory.createMicroservice<MicroserviceOptions>(
    AppModule, {
    transport: Transport.TCP,
    options: {
      host: 'B 서버 도커 컨테이너명',
      port: 'B 서버 TCP 통신에 사용할 포트',
    },
  });
  await app.listen();
}
bootstrap();

해당 서버는 A 서버에서 TCP 프로토콜을 이용한 요청에 대한 응답을 하는 마이크로서비스입니다. 그러므로 위와 같이 마이크로서비스로 설정했습니다.

 

# app.controller.ts

import { AppService } from './app.service';
import { MessagePattern } from '@nestjs/microservices';

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @MessagePattern({ cmd: 'sum' })
    accumulate(data: number[]): number {
      return (data || []).reduce((a, b) => a + b);
  }
}

위에서 언급한 A 서버의 app.service.ts 코드의 this.client.send(pattern, payload); 부분과 관련된 부분입니다. 이때 pattern으로 { cmd : 'sum' }을 이용한다고 했었습니다. 즉, 해당 pattern을 요청하면 어떻게 응답할 것인지를 구현한 부분이 바로 이 부분입니다. 이 과정에서 request-respone 스타일을 사용할 것인지 event-based 스타일을 사용할 것인지에 따라 다른 데코레이터를 사용할 수 있습니다. 일단 저는 request-response 스타일을 사용하기 위해 @MessagePattern 데코레이터를 사용했습니다. (그리고 단순하게 구현하기 위해 service 코드를 따로 작성하지 않고 controller에 로직을 바로 구현했습니다)

 

 


실행 결과

A 서버에 /sum GET 요청을 보냈더니, 결과적으로 6이라는 결과값을 반환받았습니다. 이는 A 서버에 들어간 HTTP(GET)요청이 처리되고 이후에 A 서버가 B 서버로 TCP 요청을 보냈고, 해당 요청을 통해 B 서버에서 [1,2,3]이 모두 더해진 6이라는 값이 최종적으로 응답으로 반환되었음을 알 수 있습니다.

이를 통해 TCP 프로토콜과 request-response 스타일의 마이크로서비스 동작 방식에 대해 이해할 수 있었습니다.