2026๋…„ NestJS ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค: ์•„ํ‚คํ…์ฒ˜, gRPC, ๋ฉด์ ‘ ์งˆ๋ฌธ ์™„๋ฒฝ ๊ฐ€์ด๋“œ

NestJS ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ์•„ํ‚คํ…์ฒ˜์˜ ํ•ต์‹ฌ ๊ฐœ๋…, gRPC ํŠธ๋žœ์ŠคํฌํŠธ ๊ตฌ์„ฑ, ์ŠคํŠธ๋ฆฌ๋ฐ ํŒจํ„ด, ์•ˆ์ •์„ฑ ํŒจํ„ด, ๋ฉด์ ‘ ๋นˆ์ถœ ์งˆ๋ฌธ์„ ์‹ค๋ฌด ์ค‘์‹ฌ์œผ๋กœ ๋‹ค๋ฃน๋‹ˆ๋‹ค.

NestJS ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ์•„ํ‚คํ…์ฒ˜์™€ gRPC ๊ตฌ์„ฑ ๊ฐ€์ด๋“œ

NestJS ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค๋Š” 2026๋…„ ํ˜„์žฌ Node.js ๊ธฐ๋ฐ˜ ๋ถ„์‚ฐ ์‹œ์Šคํ…œ ๊ตฌ์ถ•์˜ ์‚ฌ์‹ค์ƒ ํ‘œ์ค€์œผ๋กœ ์ž๋ฆฌ ์žก์•˜์Šต๋‹ˆ๋‹ค. gRPC ํŠธ๋žœ์ŠคํฌํŠธ, ์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜ ํ†ต์‹ , ์„œํ‚ท ๋ธŒ๋ ˆ์ด์ปค ํŒจํ„ด๊นŒ์ง€ NestJS๊ฐ€ ์ œ๊ณตํ•˜๋Š” ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ๋„๊ตฌ๋Š” ๋Œ€๊ทœ๋ชจ ๋ฐฑ์—”๋“œ ์‹œ์Šคํ…œ์˜ ๋ณต์žก์„ฑ์„ ํšจ๊ณผ์ ์œผ๋กœ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค. ์ด ๊ธ€์—์„œ๋Š” NestJS ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ์•„ํ‚คํ…์ฒ˜์˜ ํ•ต์‹ฌ ๊ฐœ๋…๋ถ€ํ„ฐ gRPC ๊ตฌ์„ฑ, ์ŠคํŠธ๋ฆฌ๋ฐ ํŒจํ„ด, ์•ˆ์ •์„ฑ ํŒจํ„ด, ๊ทธ๋ฆฌ๊ณ  ๊ธฐ์ˆ  ๋ฉด์ ‘์—์„œ ๋นˆ์ถœ๋˜๋Š” ์งˆ๋ฌธ๊นŒ์ง€ ์‹ค๋ฌด ์ค‘์‹ฌ์œผ๋กœ ๋‹ค๋ฃน๋‹ˆ๋‹ค.

gRPC์™€ REST ์ค‘ ์–ด๋–ค ๊ฒƒ์„ ์„ ํƒํ•ด์•ผ ํ• ๊นŒ?

๋‚ด๋ถ€ ์„œ๋น„์Šค ๊ฐ„ ํ†ต์‹ ์—๋Š” gRPC๊ฐ€ ์ ํ•ฉํ•ฉ๋‹ˆ๋‹ค. Protocol Buffers ๊ธฐ๋ฐ˜ ๋ฐ”์ด๋„ˆ๋ฆฌ ์ง๋ ฌํ™”๋กœ JSON ๋Œ€๋น„ ํŽ˜์ด๋กœ๋“œ ํฌ๊ธฐ๊ฐ€ ์ž‘๊ณ , HTTP/2 ๋ฉ€ํ‹ฐํ”Œ๋ ‰์‹ฑ์œผ๋กœ ์ง€์—ฐ ์‹œ๊ฐ„์ด ๋‚ฎ์Šต๋‹ˆ๋‹ค. ๋ฐ˜๋ฉด, ์™ธ๋ถ€ ํด๋ผ์ด์–ธํŠธ(๋ธŒ๋ผ์šฐ์ €, ๋ชจ๋ฐ”์ผ ์•ฑ) ๋Œ€์ƒ API์—๋Š” REST๊ฐ€ ์ ‘๊ทผ์„ฑ๊ณผ ํ˜ธํ™˜์„ฑ ์ธก๋ฉด์—์„œ ์œ ๋ฆฌํ•ฉ๋‹ˆ๋‹ค. ๋Œ€๋ถ€๋ถ„์˜ ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์—์„œ๋Š” ์™ธ๋ถ€์šฉ REST์™€ ๋‚ด๋ถ€์šฉ gRPC๋ฅผ ๋ณ‘ํ–‰ํ•˜๋Š” ํ•˜์ด๋ธŒ๋ฆฌ๋“œ ๊ตฌ์กฐ๋ฅผ ์ฑ„ํƒํ•ฉ๋‹ˆ๋‹ค.

NestJS ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ํŠธ๋žœ์ŠคํฌํŠธ ๋ ˆ์ด์–ด ์•„ํ‚คํ…์ฒ˜

NestJS์˜ ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ๋ชจ๋“ˆ์€ ์ „์†ก ๊ณ„์ธต์„ ์™„์ „ํžˆ ์ถ”์ƒํ™”ํ•ฉ๋‹ˆ๋‹ค. TCP, Redis, NATS, RabbitMQ, Kafka, gRPC ๋“ฑ ๋‹ค์–‘ํ•œ ํŠธ๋žœ์ŠคํฌํŠธ๋ฅผ ๋™์ผํ•œ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ ๊ธฐ๋ฐ˜ ์ธํ„ฐํŽ˜์ด์Šค๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ ์ˆ˜์ •ํ•˜์ง€ ์•Š๊ณ ๋„ ์ „์†ก ํ”„๋กœํ† ์ฝœ์„ ๊ต์ฒดํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

ํ†ต์‹  ํŒจํ„ด์€ ํฌ๊ฒŒ ๋‘ ๊ฐ€์ง€๋กœ ๊ตฌ๋ถ„๋ฉ๋‹ˆ๋‹ค. @MessagePattern ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋Š” ์š”์ฒญ-์‘๋‹ต(Request-Response) ํŒจํ„ด์„ ๊ตฌํ˜„ํ•˜๋ฉฐ, ํ˜ธ์ถœ์ž๊ฐ€ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ›์„ ๋•Œ๊นŒ์ง€ ๋Œ€๊ธฐํ•ฉ๋‹ˆ๋‹ค. ์ฃผ๋ฌธ ์ƒ์„ฑ์ด๋‚˜ ๋ฐ์ดํ„ฐ ์กฐํšŒ์ฒ˜๋Ÿผ ์ฆ‰๊ฐ์ ์ธ ์‘๋‹ต์ด ํ•„์š”ํ•œ ์ž‘์—…์— ์ ํ•ฉํ•ฉ๋‹ˆ๋‹ค. @EventPattern ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋Š” ์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜(Fire-and-Forget) ํŒจํ„ด์„ ๊ตฌํ˜„ํ•˜๋ฉฐ, ๋ฐœ์‹ ์ž๋Š” ์‘๋‹ต์„ ๊ธฐ๋‹ค๋ฆฌ์ง€ ์•Š๊ณ  ๋‹ค์Œ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค. ์•Œ๋ฆผ ์ „์†ก, ๋กœ๊ทธ ๊ธฐ๋ก, ๋น„๋™๊ธฐ ์ƒํƒœ ์—…๋ฐ์ดํŠธ์— ์ ํ•ฉํ•ฉ๋‹ˆ๋‹ค.

orders.controller.tstypescript
import { Controller } from '@nestjs/common';
import { MessagePattern, EventPattern, Payload } from '@nestjs/microservices';
import { OrdersService } from './orders.service';
import { CreateOrderDto } from './dto/create-order.dto';

@Controller()
export class OrdersController {
  constructor(private readonly ordersService: OrdersService) {}

