Khi làm việc với NestJS, chúng ta có rất nhiều cách triển khai cho các lớp vận chuyển sử dụng cho các microservices. Trong số đố, gRPC là thứ gì đó không thể bỏ qua được. Trong bài viết này chúng ta không tìm hiểu gRPC là gì mà chỉ tìm cách tích hợp nó vào hệ thông thôi.

Về mặt bản chất của gRPC là một Remote Procedure Call (RPC) framework (tạm dịch  các cuộc gọi thủ tục từ xa). Nó xây dựng ý tưởng xung quanh việc xây dựng các service thành các function mà có thể gọi từ xa (call remotely).

gRPC là 1 open-source và được phát triển bới google. Giúp truyền tải thông tin giữa các server sử dụng binary thay vì phải encode hay decode như các giao thức http thông thường. Nhờ đó làm tăng tốc độ cũng như hiệu suất.

Theo google

gRPC is a language-neutral, platform-neutral remote procedure call (RPC) framework and toolset developed at Google. It lets you define a service using Protocol Buffers, a particularly powerful binary serialization toolset and language. It then lets you generate idiomatic client and server stubs from your service definition in a variety of languages

Nói chung là có thể sử dụng để kết nối các microservice với nhiều ngôn ngữ lập trình khác nhau.

Nếu chưa theo dõi những bài trước, các bạn tham khảo bài đầu tiên tại đây.

Theo dõi source code tại đâybranch dev nha

Source microservices theo dõi tại đây

Link postman để test cho dễ.

Tạo tệp .proto

Chúng ta tiếp tục xây dựng chức năng subscriber trên 2 bộ source.

Để sử dụng gRPC, đầu tiên tạo file .proto sử dụng protocol buffer language.

Trong microservices, ta tạo file subscribers.proto bên trong module subcriber:

syntax = "proto3";
package subscribers;

service SubscriberService {
  rpc GetAllSubscribers (GetAllSubscribersParams) returns (SubscribersResponse) {}
  rpc AddSubscriber (CreateSubscriberDto) returns (Subscriber) {}
}

message GetAllSubscribersParams {}

message Subscriber {
  int32 id = 1;
  string email = 2;
  string name = 3;
}

message CreateSubscriberDto {
  string email = 1;
  string name = 2;
}

message SubscribersResponse {
  repeated Subscriber data = 1;
}

Mình sẽ khai báo những service mà chúng ta sẽ sử dụng bên dưới.

LƯU Ý: Giả sử:GetAllSubscribers chúng ta không truyền params vào để truy vấn thì chúng ta cũng phải tạo 1 cái message rỗng như mình đã khai báo. Nếu bỏ đi nó sẽ lỗi nha.

Tương tự bên project chính, chúng ta cũng tạo file tương tự. Tạo file subscribers.proto bên trong module subcriber:

syntax = "proto3";
package subscribers;

service SubscriberService {
  rpc GetAllSubscribers (GetAllSubscribersParams) returns (SubscribersResponse) {}
  rpc AddSubscriber (CreateSubscriberDto) returns (Subscriber) {}
}

message GetAllSubscribersParams {}

message Subscriber {
  int32 id = 1;
  string email = 2;
  string name = 3;
}

message CreateSubscriberDto {
  string email = 1;
  string name = 2;
}

message SubscribersResponse {
  repeated Subscriber data = 1;
}

Vì cấu trúc 2 bộ source này khá giống nhau nên các bạn để ý kỹ kẻo bị lộn nha.

Phương thức GetAllSubscribers được cho là trả về một mảng. Nhưng với gRPC nó không trả về thành 1 array mà nó trả về theo object. Thì chúng ta sẽ khai báo trả về 1 Subscriber và khai báo repeated thành 1 array.

Các giá trị ( 1, 2..) là duy nhất cho từng field, và chúng ta ưu tiên đánh số từ 1-15 ( sử dụng 1 byte hay 4 bit ) cho các field thường xuyên sử dụng.

Using gRPC with NestJS

Tiếp theo ta cần cài các thư viện liên quan.

Tại microservices cài bằng lệnh như sau:

npm install @grpc/grpc-js

Bên project chính ta cài:

npm install @grpc/grpc-js @grpc/proto-loader

Và cuối cùng ta khai báo 1 url để kết nối ở cả 2 source, mặc định là localhost:5000

GRPC_CONNECTION_URL=localhost:5000

Giống như những bài trước, ta cần khởi tạo kết nối bằng gRPC bên microservice thay vì RMQ ở bài trước:

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

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  const configService = app.get(ConfigService);

  await app.connectMicroservice<MicroserviceOptions>({
    transport: Transport.GRPC,
    options: {
      package: 'subscribers',
      protoPath: join(process.cwd(), 'src/subscriber/subscribers.proto'),
      url: configService.get('GRPC_CONNECTION_URL'),
    },
  });

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

