В процессе иллюстрации возможностей 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) в 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.
В модуле заказа сначала нужно зарегистрировать клиента следующим образом:
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.
Читайте также:
- Подключение приложений Android к серверу с помощью gRPC
- Почему NestJS — лучший фреймворк Node.js для микросервисов
- Как создать приложение на Go с gRPC
Читайте нас в Telegram, VK и Дзен
Перевод статьи Gaurav Agrawal: Step-by-Step Guide: gRPC Microservices in NestJS