  // Request-response: caller waits for the created order
  @MessagePattern('order.create')
  async createOrder(@Payload() data: CreateOrderDto) {
    return this.ordersService.create(data);
  }

  // Event-based: fire and forget, no response returned
  @EventPattern('order.shipped')
  async handleOrderShipped(@Payload() data: { orderId: string }) {
    await this.ordersService.markAsShipped(data.orderId);
  }
}

order.create ํŒจํ„ด์€ ์ฃผ๋ฌธ์„ ์ƒ์„ฑํ•˜๊ณ  ์ƒ์„ฑ๋œ ์ฃผ๋ฌธ ๊ฐ์ฒด๋ฅผ ํ˜ธ์ถœ์ž์—๊ฒŒ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. order.shipped ์ด๋ฒคํŠธ๋Š” ๋ฐฐ์†ก ์ƒํƒœ ๋ณ€๊ฒฝ์„ ๋น„๋™๊ธฐ๋กœ ์ฒ˜๋ฆฌํ•˜๋ฏ€๋กœ ํ˜ธ์ถœ ์„œ๋น„์Šค์˜ ์‘๋‹ต ์ง€์—ฐ์— ์˜ํ–ฅ์„ ์ฃผ์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์–ด๋–ค ํŒจํ„ด์„ ์„ ํƒํ• ์ง€๋Š” ๋ฐ์ดํ„ฐ ์ผ๊ด€์„ฑ ์š”๊ตฌ ์ˆ˜์ค€๊ณผ ํ˜ธ์ถœ์ž์˜ ์‘๋‹ต ๋Œ€๊ธฐ ํ•„์š” ์—ฌ๋ถ€์— ๋”ฐ๋ผ ๊ฒฐ์ •๋ฉ๋‹ˆ๋‹ค.

NestJS ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค์—์„œ gRPC ํŠธ๋žœ์ŠคํฌํŠธ ๊ตฌ์„ฑ

gRPC๋Š” Google์ด ๊ฐœ๋ฐœํ•œ ๊ณ ์„ฑ๋Šฅ ์›๊ฒฉ ํ”„๋กœ์‹œ์ € ํ˜ธ์ถœ(RPC) ํ”„๋ ˆ์ž„์›Œํฌ์ž…๋‹ˆ๋‹ค. Protocol Buffers๋ฅผ ์ธํ„ฐํŽ˜์ด์Šค ์ •์˜ ์–ธ์–ด(IDL)๋กœ ์‚ฌ์šฉํ•˜์—ฌ ์„œ๋น„์Šค ๊ณ„์•ฝ์„ ๋ช…์‹œ์ ์œผ๋กœ ์ •์˜ํ•˜๊ณ , ์ด๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ๋‹ค์–‘ํ•œ ์–ธ์–ด์˜ ํด๋ผ์ด์–ธํŠธ/์„œ๋ฒ„ ์ฝ”๋“œ๋ฅผ ์ž๋™ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. NestJS๋Š” gRPC๋ฅผ ์ผ๊ธ‰ ํŠธ๋žœ์ŠคํฌํŠธ๋กœ ์ง€์›ํ•˜๋ฉฐ, @nestjs/microservices ํŒจํ‚ค์ง€์— ๋ชจ๋“  ๊ธฐ๋Šฅ์ด ํฌํ•จ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

gRPC ์„œ๋น„์Šค ๊ตฌ์„ฑ์˜ ์ฒซ ๋‹จ๊ณ„๋Š” .proto ํŒŒ์ผ ์ •์˜์ž…๋‹ˆ๋‹ค. ์ด ํŒŒ์ผ์€ ์„œ๋น„์Šค์˜ RPC ๋ฉ”์„œ๋“œ์™€ ๋ฉ”์‹œ์ง€ ๊ตฌ์กฐ๋ฅผ ์„ ์–ธํ•˜๋ฉฐ, ํด๋ผ์ด์–ธํŠธ์™€ ์„œ๋ฒ„ ์–‘์ชฝ ๋ชจ๋‘์˜ ํ†ต์‹  ๊ณ„์•ฝ ์—ญํ• ์„ ํ•ฉ๋‹ˆ๋‹ค.

proto/users.protoprotobuf
syntax = "proto3";

package users;

service UsersService {
  rpc FindOne (UserById) returns (User);
  rpc FindMany (UserFilter) returns (stream User);
}

message UserById {
  string id = 1;
}

message UserFilter {
  string role = 1;
  int32 limit = 2;
}

message User {
  string id = 1;
  string email = 2;
  string name = 3;
  string role = 4;
}
proto3 ๋ฌธ๋ฒ•๊ณผ ํ•„๋“œ ๋ฒˆํ˜ธ ๊ทœ์น™

syntax = \"proto3\"๋Š” Protocol Buffers 3 ๋ฒ„์ „์„ ๋ช…์‹œํ•ฉ๋‹ˆ๋‹ค. ๊ฐ ํ•„๋“œ์— ํ• ๋‹น๋œ ๋ฒˆํ˜ธ(1, 2, 3...)๋Š” ๋ฐ”์ด๋„ˆ๋ฆฌ ์ธ์ฝ”๋”ฉ์—์„œ ํ•„๋“œ๋ฅผ ์‹๋ณ„ํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋˜๋ฏ€๋กœ, ํ•œ ๋ฒˆ ๋ฐฐํฌ๋œ ํ›„์—๋Š” ๋ณ€๊ฒฝํ•˜๊ฑฐ๋‚˜ ์žฌ์‚ฌ์šฉํ•ด์„œ๋Š” ์•ˆ ๋ฉ๋‹ˆ๋‹ค. ํ•„๋“œ๋ฅผ ์‚ญ์ œํ•  ๋•Œ๋Š” reserved ํ‚ค์›Œ๋“œ๋กœ ํ•ด๋‹น ๋ฒˆํ˜ธ๋ฅผ ์˜ˆ์•ฝํ•˜์—ฌ ํ–ฅํ›„ ์ถฉ๋Œ์„ ๋ฐฉ์ง€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

.proto ํŒŒ์ผ ์ •์˜๊ฐ€ ์™„๋ฃŒ๋˜๋ฉด NestJS ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ gRPC ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค๋กœ ๋ถ€ํŠธ์ŠคํŠธ๋žฉํ•ฉ๋‹ˆ๋‹ค. NestFactory.createMicroservice ๋ฉ”์„œ๋“œ์— Transport.GRPC ์˜ต์…˜์„ ์ „๋‹ฌํ•˜๋ฉด ์ „์šฉ gRPC ์„œ๋ฒ„๊ฐ€ ์ƒ์„ฑ๋ฉ๋‹ˆ๋‹ค.

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

async function bootstrap() {
  const app = await NestFactory.createMicroservice<MicroserviceOptions>(
    AppModule,
    {
      transport: Transport.GRPC,
      options: {
        package: 'users',
        protoPath: join(__dirname, 'proto/users.proto'),
        url: '0.0.0.0:5000',
      },
    },
  );
  await app.listen();
}
bootstrap();

์ปจํŠธ๋กค๋Ÿฌ์—์„œ๋Š” @GrpcMethod ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ .proto ํŒŒ์ผ์— ์ •์˜๋œ RPC ๋ฉ”์„œ๋“œ๋ฅผ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค. ์ฒซ ๋ฒˆ์งธ ์ธ์ž๋Š” ์„œ๋น„์Šค ์ด๋ฆ„, ๋‘ ๋ฒˆ์งธ ์ธ์ž๋Š” ๋ฉ”์„œ๋“œ ์ด๋ฆ„์ด๋ฉฐ, ์ด ๊ฐ’๋“ค์ด .proto ํŒŒ์ผ์˜ ์ •์˜์™€ ์ •ํ™•ํžˆ ์ผ์น˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

users.controller.tstypescript
import { Controller } from '@nestjs/common';
import { GrpcMethod } from '@nestjs/microservices';
import { UsersService } from './users.service';

@Controller()
export class UsersController {
  constructor(private readonly usersService: UsersService) {}

