这节我们来做下微服务的拆分,并把一些公共 Module 放到 Lib 里。
创建项目:
nest new exam-system

然后添加 4 个 app:
nest g app user

nest g app exam

nest g app answer

nest g app analyse

看下现在的目录:

还有 nest-cli.json

我们改下 user、exam、answer、analyse 的服务的启动端口,分别改为 3001、3002、3003、3004




跑起来:
npm run start:dev user
npm run start:dev exam
npm run start:dev answer
npm run start:dev analyse

浏览器访问这 4 个服务的接口:




没啥问题。
多个微服务之间是可以相互调用的。
在根目录安装微服务的包:
npm install @nestjs/microservices --save
然后改下 exam 微服务,添加一个消息处理函数:

@MessagePattern('sum')
sum(numArr: Array<number>): number {
return numArr.reduce((total, item) => total + item, 0);
}
在 main.ts 里注册下:

app.connectMicroservice({
transport: Transport.TCP,
options: {
port: 8888,
},
});
app.startAllMicroservices();
exam 服务暴露了 3002 的 HTTP 服务,现在用 connectMicroservice 就是再暴露 8888 的 TCP 服务。
在 answer 的服务里面调用下这个微服务:

ClientsModule.register([
{
name: "EXAM_SERVICE",
transport: Transport.TCP,
options: {
port: 8888,
},
},
]);
用客户端模块连接上 888 端口的微服务,然后在 Controller 里调用下:

import { Controller, Get, Inject } from "@nestjs/common";
import { AnswerService } from "./answer.service";
import { ClientProxy } from "@nestjs/microservices";
import { firstValueFrom } from "rxjs";
@Controller()
export class AnswerController {
constructor(private readonly answerService: AnswerService) {}
@Inject("EXAM_SERVICE")
private examClient: ClientProxy;
@Get()
async getHello() {
const value = await firstValueFrom(
this.examClient.send("sum", [1, 3, 5])
);
return this.answerService.getHello() + " " + value;
}
}
在之前的 hello world 接口里调用下微服务的 sum 方法。
用 firstValueFrom 取返回的值。
重新跑一下这两个服务:
npm run start:dev exam
npm run start:dev answer
试一下:

微服务调用成功了。
虽然是隔着网络的两个服务,但是用起来和本地的 service 体验一样,这就是 RPC(远程过程调用)
user、exam、answer、analyse 微服务,各自提供 HTTP 接口,之间还可以通过 TCP 做相互调用。
那多个微服务的公共代码呢?
放在 lib 里。
比如 RedisModule:
nest g lib redis

会让你指定一个前缀,这里用默认的 @app。
然后可以看到在 libs 目录下多了这个公共模块:

并且在 tsconfig.json 里生成了别名配置:

改下 RedisModule
import { Global, Module } from "@nestjs/common";
import { createClient } from "redis";
import { RedisService } from "./redis.service";
@Global()
@Module({
providers: [
RedisService,
{
provide: "REDIS_CLIENT",
async useFactory() {
const client = createClient({
socket: {
host: "localhost",
port: 6379,
},
});
await client.connect();
return client;
},
},
],
exports: [RedisService],
})
export class RedisModule {}
还有 RedisService
import { Inject, Injectable } from "@nestjs/common";
import { RedisClientType } from "redis";
@Injectable()
export class RedisService {
@Inject("REDIS_CLIENT")
private redisClient: RedisClientType;
async keys(pattern: string) {
return await this.redisClient.keys(pattern);
}
async get(key: string) {
return await this.redisClient.get(key);
}
async set(key: string, value: string | number, ttl?: number) {
await this.redisClient.set(key, value);
if (ttl) {
await this.redisClient.expire(key, ttl);
}
}
}
然后分别在 user 和 exam 的 service 里用一下:


import { Controller, Get, Inject } from "@nestjs/common";
import { UserService } from "./user.service";
import { RedisService } from "@app/redis";
@Controller()
export class UserController {
constructor(private readonly userService: UserService) {}
@Inject(RedisService)
redisService: RedisService;
@Get()
async getHello() {
const keys = await this.redisService.keys("*");
return this.userService.getHello() + keys;
}
}
把 redis 的容器跑起来,去 RedisInsight 里看下:

有两个 key。
把用户微服务跑起来:
npm run start:dev user
访问下:

可以看到,lib 里的 RedisService 正确引入并生效了。
在 exam 微服务里也引入下:


@Inject(RedisService)
redisService: RedisService;
@Get()
async getHello() {
const keys = await this.redisService.keys('*');
return this.examService.getHello() + keys;
}
把服务跑起来:
npm run start:dev exam
试一下:

这样,同一个模块就可以在两个微服务里使用了。
案例代码在小册仓库
总结
这节我们微服务架构的项目结构。
创建了 user、exam、answer、analyse 这 4 个 app,还有 redis 这个公共 lib。
4 个微服务都单独暴露 http 接口在不同端口,之间还可以通过 TCP 来做通信。
微服务之间的 RPC 通信用起来就和用本地的 service 一样。
libs 下的模块可以在每个 app 里引入,可以放一些公共代码。
这样,微服务架构的 monorepo 的项目就够就搭建完成了。
