본문으로 건너뛰기

안동민 개발노트

본문 시작

gRPC를 이용한 서비스 간 통신

Protocol Buffers로 서비스 계약을 정의하고 NestJS gRPC 서버와 클라이언트를 연결해 타입 기반 RPC를 실행합니다.

지난 절에서는 NestJS로 기본 마이크로서비스를 만들고 TCP 기반 서비스 간 통신을 확인했습니다.

이번 절에서는 마이크로서비스 환경에서 gRPC를 NestJS에 적용하는 방법을 살펴봅니다.

마이크로서비스 간 통신은 아키텍처의 핵심 요소이며, 선택하는 통신 프로토콜은 시스템의 성능과 효율성에 큰 영향을 미칩니다.

HTTP/1.1 기반의 RESTful API는 널리 사용되지만, 고성능이나 실시간 통신이 필요한 경우에는 한계가 있습니다.

gRPC는 이러한 요구사항을 충족시키기 위해 설계된 현대적인 RPC(Remote Procedure Call) 프레임워크입니다.

gRPC는 서비스 사이 계약을 빠른 RPC 호출로 묶는다

REST가 URL과 JSON 중심이라면, gRPC는 .proto 계약에서 서버 메서드와 메시지 타입을 먼저 정하고 HTTP/2로 호출합니다.

  1. 외부 공개 API에 익숙함

    REST 외부 공개 API에 익숙함 브라우저와 사람이 읽는 JSON 통신에 강하다.

  2. 내부 서비스 간 고성능 호출

    gRPC 내부 서비스 간 고성능 호출 타입 계약, binary payload, streaming에 강하다.

  3. 브라우저 직접 호출은 별도 계층

    주의 브라우저 직접 호출은 별도 계층 API Gateway나 gRPC-Web 경계를 함께 설계한다.

  4. .proto 계약

    service UserService rpc GetUserById(...) 팀이 공유하는 메서드와 메시지 기준입니다.

  5. stub / proxy

    client.getService<UserService>() 클라이언트는 계약에서 만든 프록시로 호출합니다.

  6. HTTP/2 RPC

    GetUserById({ id: 1 }) 서버는 같은 계약의 handler로 응답합니다.


gRPC란 무엇인가?

gRPC는 Google에서 개발한 오픈소스 고성능 RPC(Remote Procedure Call) 프레임워크입니다.

HTTP/2 프로토콜을 기반으로 하며, 프로토콜 버퍼(Protocol Buffers)를 인터페이스 정의 언어(IDL)로 사용하여 서비스 인터페이스를 정의합니다.

gRPC의 주요 특징
  • HTTP/2 기반: HTTP/2는 다음과 같은 이점을 제공합니다.
    • 멀티플렉싱(Multiplexing): 단일 TCP 연결에서 여러 요청/응답 스트림을 동시에 처리할 수 있어, 네트워크 효율성이 높습니다.
    • 헤더 압축(Header Compression): HTTP 헤더를 효율적으로 압축하여 대역폭 사용량을 줄입니다.
    • 서버 푸시(Server Push): 클라이언트가 요청하지 않아도 서버가 필요한 리소스를 미리 푸시할 수 있습니다.
  • 프로토콜 버퍼(Protocol Buffers)
    • 언어 중립적이고 플랫폼 중립적인 직렬화 메커니즘입니다.
    • 데이터를 이진 형태로 직렬화하여 JSON보다 훨씬 작고 빠릅니다.
    • .proto 파일을 사용하여 서비스 인터페이스와 메시지 구조를 정의하고, 다양한 언어로 클라이언트 및 서버 스텁 코드를 자동으로 생성할 수 있습니다.
  • 스트리밍(Streaming): 단항(Unary) 호출 외에도 클라이언트 스트리밍, 서버 스트리밍, 양방향 스트리밍을 지원하여 실시간 통신 및 대용량 데이터 전송에 효율적입니다.
  • 타입 시스템: .proto 파일에 정의된 명확한 스키마 덕분에 컴파일 시점에 타입 안정성을 보장하고, 클라이언트-서버 간 계약이 명확합니다.
  • 언어 다양성: Go, Java, Python, Node.js, C++ 등 다양한 프로그래밍 언어를 지원하여 마이크로서비스 환경에서 폴리글랏(Polyglot) 아키텍처 구현에 용이합니다.