  @GrpcMethod('UsersService', 'FindOne')
  async findOne(data: { id: string }) {
    return this.usersService.findById(data.id);
  }
}

@GrpcMethod ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๊ฐ€ .proto ํŒŒ์ผ์˜ rpc FindOne ์„ ์–ธ๊ณผ ๋งคํ•‘๋˜์–ด, gRPC ์š”์ฒญ์ด ํ•ด๋‹น ํ•ธ๋“ค๋Ÿฌ๋กœ ๋ผ์šฐํŒ…๋ฉ๋‹ˆ๋‹ค. ๋ฐ˜ํ™˜ ๊ฐ’์€ ์ž๋™์œผ๋กœ Protocol Buffers ํ˜•์‹์œผ๋กœ ์ง๋ ฌํ™”๋˜์–ด ํด๋ผ์ด์–ธํŠธ์— ์ „๋‹ฌ๋ฉ๋‹ˆ๋‹ค.

NestJS์˜ gRPC ์ŠคํŠธ๋ฆฌ๋ฐ ํŒจํ„ด

gRPC๋Š” ๋‹จํ•ญ(Unary), ์„œ๋ฒ„ ์ŠคํŠธ๋ฆฌ๋ฐ, ํด๋ผ์ด์–ธํŠธ ์ŠคํŠธ๋ฆฌ๋ฐ, ์–‘๋ฐฉํ–ฅ ์ŠคํŠธ๋ฆฌ๋ฐ์˜ ๋„ค ๊ฐ€์ง€ ํ†ต์‹  ํŒจํ„ด์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค. NestJS์—์„œ๋Š” RxJS Observable์„ ํ™œ์šฉํ•˜์—ฌ ์ŠคํŠธ๋ฆฌ๋ฐ์„ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์„œ๋ฒ„ ์ŠคํŠธ๋ฆฌ๋ฐ์€ ํด๋ผ์ด์–ธํŠธ๊ฐ€ ๋‹จ์ผ ์š”์ฒญ์„ ๋ณด๋‚ด๋ฉด ์„œ๋ฒ„๊ฐ€ ์—ฌ๋Ÿฌ ๊ฐœ์˜ ์‘๋‹ต์„ ์ˆœ์ฐจ์ ์œผ๋กœ ๋ฐ˜ํ™˜ํ•˜๋Š” ํŒจํ„ด์ž…๋‹ˆ๋‹ค. ๋Œ€๋Ÿ‰์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฉ”๋ชจ๋ฆฌ์— ํ•œ๊บผ๋ฒˆ์— ์ ์žฌํ•˜์ง€ ์•Š๊ณ  ์ฒญํฌ ๋‹จ์œ„๋กœ ์ „์†กํ•  ์ˆ˜ ์žˆ์–ด, ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ ๋ชฉ๋ก์ด๋‚˜ ์‹ค์‹œ๊ฐ„ ํ”ผ๋“œ ์ „๋‹ฌ์— ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค.

users.controller.ts โ€” server streamingtypescript
import { Observable, from } from 'rxjs';
import { map } from 'rxjs/operators';
import { GrpcMethod } from '@nestjs/microservices';

@GrpcMethod('UsersService', 'FindMany')
findMany(data: { role: string; limit: number }): Observable<any> {
  // Stream users matching the filter one by one
  const users$ = from(this.usersService.findByRole(data.role, data.limit));
  return users$.pipe(
    map((user) => ({
      id: user.id,
      email: user.email,
      name: user.name,
      role: user.role,
    })),
  );
}

from() ์—ฐ์‚ฐ์ž๊ฐ€ ๋ฐฐ์—ด์ด๋‚˜ Promise๋ฅผ Observable ์ŠคํŠธ๋ฆผ์œผ๋กœ ๋ณ€ํ™˜ํ•˜๊ณ , map() ์—ฐ์‚ฐ์ž๊ฐ€ ๊ฐ ํ•ญ๋ชฉ์„ .proto ํŒŒ์ผ์— ์ •์˜๋œ User ๋ฉ”์‹œ์ง€ ํ˜•์‹์œผ๋กœ ๋ณ€ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ํด๋ผ์ด์–ธํŠธ๋Š” ๊ฐ ํ•ญ๋ชฉ์ด ๋„์ฐฉํ•  ๋•Œ๋งˆ๋‹ค ์ˆœ์ฐจ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ, ์ „์ฒด ์‘๋‹ต์„ ๊ธฐ๋‹ค๋ฆด ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.

์–‘๋ฐฉํ–ฅ ์ŠคํŠธ๋ฆฌ๋ฐ์€ ํด๋ผ์ด์–ธํŠธ์™€ ์„œ๋ฒ„๊ฐ€ ๋™์‹œ์— ๋ฐ์ดํ„ฐ๋ฅผ ์ฃผ๊ณ ๋ฐ›๋Š” ํŒจํ„ด์ž…๋‹ˆ๋‹ค. ์‹ค์‹œ๊ฐ„ ์ฑ„ํŒ…, ํ˜‘์—… ํŽธ์ง‘, ์„ผ์„œ ๋ฐ์ดํ„ฐ ๋ชจ๋‹ˆํ„ฐ๋ง๊ณผ ๊ฐ™์€ ์‹œ๋‚˜๋ฆฌ์˜ค์—์„œ ํ™œ์šฉ๋ฉ๋‹ˆ๋‹ค. NestJS์—์„œ๋Š” ์ž…๋ ฅ ๋งค๊ฐœ๋ณ€์ˆ˜์™€ ๋ฐ˜ํ™˜๊ฐ’ ๋ชจ๋‘ Observable๋กœ ์ฒ˜๋ฆฌํ•˜๋ฉด ์–‘๋ฐฉํ–ฅ ์ŠคํŠธ๋ฆฌ๋ฐ์ด ์ž๋™์œผ๋กœ ๊ตฌ์„ฑ๋ฉ๋‹ˆ๋‹ค.

์ŠคํŠธ๋ฆฌ๋ฐ ์—ฐ๊ฒฐ์˜ ์ˆ˜๋ช… ์ฃผ๊ธฐ ๊ด€๋ฆฌ

gRPC ์ŠคํŠธ๋ฆฌ๋ฐ ์—ฐ๊ฒฐ์€ ์žฅ์‹œ๊ฐ„ ์œ ์ง€๋  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ, ์ ์ ˆํ•œ ๋ฐ๋“œ๋ผ์ธ(Deadline) ์„ค์ •๊ณผ ์—ฐ๊ฒฐ ํ•ด์ œ ์ฒ˜๋ฆฌ๊ฐ€ ํ•„์ˆ˜์ž…๋‹ˆ๋‹ค. ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์ŠคํŠธ๋ฆผ์„ ์†Œ๋น„ํ•˜์ง€ ์•Š์œผ๋ฉด ๋ฐฑํ”„๋ ˆ์…”(Backpressure) ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์„œ๋ฒ„ ์ธก ๋ฆฌ์†Œ์Šค ๋ˆ„์ˆ˜๋กœ ์ด์–ด์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

ํ•˜์ด๋ธŒ๋ฆฌ๋“œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜: ๋™์ผ ์„œ๋น„์Šค์—์„œ HTTP์™€ gRPC ๋™์‹œ ์šด์˜

ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์—์„œ๋Š” ๋‹จ์ผ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด HTTP REST API์™€ gRPC ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค๋ฅผ ๋™์‹œ์— ์ œ๊ณตํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋นˆ๋ฒˆํ•ฉ๋‹ˆ๋‹ค. NestJS์˜ ํ•˜์ด๋ธŒ๋ฆฌ๋“œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•˜๋ฉด ํ•˜๋‚˜์˜ ์ฝ”๋“œ๋ฒ ์ด์Šค์—์„œ ์—ฌ๋Ÿฌ ์ „์†ก ๊ณ„์ธต์„ ๋™์‹œ์— ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ด ํŒจํ„ด์€ API ๊ฒŒ์ดํŠธ์›จ์ด ๊ตฌํ˜„์— ํŠนํžˆ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค. ์™ธ๋ถ€ ํด๋ผ์ด์–ธํŠธ(์›น ๋ธŒ๋ผ์šฐ์ €, ๋ชจ๋ฐ”์ผ ์•ฑ)์—๋Š” HTTP REST ์—”๋“œํฌ์ธํŠธ๋ฅผ ์ œ๊ณตํ•˜๊ณ , ๋‚ด๋ถ€ ์„œ๋น„์Šค ๊ฐ„ ํ†ต์‹ ์—๋Š” gRPC๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์„ฑ๋Šฅ๊ณผ ์ ‘๊ทผ์„ฑ์„ ๋ชจ๋‘ ํ™•๋ณดํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

