Микросервисы gRPC в NestJS: пошаговое руководство

Микросервисы gRPC в NestJS: пошаговое руководство

https://t.me/ai_machinelearning_big_data

В процессе иллюстрации возможностей gRPC создадим два микросервиса: службу заказа (order-service) и службу пользователя (user-service).

В службу заказов (order-service) внедрим API MyOrders, возвращающий фиктивные заказы пользователя. Прежде чем вернуть заказ, служба связывается с помощью gRPC с user-service для проверки прав пользователя в системе.

Такой смоделированный вариант позволит демонстрировать взаимодействие между микросервисами. При этом возможны индивидуальные отличия, например служба заказа может получать данные пользователя в режиме реального времени для отправки уведомлений на мобильный телефон или по электронной почте.

Настройка проекта

Предполагается, что вы знакомы с основами NestJS. Сначала определим специфичные для gRPC элементы.

С помощью командной строки NestJS инициализируем проекты user-service и order-service.

nest new order_service
nest new user_service

Установим специфичные для gRPC зависимости.

В gRPC определяем API в файлах буфера протокола или proto. Наши клиенты NestJS должны понимать этот интерфейс, и для этого мы будем использовать пакет ts-proto, который автоматически сгенерирует код адаптера NestJS для определений proto.

npm i --save @grpc/grpc-js @grpc/proto-loader
npm i protoc-gen-ts_proto
npm install ts-proto

Примерная структура проекта:

Структура проекта user-service

Здесь будет пользовательский модуль (user) в src/user и каталог proto для хранения определений сервисов gRPC.

В proto/user.proto определим базовый getUser с помощью API Id.

syntax = "proto3";

package user;

message GetUserRequest {
  string id = 1;
}

message User {
  string id = 1;
  string name = 2;
  bool isActive = 3;
}

service UserService {
  rpc getUser(GetUserRequest) returns (User) {}
}

Для создания клиента TypeScript, поддерживаемого Nest JS, запускаем следующую команду.

protoc --plugin=protoc-gen-ts_proto=./node_modules/.bin/protoc-gen-ts_proto --ts_p

Примечание: убедитесь, что в вашей системе установлен компилятор protoc, чтобы могла выполняться следующая команда.

В результате в той же папке proto/user.ts будет создан пользовательский клиент (User Client) для использования микросервисом.

/* eslint-disable */
import { GrpcMethod, GrpcStreamMethod } from "@nestjs/microservices";
import { Observable } from "rxjs";

export const protobufPackage = "user";

export interface GetUserRequest {
  id: string;
}
export interface User {
  id: string;
  name: string;
  isActive: boolean;
}
export const USER_PACKAGE_NAME = "user";
export interface UserServiceClient {
  getUser(request: GetUserRequest): Observable<User>;
}
export interface UserServiceController {
  getUser(request: GetUserRequest): Promise<User> | Observable<User> | User;
}
export function UserServiceControllerMethods() {
  return function (constructor: Function) {
    const grpcMethods: string[] = ["getUser"];
    for (const method of grpcMethods) {
      const descriptor: any = Reflect.getOwnPropertyDescriptor(constructor.prototype, method);
      GrpcMethod("UserService", method)(constructor.prototype[method], method, descriptor);
    }
    const grpcStreamMethods: string[] = [];
    for (const method of grpcStreamMethods) {
      const descriptor: any = Reflect.getOwnPropertyDescriptor(constructor.prototype, method);
      GrpcStreamMethod("UserService", method)(constructor.prototype[method], method, descriptor);
    }
  };
}
export const USER_SERVICE_NAME = "UserService";

Папка proto и клиент TypeScript должны быть синхронизированными в службе заказа (order-service). После этого можно просто скопировать/вставить папку src/proto из службы пользователя (user-service) в службу заказа (order-service).

В контроллер user-service можно предоставлять API gRPC.

import { Controller, Logger } from '@nestjs/common';
import { GetUserRequest, User, UserServiceController, UserServiceControllerMethods } from '../proto/user/user';

