xpivot
v0.1.5
Published
Extensible dependency injection and plugin-based container framework
Readme
xpivot
可扩展的依赖注入 + 插件化容器框架
xpivot 是一个通用的模块容器框架,提供模块生命周期管理、服务依赖解析、接口式依赖注入和事件驱动分发机制。它不包含任何具体业务逻辑(HTTP、数据库、SSR 等),只负责"加载模块 → 解析依赖 → 实例化服务 → 发布事件"。
设计理念
一切皆插件
HTTP 服务器、路由系统、SSR 渲染、定时任务等,全部以插件形式被加载。核心容器只负责模块的加载、依赖解析和服务分发,不关心业务语义。
事件驱动分发
容器核心不硬编码任何接口名。各功能域插件通过 EventBusService 订阅 service:loaded 事件,按 serviceInterfaceTag 过滤自己关心的接口:
// HTTP 插件监听 IControllerService
eventBus.on('service:loaded', (event) => {
if (event.serviceInterfaceTag !== 'IControllerService') return
const routes = event.instance.getRoutes()
// 注册路由到 Express...
})
// Cron 插件监听 ICronService
eventBus.on('service:loaded', (event) => {
if (event.serviceInterfaceTag !== 'ICronService') return
const jobs = event.instance.getCronJobs()
// 启动定时任务...
})每新增一种接口类型,只需新增事件订阅,无需修改容器核心代码。
三种依赖引用方式
| 方式 | 格式 | 说明 |
|------|------|------|
| 接口引用 | 'IEmailService' | 从接口注册表查找实现了该接口的服务,解耦模块间依赖 |
| 模块内引用 | 'logger' | 在当前模块内查找,serviceTag 必须在当前模块中已声明 |
| 跨模块引用 | 'data.dataProvider' | 从指定模块获取服务实例 |
解析优先级:接口引用 > 跨模块引用 > 模块内引用。
安装
npm install xpivot快速开始
import { ModuleContainer, EVENT_BUS_INTERFACE_TAG, type IEventBusService } from 'xpivot'
// 1. 创建容器
const container = new ModuleContainer()
// 2. 定义一个监听模块(通过 DI 注入 EventBusService 订阅事件)
const listenerModule = {
name: 'listener',
serviceDefs: [
{
serviceTag: 'controllerListener',
serviceImpl: (deps: Record<string, any>) => {
const eventBus = deps.eventBusService as IEventBusService
eventBus.on('service:loaded', (event) => {
if (event.serviceInterfaceTag === 'IControllerService') {
console.log(`Got routes from ${event.moduleName}`)
}
})
return {}
},
dependencies: [EVENT_BUS_INTERFACE_TAG],
},
],
}
// 3. 定义业务模块
const myModule = {
name: 'myModule',
serviceDefs: [
{
serviceTag: 'logger',
serviceImpl: class Logger {
log(msg: string) { console.log(msg) }
},
},
{
serviceTag: 'cache',
serviceImpl: class Cache {
constructor({ logger }: { logger: any }) {}
},
dependencies: ['logger'], // 模块内引用
},
],
}
// 4. 加载模块(监听模块必须先于业务模块加载)
await container.loadModules([listenerModule, myModule])
// 5. 获取服务
const cache = container.getService('myModule', 'cache')核心概念
Service(服务)
服务是容器管理的最小单元,可以是 class 或 factory function:
// class 形式
class UserService {
constructor({ db }: { db: any }) {
this.db = db
}
getUsers() { /* ... */ }
}
// factory function 形式
function createLogger(deps: { config: any }) {
return {
log(msg: string) { console.log(msg) }
}
}ServiceDef(服务定义)
描述一个服务的标识、实现和依赖:
{
serviceTag: 'userService', // 模块内唯一标识
serviceImpl: UserService, // class 或 factory function
dependencies: ['mdb.db'], // 可选:依赖的其他服务
serviceInterfaceTag: 'IUserService', // 可选:实现的接口契约
}Module(模块)
一组相关服务的集合,可以声明对其他模块的依赖:
{
name: 'authServer',
moduleDependencies: ['mdb'], // 可选:依赖的其他模块
serviceDefs: [/* ServiceDef[] */],
}Container(容器)
管理模块生命周期的核心,提供模块加载/卸载、服务获取、事件发布等能力。
EventBusService(事件总线)
事件驱动的核心。容器内置 EventBusService 实例,在服务实例化后自动 emit service:loaded 事件,各功能域插件通过订阅事件按 serviceInterfaceTag 过滤自己关心的服务。
依赖注入
模块内引用
同一模块内的服务可以互相引用:
const module = {
name: 'app',
serviceDefs: [
{
serviceTag: 'logger',
serviceImpl: class Logger { /* ... */ },
},
{
serviceTag: 'userService',
serviceImpl: class UserService {
constructor({ logger }: { logger: any }) {
this.logger = logger
}
},
dependencies: ['logger'], // ← 模块内引用
},
],
}跨模块引用
通过 moduleName.serviceTag 格式引用其他模块的服务:
const authModule = {
name: 'authServer',
moduleDependencies: ['mdb'], // ← 声明模块依赖
serviceDefs: [
{
serviceTag: 'authController',
serviceImpl: class AuthController {
constructor({ mongoDBService }: { mongoDBService: any }) {
this.db = mongoDBService
}
},
dependencies: ['IMongoDBService'], // ← 接口引用(推荐)
// 或:dependencies: ['mdb.mongoDBService'], // ← 跨模块引用
},
],
}注意:使用跨模块引用时,必须在
moduleDependencies中声明依赖的模块名,否则加载时会抛出错误。
接口引用
通过接口名引用服务,无需关心服务来自哪个模块:
// mdb 模块注册了 IMongoDBService 接口
const mdbModule = {
name: 'mdb',
serviceDefs: [
{
serviceTag: 'mongoDBService',
serviceImpl: MongoDBService,
serviceInterfaceTag: 'IMongoDBService', // ← 注册接口
},
],
}
// auth 模块通过接口引用 mdb 的服务
const authModule = {
name: 'authServer',
moduleDependencies: ['mdb'],
serviceDefs: [
{
serviceTag: 'initService',
serviceImpl: class InitService {
constructor({ mongoDBService }: { mongoDBService: any }) {
this.db = mongoDBService
}
},
dependencies: ['IMongoDBService'], // ← 接口引用
},
],
}接口引用的优先级最高,框架会先查接口注册表,未命中再按跨模块/模块内引用解析。
依赖注入的执行流程
loadModules([moduleA, moduleB])
│
├── 加载 moduleA
│ ├── 检查 moduleDependencies 是否已加载
│ ├── 遍历 serviceDefs,逐个实例化服务
│ │ ├── 解析 dependencies(接口 → 跨模块 → 模块内)
│ │ ├── 注入依赖对象 { serviceTag: instance }
│ │ └── new ServiceImpl(depsRecord) 或 factory(depsRecord)
│ ├── 注册到接口注册表(如果有 serviceInterfaceTag)
│ ├── emit service:loaded 事件(EventBusService 通知所有订阅者)
│ └── 调用服务的 initialize()(如果存在)
│
└── 加载 moduleB(同上)事件驱动分发
工作原理
- 订阅阶段:各功能域插件在服务的
initialize()方法中通过EventBusService订阅service:loaded事件 - 加载阶段:容器实例化服务后,自动 emit
service:loaded事件 - 分发阶段:订阅者根据
serviceInterfaceTag过滤,处理自己关心的服务
eventBus.on('service:loaded', handler)
│
▼
EventBusService
┌──────────────────────────────────┐
│ Map<event, Set<listener>> │
│ 'service:loaded' → {fnA, fnB} │
│ 'module:loaded' → {fnC} │
└──────────────────────────────────┘
│
▼
loadModules([authModule])
│
▼
实例化 authController (serviceInterfaceTag: 'IControllerService')
│
▼
emit 'service:loaded' 事件
│
▼
handler 过滤 serviceInterfaceTag → 收集路由、注册到 Express订阅示例
// 通过 DI 注入 EventBusService 和 IHttpServerService(在构造函数阶段获取)
class ControllerListener {
constructor({ eventBusService, httpServerService }: {
eventBusService: IEventBusService
httpServerService: IHttpServerService
}) {
eventBusService.on('service:loaded', (event) => {
if (event.serviceInterfaceTag !== 'IControllerService') return
const routes = event.instance.getRoutes()
httpServerService.addRoutes(routes)
})
}
}
const controllerListenerDef = {
serviceTag: 'controllerListener',
serviceImpl: ControllerListener,
dependencies: [EVENT_BUS_INTERFACE_TAG, HTTP_SERVER_INTERFACE_TAG],
}多播
一个事件可以注册多个监听器,所有匹配的监听器都会被调用:
eventBus.on('service:loaded', (event) => {
if (event.serviceInterfaceTag !== 'IControllerService') return
// 监听器 A:收集路由
})
eventBus.on('service:loaded', (event) => {
if (event.serviceInterfaceTag !== 'IControllerService') return
// 监听器 B:记录日志
})模块生命周期
状态流转
┌──────────┐
│ Unloaded │
└────┬─────┘
│ loadModules()
▼
┌──────────┐
┌───────│ Loading │
│ └────┬─────┘
│ │
error 成功
│ │
▼ ▼
┌──────────┐ ┌──────────┐
│ Error │ │ Loaded │
└──────────┘ └────┬─────┘
│ unloadModule()
▼
┌───────────┐
│ Unloading │
└─────┬─────┘
│
▼
┌──────────┐
│ Unloaded │
└──────────┘生命周期事件
容器在模块生命周期的关键点通过 EventBusService 自动 emit 事件,业务模块可订阅监听:
// 定义一个监听模块,通过 DI 注入 EventBusService
const lifecycleLoggerModule = {
name: 'lifecycleLogger',
serviceDefs: [
{
serviceTag: 'lifecycleLogger',
serviceImpl: (deps: Record<string, any>) => {
const eventBus = deps.eventBusService as IEventBusService
eventBus.on('module:loaded', (event) => {
console.log(`Module loaded: ${event.moduleName} (${event.module.services.size} services)`)
})
eventBus.on('module:unloaded', (event) => {
console.log(`Module unloaded: ${event.moduleName}`)
})
eventBus.on('module:error', (event) => {
console.error(`Module ${event.moduleName} error:`, event.error)
})
return {}
},
dependencies: [EVENT_BUS_INTERFACE_TAG],
},
],
}
// 先加载监听模块,再加载业务模块
await container.loadModules([lifecycleLoggerModule, ...businessModules])服务初始化
如果服务实例有 initialize() 方法,容器会在模块加载完成后自动调用:
class MongoDBService {
async initialize() {
// 连接数据库
this.client = new MongoClient(uri)
await this.client.connect()
}
}模块卸载
卸载模块时,容器会:
- 对每个服务 emit
service:unloaded事件(在清理前发出,订阅者仍可访问实例) - 清理服务实例
- 从接口注册表中移除该模块注册的接口
- 发出
module:unloaded事件
await container.unloadModule('authServer')模块重载
重新加载模块会先卸载再加载,支持传入新的模块定义实现热更新:
// 使用原始模块定义重载
await container.reloadModule('authServer')
// 使用新模块定义重载(热更新)
await container.reloadModule('authServer', newAuthModule)事件系统
容器提供内置的 EventBusService 事件总线,替代原先的 on/off 方法。EventBusService 在容器构造时自动创建并注册为 IEventBusService 接口,业务模块可通过依赖注入获取:
import { EVENT_BUS_INTERFACE_TAG, type IEventBusService } from 'xpivot'
// 通过 dependencies 接口引用注入
class MyService {
constructor({ eventBusService }: { eventBusService: IEventBusService }) {
this.eventBus = eventBusService
}
// 在构造函数中订阅事件
}
const myModule = {
name: 'myModule',
serviceDefs: [
{
serviceTag: 'myService',
serviceImpl: MyService,
dependencies: [EVENT_BUS_INTERFACE_TAG], // ← 接口引用注入
},
],
}容器内置事件
容器在模块生命周期关键点自动 emit 以下事件:
模块级事件
| 事件名 | 载荷 | 说明 |
|------|------|------|
| module:loaded | { moduleName, module, timestamp } | 模块加载完成 |
| module:unloaded | { moduleName, timestamp } | 模块卸载完成 |
| module:error | { moduleName, error, timestamp } | 模块加载错误 |
| module:state_changed | { moduleName, oldState, newState, timestamp } | 模块状态变化 |
服务级事件
| 事件名 | 载荷 | 说明 |
|------|------|------|
| service:loaded | { moduleName, serviceTag, serviceInterfaceTag?, instance, loadedModule } | 服务实例化完成 |
| service:unloaded | { moduleName, serviceTag, serviceInterfaceTag?, instance, loadedModule } | 服务即将被清理(卸载前发出,订阅者仍可访问实例) |
统一分发机制:
service:loaded事件是容器唯一的服务分发渠道。所有功能域插件(HTTP、Cron、SSR 等)通过 EventBus 订阅此事件,按serviceInterfaceTag过滤处理。不再需要registerServiceHandler()等注册式分发 API。
订阅示例
// 在服务中订阅容器事件
class LifecycleLogger {
constructor({ eventBusService }: { eventBusService: IEventBusService }) {
// 监听模块加载完成
eventBusService.on('module:loaded', (event) => {
console.log(`[${event.moduleName}] loaded at ${new Date(event.timestamp)}`)
})
// 监听模块错误
eventBusService.on('module:error', (event) => {
console.error(`[${event.moduleName}] error:`, event.error)
})
// 监听模块卸载
eventBusService.on('module:unloaded', (event) => {
console.log(`[${event.moduleName}] unloaded`)
})
// 监听状态变化
eventBusService.on('module:state_changed', (event) => {
console.log(`[${event.moduleName}] ${event.oldState} → ${event.newState}`)
})
}
}自定义事件
业务模块也可以通过 EventBusService 发布自定义事件,供其他模块消费:
// 发布方
const off = eventBusService.on('my:custom-event', (payload) => {
console.log('Received:', payload)
})
// 订阅方
await eventBusService.emit('my:custom-event', { key: 'value' })
// 取消订阅
off()
// 或
eventBusService.off('my:custom-event', listener)API 参考
ModuleContainer
模块操作
| 方法 | 参数 | 返回值 | 说明 |
|------|------|--------|------|
| loadModules(modules) | IModule[] | Promise<void> | 批量加载模块(跳过已加载的) |
| unloadModule(name) | string | Promise<void> | 卸载模块,清理服务和接口注册 |
| reloadModule(name, newModule?) | string, IModule? | Promise<void> | 重新加载模块(先卸载再加载) |
查询方法
| 方法 | 参数 | 返回值 | 说明 |
|------|------|--------|------|
| isModuleLoaded(name) | string | boolean | 检查模块是否已加载 |
| getLoadedModule(name) | string | ILoadedModule \| undefined | 获取已加载模块 |
| getModuleState(name) | string | ModuleState | 获取模块状态 |
| getService(moduleName, serviceTag) | string, string | T | 获取服务实例(不存在则抛错) |
| getAllServices() | - | Map<string, IService> | 扁平服务池(活视图,键为 `${moduleName}.${serviceTag}`,含全部已加载模块的服务;随模块加载/卸载实时更新,适合作为请求上下文服务池来源) |
事件监听:通过 DI 注入
IEventBusService(dependencies: [EVENT_BUS_INTERFACE_TAG])获取事件总线实例。
EventBusService
| 方法 | 参数 | 返回值 | 说明 |
|------|------|--------|------|
| on(event, listener) | string, (payload: any) => void \| Promise<void> | () => void | 订阅事件,返回取消订阅函数 |
| once(event, listener) | string, (payload: any) => void \| Promise<void> | () => void | 订阅事件(只触发一次) |
| off(event, listener) | string, Function | void | 取消订阅 |
| emit(event, payload?) | string, any | Promise<void> | 发布事件 |
| clear(event?) | string? | void | 移除指定事件(或所有事件)的监听器 |
| listenerCount(event) | string | number | 获取监听器数量 |
工具函数
| 函数 | 参数 | 返回值 | 说明 |
|------|------|--------|------|
| instantiateService(def, existingServices, loadedModules, interfaceRegistry?) | IServiceDef, Map, Map, IInterfaceRegistry? | IService | 实例化服务,解析依赖 |
| parseDependencyRef(ref) | string | { moduleName?: string, serviceTag: string } | 解析依赖引用格式 |
| isConstructor(fn) | any | boolean | 判断是否为 class 构造器 |
| isInterfaceRef(ref) | string | boolean | 判断是否为接口引用(大写字母开头且不含点号) |
类型定义
IServiceDef
interface IServiceDef {
serviceTag: string // 模块内唯一标识
serviceImpl: IServiceMaker // class 或 factory function
dependencies?: string[] // 依赖的其他服务
serviceInterfaceTag?: string // 接口契约标签
}IModule
interface IModule {
name: string // 模块名称(全局唯一)
moduleDependencies?: string[] // 依赖的其他模块
serviceDefs: IServiceDef[] // 服务定义列表
}ILoadedModule
interface ILoadedModule {
name: string
state: ModuleState
services: Map<string, IService>
sourceModule?: IModule
loadedAt?: number
}IEventBusService
interface IEventBusService {
on(event: string, listener: (payload: any) => void | Promise<void>): () => void
once(event: string, listener: (payload: any) => void | Promise<void>): () => void
off(event: string, listener: (payload: any) => void | Promise<void>): void
emit(event: string, payload?: any): void | Promise<void>
clear(event?: string): void
listenerCount(event: string): number
}ModuleState
enum ModuleState {
Unloaded = 'unloaded',
Loading = 'loading',
Loaded = 'loaded',
Error = 'error',
Unloading = 'unloading',
}完整示例
import { ModuleContainer, EVENT_BUS_INTERFACE_TAG, type IEventBusService, type IModule, type IServiceDef } from 'xpivot'
// ==================== 定义模块 ====================
// 数据库模块
const mongoDBService: IServiceDef = {
serviceTag: 'mongoDBService',
serviceImpl: class MongoDBService {
async initialize() {
console.log('[DB] Connecting...')
}
getCollection(name: string) { /* ... */ }
},
serviceInterfaceTag: 'IMongoDBService',
}
const dbModule: IModule = {
name: 'db',
serviceDefs: [mongoDBService],
}
// 日志模块
const loggerService: IServiceDef = {
serviceTag: 'logger',
serviceImpl: class Logger {
log(msg: string) { console.log(`[LOG] ${msg}`) }
},
serviceInterfaceTag: 'ILoggerService',
}
const loggerModule: IModule = {
name: 'logger',
serviceDefs: [loggerService],
}
// 用户模块(依赖 db 和 logger)
const userService: IServiceDef = {
serviceTag: 'userService',
serviceImpl: class UserService {
constructor({ mongoDBService, logger }: any) {
this.db = mongoDBService
this.logger = logger
}
async getUsers() {
this.logger.log('Fetching users...')
return this.db.getCollection('users')
}
},
dependencies: ['IMongoDBService', 'ILoggerService'], // 接口引用
serviceInterfaceTag: 'IUserService',
}
const userModule: IModule = {
name: 'user',
moduleDependencies: ['db', 'logger'],
serviceDefs: [userService],
}
// ==================== 使用容器 ====================
const container = new ModuleContainer()
// 按依赖顺序加载模块
await container.loadModules([dbModule, loggerModule, userModule])
// 一般不从外部获取service,但目前提供这样的接口
// 获取服务
const userService = container.getService('user', 'userService')
const db = container.getService('db', 'mongoDBService')
// 使用服务
const users = await userService.getUsers()
// 卸载模块
await container.unloadModule('user')