- vừa được xem lúc

NestJS: Microservices với gRPC, API Gateway, và Authentication — Part 1/2

0 0 13

Người đăng: Nguyen Ngoc Hiep

Theo Viblo Asia

NestJS: Microservices với gRPC, API Gateway, và Authentication — Part 1/2

Hôm nay, tôi muốn giới thiệu với bạn về Microservices trong NestJS (TypeScript) kết hợp với framework gRPC của Google và API Gateway để xử lý các yêu cầu HTTP đến và xác thực dựa trên JWT. Bài viết này rất dài, vì vậy tôi đã chia nó thành 2 phần.

  • Introduction, Preparation, Databases, Shared Proto Project
  • Authentication Microservice, Product Microservice, Order Microservice

Application Infrastructure

Trong bài viết này, tôi quyết định viết mã cho một dự án Microservice thương mại điện tử đơn giản với API Gateway quản lý các yêu cầu HTTP đến và chuyển tiếp chúng đến các Microservices, tổng cộng sẽ có 3 service.

  • Service đầu tiên sẽ là xác thực, nơi người dùng có thể đăng ký và đăng nhập trong khi chúng tôi xác thực quyền của yêu cầu.
  • Service thứ hai sẽ xử lý sản phẩm, tạo sản phẩm mới và tìm sản phẩm dựa trên ID của nó.
  • Service thứ ba sẽ xử lý các đơn hàng cho ứng dụng thương mại điện tử nhỏ của chúng ta.

Bạn sẽ thấy rằng mỗi dịch vụ này đơn giản nhất có thể để giới thiệu bạn về những điều cơ bản của microservices trong NestJS và TypeScript, mà không làm phức tạp ứng dụng của chúng ta.

Trước khi chúng ta bắt đầu với hướng dẫn này, hãy nói ngắn gọn về các framework và khái niệm mà chúng ta sẽ sử dụng ở đây.

NestJS là gì?

NestJS là một framework để xây dựng các ứng dụng web Node.js hiệu quả và có khả năng mở rộng. Nó sử dụng JavaScript hiện đại và được xây dựng bằng TypeScript. Nếu bạn phát triển một API bằng TypeScript, thì NestJS là lựa chọn lý tưởng! Nó được lấy cảm hứng mạnh mẽ từ Spring và Angular.

gRPC là gì?

gRPC là một framework RPC hiện đại, mã nguồn mở, hiệu suất cao có thể chạy trong mọi môi trường. Nó có thể kết nối các dịch vụ trong và giữa các trung tâm dữ liệu một cách hiệu quả với sự hỗ trợ có thể cắm được cho cân bằng tải, theo dõi, kiểm tra sức khỏe và xác thực.

API Gateway là gì?

API Gateway là một điểm đầu vào cho tất cả các khách hàng, trong trường hợp của chúng ta, cho tất cả các yêu cầu từ khách hàng dựa trên HTTP. Yêu cầu chỉ đơn giản được chuyển tiếp/định tuyến đến service phù hợp. Nó xử lý các yêu cầu khác bằng cách phân tán đến nhiều service.

Các yêu cầu tiên quyết

Bạn cần có hiểu biết cơ bản về TypeScript, RPC, Git (+ Github), PostgreSQL mà bạn đã cài đặt trên máy của mình. Tôi sẽ chọn Visual Studio Code làm trình soạn thảo mã nguồn của mình. Bạn có thể sử dụng bất kỳ trình soạn thảo nào mà bạn ưa thích.

Database

Đầu tiên, chúng ta cần tạo 3 cơ sở dữ liệu PostgreSQL. Điều này là do chúng ta sẽ tuân theo mô hình Cơ sở dữ liệu cho mỗi service. Mỗi microservice sẽ có cơ sở dữ liệu riêng của nó để độc lập khi quản lý dữ liệu.

psql postgres
CREATE DATABASE micro_auth;
CREATE DATABASE micro_product;
CREATE DATABASE micro_order;

Tạo project

Tiếp tục với NestJS. Chúng ta sẽ cài đặt NestJS CLI.

npm i -g @nestjs/cli

Chúng ta khởi tạo 4 dự án NestJS mới bằng CLI của nó. Ngoài ra, chúng ta tạo một dự án cho các tệp proto mà chúng ta sẽ chia sẻ qua Github.