gRPC가 RESTful API보다 유리한 경우
  • 서비스 간 고성능 통신이 필요한 경우 (낮은 지연 시간, 높은 처리량)
  • 대용량 데이터 스트리밍이 필요한 경우
  • 폴리글랏(Polyglot) 마이크로서비스 아키텍처를 구축하는 경우
  • 명확한 인터페이스 정의를 통해 팀 간 협업을 강화하려는 경우

gRPC의 장점은 .proto 계약이 서버 구현, 클라이언트 프록시, 런타임 호출까지 같은 기준으로 이어질 때 드러납니다.

다음 다이어그램은 계약 변경이 어디까지 영향을 주는지 추적하는 기준입니다.

.proto 변경은 서버, 클라이언트, 런타임 호출까지 번진다

gRPC의 강점은 하나의 계약이 여러 산출물에 동시에 적용된다는 점입니다. 그래서 계약 변경의 영향 범위를 먼저 봐야 합니다.

  1. 1. proto file

    message User { int32 id = 1; } 필드 번호와 타입이 호환성 기준입니다.

  2. 2. generated type

    UserServiceClient UserServiceController 프록시와 handler 타입이 계약에서 만들어집니다.

  3. 3. runtime call

    GetUserById({ id }) 요청/응답 payload가 같은 이름과 타입으로 흐릅니다.

변경영향먼저 확인할 곳
필드 추가기본값과 optional 처리message schema
필드 번호 변경이전 payload 해석 실패호환성 테스트
service/method 이름 변경프록시와 handler 매핑 실패Nest decorator와 client token

NestJS에서 gRPC 설정 및 구현

gRPC 서비스 간 통신은 proto 계약, 채널 설정, 메서드 타입, deadline, 오류 상태 매핑 기준으로 읽습니다.

서버와 클라이언트는 같은 proto, package, url을 본다

NestJS gRPC 구현은 서버 microservice 설정과 클라이언트 proxy 설정이 같은 계약 파일을 가리키는지부터 확인합니다.

  1. Users service
    gRPC 서버

    Users service transport: Transport.GRPC package: "users" url: "localhost:50051" handler는 @GrpcMethod 로 proto method에 매핑됩니다.

  2. Orders service
    gRPC 클라이언트

    Orders service ClientsModule.register name: "USERS_SERVICE" transport: Transport.GRPC ClientGrpc에서 UserService proxy를 꺼내 호출합니다.

  3. 공유 계약
    protoPath와 package

    공유 계약 proto/users.proto package users 경로, package 이름, service 이름이 어긋나면 연결은 되어도 메서드를 찾지 못합니다.

  4. 호출 처리
    Observable 응답

    호출 처리 lastValueFrom( userService.GetUserById({ id }) ) Nest gRPC 클라이언트 호출은 Observable을 반환하므로 응답 처리 기준을 정합니다.

NestJS는 @nestjs/microservices 패키지를 통해 gRPC 전송 계층을 지원합니다.

시나리오: 이전 절과 동일하게 Users 서비스와 Orders 서비스가 있으며, Orders 서비스가 gRPC를 통해 Users 서비스의 사용자 정보를 요청합니다.

Protobuf 파일 정의

가장 먼저 gRPC 서비스의 인터페이스와 메시지 구조를 정의하는 .proto 파일을 생성합니다.

이 파일은 클라이언트와 서버 간의 계약서 역할을 합니다.