main.ts โ€” hybrid applicationtypescript
import { NestFactory } from '@nestjs/core';
import { MicroserviceOptions, Transport } from '@nestjs/microservices';
import { join } from 'path';
import { AppModule } from './app.module';

async function bootstrap() {
  // HTTP server on port 3000
  const app = await NestFactory.create(AppModule);

  // gRPC microservice on port 5000
  app.connectMicroservice<MicroserviceOptions>({
    transport: Transport.GRPC,
    options: {
      package: 'users',
      protoPath: join(__dirname, 'proto/users.proto'),
      url: '0.0.0.0:5000',
    },
  });

  await app.startAllMicroservices();
  await app.listen(3000);
}
bootstrap();

NestFactory.create๋กœ HTTP ์„œ๋ฒ„๋ฅผ ์ƒ์„ฑํ•˜๊ณ , connectMicroservice๋กœ gRPC ํŠธ๋žœ์ŠคํฌํŠธ๋ฅผ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. startAllMicroservices()๊ฐ€ gRPC ์„œ๋ฒ„๋ฅผ ์‹œ์ž‘ํ•œ ๋’ค, listen(3000)์ด HTTP ์„œ๋ฒ„๋ฅผ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค. ๋™์ผํ•œ ์„œ๋น„์Šค ํด๋ž˜์Šค์™€ ์˜์กด์„ฑ ์ฃผ์ž… ์ปจํ…Œ์ด๋„ˆ๋ฅผ ๊ณต์œ ํ•˜๋ฏ€๋กœ, ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์˜ ์ค‘๋ณต ์—†์ด ๋‘ ํ”„๋กœํ† ์ฝœ์„ ์ง€์›ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋ ˆ๊ฑฐ์‹œ REST API๋ฅผ ์œ ์ง€ํ•˜๋ฉด์„œ ์ ์ง„์ ์œผ๋กœ ๋‚ด๋ถ€ ํ†ต์‹ ์„ gRPC๋กœ ์ „ํ™˜ํ•˜๋Š” ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์‹œ๋‚˜๋ฆฌ์˜ค์—์„œ๋„ ์ด ์ ‘๊ทผ ๋ฐฉ์‹์ด ํšจ๊ณผ์ ์ž…๋‹ˆ๋‹ค.

Node.js / NestJS ๋ฉด์ ‘ ์ค€๋น„๊ฐ€ ๋˜์…จ๋‚˜์š”?

์ธํ„ฐ๋ž™ํ‹ฐ๋ธŒ ์‹œ๋ฎฌ๋ ˆ์ดํ„ฐ, flashcards, ๊ธฐ์ˆ  ํ…Œ์ŠคํŠธ๋กœ ์—ฐ์Šตํ•˜์„ธ์š”.

์„œ๋น„์Šค ๊ฒฝ๊ณ„์™€ ๋„๋ฉ”์ธ ์ฃผ๋„ ์„ค๊ณ„

๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ์•„ํ‚คํ…์ฒ˜์—์„œ ๊ฐ€์žฅ ์–ด๋ ค์šด ๊ณผ์ œ๋Š” ์„œ๋น„์Šค ๊ฒฝ๊ณ„๋ฅผ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์ •์˜ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ž˜๋ชป๋œ ๊ฒฝ๊ณ„ ์„ค์ •์€ ๋ถ„์‚ฐ ๋ชจ๋†€๋ฆฌ์Šค(Distributed Monolith)๋ผ๋Š” ์ตœ์•…์˜ ๊ฒฐ๊ณผ๋ฅผ ์ดˆ๋ž˜ํ•ฉ๋‹ˆ๋‹ค. ๋„คํŠธ์›Œํฌ ํ˜ธ์ถœ์˜ ๋ณต์žก์„ฑ์€ ๋Š˜์–ด๋‚˜์ง€๋งŒ, ๋…๋ฆฝ์  ๋ฐฐํฌ์™€ ํ™•์žฅ์ด๋ผ๋Š” ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค์˜ ๋ณธ์งˆ์  ์ด์ ์€ ์–ป์ง€ ๋ชปํ•˜๋Š” ์ƒํ™ฉ์ž…๋‹ˆ๋‹ค.

๋„๋ฉ”์ธ ์ฃผ๋„ ์„ค๊ณ„(DDD)์˜ ๋ฐ”์šด๋””๋“œ ์ปจํ…์ŠคํŠธ(Bounded Context) ๊ฐœ๋…์€ ์„œ๋น„์Šค ๋ถ„๋ฆฌ์˜ ํ•ต์‹ฌ ๊ธฐ์ค€์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ๊ฐ ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค๋Š” ํ•˜๋‚˜์˜ ๋ฐ”์šด๋””๋“œ ์ปจํ…์ŠคํŠธ๋ฅผ ๋‹ด๋‹นํ•˜๋ฉฐ, ํ•ด๋‹น ๋„๋ฉ”์ธ์˜ ์šฉ์–ด์™€ ๊ทœ์น™์„ ๋…์ž์ ์œผ๋กœ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค. ์ „์ž์ƒ๊ฑฐ๋ž˜ ์‹œ์Šคํ…œ์„ ์˜ˆ๋กœ ๋“ค๋ฉด, ์ฃผ๋ฌธ(Orders), ์‚ฌ์šฉ์ž(Users), ๊ฒฐ์ œ(Payments), ์žฌ๊ณ (Inventory)๊ฐ€ ๊ฐ๊ฐ ๋…๋ฆฝ๋œ ๋ฐ”์šด๋””๋“œ ์ปจํ…์ŠคํŠธ์— ํ•ด๋‹นํ•ฉ๋‹ˆ๋‹ค.

์„œ๋น„์Šค ๊ฐ„ ๊ฒฐํ•ฉ๋„๋ฅผ ๋‚ฎ์ถ”๋ ค๋ฉด ๋™๊ธฐ์  ์ง์ ‘ ํ˜ธ์ถœ๋ณด๋‹ค ๋„๋ฉ”์ธ ์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜์˜ ๋น„๋™๊ธฐ ํ†ต์‹ ์„ ์šฐ์„ ์ ์œผ๋กœ ๊ณ ๋ คํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ฃผ๋ฌธ ์„œ๋น„์Šค๊ฐ€ ๊ฒฐ์ œ ์„œ๋น„์Šค๋ฅผ ์ง์ ‘ ํ˜ธ์ถœํ•˜๋Š” ๋Œ€์‹ , OrderCreated ์ด๋ฒคํŠธ๋ฅผ ๋ฐœํ–‰ํ•˜๊ณ  ๊ฒฐ์ œ ์„œ๋น„์Šค๊ฐ€ ์ด๋ฅผ ๊ตฌ๋…ํ•˜๋Š” ๋ฐฉ์‹์ž…๋‹ˆ๋‹ค. ์ด ๊ตฌ์กฐ์—์„œ๋Š” ๊ฒฐ์ œ ์„œ๋น„์Šค์— ์žฅ์• ๊ฐ€ ๋ฐœ์ƒํ•˜๋”๋ผ๋„ ์ฃผ๋ฌธ ์„œ๋น„์Šค์˜ ๊ฐ€์šฉ์„ฑ์— ์˜ํ–ฅ์„ ์ฃผ์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