Tương tự bên project chính ta cũng khai báo lại kết nối đến gRPC:

import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { SubscriberController } from './controllers/subscriber.controller';
import { ClientProxyFactory, Transport } from '@nestjs/microservices';
import { join } from 'path';

@Module({
  imports: [ConfigModule],
  controllers: [SubscriberController],
  providers: [
    {
      provide: 'SUBSCRIBER_SERVICE',
      useFactory: (configService: ConfigService) => {
        return ClientProxyFactory.create({
          transport: Transport.GRPC,
          options: {
            package: 'subscribers',
            protoPath: join(process.cwd(), 'src/subscriber/subscribers.proto'),
            url: configService.get('GRPC_CONNECTION_URL'),
          },
        });
      },
      inject: [ConfigService],
    },
  ],
})
export class SubscriberModule {}

Quay trở lại với microservice của chúng ta. Chúng ta khai báo những nơi xử lý cái yêu cầu bên trong controller:

import { Controller } from '@nestjs/common';
import { SubscriberService } from './subscriber.service';
import {
  EventPattern,
  MessagePattern,
  Payload,
  GrpcMethod,
} from '@nestjs/microservices';
import { CreateSubscriberDto } from './subscriber.dto';

@Controller('subscriber')
export class SubscriberController {
  constructor(private readonly subscriberService: SubscriberService) {}

  ...

  @GrpcMethod('SubscriberService', 'AddSubscriber')
  async addSubscriberGrpcMethod(createSubscriberDto: CreateSubscriberDto) {
    return this.subscriberService.addSubscriber(createSubscriberDto);
  }

  @GrpcMethod('SubscriberService', 'GetAllSubscribers')
  async getAllSubscribersGrpcMethod() {
    return {
      data: await this.subscriberService.getAllSubscriber(),
    };
  }
}

2 tham số truyền vào GrpcMethod các bạn để ý xíu là thấy ngay, cái đầu tiên chính là service mà mình đã tạo trong file .proto bên trên, tương tự tham số 2 là function xử lý, và chúng ta khai báo vào đây để xử lý.

Tới đây là xong phần microservice rồi.

Tiếp tục với project chính của chúng ta:

Để dễ theo dõi các function của .proto ta tạo ra 1 interface đơn giản:

export default interface SubscriberInterface {
  addSubscriber(subscriber): Promise<any>;
  // eslint-disable-next-line @typescript-eslint/ban-types
  getAllSubscribers(params: {}): Promise<{ data: [] }>;
}

Ở phần getAllSubscribers mình sẽ nhận data là 1 array mình khai báo trong .proto bên trên nha.

Ta khai báo các route mới để sử dụng gRPC bên trong controller đơn giản như sau:

import {
  ClassSerializerInterceptor,
  Controller,
  Get,
  Inject,
  OnModuleInit,
  Post,
  Req,
  UseGuards,
  UseInterceptors,
} from '@nestjs/common';
import { ClientGrpc } from '@nestjs/microservices';
import { AuthGuard } from '@nestjs/passport';
import SubscriberInterface from '../subscriber.interface';

@Controller('subscriber')
@UseInterceptors(ClassSerializerInterceptor)
export class SubscriberController implements OnModuleInit {
  private gRpcService: SubscriberInterface;

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

  onModuleInit(): any {
    this.gRpcService =
      this.client.getService<SubscriberInterface>('SubscriberService');
  }

  @Get()
  async getSubscribers() {
    return this.gRpcService.getAllSubscribers({});
  }

  @Post()
  @UseGuards(AuthGuard('jwt'))
  async createPost(@Req() req: any) {
    return this.gRpcService.addSubscriber({
      email: req.user.email,
      name: req.user.name,
    });
  }
}

getAllSubscribers dù không truyền params gì thì chúng ta cũng phải truyền vào 1 object rổng nha. Ccacs

Tới đây là xong rồi, các bạn run 2 source lên và vào postman test nha.

Kết luận

Qua bài hôm nay chúng ta đã căn bản hiểu và sử dụng được gRPC với NestJS. Và chúng ta có thêm lựa chọn cho mình khi sử dụng tích hợp mô hình microservice vào hệ thống của chúng ta. Để hiểu sâu hơn các bạn phải tự tìm hiểu thêm và thực hành nhiều hơn nhé.

Rất mong được sự ủng hộ của mọi người để mình có động lực ra những bài viết tiếp theo.
{\__/}
( ~.~ )
/ > ♥️ I LOVE YOU 3000

JUST DO IT!