mkdir grpc-nest-proto
nest new grpc-nest-api-gateway -p npm
nest new grpc-nest-auth-svc -p npm
nest new grpc-nest-product-svc -p npm
nest new grpc-nest-order-svc -p npm

Shared Proto Repository

Đầu tiên chúng ta cần khởi tạo project grpc-proto

cd grpc-nest-proto npm init --y git init mkdir proto touch proto/auth.proto && touch proto/product.proto && touch proto/order.proto code .

project của chúng ta sẽ trông như này

Sau đó, chúng ta sẽ thêm một số mã vào các tệp proto của mình.

proto/auth.proto
syntax = "proto3"; package auth; service AuthService { rpc Register (RegisterRequest) returns (RegisterResponse) {} rpc Login (LoginRequest) returns (LoginResponse) {} rpc Validate (ValidateRequest) returns (ValidateResponse) {}
} // Register message RegisterRequest { string email = 1; string password = 2;
} message RegisterResponse { int32 status = 1; repeated string error = 2;
} // Login message LoginRequest { string email = 1; string password = 2;
} message LoginResponse { int32 status = 1; repeated string error = 2; string token = 3;
} // Validate message ValidateRequest { string token = 1;
} message ValidateResponse { int32 status = 1; repeated string error = 2; int32 userId = 3;
}
proto/order.proto
syntax = "proto3"; package order; service OrderService { rpc CreateOrder (CreateOrderRequest) returns (CreateOrderResponse) {}
} message CreateOrderRequest { int32 productId = 1; int32 quantity = 2; int32 userId = 3;
} message CreateOrderResponse { int32 status = 1; repeated string error = 2; int32 id = 3;
}
proto/product.proto
syntax = "proto3"; package product; service ProductService { rpc CreateProduct (CreateProductRequest) returns (CreateProductResponse) {} rpc FindOne (FindOneRequest) returns (FindOneResponse) {} rpc DecreaseStock (DecreaseStockRequest) returns (DecreaseStockResponse) {}
} // CreateProduct message CreateProductRequest { string name = 1; string sku = 2; int32 stock = 3; double price = 4;
} message CreateProductResponse { int32 status = 1; repeated string error = 2; int32 id = 3;
} // FindOne message FindOneData { int32 id = 1; string name = 2; string sku = 3; int32 stock = 4; double price = 5;
} message FindOneRequest { int32 id = 1;
} message FindOneResponse { int32 status = 1; repeated string error = 2; FindOneData data = 3;
} // DecreaseStock message DecreaseStockRequest { int32 id = 1;
} message DecreaseStockResponse { int32 status = 1; repeated string error = 2;
}

Sau đó bạn cần public project lên github

API GATEWAY

Trong project này, chúng tôi sẽ chuyển tiếp các yêu cầu HTTP tới microservice của mình.

Cài đặt
 npm i @nestjs/microservices @grpc/grpc-js @grpc/proto-loader npm i -D @types/node ts-proto
Cấu trúc dự án
nest g mo auth && nest g co auth --no-spec && nest g s auth --no-spec
nest g mo product && nest g co product --no-spec
nest g mo order && nest g co order --no-spec
touch src/auth/auth.guard.ts
Scripts

Thêm 5 script này vào package.json, thay {{link_git_proto}} bằng link git proto bạn đã public bên trên

"proto:install": "npm i {{link_git_proto}}",
"proto:auth": "protoc --plugin=node_modules/.bin/protoc-gen-ts_proto -I=./node_modules/grpc-nest-proto/proto --ts_proto_out=src/auth/ node_modules/grpc-nest-proto/proto/auth.proto --ts_proto_opt=nestJs=true --ts_proto_opt=fileSuffix=.pb",
"proto:order": "protoc --plugin=node_modules/.bin/protoc-gen-ts_proto -I=./node_modules/grpc-nest-proto/proto --ts_proto_out=src/order/ node_modules/grpc-nest-proto/proto/order.proto --ts_proto_opt=nestJs=true --ts_proto_opt=fileSuffix=.pb",
"proto:product": "protoc --plugin=node_modules/.bin/protoc-gen-ts_proto -I=./node_modules/grpc-nest-proto/proto --ts_proto_out=src/product/ node_modules/grpc-nest-proto/proto/product.proto --ts_proto_opt=nestJs=true --ts_proto_opt=fileSuffix=.pb",
"proto:all": "npm run proto:auth && npm run proto:order && npm run proto:product"