// proto/users.proto (프로젝트 루트 또는 shared/proto 폴더에 생성)

syntax = "proto3"; // Protocol Buffers 3 문법 사용

package users; // 패키지 이름

// UserService 정의
service UserService {
  // GetUserById RPC 메서드 정의
  // UserByIdRequest 메시지를 받아 User 메시지를 반환
  rpc GetUserById (UserByIdRequest) returns (User);
  // CreateUser RPC 메서드 정의
  // CreateUserRequest 메시지를 받아 User 메시지를 반환
  rpc CreateUser (CreateUserRequest) returns (User);
}

// UserByIdRequest 메시지 정의
message UserByIdRequest {
  int32 id = 1; // 필드 번호 1
}

// CreateUserRequest 메시지 정의
message CreateUserRequest {
  string name = 1;
  string email = 2;
}

// User 메시지 정의 (응답 및 반환 타입)
message User {
  int32 id = 1;
  string name = 2;
  string email = 3;
}
  • syntax = "proto3";: Protocol Buffers 3 문법을 사용합니다.
  • package users;: 네임스페이스를 정의하여 메시지 이름 충돌을 방지합니다.
  • service UserService { ... }: gRPC 서비스와 그 안에 포함될 RPC 메서드를 정의합니다.
  • rpc MethodName (RequestMessage) returns (ResponseMessage);: RPC 메서드를 정의합니다.
  • message MessageName { ... }: 데이터 구조를 정의합니다. 각 필드는 타입, 이름, 그리고 고유한 필드 번호를 가집니다.
proto 파일은 서비스 이름, 메서드, 메시지 번호를 함께 읽는다

gRPC 계약은 TypeScript 인터페이스보다 더 엄격합니다. 필드 번호와 RPC method가 런타임 payload 해석 기준이 됩니다.

  1. users.proto 핵심 조각

    syntax = "proto3"; package users; service UserService { rpc GetUserById (UserByIdRequest) returns (User); } message User { int32 id = 1; string name = 2; string email = 3; }

  2. 읽는 순서

    package Nest option의 package와 일치 service getService와 @GrpcMethod의 기준 rpc 요청 message와 응답 message를 연결 field number binary payload의 호환성 기준

gRPC 사용자 서비스 구축

단계 1: 필요한 패키지 설치

users-service 프로젝트에서 설치합니다.

npm install @nestjs/microservices @grpc/grpc-js @grpc/proto-loader
npm install --save-dev @types/grpc__proto-loader
  • @grpc/grpc-js: gRPC Node.js 핵심 라이브러리입니다.
  • @grpc/proto-loader: .proto 파일을 로드하고 JavaScript 객체로 변환하는 유틸리티입니다.
단계 2: main.ts 파일 수정 (gRPC 마이크로서비스 서버 설정)
users-service/src/main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { MicroserviceOptions, Transport } from '@nestjs/microservices';
import { join } from 'path'; // path 모듈 임포트

async function bootstrap() {
  const app = await NestFactory.createMicroservice<MicroserviceOptions>(AppModule, {
    transport: Transport.GRPC, // gRPC 전송 방식 사용
    options: {
      package: 'users', // .proto 파일에 정의된 패키지 이름
      protoPath: join(__dirname, '../../proto/users.proto'), // .proto 파일 경로
      url: 'localhost:50051', // gRPC 서버가 리스닝할 주소와 포트
      loader: {
        keepCase: true, // 필드 이름을 스네이크 케이스로 유지 (선택 사항)
        longs: String, // 64비트 정수를 문자열로 처리 (선택 사항, 데이터 정밀도 이슈 방지)
        enums: String, // enum 값을 문자열로 처리 (선택 사항)
        defaults: true, // 기본값 포함 (선택 사항)
        oneofs: true, // oneof 필드 처리 (선택 사항)
      },
    },
  });
  await app.listen();
  console.log('Users gRPC Microservice is listening on localhost:50051');
}
bootstrap();
  • transport: Transport.GRPC: gRPC 전송 방식을 사용합니다.
  • package: .proto 파일의 package 이름과 일치해야 합니다.
  • protoPath: .proto 파일의 실제 경로를 지정합니다. join(__dirname, '../../proto/users.proto')users-service/src에서 두 단계 위로 올라가 proto 폴더를 찾는 경로입니다. 필요에 따라 조정하세요.
  • url: gRPC 서버가 바인딩될 주소입니다.
  • loader: proto-loader의 추가 옵션으로, 데이터 타입 변환 및 필드명 처리 방식을 설정할 수 있습니다.
