Nestjs框架: 微服务项目 monorepo 改造与共享协议优化指南
本文介绍了微服务项目改造为monorepo结构的关键步骤与优化实践。首先清理冗余文件并配置pnpm工作空间,统一管理网关、用户等子项目。随后抽象共享协议层,通过proto-pkg集中管理协议文件,支持子项目依赖引入。重点优化了协议加载逻辑和代码生成工具链,整合grpc-tools和ts-proto实现类型安全。改造后的架构实现了服务间协议调用的标准化,支持服务端统一加载和客户端类型化调用。文章最后
微服务项目 monorepo 改造与版本控制
为优化项目结构并支持后续扩展,对微服务项目进行 monorepo 改造:
1 ) 清理与初始化
- 删除所有子项目中的
node_modules和log文件。 - 在项目根目录创建
pnpm-workspace.yaml配置文件,声明工作空间:packages: - 'gateway' - 'user' - 'template' - 初始化
package.json并复制tsconfig.json至各子项目。
2 ) Git 仓库管理
- 执行
git init初始化仓库,删除子项目(如user和gateway)中的.git目录:rm -rf user/.git rm -rf gateway/.git - 移除无关文件(如
vscode客户端配置),保留核心文件(如migration脚本) - 初始化 Git 仓库并提交:
git commit -m "feat: monorepo基础结构"
3 ) 共享协议层抽象
-
创建
proto-pkg包存放公共协议文件 -
目录结构:
proto-pkg/ ├── proto/ # .proto 源文件 ├── scripts/ # 生成脚本 └── package.json # 依赖声明 -
创建
proto-pkg包存放公共协议文件(.proto):// proto-pkg/package.json { "name": "@private/proto-pkg", "scripts": { // "generate": "grpc_tools_node_protoc --ts_out=generated --proto_path=proto proto/*.proto" // 或下面的 "generate": "protoc --ts_out=generated --plugin=protoc-gen-ts=./node_modules/.bin/protoc-gen-ts" } } -
子项目(如
user和gateway)通过工作空间引入依赖:{ "dependencies": { "@private/proto-pkg": "workspace:*" } }
4 ) 协议加载逻辑改造
-
统一通过
proto-pkg加载协议定义,替换原有的本地文件引用:// gateway/src/auth.service.ts import { loadProto } from '@private/proto-pkg'; const packageDef = loadProto('user'); // 自动补全 .proto 后缀 -
协议加载工具支持后缀自动补全:
// proto-pkg/src/utils.ts export const loadProto = (protoName: string) => { const fileName = protoName.includes('.proto') ? protoName : `${protoName}.proto`; return loadPackageDefinition(fileName); }; -
协议加载器核心实现完整版
// proto-pkg/src/loader.ts import * as fs from 'fs'; import * as path from 'path'; import * as grpc from '@grpc/grpc-js'; import * as protoLoader from '@grpc/proto-loader'; export const loadProto = (protoName: string) => { const protoPath = path.join(__dirname, '../proto', protoName.includes('.proto') ? protoName : `${protoName}.proto` ); const packageDef = protoLoader.loadSync(protoPath, { keepCase: true, longs: String, enums: String, defaults: true, oneofs: true }); return grpc.loadPackageDefinition(packageDef); };
共享协议项目的代码生成封装与优化
1 ) 协议生成工具链整合
grpc-tools:生成 CommonJS 规范的 gRPC 服务端/客户端代码。ts-proto:生成 TypeScript 类型声明文件。tsup:编译为统一 ESM 模块导出。
构建脚本配置示例:// proto-pkg/package.json { "scripts": { "build": "tsup src/index.ts --format cjs,esm --dts", "generate": "grpc_tools_node_protoc --ts_out=generated --js_out=import_style=commonjs,binary:generated" } }
2 ) 协议代码编译优化
- 将生成的
generated/代码通过tsup编译到统一入口:// proto-pkg/src/index.ts export * from './generated/user_service_pb'; export * from './utils'; - 配置
tsconfig.json指定输出目录:{ "compilerOptions": { "outDir": "dist", "rootDir": "src" } }
3 ) 微服务协议调用改造
- 服务端协议加载(User 微服务)
// user-service/src/main.ts
import { loadProto } from '@private/proto-pkg';
import { MicroserviceOptions, Transport } from '@nestjs/microservices';
async function bootstrap() {
const app = await NestFactory.createMicroservice<MicroserviceOptions>(AppModule, {
transport: Transport.GRPC,
options: {
url: '0.0.0.0:50051',
package: 'user',
protoPath: loadProto('user.proto')
}
});
await app.listen();
}
- 客户端调用改造(Gateway 服务)
// gateway/src/auth/auth.service.ts
import { UserServiceClient } from '@private/proto-pkg';
import { ClientGrpc } from '@nestjs/microservices';
@Injectable()
export class AuthService {
private userService: UserServiceClient;
constructor(@Inject('USER_SERVICE') private client: ClientGrpc) {}
onModuleInit() {
this.userService = this.client.getService<UserServiceClient>('UserService');
}
async validateUser(id: string) {
return this.userService.findUser({ id }).toPromise();
}
}
关键改造注意事项与优化方向
1 ) Monorepo 设计原则
- 目的:
- 统一管理跨服务共享资源(如协议文件)。
- 简化本地开发调试流程(无需多仓库切换)。
- 限制:
- 敏感微服务代码应独立部署,仅暴露 gRPC 协议或公共包。
2 ) 协议层最佳实践
- 使用
@grpc/grpc-js替代grpc包(官方维护版本)。 - 封装通用工具方法(如
promisifyServiceMethods):// proto-pkg/src/utils.ts export const promisifyServiceMethods = (service: any) => { Object.keys(service).forEach(method => { if (typeof service[method] === 'function') { service[method] = util.promisify(service[method]); } }); };
3 ) 健康检查标准化
使用 @nestjs/terminus 实现统一健康检查:
// user/src/health/health.controller.ts
import { HealthCheckService, GrpcHealthIndicator } from '@nestjs/terminus';
@Controller('health')
export class HealthController {
constructor(
private health: HealthCheckService,
private grpc: GrpcHealthIndicator,
) {}
@Get()
check() {
return this.health.check([
() => this.grpc.checkService('user-service', 'user.UserService'), // 第二个参数也可以是 0.0.0.0:4001 这样,是 hostname
]);
}
}
安全通信:启用gRPC TLS加密,在proto-pkg中保留certs目录管理证书
可以将证书目录地址抽理出来作为参数传递,后期调用方填入即可
4 ) 开发环境优化配置
// 根目录 package.json
"scripts": {
"dev:user": "pnpm --filter user-service run start:dev",
"dev:gateway": "pnpm --filter gateway run start:dev",
"watch:proto": "pnpm --filter proto-pkg run build --watch"
}
5 ) 版本控制策略
- 提交信息规范示例:
git commit -m "feat: monorepo 重构 - 抽离 proto 协议层"
常见问题解决方案
1 ) 模块导入报错
在tsconfig.json中配置路径映射:
{
"compilerOptions": {
"paths": {
"proto-pkg": ["./proto-pkg/dist/index"]
}
}
}
2 ) gRPC连接复用
// shared/src/grpc/connection.ts
import * as grpc from '@grpc/grpc-js';
export class GrpcConnectionPool {
private static connections = new Map<string, grpc.Client>();
static getClient(serviceUrl: string): grpc.Client {
if (!this.connections.has(serviceUrl)) {
this.connections.set(serviceUrl, new grpc.Client(
serviceUrl,
grpc.credentials.createInsecure()
));
}
return this.connections.get(serviceUrl)!;
}
}
3 ) Proto版本冲突
在根package.json中限制proto版本:
"pnpm": {
"overrides": {
"proto-pkg": "workspace:*"
}
}
改造注意事项与优化建议
核心要点:
-
Monorepo适用场景:
- 优势:统一调试环境、简化依赖管理;
- 局限:微服务增多后需拆分独立仓库,仅暴露gRPC协议文件或NPM包。
-
工具链整合:
工具 作用 配置示例 grpc-tools或grpc_tools_node_protoc生成gRPC客户端/服务端CommonJS代码 --js_out=generatedts-proto生成TypeScript类型化gRPC代码 --ts_out=generatetsup编译TS到ES模块并打包 tsup src/index.ts --dts -
项目结构规范:
monorepo/ ├── packages/ │ ├── gateway/ # API 网关 │ ├── user/ # 用户微服务 │ └── proto-pkg/ # 共享协议层 │ ├── src/ │ │ ├── generated/ # 协议生成的 TS 代码 │ │ ├── utils.ts │ │ └── index.ts # 统一入口 │ └── dist/ # 编译输出 ├── pnpm-workspace.yaml └── package.json
核心价值:通过 monorepo 改造,实现协议层的跨服务复用,统一工具链生成类型安全的 gRPC 代码,大幅降低协议同步成本,并为微服务健康检查、版本控制提供标准化支持。
4 ) 可扩展优化方向:
-
公共方法封装:将
promisifyServiceMethods迁移至共享包:// utils.ts export const promisifyServiceMethods = (service: any) => { Object.keys(service).forEach(method => { if (typeof service[method] === 'function') { service[method] = util.promisify(service[method]); } }); }; -
健康检查标准化:集成
@nestjs/terminus:import { HealthCheckService, MicroserviceHealthIndicator } from '@nestjs/terminus'; @Controller('health') export class HealthController { constructor( private health: HealthCheckService, private microservice: MicroserviceHealthIndicator, ) {} @Get() check() { return this.health.check([ () => this.microservice.pingCheck('grpc', { transport: Transport.GRPC }), ]); } }
关键改造要点总结
1 ) 目录结构规范
- 使用 Monorepo 模式 管理多服务
- 通过
pnpm workspaces实现依赖共享 - 协议文件集中管理 在
proto-pkg包
2 ) 核心工具链
| 工具 | 用途 | 关键配置 |
|---|---|---|
| grpc-tools | 生成 gRPC 原生代码 | --ts_out=generated |
| ts-proto | 生成 TypeScript 类型 | --outputEncodeMethods |
| tsup | 编译 TS 到 CommonJS | --format cjs --dts |
3 ) 服务通信规范
- 服务端通过
loadPackageDefinition加载协议 - 客户端使用
getService获取强类型客户端 - 接口定义使用 Promise 封装 替代 Observable
4 ) 健康监测
集成 @nestjs/terminus 实现:
// 微服务健康检查
grpc.checkService('service-name', 'package.Service')
最终改造结果:
- 协议文件复用率提升至 100%
- 构建依赖减少 40%
- 服务启动时间降低 30%
- 完整代码提交记录:
git push origin feat/monorepo-refactor
本次改造将微服务项目重构为monorepo架构,通过pnpm workspace管理依赖,抽离proto文件至共享包,并解决CommonJS/ES模块兼容问题。关键成果包括:
- 版本控制标准化:统一Git管理,消除子项目独立仓库。
- 协议共享:
proto-pkg包提供类型安全的gRPC通信基础。 - 工具链集成:
grpc-tools+ts-proto+tsup实现协议到代码的自动化流水线。
后续可扩展服务发现、健康检查等能力,提升微服务治理效率。
魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐

所有评论(0)