chạy script:

npm run proto:install && npm run proto:all
  • proto:install sẽ cài đặt kho lưu trữ proto được chia sẻ dưới dạng gói NPM
  • proto:all sẽ tạo các tệp protobuf với hậu tố bên .pb.ts trong các mô-đun: auth, orderproduct

dự án sẽ trông như thế này:

Auth

Auth module
import { Global, Module } from '@nestjs/common';
import { ClientsModule, Transport } from '@nestjs/microservices';
import { AuthController } from './auth.controller';
import { AUTH_SERVICE_NAME, AUTH_PACKAGE_NAME } from './auth.pb';
import { AuthService } from './auth.service'; @Global()
@Module({ imports: [ ClientsModule.register([ { name: AUTH_SERVICE_NAME, transport: Transport.GRPC, options: { url: '0.0.0.0:50051', package: AUTH_PACKAGE_NAME, protoPath: 'node_modules/grpc-nest-proto/proto/auth.proto', }, }, ]), ], controllers: [AuthController], providers: [AuthService], exports: [AuthService],
})
export class AuthModule {}
Auth Service
import { Inject, Injectable } from '@nestjs/common';
import { ClientGrpc } from '@nestjs/microservices';
import { firstValueFrom } from 'rxjs';
import { AuthServiceClient, AUTH_SERVICE_NAME, ValidateResponse } from './auth.pb'; @Injectable()
export class AuthService { private svc: AuthServiceClient; @Inject(AUTH_SERVICE_NAME) private readonly client: ClientGrpc; public onModuleInit(): void { this.svc = this.client.getService<AuthServiceClient>(AUTH_SERVICE_NAME); } public async validate(token: string): Promise<ValidateResponse> { return firstValueFrom(this.svc.validate({ token })); }
}
Auth Guard
import { Injectable, CanActivate, ExecutionContext, HttpStatus, UnauthorizedException, Inject } from '@nestjs/common';
import { Request } from 'express';
import { ValidateResponse } from './auth.pb';
import { AuthService } from './auth.service'; @Injectable()
export class AuthGuard implements CanActivate { @Inject(AuthService) public readonly service: AuthService; public async canActivate(ctx: ExecutionContext): Promise<boolean> | never { const req: Request = ctx.switchToHttp().getRequest(); const authorization: string = req.headers['authorization']; if (!authorization) { throw new UnauthorizedException(); } const bearer: string[] = authorization.split(' '); if (!bearer || bearer.length < 2) { throw new UnauthorizedException(); } const token: string = bearer[1]; const { status, userId }: ValidateResponse = await this.service.validate(token); req.user = userId; if (status !== HttpStatus.OK) { throw new UnauthorizedException(); } return true; }
}
Auth Controller
import { Body, Controller, Inject, OnModuleInit, Post, Put } from '@nestjs/common';
import { ClientGrpc } from '@nestjs/microservices';
import { Observable } from 'rxjs';
import { AuthServiceClient, RegisterResponse, RegisterRequest, AUTH_SERVICE_NAME, LoginRequest, LoginResponse } from './auth.pb'; @Controller('auth')
export class AuthController implements OnModuleInit { private svc: AuthServiceClient; @Inject(AUTH_SERVICE_NAME) private readonly client: ClientGrpc; public onModuleInit(): void { this.svc = this.client.getService<AuthServiceClient>(AUTH_SERVICE_NAME); } @Post('register') private async register(@Body() body: RegisterRequest): Promise<Observable<RegisterResponse>> { return this.svc.register(body); } @Put('login') private async login(@Body() body: LoginRequest): Promise<Observable<LoginResponse>> { return this.svc.login(body); }
}

Order