์•ˆํ‹ฐ ๋ถ€ํŒจ ๊ณ„์ธต(Anti-Corruption Layer, ACL)์€ ์™ธ๋ถ€ ์‹œ์Šคํ…œ์ด๋‚˜ ๋ ˆ๊ฑฐ์‹œ ์„œ๋น„์Šค์™€ ํ†ตํ•ฉํ•  ๋•Œ ๋‚ด๋ถ€ ๋„๋ฉ”์ธ ๋ชจ๋ธ์˜ ์ˆœ์ˆ˜์„ฑ์„ ๋ณดํ˜ธํ•˜๋Š” ํŒจํ„ด์ž…๋‹ˆ๋‹ค. ์™ธ๋ถ€ ๋ชจ๋ธ๊ณผ ๋‚ด๋ถ€ ๋ชจ๋ธ ์‚ฌ์ด์— ๋ณ€ํ™˜ ๊ณ„์ธต์„ ๋‘์–ด, ์™ธ๋ถ€ ์‹œ์Šคํ…œ์˜ ๋ณ€๊ฒฝ์ด ๋‚ด๋ถ€ ๋„๋ฉ”์ธ์— ์ง์ ‘ ์ „ํŒŒ๋˜๋Š” ๊ฒƒ์„ ์ฐจ๋‹จํ•ฉ๋‹ˆ๋‹ค.

์•ˆ์ •์„ฑ ํŒจํ„ด: ํƒ€์ž„์•„์›ƒ, ์žฌ์‹œ๋„, ์„œํ‚ท ๋ธŒ๋ ˆ์ด์ปค

๋ถ„์‚ฐ ์‹œ์Šคํ…œ์—์„œ ๋„คํŠธ์›Œํฌ ์žฅ์• , ์„œ๋น„์Šค ๊ณผ๋ถ€ํ•˜, ์ผ์‹œ์  ์˜ค๋ฅ˜๋Š” ํ”ผํ•  ์ˆ˜ ์—†๋Š” ํ˜„์‹ค์ž…๋‹ˆ๋‹ค. ๋‹จ์ผ ์„œ๋น„์Šค์˜ ์žฅ์• ๊ฐ€ ์ „์ฒด ์‹œ์Šคํ…œ์œผ๋กœ ์ „ํŒŒ๋˜๋Š” ์—ฐ์‡„ ์‹คํŒจ(Cascading Failure)๋ฅผ ๋ฐฉ์ง€ํ•˜๋ ค๋ฉด, ์ฒด๊ณ„์ ์ธ ์•ˆ์ •์„ฑ ํŒจํ„ด์„ ์ ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

ํƒ€์ž„์•„์›ƒ์€ ๊ฐ€์žฅ ๊ธฐ๋ณธ์ ์ธ ๋ฐฉ์–ด ์ˆ˜๋‹จ์ž…๋‹ˆ๋‹ค. ์‘๋‹ต์ด ์ง€์ •๋œ ์‹œ๊ฐ„ ๋‚ด์— ๋„์ฐฉํ•˜์ง€ ์•Š์œผ๋ฉด ์š”์ฒญ์„ ์‹คํŒจ๋กœ ์ฒ˜๋ฆฌํ•˜์—ฌ, ๋А๋ฆฐ ์„œ๋น„์Šค์— ๋ฌดํ•œ์ • ๋Œ€๊ธฐํ•˜๋Š” ์ƒํ™ฉ์„ ๋ฐฉ์ง€ํ•ฉ๋‹ˆ๋‹ค. ์žฌ์‹œ๋„ ํŒจํ„ด์€ ์ผ์‹œ์  ์žฅ์• ๋ฅผ ๊ทน๋ณตํ•˜๋Š” ๋ฐ ์œ ์šฉํ•˜์ง€๋งŒ, ์ง€์ˆ˜ ๋ฐฑ์˜คํ”„(Exponential Backoff)์™€ ์ง€ํ„ฐ(Jitter)๋ฅผ ์ ์šฉํ•˜์ง€ ์•Š์œผ๋ฉด ์žฅ์•  ์ƒํ™ฉ์„ ์•…ํ™”์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

orders.service.tstypescript
import { Inject, Injectable } from '@nestjs/common';
import { ClientGrpc } from '@nestjs/microservices';
import { firstValueFrom, timeout, retry } from 'rxjs';

@Injectable()
export class OrdersService {
  private usersService: any;

  constructor(@Inject('USERS_PACKAGE') private client: ClientGrpc) {}

  onModuleInit() {
    this.usersService = this.client.getService('UsersService');
  }

  async getOrderWithUser(orderId: string, userId: string) {
    // 3-second deadline, 2 retries with exponential backoff
    const user = await firstValueFrom(
      this.usersService.findOne({ id: userId }).pipe(
        timeout(3000),
        retry({ count: 2, delay: (err, retryCount) => {
          const jitter = Math.random() * 100;
          return new Promise(r => setTimeout(r, 1000 * retryCount + jitter));
        }}),
      ),
    );
    return { orderId, user };
  }
}

์œ„ ์ฝ”๋“œ์—์„œ timeout(3000)์€ 3์ดˆ ์ด๋‚ด์— ์‘๋‹ต์ด ์—†์œผ๋ฉด TimeoutError๋ฅผ ๋ฐœ์ƒ์‹œํ‚ต๋‹ˆ๋‹ค. retry ์—ฐ์‚ฐ์ž๋Š” ์ตœ๋Œ€ 2ํšŒ ์žฌ์‹œ๋„๋ฅผ ์ˆ˜ํ–‰ํ•˜๋ฉฐ, 1000 * retryCount๋กœ ์ง€์ˆ˜ ๋ฐฑ์˜คํ”„๋ฅผ, Math.random() * 100์œผ๋กœ ์ง€ํ„ฐ๋ฅผ ์ ์šฉํ•ฉ๋‹ˆ๋‹ค. ์ง€ํ„ฐ๋Š” ๋‹ค์ˆ˜์˜ ํด๋ผ์ด์–ธํŠธ๊ฐ€ ๋™์‹œ์— ์žฌ์‹œ๋„ํ•˜๋ฉด์„œ ๋ฐœ์ƒํ•˜๋Š” ์ฌ๋”๋ง ํ—ˆ๋“œ(Thundering Herd) ๋ฌธ์ œ๋ฅผ ์™„ํ™”ํ•ฉ๋‹ˆ๋‹ค.

์„œํ‚ท ๋ธŒ๋ ˆ์ด์ปค ํŒจํ„ด์€ ์—ฐ์†์ ์ธ ์‹คํŒจ๊ฐ€ ๊ฐ์ง€๋˜๋ฉด ํ•ด๋‹น ์„œ๋น„์Šค๋กœ์˜ ์š”์ฒญ์„ ์ฆ‰์‹œ ์ฐจ๋‹จํ•˜์—ฌ ์žฅ์•  ์ „ํŒŒ๋ฅผ ๋ฐฉ์ง€ํ•ฉ๋‹ˆ๋‹ค. Closed(์ •์ƒ), Open(์ฐจ๋‹จ), Half-Open(์‹œํ—˜) ์„ธ ๊ฐ€์ง€ ์ƒํƒœ๋กœ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค. NestJS์—์„œ๋Š” opossum ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋‚˜ @nestjs/terminus ํ—ฌ์Šค ์ฒดํฌ ๋ชจ๋“ˆ๊ณผ ๊ฒฐํ•ฉํ•˜์—ฌ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์žฌ์‹œ๋„ ํŒจํ„ด๊ณผ ๋ฉฑ๋“ฑ์„ฑ(Idempotency)

์žฌ์‹œ๋„ ํŒจํ„ด์„ ์ ์šฉํ•  ๋•Œ๋Š” ํ•ด๋‹น ์ž‘์—…์ด ๋ฉฑ๋“ฑ์„ฑ์„ ๋ณด์žฅํ•˜๋Š”์ง€ ๋ฐ˜๋“œ์‹œ ํ™•์ธํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ฃผ๋ฌธ ์ƒ์„ฑ์ฒ˜๋Ÿผ ๋ถ€์ˆ˜ ํšจ๊ณผ๊ฐ€ ์žˆ๋Š” ์ž‘์—…์„ ๋ฉฑ๋“ฑ์„ฑ ํ‚ค ์—†์ด ์žฌ์‹œ๋„ํ•˜๋ฉด ์ค‘๋ณต ์ฃผ๋ฌธ์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์กฐํšŒ ์ž‘์—…์€ ๋ณธ์งˆ์ ์œผ๋กœ ๋ฉฑ๋“ฑํ•˜์ง€๋งŒ, ๋ณ€๊ฒฝ ์ž‘์—…์—๋Š” ๋ณ„๋„์˜ ๋ฉฑ๋“ฑ์„ฑ ๋ฉ”์ปค๋‹ˆ์ฆ˜์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

