设计目标
ConsulCoreModule 的核心职责是统一管理所有 Consul 客户端实例和 ConsulService 实例。通过将实例管理集中到一个核心模块中,可以:
- 在不同数据中心之间灵活切换客户端
- 通过单例模式共享 gRPC 客户端实例
- 通过 DI 系统全局暴露实例,供其他模块使用
ConsulModuleOptions 接口定义
// src/consul/interfaces/consul.interface.ts
import Consul from 'consul';
export interface ConsulModuleOptions {
// 模块名称,作为实例的唯一标识
name: string;
// Consul 客户端配置(连接地址、端口等)
options: Consul.ConsulOptions;
// 微服务配置(gRPC 服务地址、proto 文件路径等)
services: ConsulServiceOptions | ConsulServiceOptions[];
}
export interface ConsulServiceOptions {
name: string;
url: string;
protoPath: string;
package: string;
}
typescript
ConsulCoreModule 实现
核心结构
// src/consul/consul-core.module.ts
import { Global, Module, DynamicModule, Provider } from '@nestjs/common';
import Consul from 'consul';
import { ConsulModuleOptions } from './interfaces/consul.interface';
import { ConsulService } from './consul.service';
// 全局常量,用于 DI 注入
export const CONSUL_SERVICES = 'CONSUL_SERVICES';
export const CONSUL_CLIENTS = 'CONSUL_CLIENTS';
@Global()
@Module({})
export class ConsulCoreModule {
// 静态属性:存储所有实例
private static consulServices: Record<string, any> = {};
private static consulClients: Map<string, Consul.Consul> = new Map();
static forRoot(options: ConsulModuleOptions): DynamicModule {
// 创建 ConsulService 实例
const consulService = new ConsulService(options);
// 存储到静态 Map 中
ConsulCoreModule.consulServices[options.name] = consulService;
ConsulCoreModule.consulClients.set(options.name, consulService.getConsulClient());
// Provider:暴露 ConsulService 实例集合
const consulServicesProvider: Provider = {
provide: CONSUL_SERVICES,
useValue: ConsulCoreModule.consulServices,
};
// Provider:暴露 Consul 客户端实例集合
const consulClientsProvider: Provider = {
provide: CONSUL_CLIENTS,
useValue: ConsulCoreModule.consulClients,
};
return {
module: ConsulCoreModule,
providers: [consulServicesProvider, consulClientsProvider],
exports: [consulServicesProvider, consulClientsProvider],
};
}
}
typescript
调用方式变更
原先 AppModule 中的调用方式需要简化:
// ❌ 旧方式:传递多个参数
ConsulModule.forRoot({
name: 'default',
consulOptions: { host: 'localhost', port: 8500 },
services: [...],
})
// ✅ 新方式:统一配置
ConsulModule.forRoot({
name: 'default',
options: { host: 'localhost', port: 8500 },
services: [
{ name: 'user-service', url: 'localhost:40001', protoPath: '...', package: 'user' },
{ name: 'user-service-2', url: 'localhost:40002', protoPath: '...', package: 'user' },
],
})
typescript
ConsulService 改造
构造函数变更
// src/consul/consul.service.ts
import Consul from 'consul';
import { ConsulModuleOptions, ConsulServiceOptions } from './interfaces/consul.interface';
export class ConsulService {
private options: Consul.ConsulOptions;
private services: ConsulServiceOptions | ConsulServiceOptions[];
private name: string;
private consulClient: Consul.Consul;
constructor(private readonly consulModuleOptions: ConsulModuleOptions) {
this.options = consulModuleOptions.options;
this.services = consulModuleOptions.services;
this.name = consulModuleOptions.name;
// 在构造函数中直接初始化 Consul 客户端
this.initConsul();
}
private initConsul(): void {
this.consulClient = new Consul(this.options);
console.log(`Consul client initialized: ${this.name}`);
}
/**
* 暴露 Consul 客户端实例
* 供 ConsulCoreModule 在 Map 中存储
*/
getConsulClient(): Consul.Consul {
return this.consulClient;
}
}
typescript
移除 NestJS 生命周期钩子
原先通过 OnModuleInit 生命周期钩子来初始化 Consul,现在改为在构造函数中直接初始化。因为 NestJS 的生命周期是从构造函数开始的,构造函数执行时就可以完成初始化工作,不需要额外的钩子。
// ❌ 旧方式
export class ConsulService implements OnModuleInit {
onModuleInit() {
this.initConsul();
}
}
// ✅ 新方式:构造函数中直接初始化
export class ConsulService {
constructor(private readonly consulModuleOptions: ConsulModuleOptions) {
this.initConsul(); // 构造函数中直接初始化
}
}
typescript
实例管理架构
ConsulCoreModule (全局模块)
├── consulServices: Record<string, ConsulService>
│ ├── 'dc1-default' → ConsulService (dc1 实例)
│ └── 'dc2-default' → ConsulService (dc2 实例)
│
└── consulClients: Map<string, Consul.Consul>
├── 'dc1-default' → Consul Client (localhost:8500)
└── 'dc2-default' → Consul Client (localhost:8501)
text
通过全局的 DI 注入,任何模块都可以获取到所有注册的 Consul 实例:
@Injectable()
export class SomeService {
constructor(
@Inject(CONSUL_SERVICES) private readonly consulServices: Record<string, ConsulService>,
@Inject(CONSUL_CLIENTS) private readonly consulClients: Map<string, Consul.Consul>,
) {
// 获取 dc1 的 Consul 客户端
const dc1Client = this.consulClients.get('dc1-default');
}
}
typescript
清理旧代码
创建 ConsulCoreModule 后,需要清理 ConsulModule 中的旧代码:
- 删除旧的
providers和exports中的 Token 注入 - 删除旧的依赖注入相关方法
- 将
ConsulModule.forRoot()的调用改为委托给ConsulCoreModule.forRoot() - 更新
ConsulServiceOptions为ConsulModuleOptions
// src/consul/consul.module.ts (简化后)
import { Module, DynamicModule } from '@nestjs/common';
import { ConsulCoreModule } from './consul-core.module';
import { ConsulModuleOptions } from './interfaces/consul.interface';
@Module({})
export class ConsulModule {
static forRoot(options: ConsulModuleOptions): DynamicModule {
// 委托给 CoreModule 处理
return ConsulCoreModule.forRoot(options);
}
}
typescript
下一步
ConsulCoreModule 目前完成了 Consul 客户端实例的统一管理。下一步需要在 ConsulService 中初始化 gRPC 客户端,并将这些客户端也纳入统一管理体系,实现微服务实例的动态切换。
↑