Order module
import { Module } from '@nestjs/common';
import { ClientsModule, Transport } from '@nestjs/microservices';
import { ORDER_SERVICE_NAME, ORDER_PACKAGE_NAME } from './order.pb';
import { OrderController } from './order.controller'; @Module({ imports: [ ClientsModule.register([ { name: ORDER_SERVICE_NAME, transport: Transport.GRPC, options: { url: '0.0.0.0:50052', package: ORDER_PACKAGE_NAME, protoPath: 'node_modules/grpc-nest-proto/proto/order.proto', }, }, ]), ], controllers: [OrderController],
})
export class OrderModule {}
Order controller
import { Controller, Inject, Post, OnModuleInit, UseGuards, Req } from '@nestjs/common';
import { ClientGrpc } from '@nestjs/microservices';
import { Observable } from 'rxjs';
import { CreateOrderResponse, OrderServiceClient, ORDER_SERVICE_NAME, CreateOrderRequest } from './order.pb';
import { AuthGuard } from '../auth/auth.guard';
import { Request } from 'express'; @Controller('order')
export class OrderController implements OnModuleInit { private svc: OrderServiceClient; @Inject(ORDER_SERVICE_NAME) private readonly client: ClientGrpc; public onModuleInit(): void { this.svc = this.client.getService<OrderServiceClient>(ORDER_SERVICE_NAME); } @Post() @UseGuards(AuthGuard) private async createOrder(@Req() req: Request): Promise<Observable<CreateOrderResponse>> { const body: CreateOrderRequest = req.body; body.userId = <number>req.user; return this.svc.createOrder(body); }
}

Product

Product Module
import { Module } from '@nestjs/common';
import { ClientsModule, Transport } from '@nestjs/microservices';
import { PRODUCT_PACKAGE_NAME, PRODUCT_SERVICE_NAME } from './product.pb';
import { ProductController } from './product.controller'; @Module({ imports: [ ClientsModule.register([ { name: PRODUCT_SERVICE_NAME, transport: Transport.GRPC, options: { url: '0.0.0.0:50053', package: PRODUCT_PACKAGE_NAME, protoPath: 'node_modules/grpc-nest-proto/proto/product.proto', }, }, ]), ], controllers: [ProductController],
})
export class ProductModule {}
Product Controller
import { Controller, Get, Inject, OnModuleInit, Param, ParseIntPipe, UseGuards, Post, Body } from '@nestjs/common';
import { ClientGrpc } from '@nestjs/microservices';
import { Observable } from 'rxjs';
import { FindOneResponse, ProductServiceClient, PRODUCT_SERVICE_NAME, CreateProductResponse, CreateProductRequest,
} from './product.pb';
import { AuthGuard } from '../auth/auth.guard'; @Controller('product')
export class ProductController implements OnModuleInit { private svc: ProductServiceClient; @Inject(PRODUCT_SERVICE_NAME) private readonly client: ClientGrpc; public onModuleInit(): void { this.svc = this.client.getService<ProductServiceClient>(PRODUCT_SERVICE_NAME); } @Post() @UseGuards(AuthGuard) private async createProduct(@Body() body: CreateProductRequest): Promise<Observable<CreateProductResponse>> { return this.svc.createProduct(body); } @Get(':id') @UseGuards(AuthGuard) private async findOne(@Param('id', ParseIntPipe) id: number): Promise<Observable<FindOneResponse>> { return this.svc.findOne({ id }); }
}

API Gateway đã được hoàn thành. Bây giờ chúng ta có thể chạy nó.

Bình luận

Bài viết tương tự

- vừa được xem lúc

Tìm hiểu về NestJS (Phần 2)

Trong bài viết trước, mình đã giới thiệu về NestJS và các thành phần cơ bản của framework này cũng như xây dựng demo một api bằng NestJS. Như mình đã giới thiệu, NestJS có một hệ sinh thái hỗ trợ cho chúng ta trong quá trình phát triển mà các framework khác như Express, Fastify,... phải tự build hoặ

0 0 170

- vừa được xem lúc

NestJS - tìm hiểu và sử dụng Pipes

I. Giới thiệu.

0 0 38

- vừa được xem lúc

Authentication Với NestJS và Passport (Phần 1)

Authentication, hay xác thực thông tin người dùng, là một trong những tính năng cơ bản nhất của phần lớn ứng dụng Web. Trong bài viết này, mình xin chia sẻ phương pháp sử dụng passportjs để xây dựng t

0 0 93

- vừa được xem lúc

Authentication Với NestJS và Passport (Phần 2)

I. Giới thiệu.

0 0 172

- vừa được xem lúc

Middleware, Interceptor and Pipes in Nestjs

Middleware, Interceptor và Pipes củng không quá xa lạ với những anh em code Nestjs.Nhưng ai trong.

0 0 172

- vừa được xem lúc

NestJS - framework thần thánh cho Nodejs

Đọc thì có vẻ giật tít nhưng khoan, mọi chuyện không như bạn nghĩ, hãy nghe mình giải thích . 1.

0 0 58