NestJS ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ๋ฉด์ ‘ ์งˆ๋ฌธ

๊ธฐ์ˆ  ๋ฉด์ ‘์—์„œ ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ์•„ํ‚คํ…์ฒ˜๋Š” ์‹œ๋‹ˆ์–ด ๊ฐœ๋ฐœ์ž์™€ ๋ฐฑ์—”๋“œ ์—”์ง€๋‹ˆ์–ด์—๊ฒŒ ํ•ต์‹ฌ ํ‰๊ฐ€ ํ•ญ๋ชฉ์ž…๋‹ˆ๋‹ค. ๋‹ค์Œ์€ NestJS ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ๊ด€๋ จ ๋นˆ์ถœ ์งˆ๋ฌธ๊ณผ ๊ทธ์— ๋Œ€ํ•œ ๋ชจ๋ฒ” ๋‹ต๋ณ€์ž…๋‹ˆ๋‹ค.

Q1: NestJS์—์„œ @MessagePattern๊ณผ @EventPattern์˜ ์ฐจ์ด์ ์„ ์„ค๋ช…ํ•˜์‹ญ์‹œ์˜ค.

@MessagePattern์€ ์š”์ฒญ-์‘๋‹ต ํŒจํ„ด์„ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค. ํ˜ธ์ถœ์ž๊ฐ€ ์‘๋‹ต์„ ์ˆ˜์‹ ํ•  ๋•Œ๊นŒ์ง€ ๋Œ€๊ธฐํ•˜๋ฏ€๋กœ, ๋ฐ์ดํ„ฐ ์กฐํšŒ๋‚˜ ๋™๊ธฐ์  ์ฒ˜๋ฆฌ๊ฐ€ ํ•„์š”ํ•œ ์ž‘์—…์— ์ ํ•ฉํ•ฉ๋‹ˆ๋‹ค. @EventPattern์€ ์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜ ๋น„๋™๊ธฐ ํ†ต์‹ ์„ ๊ตฌํ˜„ํ•˜๋ฉฐ, ๋ฐœ์‹ ์ž๋Š” ์‘๋‹ต์„ ๊ธฐ๋‹ค๋ฆฌ์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์•Œ๋ฆผ ์ „์†ก, ๊ฐ์‚ฌ ๋กœ๊ทธ ๊ธฐ๋ก, ๋น„๋™๊ธฐ ์ƒํƒœ ์—…๋ฐ์ดํŠธ์— ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. ์„ ํƒ ๊ธฐ์ค€์€ ๋ฐ์ดํ„ฐ ์ผ๊ด€์„ฑ ์š”๊ตฌ ์ˆ˜์ค€๊ณผ ์„œ๋น„์Šค ๊ฐ„ ๊ฒฐํ•ฉ๋„ ํ—ˆ์šฉ ๋ฒ”์œ„์— ๋”ฐ๋ผ ๊ฒฐ์ •๋ฉ๋‹ˆ๋‹ค.

Q2: gRPC๊ฐ€ ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ๋‚ด๋ถ€ ํ†ต์‹ ์—์„œ REST๋ณด๋‹ค ์œ ๋ฆฌํ•œ ์ ์€ ๋ฌด์—‡์ž…๋‹ˆ๊นŒ?

gRPC๋Š” Protocol Buffers๋ฅผ ์‚ฌ์šฉํ•œ ๋ฐ”์ด๋„ˆ๋ฆฌ ์ง๋ ฌํ™”๋กœ JSON ๋Œ€๋น„ ํŽ˜์ด๋กœ๋“œ ํฌ๊ธฐ๊ฐ€ ์ž‘๊ณ  ํŒŒ์‹ฑ ์†๋„๊ฐ€ ๋น ๋ฆ…๋‹ˆ๋‹ค. HTTP/2 ๊ธฐ๋ฐ˜์œผ๋กœ ๋‹จ์ผ ์—ฐ๊ฒฐ์—์„œ ์—ฌ๋Ÿฌ ์š”์ฒญ์„ ๋™์‹œ์— ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฉ€ํ‹ฐํ”Œ๋ ‰์‹ฑ์„ ์ง€์›ํ•˜๋ฉฐ, ํ—ค๋” ์••์ถ•(HPACK)์œผ๋กœ ๋„คํŠธ์›Œํฌ ์˜ค๋ฒ„ํ—ค๋“œ๋ฅผ ์ค„์ž…๋‹ˆ๋‹ค. .proto ํŒŒ์ผ์„ ํ†ตํ•œ ๊ณ„์•ฝ ์šฐ์„ (Contract-First) ์„ค๊ณ„๋กœ ํƒ€์ž… ์•ˆ์ „์„ฑ์„ ๋ณด์žฅํ•˜๊ณ , ์„œ๋ฒ„/ํด๋ผ์ด์–ธํŠธ ์ŠคํŠธ๋ฆฌ๋ฐ ๋ฐ ์–‘๋ฐฉํ–ฅ ์ŠคํŠธ๋ฆฌ๋ฐ์„ ๋„ค์ดํ‹ฐ๋ธŒ๋กœ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.

Q3: ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ํ™˜๊ฒฝ์—์„œ ๋ถ„์‚ฐ ํŠธ๋žœ์žญ์…˜์€ ์–ด๋–ป๊ฒŒ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๊นŒ?

๋ถ„์‚ฐ ํ™˜๊ฒฝ์—์„œ 2PC(Two-Phase Commit)๋Š” ์„ฑ๋Šฅ๊ณผ ๊ฐ€์šฉ์„ฑ ๋ฌธ์ œ๊ฐ€ ์žˆ์œผ๋ฏ€๋กœ, ์‚ฌ๊ฐ€(Saga) ํŒจํ„ด์ด ์‚ฌ์‹ค์ƒ ํ‘œ์ค€์ž…๋‹ˆ๋‹ค. ์‚ฌ๊ฐ€๋Š” ๊ฐ ์„œ๋น„์Šค์˜ ๋กœ์ปฌ ํŠธ๋žœ์žญ์…˜์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์‹คํ–‰ํ•˜๊ณ , ํŠน์ • ๋‹จ๊ณ„๊ฐ€ ์‹คํŒจํ•˜๋ฉด ์ด์ „ ๋‹จ๊ณ„์˜ ๋ณด์ƒ ํŠธ๋žœ์žญ์…˜(Compensating Transaction)์„ ์‹คํ–‰ํ•˜์—ฌ ์ตœ์ข…์  ์ผ๊ด€์„ฑ(Eventual Consistency)์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค. ์ฝ”๋ ˆ์˜ค๊ทธ๋ž˜ํ”ผ ๋ฐฉ์‹์€ ์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜์œผ๋กœ ๊ฐ ์„œ๋น„์Šค๊ฐ€ ์ž์œจ์ ์œผ๋กœ ๋ฐ˜์‘ํ•˜๋ฉฐ, ์˜ค์ผ€์ŠคํŠธ๋ ˆ์ด์…˜ ๋ฐฉ์‹์€ ์ค‘์•™ ์ฝ”๋””๋„ค์ดํ„ฐ๊ฐ€ ์›Œํฌํ”Œ๋กœ์šฐ๋ฅผ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค. ๋ณต์žกํ•œ ๋น„์ฆˆ๋‹ˆ์Šค ํ”„๋กœ์„ธ์Šค์—์„œ๋Š” ์˜ค์ผ€์ŠคํŠธ๋ ˆ์ด์…˜์ด ๋””๋ฒ„๊น…๊ณผ ๋ชจ๋‹ˆํ„ฐ๋ง ์ธก๋ฉด์—์„œ ์œ ๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