@Controller()
@UserServiceControllerMethods()
export class UserController implements UserServiceController {
  private readonly logger = new Logger(UserController.name);
  getUser(request: GetUserRequest): Promise<User> {
    this.logger.log(request);
    // Реализуйте свою логику для получения элемента на основе запроса.
    // Вы можете использовать request.itemId для получения определенного элемента из вашего источника данных.
    const item: User = {
      id: request.id,
      name: 'Sample Item',
      isActive: true
    };
    return Promise.resolve(item);
  }
} 

В пользовательском модуле зарегистрируем клиентский модуль gRPC.

import { Module } from "@nestjs/common";
import { ClientsModule, Transport } from "@nestjs/microservices";
import { UserController } from "./user.controller";

@Module({
  imports: [
    ClientsModule.register([
      {
        name: 'USER_PACKAGE',
        transport: Transport.GRPC,
        options: {
          package: 'user',
          protoPath: 'src/proto/user/user.proto',
        },
      },
    ])
  ],
  controllers: [UserController],
  providers: [],
})
export class UserModule {}

Теперь в файле приложения main.ts нужно просто разрешить микросервис gRPC.

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

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.connectMicroservice<MicroserviceOptions>({
    transport: Transport.GRPC, // Используем Transport.GRPC для gRPC
    options: {
      url: 'localhost:5000',
      protoPath: join(__dirname, '../src/proto/user/user.proto'),
      package: 'user'
  }});
  await app.startAllMicroservices();
  await app.listen(3000);
}
bootstrap();

Теперь API-интерфейс getUser готов к использованию клиентом order-service.

В сервисе заказов нужно будет использовать API getUser, реализуя это микросервисное взаимодействие, для чего нужно настроить клиент gRPC и использовать доступные с ним API.

Структура проекта order-service

В модуле заказа сначала нужно зарегистрировать клиента следующим образом:

import { Module } from "@nestjs/common";
import { ClientsModule, Transport } from "@nestjs/microservices";
import { OrderController } from "./order.controller";
import { OrderService } from "./order.service";

@Module({
  imports: [
    ClientsModule.register([
      {
        name: 'USER_PACKAGE',
        transport: Transport.GRPC,
        options: {
          url: 'localhost:5000',
          package: 'user',
          protoPath: 'src/proto/user/user.proto',
        },
      },
    ])
  ],
  controllers: [OrderController],
  providers: [OrderService],
  exports: []
})
export class OrderModule {}

Затем в службе заказов мы cможем использовать API gRPC, чтобы получить сведения о пользователе для API myOrder.

import { BadRequestException, Inject, Injectable, Logger, OnModuleInit } from "@nestjs/common";
import { ClientGrpc } from "@nestjs/microservices";
import { UserServiceClient } from "../proto/user/user";
import { firstValueFrom } from "rxjs";


@Injectable()
export class OrderService implements OnModuleInit {

  private logger = new Logger(OrderService.name);

  private userServiceClient: UserServiceClient;

  constructor(
    @Inject('USER_PACKAGE')
    private grpcClient: ClientGrpc,
    ) {}

  onModuleInit() {
      this.userServiceClient = this.grpcClient.getService<UserServiceClient>('UserService');
  }

  async getOrders(userId: string) {
    const user = await firstValueFrom(this.userServiceClient.getUser({ id: userId }));
    this.logger.log(user);
    if(!user || !user.isActive) throw new BadRequestException("User Not found or inactive");
    return {
        'id': 1,
        'name': 'Dummy Order',
        'status': 'PAID',
        'price': '10$'
    }
  }
}

И наконец, мы сможем предоставить API в контроллере заказов.

import { Controller, Get, Inject, OnModuleInit } from '@nestjs/common';
import { UserServiceClient } from '../proto/user/user';
import { ClientGrpc } from '@nestjs/microservices';
import { OrderService } from './order.service';


@Controller('order')
export class OrderController {

  constructor(
    private orderService: OrderService,
  ) {}


  @Get('myOrders')
  getOrders() {
    return this.orderService.getOrders('userId1'); //Dummy user id
  }
}

Таким образом можно создать базовую настройку для взаимодействия с помощью gRPC между двумя микросервисами.

Полный рабочий репозиторий находится на Github.


Источник


Report Page