Why
微服务的优缺点
这篇文章只是讲了比较基础的demo
Setup
首先需要安装nestjs的命令行工具
npm i -g @nestjs/cli
然后使用nest命令创建一个项目
nest new micro-service-demo
安装微服务相关库,yarn add -S @grpc/grpc-js @grpc/proto-loader @nestjs/microservices
这个时候是一个标准的项目开发模式。但是,我们开发微服务则需要将一个功能比较多的服务拆分。所以,可以通过nestjs自带的 Monorepo mode 来开发
通过nest g app api-gateway & nest g app auth-service & nest g app order-service & nest g app product-service
来生成对应的服务
然后在package.json
中的scripts
中添加上对应的启动方式
1 2 3 4
| { "start:order": "nest start order-service --watch", "start:product": "nest start product-service --watch", }
|
其中:
- api-gateway: 为api网关,作用是作为代理,连接各个微服务,为client提供接口
- auth: 登录以及认证
- order: 订单服务
- product: 产品服务
我们这里暂时不做复杂功能,只实现order微服务和product微服务。并在api-gateway中使用grpc的方式访问order服务,order的service再访问product微服务。
实现
proto文件
在根目录创建protos
目录,该目录用于定义proto文件,它定义程序中需要处理的结构化数据
首先是创建order.proto
文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| 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; }
|
然后是product.proto
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| syntax = "proto3";
package product;
message DecreaseStockRequest { int32 id = 1; int32 orderId = 2; }
message DecreaseStockResponse { int32 status = 1; repeated string error = 2; }
service ProductService { rpc DecreaseStock(DecreaseStockRequest) returns (DecreaseStockResponse); }
|
创建微服务
首先我们先安装yarn add ts-proto
,该库用于将我们的proto
文件转化成强类型的 TS 文件
然后在 package.json
中的scripts
中添加如下命令
1 2 3
| { "proto:all": "protoc --plugin=./node_modules/.bin/protoc-gen-ts_proto --ts_proto_out=./protos/pbs ./protos/**.proto --ts_proto_opt=nestJs=true --ts_proto_opt=fileSuffix=.pb --ts_proto_opt=outputIndex=true" }
|
具体的参数意思可以参考这里
运行命令后会在protos
中生成出一个pbs
目录,里面包含了相关的ts文件
(TODO: 这里的后续可以通过Nestjs中的lib将这些文件单独提出到公共库中)
然后再创建order.client.options.ts
,用于生成微服务相关的配置
1 2 3 4 5 6 7 8 9 10 11 12 13
| import { ClientOptions, Transport } from '@nestjs/microservices'; import { ORDER_PACKAGE_NAME } from './order.pb'; import * as path from 'path'; import * as process from 'process';
export const orderClient: ClientOptions = { transport: Transport.GRPC, options: { package: ORDER_PACKAGE_NAME, protoPath: path.join(process.cwd(), './protos/pbs/index.order.proto'), url: 'localhost:50001', }, };
|
然后修改位于order-service
目录中的main
文件
1 2 3 4 5 6 7 8 9 10 11 12 13
| import { NestFactory } from '@nestjs/core'; import { OrderServiceModule } from './order-service.module'; import { MicroserviceOptions } from '@nestjs/microservices'; import { orderClient } from '../../../protos/order.client.options';
async function bootstrap() { const app = await NestFactory.createMicroservice<MicroserviceOptions>( OrderServiceModule, orderClient, ); await app.listen(); } bootstrap();
|
在order-service/order-service.module
中导入product微服务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| import { Module } from '@nestjs/common'; import { OrderServiceController } from './order-service.controller'; import { OrderServiceService } from './order-service.service'; import { ClientsModule } from '@nestjs/microservices'; import { PRODUCT_SERVICE_NAME } from '../../../protos/product.pb'; import { productClient } from '../../../protos/product.client.options';
@Module({ imports: [ ClientsModule.register([ { name: PRODUCT_SERVICE_NAME, ...productClient, }, ]), ], controllers: [OrderServiceController], providers: [OrderServiceService], }) export class OrderServiceModule {}
|
修改order-service/order-service.controller
文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| import { Controller, Get } from '@nestjs/common'; import { OrderServiceService } from './order-service.service'; import { CreateOrderResponse, ORDER_SERVICE_NAME, } from '../../../protos/order.pb'; import { GrpcMethod } from '@nestjs/microservices';
@Controller() export class OrderServiceController { constructor(private readonly orderServiceService: OrderServiceService) {}
@GrpcMethod(ORDER_SERVICE_NAME) createOrder(): Promise<CreateOrderResponse> { return this.orderServiceService.createOrder('1'); } }
|
修改order-service/order-service.service
文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| import { Inject, Injectable, OnModuleInit } from '@nestjs/common'; import { CreateOrderResponse } from '../../../protos/order.pb'; import { PRODUCT_SERVICE_NAME, ProductServiceClient, } from '../../../protos/product.pb'; import { ClientGrpc } from '@nestjs/microservices'; import { firstValueFrom } from 'rxjs';
@Injectable() export class OrderServiceService implements OnModuleInit { productServiceClient: ProductServiceClient;
@Inject(PRODUCT_SERVICE_NAME) readonly clientGrpc: ClientGrpc;
public onModuleInit(): any { this.productServiceClient = this.clientGrpc.getService(PRODUCT_SERVICE_NAME); }
async createOrder(productId: string): Promise<CreateOrderResponse> {
const data = { id: 1, orderId: 2 }; const response = await firstValueFrom( this.productServiceClient.decreaseStock(data), );
if (response.status === 200) { return { status: 200, error: [], id: 1, }; }
return { status: 500, error: [], id: 2, }; } }
|
然后再按照上面的步骤修改修改一下product-service
中的main.ts
然后是修改product-service.controller
文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| import { Controller, Get } from '@nestjs/common'; import { ProductServiceService } from './product-service.service'; import { DecreaseStockResponse, PRODUCT_SERVICE_NAME, } from '../../../protos/product.pb'; import { GrpcMethod } from '@nestjs/microservices';
@Controller() export class ProductServiceController { constructor(private readonly productServiceService: ProductServiceService) {}
@GrpcMethod(PRODUCT_SERVICE_NAME) decreaseStock(): Promise<DecreaseStockResponse> { return this.productServiceService.decreaseStock(); } }
|
然后是修改product-service.service.ts
1 2 3 4 5 6 7 8 9 10 11 12
| import { Injectable } from '@nestjs/common'; import { DecreaseStockResponse } from '../../../protos/product.pb';
@Injectable() export class ProductServiceService { async decreaseStock(): Promise<DecreaseStockResponse> { return { status: 200, error: [], }; } }
|
最后是在api-gateway
中的module
文件注册order微服务即可
api-gateway.module.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| import { Module } from '@nestjs/common'; import { ApiGatewayController } from './api-gateway.controller'; import { ApiGatewayService } from './api-gateway.service'; import { ClientsModule } from '@nestjs/microservices'; import { ORDER_SERVICE_NAME } from '../../../protos/order.pb'; import { orderClient } from '../../../protos/order.client.options';
@Module({ imports: [ ClientsModule.register([ { name: ORDER_SERVICE_NAME, ...orderClient, }, ]), ], controllers: [ApiGatewayController], providers: [ApiGatewayService], }) export class ApiGatewayModule {}
|
修改api-gateway.controller
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| import { Controller, Get, Post } from '@nestjs/common'; import { ApiGatewayService } from './api-gateway.service'; import { CreateOrderResponse } from '../../../protos/order.pb';
@Controller() export class ApiGatewayController { constructor(private readonly apiGatewayService: ApiGatewayService) {}
@Post('order') createOrder(): Promise<CreateOrderResponse> { const data = { productId: 1, quantity: 1, userId: 1, }; return this.apiGatewayService.createOrder(data); } }
|
到这里,一个简单的微服务就搭建完成了
测试
接着就是启动api-gateway服务、order服务、product服务
在命令行输入curl -X POST http://localhost:3000/order
然后输出{"status":200,"id":1}%
即表示成功
相关链接
protoc: command not found (Linux)