Q4: ์„œํ‚ท ๋ธŒ๋ ˆ์ด์ปค ํŒจํ„ด์˜ ์„ธ ๊ฐ€์ง€ ์ƒํƒœ์™€ ์ „ํ™˜ ์กฐ๊ฑด์„ ์„ค๋ช…ํ•˜์‹ญ์‹œ์˜ค.

Closed ์ƒํƒœ์—์„œ๋Š” ๋ชจ๋“  ์š”์ฒญ์ด ๋Œ€์ƒ ์„œ๋น„์Šค๋กœ ์ •์ƒ ์ „๋‹ฌ๋ฉ๋‹ˆ๋‹ค. ์‹คํŒจ์œจ์ด ์„ค์ •๋œ ์ž„๊ณ„๊ฐ’(์˜ˆ: 50%)์„ ์ดˆ๊ณผํ•˜๋ฉด Open ์ƒํƒœ๋กœ ์ „ํ™˜๋˜๋ฉฐ, ๋ชจ๋“  ์š”์ฒญ์ด ์ฆ‰์‹œ ์‹คํŒจ ์ฒ˜๋ฆฌ๋ฉ๋‹ˆ๋‹ค. ์„ค์ •๋œ ๋Œ€๊ธฐ ์‹œ๊ฐ„์ด ๊ฒฝ๊ณผํ•˜๋ฉด Half-Open ์ƒํƒœ๋กœ ์ „ํ™˜๋˜์–ด, ์ œํ•œ๋œ ์ˆ˜์˜ ์‹œํ—˜ ์š”์ฒญ์„ ๋ณด๋ƒ…๋‹ˆ๋‹ค. ์‹œํ—˜ ์š”์ฒญ์ด ์„ฑ๊ณตํ•˜๋ฉด Closed๋กœ ๋ณต๊ท€ํ•˜๊ณ , ์‹คํŒจํ•˜๋ฉด ๋‹ค์‹œ Open ์ƒํƒœ๋กœ ์ „ํ™˜๋ฉ๋‹ˆ๋‹ค. ์ด ํŒจํ„ด์€ ์žฅ์•  ์„œ๋น„์Šค์— ๋Œ€ํ•œ ๋ถˆํ•„์š”ํ•œ ์š”์ฒญ์„ ์ฐจ๋‹จํ•˜์—ฌ ์ „์ฒด ์‹œ์Šคํ…œ์˜ ์‘๋‹ต ์‹œ๊ฐ„๊ณผ ๋ฆฌ์†Œ์Šค๋ฅผ ๋ณดํ˜ธํ•ฉ๋‹ˆ๋‹ค.

Q5: NestJS ํ•˜์ด๋ธŒ๋ฆฌ๋“œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์‹ค๋ฌด ํ™œ์šฉ ์‹œ๋‚˜๋ฆฌ์˜ค๋ฅผ ์„ค๋ช…ํ•˜์‹ญ์‹œ์˜ค.

API ๊ฒŒ์ดํŠธ์›จ์ด ํŒจํ„ด์ด ๋Œ€ํ‘œ์ ์ž…๋‹ˆ๋‹ค. HTTP ์—”๋“œํฌ์ธํŠธ๋Š” ์™ธ๋ถ€ ํด๋ผ์ด์–ธํŠธ์— RESTful API๋ฅผ ์ œ๊ณตํ•˜๊ณ , gRPC๋Š” ๋‚ด๋ถ€ ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ๊ฐ„ ๊ณ ์„ฑ๋Šฅ ํ†ต์‹ ์„ ๋‹ด๋‹นํ•ฉ๋‹ˆ๋‹ค. ๋ ˆ๊ฑฐ์‹œ REST API๋ฅผ ์œ ์ง€ํ•˜๋ฉด์„œ ์ƒˆ๋กœ์šด ๋‚ด๋ถ€ ํ†ต์‹ ์„ gRPC๋กœ ๊ตฌ์ถ•ํ•˜๋Š” ์ ์ง„์  ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์—๋„ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค. ๋‹จ์ผ ๋ฐฐํฌ ๋‹จ์œ„์—์„œ ์—ฌ๋Ÿฌ ํ”„๋กœํ† ์ฝœ์„ ์ง€์›ํ•ด์•ผ ํ•˜๊ฑฐ๋‚˜, ์šด์˜ ๋ณต์žก์„ฑ์„ ์ค„์ด๋ฉด์„œ ์„ฑ๋Šฅ์„ ๊ฐœ์„ ํ•ด์•ผ ํ•˜๋Š” ์ƒํ™ฉ์—์„œ ์ ํ•ฉํ•œ ์„ ํƒ์ž…๋‹ˆ๋‹ค.

Q6: ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ๊ฒฝ๊ณ„๋ฅผ ์ •์˜ํ•  ๋•Œ ๊ฐ€์žฅ ์ค‘์š”ํ•œ ์›์น™์€ ๋ฌด์—‡์ž…๋‹ˆ๊นŒ?

๋„๋ฉ”์ธ ์ฃผ๋„ ์„ค๊ณ„์˜ ๋ฐ”์šด๋””๋“œ ์ปจํ…์ŠคํŠธ๋ฅผ ๊ธฐ์ค€์œผ๋กœ ์„œ๋น„์Šค๋ฅผ ๋ถ„๋ฆฌํ•˜๋Š” ๊ฒƒ์ด ํ•ต์‹ฌ์ž…๋‹ˆ๋‹ค. ๊ฐ ์„œ๋น„์Šค๋Š” ๋…๋ฆฝ์ ์œผ๋กœ ๋ฐฐํฌ ๊ฐ€๋Šฅํ•ด์•ผ ํ•˜๋ฉฐ, ์ž์ฒด ๋ฐ์ดํ„ฐ ์ €์žฅ์†Œ๋ฅผ ๋ณด์œ ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค(Database-per-Service ํŒจํ„ด). ์„œ๋น„์Šค ๊ฐ„ ๋ฐ์ดํ„ฐ ๊ณต์œ ๋Š” API๋ฅผ ํ†ตํ•ด์„œ๋งŒ ์ด๋ฃจ์–ด์ ธ์•ผ ํ•˜๋ฉฐ, ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ์ง์ ‘ ๊ณต์œ ํ•˜๋ฉด ๊ฒฐํ•ฉ๋„๊ฐ€ ๋†’์•„์ ธ ๋…๋ฆฝ์  ๋ฐฐํฌ๊ฐ€ ๋ถˆ๊ฐ€๋Šฅํ•ด์ง‘๋‹ˆ๋‹ค. ๊ฒฝ๊ณ„๊ฐ€ ์˜ฌ๋ฐ”๋ฅด์ง€ ์•Š์œผ๋ฉด ์„œ๋น„์Šค ๊ฐ„ ๊ณผ๋„ํ•œ ํ†ต์‹ ์ด ๋ฐœ์ƒํ•˜์—ฌ ๋ถ„์‚ฐ ๋ชจ๋†€๋ฆฌ์Šค๋กœ ์ „๋ฝํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์—ฐ์Šต์„ ์‹œ์ž‘ํ•˜์„ธ์š”!

๋ฉด์ ‘ ์‹œ๋ฎฌ๋ ˆ์ดํ„ฐ์™€ ๊ธฐ์ˆ  ํ…Œ์ŠคํŠธ๋กœ ์ง€์‹์„ ํ…Œ์ŠคํŠธํ•˜์„ธ์š”.

๊ฒฐ๋ก 