단계 3: 사용자 마이크로서비스 컨트롤러 (gRPC 핸들러) 구현

@GrpcMethod() 데코레이터를 사용하여 gRPC 메서드를 구현합니다.

users-service/src/users/users.controller.ts (수정)
import { Controller } from '@nestjs/common';
import { GrpcMethod } from '@nestjs/microservices'; // GrpcMethod 임포트
import { UsersService } from './users.service';

interface User { // proto 파일과 일치하도록 타입 정의 (필요시)
  id: number;
  name: string;
  email: string;
}

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

  // .proto 파일의 UserService.GetUserById 메서드에 매핑
  @GrpcMethod('UserService', 'GetUserById')
  getUserById(data: { id: number }): User { // data는 UserByIdRequest 메시지
    console.log(`Users gRPC Service: Received request for user ID: ${data.id}`);
    const user = this.usersService.findOne(data.id);
    if (!user) {
      // gRPC는 에러 코드와 메시지를 통해 에러를 전달합니다.
      // throw new RpcException({ code: status.NOT_FOUND, message: 'User not found' });
      // 간단한 예제에서는 null 반환 또는 특정 값으로 대체
      return { id: 0, name: '', email: '' }; // .proto에서 non-nullable이므로 빈 객체 반환
    }
    return user;
  }

  // .proto 파일의 UserService.CreateUser 메서드에 매핑
  @GrpcMethod('UserService', 'CreateUser')
  createUser(data: { name: string; email: string }): User { // data는 CreateUserRequest 메시지
    console.log(`Users gRPC Service: Received request to create user: ${JSON.stringify(data)}`);
    const newUser = this.usersService.create(data);
    return newUser;
  }
}
  • @GrpcMethod('ServiceName', 'MethodName'): 첫 번째 인자는 .proto 파일에 정의된 서비스 이름(UserService), 두 번째 인자는 해당 서비스 내의 메서드 이름(GetUserById)입니다.
  • 메서드의 인자는 .proto 파일에 정의된 요청 메시지의 타입과 일치합니다.
  • 반환 타입도 .proto 파일에 정의된 응답 메시지의 타입과 일치해야 합니다.
단계 4: 사용자 서비스 구현 (동일)

users-service/src/users/users.service.ts 파일은 이전과 동일하게 유지됩니다.

단계 5: UsersModuleAppModule 구성 (동일)

이전 TCP 예제와 동일하게 UsersModuleAppModule에 임포트합니다.

주문 서비스(gRPC 클라이언트) 구축

단계 1: 필요한 패키지 설치

orders-service 프로젝트에서 설치합니다.

npm install @nestjs/microservices @grpc/grpc-js @grpc/proto-loader rxjs
npm install --save-dev @types/grpc__proto-loader
단계 2: orders.module.ts 파일 수정 (gRPC 클라이언트 설정)

ClientsModule을 사용하여 gRPC 클라이언트를 등록합니다.

orders-service/src/orders/orders.module.ts (수정)
import { Module } from '@nestjs/common';
import { ClientsModule, Transport } from '@nestjs/microservices';
import { OrdersController } from './orders.controller';
import { OrdersService } from './orders.service';
import { join } from 'path'; // path 모듈 임포트

@Module({
  imports: [
    ClientsModule.register([
      {
        name: 'USERS_SERVICE', // 클라이언트 프록시 토큰
        transport: Transport.GRPC, // gRPC 전송 방식 사용
        options: {
          package: 'users', // 연결할 gRPC 서비스의 패키지 이름
          protoPath: join(__dirname, '../../proto/users.proto'), // .proto 파일 경로
          url: 'localhost:50051', // 연결할 gRPC 서버의 주소와 포트
          loader: {
            keepCase: true,
            longs: String,
            enums: String,
            defaults: true,
            oneofs: true,
          },
        },
      },
    ]),
  ],
  controllers: [OrdersController],
  providers: [OrdersService],
})
export class OrdersModule {}
  • transport: Transport.GRPC: gRPC 클라이언트를 설정합니다.
  • protoPath, package, url, loader 옵션은 서버 설정과 동일하게 연결할 gRPC 서비스의 정보를 정확히 지정해야 합니다.
단계 3: 주문 서비스 컨트롤러 (gRPC 클라이언트 호출)

ClientGrpc 인터페이스와 @GrpcService() 데코레이터를 사용하여 gRPC 서비스를 호출합니다.

orders-service/src/orders/orders.controller.ts (수정)
import { Controller, Get, Post, Body, Param, Inject, OnModuleInit } from '@nestjs/common';
import { ClientGrpc } from '@nestjs/microservices'; // ClientGrpc 임포트
import { OrdersService } from './orders.service';
import { Observable } from 'rxjs'; // Observable 임포트
import { toPromise } from 'rxjs-await'; // toPromise 임포트 (RxJS 6+에서 toPromise()는 직접 제공되지 않을 수 있음)

// toPromise()가 없으면 rxjs/operators의 lastValueFrom()을 사용할 수 있습니다.
import { lastValueFrom } from 'rxjs';

// .proto 파일에 정의된 서비스 인터페이스 정의
interface UserService {
  GetUserById(data: { id: number }): Observable<User>; // gRPC 메서드명과 매핑
  CreateUser(data: { name: string; email: string }): Observable<User>;
}

interface User { // proto 파일과 일치하도록 타입 정의
  id: number;
  name: string;
  email: string;
}

@Controller('orders')
export class OrdersController implements OnModuleInit {
  private userService: UserService; // gRPC 서비스 프록시를 저장할 변수

  constructor(
    private readonly ordersService: OrdersService,
    @Inject('USERS_SERVICE') private readonly client: ClientGrpc, // gRPC 클라이언트 주입
  ) {}

  // 모듈 초기화 시 gRPC 서비스 프록시를 가져옵니다.
  onModuleInit() {
    // getService<T>('ServiceName')을 통해 gRPC 서비스의 프록시를 가져옵니다.
    this.userService = this.client.getService<UserService>('UserService');
  }

  @Get(':orderId/user')
  async getOrderUser(@Param('orderId') orderId: string): Promise<User | string> {
    const userId = parseInt(orderId, 10);

    try {
      // gRPC 서비스 메서드 호출
      const user = await lastValueFrom(this.userService.GetUserById({ id: userId }));
      // gRPC는 존재하지 않는 경우 필드 기본값을 반환할 수 있으므로, 유효성 검사 필요
      if (!user || user.id === 0) { // 예: id가 0이면 찾지 못했다고 가정
        return `User for order ${orderId} not found in Users gRPC Service.`;
      }
      return user;
    } catch (error) {
      console.error('gRPC Error:', error.details);
      return `Error fetching user from gRPC service: ${error.details || error.message}`;
    }
  }

  @Post('/create-order-with-user-grpc')
  async createOrderWithUserGrpc(@Body() orderDto: { userId: number; item: string; userName?: string; userEmail?: string }): Promise<any> {
    const { userId, item, userName, userEmail } = orderDto;

    let user: User;
    if (userId) { // 기존 사용자 조회
      try {
        user = await lastValueFrom(this.userService.GetUserById({ id: userId }));
        if (!user || user.id === 0) { // 사용자가 없거나 유효하지 않다면
          return { message: `User with ID ${userId} not found in gRPC service.` };
        }
      } catch (error) {
        console.error('gRPC Error:', error.details);
        return { message: `Error fetching user from gRPC service: ${error.details || error.message}` };
      }
    } else if (userName && userEmail) { // 새 사용자 생성
      try {
        user = await lastValueFrom(this.userService.CreateUser({ name: userName, email: userEmail }));
      } catch (error) {
        console.error('gRPC Error:', error.details);
        return { message: `Error creating user via gRPC service: ${error.details || error.message}` };
      }
    } else {
      return { message: 'Either userId or (userName and userEmail) must be provided.' };
    }

    const order = this.ordersService.createOrder(user.id, item);
    return { order, user };
  }
}
  • implements OnModuleInit: onModuleInit() 라이프사이클 훅을 사용하여 모듈 초기화 시점에 gRPC 서비스를 주입받습니다.
  • @Inject('USERS_SERVICE') private readonly client: ClientGrpc: orders.module.ts에서 등록한 USERS_SERVICE 클라이언트를 주입받습니다. ClientGrpc는 gRPC 클라이언트의 인스턴스입니다.
  • this.client.getService<UserService>('UserService'): 주입받은 ClientGrpc 인스턴스에서 .proto 파일에 정의된 UserService의 프록시 객체를 가져옵니다. 이 프록시 객체는 .proto에 정의된 메서드들을 (GetUserById, CreateUser 등) 가지고 있습니다.
  • this.userService.GetUserById({ id: userId }): 프록시 객체를 통해 gRPC 메서드를 호출합니다. 이 메서드는 Observable을 반환하므로, lastValueFrom()을 사용하여 Promise로 변환하여 비동기 처리합니다.
  • gRPC는 에러 발생 시 RpcException을 발생시키며, details 속성으로 상세 메시지를 전달할 수 있습니다.
단계 4: 주문 서비스 구현 (동일)

orders-service/src/orders/orders.service.ts 파일은 이전과 동일하게 유지됩니다.

단계 5: AppModuleOrdersModule 임포트 (동일)

이전과 동일하게 OrdersModuleAppModule에 임포트합니다.


통신 경로 실행 검증

Users Service 시작
  • cd users-service
  • npm run start:dev
  • 콘솔에 Users gRPC Microservice is listening on localhost:50051 메시지 확인
Orders Service 시작
  • cd orders-service
  • npm run start:dev
  • 콘솔에 Orders Service is listening on port 3000 (HTTP) 메시지 확인
API 테스트 (Postman 또는 cURL)
  • 사용자 정보 가져오기 (gRPC 통신)
    • GET http://localhost:3000/orders/1/user
    • 응답: {"id":1,"name":"Alice","email":"alice@example.com"}
    • Users Service 콘솔: Users gRPC Service: Received request for user ID: 1 메시지가 출력되는 것을 확인하여 gRPC 통신이 성공적으로 이루어졌음을 확인합니다.
  • 새로운 사용자 생성 및 주문 처리 (gRPC 통신)
    • POST http://localhost:3000/orders/create-order-with-user-grpc
    • Headers: Content-Type: application/json
    • Body
      {
        "userName": "Charlie",
        "userEmail": "charlie@example.com",
        "item": "Keyboard"
      }
    • 응답: {"order":{"id":1,"userId":3,"item":"Keyboard","createdAt":"2023-06-23T...Z"},"user":{"id":3,"name":"Charlie","email":"charlie@example.com"}}
    • Users Service 콘솔: Users gRPC Service: Received request to create user: {"name":"Charlie","email":"charlie@example.com"} 메시지가 출력되는 것을 확인합니다.

gRPC에서는 HTTP 엔드포인트 성공뿐 아니라 .proto 계약, 패키지/서비스 이름, Observable 처리까지 함께 맞아야 합니다.

아래 다이어그램은 실행 검증에서 확인할 계층을 한 장으로 정리합니다.

실행 검증은 HTTP 성공보다 gRPC 경로를 끝까지 본다

Orders HTTP endpoint가 응답해도, 실제로 Users gRPC handler까지 도달했는지 로그와 payload를 같이 확인합니다.

  1. HTTP 진입

    GET /orders/1/user Orders 서비스가 외부 요청을 받습니다.

  2. gRPC 호출

    GetUserById({ id: 1 }) ClientGrpc proxy가 Users 서비스로 호출합니다.

  3. handler 로그

    Received request for user ID: 1 Users handler 도착 여부가 최종 확인점입니다.

  4. 계약

    package, service, method 이름이 같은지 확인합니다.

  5. 응답 처리

    Observable을 Promise로 변환하고 예외를 잡습니다.

  6. 상태 매핑

    not found, unavailable 같은 gRPC status를 API 응답으로 바꿉니다.


이 예시는 NestJS를 사용하여 gRPC 기반 마이크로서비스를 구축하고 서비스 간 통신을 구현하는 방법을 보여줍니다.

.proto 파일은 서비스 인터페이스를 명시하고, HTTP/2 기반 통신은 내부 서비스 간 요청/응답과 스트리밍 흐름을 구성하는 데 사용됩니다.

gRPC는 내부 서비스 간 통신에 적합하지만, 외부 클라이언트(브라우저)와 통신하려면 API Gateway나 gRPC-Web 같은 추가 계층이 필요할 수 있습니다.

설계 시에는 프로토콜 경계, 메시지 스키마, 장애 전파 방식을 함께 검토해야 합니다.

마지막으로 gRPC 설계를 두 관점에서 정리합니다.

먼저 proto 파일, Nest 핸들러, 클라이언트 프록시가 같은 계약을 공유하는지 확인합니다.

proto, Nest handler, client proxy 이름을 삼각형으로 맞춘다

gRPC가 동작하려면 세 위치가 같은 service/method 계약을 바라봐야 합니다. 하나라도 틀리면 런타임에서 메서드가 비어 보입니다.

  1. proto

    service UserService { rpc GetUserById(...) } 원본 계약입니다.

  2. Nest handler

    @GrpcMethod( "UserService", "GetUserById" ) 서버 구현이 method에 붙습니다.

  3. Client proxy

    client.getService<UserService>( "UserService" ) 클라이언트가 같은 service를 꺼냅니다.

운영 단계에서는 같은 trace 안에서 계약, 시간 예산, status 매핑이 어디서 어긋나는지 구분해야 합니다.

아래 다이어그램은 배포 전후에 확인할 gRPC 품질 기준을 묶어 보여줍니다.

운영에서는 계약, 시간 예산, status 매핑을 같이 본다

gRPC 호출은 빠르지만 장애가 감춰지면 더 어렵습니다. 배포 전후에는 같은 trace 안에서 세 기준을 함께 확인합니다.

  1. proto 호환성

    계약 proto 호환성 필드 번호, optional/default, service 이름 변경을 추적합니다.

  2. deadline과 retry

    시간 deadline과 retry 느린 내부 호출이 HTTP 요청 전체를 붙잡지 않게 제한합니다.

  3. status code 매핑

    상태 status code 매핑 NOT_FOUND, UNAVAILABLE 같은 상태를 API 의미로 바꿉니다.

문제신호대응
계약 불일치method not foundproto, decorator, getService 이름 비교
느린 호출deadline exceededtimeout, retry, fallback 기준 설정
서버 장애UNAVAILABLE상태 매핑과 circuit breaker 검토

이것으로 7장 마이크로서비스 아키텍처의 두 번째 절을 마칩니다.