Teable v2 Core 集中式 DI 注册架构:registerV2CoreServices 的设计与使用指南
2026/9/13 23:21:26 网站建设 项目流程

Teable v2 Core 集中式 DI 注册架构:registerV2CoreServices 的设计与使用指南

【免费下载链接】teable✨ AI Spreadsheet for Business项目地址: https://gitcode.com/GitHub_Trending/te/teable

导读

本文基于 Teable 开源仓库中 packages/v2/core/src/di/ARCHITECTURE.md 展开,深入剖析 v2 引擎核心服务(application services、ports 默认实现)的集中式依赖注入(DI)注册方案。你将掌握registerV2CoreServices的"先注册优先"覆盖机制、三步式容器装配流程、v2CoreTokens令牌体系,以及如何在 Node 与浏览器容器包中复用这套注册逻辑,避免在多容器包间复制注册代码。

一、为什么需要集中式 DI 注册

Teable 的 v2 引擎同时面向 Node.js 服务端(Postgres + pg 驱动)与浏览器环境(Pglite、Noop),存在多个"容器包"(container 包)。如果每个容器包各自重复注册 core 内部服务,会产生三个问题:

  1. 注册逻辑重复:Node 容器、浏览器容器、测试容器都要为同一批应用服务编写几乎相同的c.register(...)代码,维护成本高、容易漂移。
  2. 默认实现难以覆盖:core 内部服务往往需要外部容器按运行环境提供不同的基础设施实现(例如tableRepository在 Postgres 与 Pglite 下完全不同)。
  3. 生命周期不一致:各容器可能对同一服务使用不同的 lifecycle(Singleton/Transient),行为不可预期。

因此,di目录(packages/v2/core/src/di)的职责被明确定义为:

  • 为 v2 core 内部服务提供集中式 DI 注册
  • 允许外部容器覆盖默认实现
  • 消除跨容器包的注册重复

二、目录结构与文件职责

di目录下的文件与职责如下:

文件角色职责
ARCHITECTURE.md架构说明描述 DI 注册范围、设计意图与使用模式
index.tsbarrel 导出对外重新导出注册工具函数
registerCoreServices.ts注册工具注册全部 core 应用服务,支持覆盖(override)
registerRecordWritePlugin.ts插件注册按名称去重地追加记录写入插件
registerFieldOperationPlugin.ts 等插件注册字段/表/视图/数据安全限额插件的注册辅助函数

index.ts仅做一件事:

export * from './registerCoreServices';

也就是说,对外(@teable/v2-core)只暴露注册工具,容器包只需import { registerV2CoreServices } from '@teable/v2-core'即可。

三、核心机制:"先注册优先"的覆盖策略

registerV2CoreServices的函数签名如下(见 registerCoreServices.ts):

export const registerV2CoreServices = ( container: DependencyContainer, options: IRegisterCoreServicesOptions = {} ): DependencyContainer => {

其中IRegisterCoreServicesOptions只有一个可选字段:

export interface IRegisterCoreServicesOptions { /** * Lifecycle for registered services. * @default 'Singleton' */ lifecycle?: Lifecycle; }

lifecycle默认值为Lifecycle.Singleton,即所有由本函数注册的服务默认以单例生命周期装配:

const lifecycle = options.lifecycle ?? Lifecycle.Singleton;

覆盖机制的核心是:注册前先检查container.isRegistered(token),只有尚未注册的令牌才会被写入默认实现。例如:

if (!container.isRegistered(v2CoreTokens.tableUpdateFlow)) { container.register(v2CoreTokens.tableUpdateFlow, TableUpdateFlow, { lifecycle }); } if (!container.isRegistered(v2CoreTokens.tableQueryService)) { container.register(v2CoreTokens.tableQueryService, TableQueryService, { lifecycle }); }

由于函数返回传入的DependencyContainer,可以链式继续装配其他模块(如registerCommandExplainModule(c)的写法)。

覆盖失败场景

需要注意,覆盖必须发生在调用registerV2CoreServices之前。如果先调用registerV2CoreServices(c)c.register(v2CoreTokens.tableQueryService, Custom),由于令牌已被注册,自定义实现会被静默忽略(isRegistered返回 true,跳过默认注册,但外部重复注册依赖具体容器实现,tsyringe 通常以先注册者为准)。正确的覆盖姿势见下文使用模式。

四、三步式使用模式

原文档给出了容器包的标准装配流程,这里结合源码补充细节:

// In container packages (e.g. @teable/v2-container-node): import { registerV2CoreServices, v2CoreTokens } from "@teable/v2-core"; import { Lifecycle } from "@teable/v2-di"; // 1. Register infrastructure first (ports implementations) c.register(v2CoreTokens.tableRepository, PostgresTableRepository); c.register(v2CoreTokens.unitOfWork, PostgresUnitOfWork); // ... // 2. Optionally override core services BEFORE calling registerV2CoreServices // c.register(v2CoreTokens.tableQueryService, CustomTableQueryService); // 3. Register core services (skips already-registered tokens) registerV2CoreServices(c, { lifecycle: Lifecycle.Singleton });

第一步:注册基础设施(ports 实现)tableRepositoryunitOfWorktableRecordQueryRepositorytableSchemaRepository等端口令牌由容器包根据运行环境提供真实实现,它们是 core 服务的依赖底座。

第二步(可选):覆盖 core 服务。如果某个 core 服务(如tableQueryService)需要定制,就在调用registerV2CoreServices之前先注册自己的实现。

第三步:批量注册 core 服务registerV2CoreServices会跳过所有已注册的令牌,因此先注册的自定义实现天然胜出。

五、核心服务注册清单

registerV2CoreServices中注册的服务及其职责如下(对应源码注释与表格,见 registerCoreServices.ts):

Token服务职责
tableUpdateFlowTableUpdateFlow事务化的表更新工作流
tableQueryServiceTableQueryService通用表查询操作
fieldCreationSideEffectServiceFieldCreationSideEffectService跨表字段创建副作用
fieldDeletionSideEffectServiceFieldDeletionSideEffectService跨表字段删除副作用
fieldUpdateSideEffectServiceFieldUpdateSideEffectService依赖字段的级联更新
tableDeletionSideEffectServiceTableDeletionSideEffectService删除表前的跨表清理
foreignTableLoaderServiceForeignTableLoaderService加载并校验外键表引用
linkTitleResolverServiceLinkTitleResolverService将链接标题解析为记录 ID(typecast 支持)
attachmentValueResolverServiceAttachmentValueResolverService写入时解析附件值
userValueResolverServiceUserValueResolverService写入时解析用户值
recordMutationSpecResolverServiceRecordMutationSpecResolverService解析 spec 中的外部值
recordWritePluginRunnerRecordWritePluginRunner运行类型化记录写入插件
recordWriteSideEffectServiceRecordWriteSideEffectService收集记录写入时的表副作用
recordCreationServiceRecordCreationService共享的单记录创建工作流
schemaOperationRunnerServiceSchemaOperationRunnerService运行 schema 操作的修复处理器

除上述"文档核心表"之外,源码中还注册了大量其他服务,包括:tableCreationService(批量建表 + 副作用)、linkFieldUpdateSideEffectService(链接字段的对称建/删)、fieldUndoRedoSnapshotService/fieldUndoRedoReplayService(字段级撤销重做)、fieldCrossTableUpdateSideEffectServicerecordBulkUpdateServicerecordReorderServicerecordWriteUndoRedoPlanServicerecordBatchCreationServicepasteStreamApplicationServicerestoreFieldStreamApplicationServicedeleteByRangeApplicationServiceduplicateRecordsApplicationServiceattachmentValueDecoratorServicerecordChangedValueDecoratorServiceundoRedoService(UndoRedoStackService)等,最终返回同一个 container。

六、v2CoreTokens:令牌体系

所有服务令牌集中定义在 packages/v2/core/src/ports/tokens.ts 的v2CoreTokens对象中,每个令牌是一个带描述字符串的Symbol,例如:

export const v2CoreTokens = { tableRepository: Symbol('v2.core.tableRepository'), tableQueryService: Symbol('v2.core.tableQueryService'), // ... unitOfWork: Symbol('v2.core.unitOfWork'), commandBus: Symbol('v2.core.commandBus'), eventBus: Symbol('v2.core.eventBus'), // ... } as const;

使用Symbol作为令牌可以避免字符串令牌之间的命名冲突,并保证跨包引用同一符号。令牌类别大致分为:

  • 基础设施端口baseRepositorytableRepositorytableRecordQueryRepositorytableRecordRepositorytableSchemaRepositoryschemaOperationRepositoryunitOfWorkcommandBusinternalCommandBusqueryBuseventBusrealtimeEngineloggertracerhashercsvParserdotTeaParser
  • 应用服务tableUpdateFlowtableQueryServicetableCreationService、各副作用服务、recordCreationServicerecordBulkUpdateService等;
  • 插件集合recordWritePluginsfieldOperationPluginstableOperationPluginsviewOperationPluginstableDataSafetyLimitPlugins
  • 可选能力undoRedoStorerecordOrderCalculatorattachmentUrlSignerServicecomputedFieldBackfillServicefieldTrashRepository等。

@teable/v2-di还提供createToken<T>(description)帮助函数(见 packages/v2/di/src/index.ts),其实现本质就是Symbol(description)的类型化封装。

七、默认实现与覆盖点(Noop 策略)

为了让 core 服务在任何环境下都能启动registerV2CoreServices为若干"必须由适配器提供"的能力注册了 Noop 默认实现,容器包可按需覆盖:

令牌默认实现覆盖场景
attachmentUrlSignerServiceNoopAttachmentUrlSignerServiceNest 适配器需提供真实 URL 签名
recordOrderCalculatorNoopRecordOrderCalculator适配器必须提供,否则排序计算返回notImplemented错误
undoRedoStoreNoopUndoRedoStore需要持久化撤销重做栈的容器
fieldTrashRepositoryNoopFieldTrashRepository需要字段回收站能力时
computedFieldBackfillServiceNoopComputedFieldBackfillService计算字段回填能力
tableQueryObservabilityNoopTableQueryObservability查询可观测性埋点
fieldDeleteSnapshotSinkNoopFieldDeleteSnapshotSink字段删除快照归档

例如NoopRecordOrderCalculator(见 packages/v2/core/src/ports/defaults/NoopRecordOrderCalculator.ts)直接返回err(domainError.notImplemented(...)),提醒调用方该能力需要由适配器补齐。

插件注册的幂等性

registerV2CoreServices内部还会装配"数据安全限额"(TableDataSafetyLimit)体系,涉及四类插件注册辅助函数(record / field / table / view)。以registerRecordWritePlugin(见 packages/v2/core/src/di/registerRecordWritePlugin.ts)为例,它按插件名称去重,同名插件重复注册时返回registered: false并保留已有实例,从而保证多次调用registerV2CoreServices或组合多个注册源时插件列表不膨胀。

八、容器包中的实际应用

Node.js 容器(Postgres)

packages/v2/container-node/src/index.ts 中的registerV2NodePgDependencies完整演示了装配流程:先注册 Postgres 数据源(meta/data 库、schema)、PostgresUnitOfWork、logger/tracer、内存版 Command/Query/Event 总线、CSV 解析器、crypto hasher 与tableDataSafetyLimits,最后才调用:

// Register core services (uses defaults unless already registered) registerV2CoreServices(c, { lifecycle: Lifecycle.Singleton });

并提供createV2NodePgContainer(内部container.createChildContainer())创建独立子容器。

浏览器容器(Pglite / Noop)

packages/v2/container-browser/src/index.ts 提供两条路径:

  • registerV2BrowserPgliteDependencies:使用 Pglite 内存库注册 Postgres 适配器与PostgresUnitOfWork,再调用registerV2CoreServices
  • registerV2BrowserNoopDependencies:为所有仓储端口注册Noop*实现后,同样调用registerV2CoreServices

两个容器包都通过同一行调用获得全部 core 服务,这正是"消除跨容器注册重复"这一设计目标的直接体现。关于数据安全限额的环境变量解析,可参考 packages/v2/container-node/src/tableDataSafetyLimits.ts(如TABLE_LIMIT_RECORDS_PER_TABLE_MAXTABLE_LIMIT_FIELDS_PER_TABLE_MAX等)。

九、总结

Teable v2 的di架构以registerV2CoreServices为单一事实来源:默认Singleton生命周期、isRegistered先行检查实现"先注册优先"覆盖、Noop 默认值保证开箱即用、插件注册函数保证幂等。理解这套机制后,接入新容器(如新的数据库适配器或测试环境)只需三步:注册基础设施端口 → 按需覆盖 core 服务 → 调用registerV2CoreServices,无需再为每个容器包复制几十行重复注册代码。

【免费下载链接】teable✨ AI Spreadsheet for Business项目地址: https://gitcode.com/GitHub_Trending/te/teable

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询