NestJS ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ์•„ํ‚คํ…์ฒ˜๋Š” Node.js ๊ธฐ๋ฐ˜ ๋ถ„์‚ฐ ์‹œ์Šคํ…œ ๊ตฌ์ถ•์— ํ•„์š”ํ•œ ํ•ต์‹ฌ ๋„๊ตฌ์™€ ํŒจํ„ด์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ์ด ๊ธ€์—์„œ ๋‹ค๋ฃฌ ๋‚ด์šฉ์„ ์š”์•ฝํ•˜๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

  • ํŠธ๋žœ์ŠคํฌํŠธ ๋ ˆ์ด์–ด ์ถ”์ƒํ™”: NestJS๋Š” TCP, Redis, NATS, RabbitMQ, Kafka, gRPC ๋“ฑ ๋‹ค์–‘ํ•œ ์ „์†ก ๊ณ„์ธต์„ ๋™์ผํ•œ ์ธํ„ฐํŽ˜์ด์Šค๋กœ ์ถ”์ƒํ™”ํ•˜์—ฌ, ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ๋ณ€๊ฒฝ ์—†์ด ํ”„๋กœํ† ์ฝœ์„ ๊ต์ฒดํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค
  • ํ†ต์‹  ํŒจํ„ด ์„ ํƒ: @MessagePattern์€ ๋™๊ธฐ์  ์š”์ฒญ-์‘๋‹ต์—, @EventPattern์€ ๋น„๋™๊ธฐ ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ์— ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. ๋ฐ์ดํ„ฐ ์ผ๊ด€์„ฑ ์š”๊ตฌ์‚ฌํ•ญ์— ๋”ฐ๋ผ ์ ์ ˆํ•œ ํŒจํ„ด์„ ์„ ํƒํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค
  • gRPC ํŠธ๋žœ์ŠคํฌํŠธ: Protocol Buffers ๊ธฐ๋ฐ˜์˜ ํƒ€์ž… ์•ˆ์ „ํ•œ ๊ณ ์„ฑ๋Šฅ ํ†ต์‹ ์„ ์ œ๊ณตํ•˜๋ฉฐ, ์„œ๋ฒ„ ์ŠคํŠธ๋ฆฌ๋ฐ๊ณผ ์–‘๋ฐฉํ–ฅ ์ŠคํŠธ๋ฆฌ๋ฐ์œผ๋กœ ๋Œ€๋Ÿ‰ ๋ฐ์ดํ„ฐ ์ „์†ก๊ณผ ์‹ค์‹œ๊ฐ„ ํ†ต์‹ ์„ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค
  • ํ•˜์ด๋ธŒ๋ฆฌ๋“œ ์•„ํ‚คํ…์ฒ˜: ๋‹จ์ผ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ HTTP์™€ gRPC๋ฅผ ๋™์‹œ์— ์šด์˜ํ•˜์—ฌ, ์™ธ๋ถ€์šฉ REST API์™€ ๋‚ด๋ถ€์šฉ ๊ณ ์„ฑ๋Šฅ gRPC๋ฅผ ํšจ์œจ์ ์œผ๋กœ ๋ถ„๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค
  • ๋„๋ฉ”์ธ ์ฃผ๋„ ์„ค๊ณ„ ๊ธฐ๋ฐ˜ ์„œ๋น„์Šค ๊ฒฝ๊ณ„: ๋ฐ”์šด๋””๋“œ ์ปจํ…์ŠคํŠธ๋ฅผ ๊ธฐ์ค€์œผ๋กœ ์„œ๋น„์Šค๋ฅผ ๋ถ„๋ฆฌํ•˜๊ณ , ๋„๋ฉ”์ธ ์ด๋ฒคํŠธ๋ฅผ ํ†ตํ•œ ๋А์Šจํ•œ ๊ฒฐํ•ฉ์œผ๋กœ ๋…๋ฆฝ์  ๋ฐฐํฌ์™€ ํ™•์žฅ์„ ๋‹ฌ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค
  • ์•ˆ์ •์„ฑ ํŒจํ„ด ์ ์šฉ: ํƒ€์ž„์•„์›ƒ, ์ง€์ˆ˜ ๋ฐฑ์˜คํ”„ ์žฌ์‹œ๋„, ์„œํ‚ท ๋ธŒ๋ ˆ์ด์ปค ํŒจํ„ด์„ ์กฐํ•ฉํ•˜์—ฌ ์—ฐ์‡„ ์‹คํŒจ๋ฅผ ๋ฐฉ์ง€ํ•˜๊ณ  ์‹œ์Šคํ…œ ๋ณต์›๋ ฅ์„ ํ™•๋ณดํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค
  • ๋ฉด์ ‘ ๋Œ€๋น„: ํŠธ๋žœ์ŠคํฌํŠธ ํŒจํ„ด, gRPC์˜ ๊ธฐ์ˆ ์  ์žฅ์ , ์‚ฌ๊ฐ€ ํŒจํ„ด, ์„œํ‚ท ๋ธŒ๋ ˆ์ด์ปค ์ƒํƒœ ์ „์ด, ์„œ๋น„์Šค ๊ฒฝ๊ณ„ ์„ค๊ณ„์— ๋Œ€ํ•œ ์‹ค๋ฌด ์ˆ˜์ค€์˜ ์ดํ•ด๊ฐ€ ์š”๊ตฌ๋ฉ๋‹ˆ๋‹ค

ํƒœ๊ทธ

#nestjs
#microservices
#grpc
#node.js
#typescript
#architecture

๊ณต์œ 

๊ด€๋ จ ๊ธฐ์‚ฌ

๊ธฐ์ˆ  ๋ฉด์ ‘์„ ์œ„ํ•œ NestJS Guards, Interceptors, ๋ชจ๋“ˆํ˜• ์•„ํ‚คํ…์ฒ˜

NestJS ๋ฉด์ ‘: Guards, Interceptors, ๋ชจ๋“ˆํ˜• ์•„ํ‚คํ…์ฒ˜

Guards, Interceptors, ๋ชจ๋“ˆํ˜• ์•„ํ‚คํ…์ฒ˜์— ๊ด€ํ•œ NestJS ๊ธฐ์ˆ  ๋ฉด์ ‘์˜ ๋นˆ์ถœ ์งˆ๋ฌธ์„ ๊ตฌ์ฒด์ ์ธ TypeScript ์ฝ”๋“œ ์˜ˆ์ œ์™€ ๊ธฐ์ˆ ์  ์„ค๋ช…๊ณผ ํ•จ๊ป˜ ๋‹ค๋ฃน๋‹ˆ๋‹ค.

๋ชจ๋˜ํ•œ ๋ฐฑ์—”๋“œ ์Šคํƒ ๊ตฌ์ถ•์„ ์œ„ํ•œ NestJS์™€ Prisma

NestJS + Prisma: Node.js๋ฅผ ์œ„ํ•œ ๋ชจ๋˜ ๋ฐฑ์—”๋“œ ์Šคํƒ

NestJS์™€ Prisma๋กœ ๋ชจ๋˜ํ•œ ๋ฐฑ์—”๋“œ API๋ฅผ ๊ตฌ์ถ•ํ•˜๊ธฐ ์œ„ํ•œ ์™„์ „ํ•œ ๊ฐ€์ด๋“œ์ž…๋‹ˆ๋‹ค. ์„ค์ •, ๋ชจ๋ธ, ์„œ๋น„์Šค, ํŠธ๋žœ์žญ์…˜ ๋ฐ ๋ชจ๋ฒ” ์‚ฌ๋ก€๋ฅผ ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค.

NestJS๋กœ ์™„์ „ํ•œ REST API๋ฅผ ๊ตฌ์ถ•ํ•˜๊ธฐ ์œ„ํ•œ ๊ฐ€์ด๋“œ

NestJS: ์™„์ „ํ•œ REST API ๊ตฌ์ถ• ๊ฐ€์ด๋“œ

NestJS๋กœ ์ „๋ฌธ์ ์ธ REST API๋ฅผ ๊ตฌ์ถ•ํ•˜๋Š” ์™„๋ฒฝ ๊ฐ€์ด๋“œ์ž…๋‹ˆ๋‹ค. ์ปจํŠธ๋กค๋Ÿฌ, ์„œ๋น„์Šค, ๋ชจ๋“ˆ ๊ตฌ์„ฑ, class-validator๋ฅผ ํ™œ์šฉํ•œ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ, ์—๋Ÿฌ ํ•ธ๋“ค๋ง์„ ์‹ค์ „ ์ฝ”๋“œ๋กœ ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค.