尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

teable 表规格系统架构解析:domain/table/specs 的 Specification 模式与访问者模式实践

teable 表规格系统架构解析:domain/table/specs 的 Specification 模式与访问者模式实践 teable 表规格系统架构解析domain/table/specs 的 Specification 模式与访问者模式实践【免费下载链接】teable✨ AI Spreadsheet for Business项目地址: https://gitcode.com/GitHub_Trending/te/teable导读本文以 teableAI Spreadsheet for Business开源仓库中packages/v2/core/src/domain/table/specs/目录的架构文档为核心深入剖析该目录所实现的表规格Table Spec系统它如何基于经典 Specification规格模式封装对 Table 聚合根的筛选与变更意图如何借助访问者Visitor模式将领域意图翻译为内存过滤与持久化where/update负载以及TableSpecBuilder如何以流式 API 完成 and/or/not 组合。读完本文你将掌握这套规格系统的设计动机、文件职责划分、核心接口契约以及从源码到测试的完整验证路径可直接用于理解或扩展 teable 的领域层代码。1. 规格目录的定位与职责packages/v2/core/src/domain/table/specs/ARCHITECTURE.md是该目录的架构说明文档开篇就明确了整个目录的职责边界提供表相关的规格Spec与规格构建器Spec Builder所有关于某张表是否满足某个条件或对表执行某个变更的意图都被封装为规格对象用于内存过滤与持久化翻译in-memory filtering and persistence translation同一份规格既可以调用isSatisfiedBy在内存中对Table对象求值也可以通过访问者接口被翻译成查询或更新负载部分规格同时充当变更规格mutate specs例如TableByNameSpec在内存求值时是按名称匹配而其mutate方法则直接执行t.rename(...)实现了筛选即变更的双重身份ITableSpecVisitor定义了表级访问钩子为 where/update 翻译提供了类型安全的访问点。从更上层的角度看该目录是 teable v2 核心领域层中以规格对象统一表达查询与变更这一设计思路的表级落点。其下的规格分为两大阵营查询型规格TableByBaseIdSpec、TableByIdSpec、TableByIdsSpec、TableByNameSpec、TableByNameLikeSpec等与变更型规格TableAddFieldSpec、TableRemoveFieldSpec、TableUpdateViewColumnMetaSpec以及庞大的field-updates/子目录。2. 文件清单与职责速览原架构文档以文件清单的形式逐一说明了目录内每个文件的角色与目的这是理解该系统最直接的索引。以下按原文档顺序整理并补充目录中实际存在、值得关注的同类文件文件角色目的ARCHITECTURE.md架构说明描述表规格系统整体设计ITableSpecVisitor.ts访问者接口为查询/更新负载翻译提供表级访问方法TableAddFieldSpec.ts变更规格向表追加一个字段TableRemoveFieldSpec.ts变更规格从表移除一个字段TableUpdateViewColumnMetaSpec.ts变更规格在表变更过程中携带视图列元数据更新TableByBaseIdSpec.ts查询规格按 BaseId 过滤TableByIdSpec.ts查询规格按 TableId 过滤TableByIdsSpec.ts查询规格按多个 TableId 过滤TableByNameLikeSpec.ts查询规格按名称模糊匹配TableByNameSpec.ts查询规格/变更规格按名称精确匹配mutate时执行重命名TableSpecBuilder.spec.ts构建器测试验证 and/or/not 组合行为TableSpecBuilder.ts规格构建器流式构建表规格TableSpecs.spec.ts规格测试逐一验证各规格的isSatisfiedBy此外目录中还有一批未被原文档逐一列举、但同属该系统的规格TableAddFieldsSpec批量加字段、TableAddSelectOptionsSpec追加选项、TableDuplicateFieldSpec复制字段、TableRenameSpec重命名、TableByIncomingReferenceToTableSpec跨 Base 反向引用查询、TableUpdateField*Spec系列字段名、类型、约束、AI 配置、描述、错误状态等通用字段更新以及field-updates/子目录下按字段类型组织的三十余个更新规格单行文本、长文本、数字、日期、勾选、评分、用户、按钮、单选/多选、公式、链接、查找、汇总等详见packages/v2/core/src/domain/table/specs/field-updates/。visitors/子目录则提供了TableSpecEventVisitor、TableEventGeneratingSpecVisitor、FieldUpdateSemanticsVisitor等访问者实现。3. 核心契约ISpecification 与 ISpecVisitor规格系统的基石位于共享层packages/v2/core/src/domain/shared/specification/其架构说明packages/v2/core/src/domain/shared/specification/ARCHITECTURE.md指出该层提供规格模式的核心抽象与组合能力并明确组合规格会暴露子访问器如leftSpec、rightSpec、innerSpec供适配器进行树遍历。3.1 ISpecification 接口ISpecification.ts定义了每个规格必须实现的三方法契约export interface ISpecificationT any, V extends ISpecVisitor ISpecVisitor { isSatisfiedBy(t: T): boolean; // 内存求值t 是否满足该规格 mutate(t: T): ResultT, DomainError; // 变更对 t 施加规格所表达的修改 accept(v: V): Resultvoid, DomainError; // 访问者将规格分发给对应的 visit 方法 }三个方法分别对应规格模式的三种典型用途isSatisfiedBy纯内存判定返回布尔值用于内存仓储过滤如packages/v2/core/src/ports/memory/MemoryTableRepository.tsmutate返回ResultT, DomainError采用 neverthrow 的 Result 类型显式建模失败路径将规格即变更落到实处accept接受一个规格访问者交由访问者针对该规格类型执行翻译逻辑生成 SQL where/update 负载等。3.2 ITableSpecVisitor表级访问钩子ITableSpecVisitor.ts继承自共享的ISpecVisitor为所有表规格定义了统一的访问方法全部返回ResultTResult, DomainError。接口按注释分块组织可归纳为四组既有规格组visitTableAddField、visitTableAddFields、visitTableAddSelectOptions、visitTableDuplicateField、visitTableRemoveField、visitTableUpdateViewColumnMeta、visitTableUpdateViewQueryDefaults、visitTableRename以及查询类的visitTableByBaseId、visitTableById、visitTableByIds、visitTableByName、visitTableByNameLike、visitTableByIncomingReferenceToTable通用字段更新组visitTableUpdateFieldName、visitTableUpdateFieldDbFieldName、visitTableUpdateFieldType、visitTableUpdateFieldConstraints、visitTableUpdateFieldAiConfig、visitTableUpdateFieldDescription、visitTableUpdateFieldHasError按字段类型的更新组覆盖 SingleLineText、LongText、Number、Date、Checkbox、Rating、User、Button、SingleSelect、MultipleSelect、Formula、Link、Lookup、Rollup 等字段的格式化、默认值、展示方式、选项、表达式等更新方法链接与汇总专项visitUpdateLinkConfig、visitUpdateLinkRelationship、visitRemoveSymmetricLinkField、visitUpdateLookupOptions、visitUpdateRollupConfig/Expression/Formatting/ShowAs/TimeZone。从源码结构看这套接口正是持久化翻译的枢纽任何想把规格翻译成 SQL 的适配器如 PostgreSQL 适配器中的 where/update 生成器都通过实现该接口来完成类型安全的翻译。3.3 组合规格与 MutateOnlySpec共享层提供了三类组合规格AndSpecAND 组合、OrSpecOR 组合、NotSpec否定并暴露leftSpec/rightSpec/innerSpec子访问器供遍历使用。变更类规格通常继承MutateOnlySpec基类它提供中性的isSatisfiedBy不参与内存筛选让变更意图与筛选意图在类型层面清晰分离。4. 查询规格实现剖析以三个典型查询规格为例观察筛选的实现方式。它们的结构高度一致私有构造函数 静态create工厂 值访问器 isSatisfiedBy/mutate/accept三方法。4.1 TableByIdSpec按 ID 精确匹配TableByIdSpec.ts的求值逻辑极为简洁isSatisfiedBy(t: Table): boolean { return t.id().equals(this.tableIdValue); }mutate返回ok(t)无变更语义纯筛选accept将自身分发给v.visitTableById(this)。TableByBaseIdSpec、TableByIdsSpec的实现同构只是分别比较baseId()与对 ID 列表的逐个比对。4.2 TableByNameSpec兼具筛选与变更双重身份TableByNameSpec.ts是原文档点名的规格同时充当变更规格的例子isSatisfiedBy(t: Table): boolean { return t.name().equals(this.tableNameValue); } mutate(t: Table): ResultTable, DomainError { return t.rename(this.tableNameValue); }同一份规格在内存筛选中表达名称等于 X 的表在变更场景中则直接调用Table.rename完成重命名避免为同一意图维护两套对象。4.3 TableByNameLikeSpec模糊匹配TableByNameLikeSpec.ts以includes实现名称子串匹配mutate保持中性ok(t)isSatisfiedBy(t: Table): boolean { return t.name().toString().includes(this.tableNameValue.toString()); }值得注意该文件字符串化后再includes的语义与 SQL 层LIKE %name%的翻译由访问者适配器完成领域层只关心模糊匹配这一不变意图。5. 变更规格实现剖析以 TableAddFieldSpec 为例TableAddFieldSpec.ts继承自MutateOnlySpec其mutate委托给聚合根mutate(t: Table): ResultTable, DomainError { return t.addField(this.fieldValue, { domainContext: this.options?.domainContext, }); }它额外支持可选的domainContextIDomainContext用于在变更时携带领域上下文信息。TableRemoveFieldSpec与TableUpdateViewColumnMetaSpec遵循同样的规格承载变更数据 →mutate委托聚合根方法 →accept分发访问者模式区别仅在于承载的数据类型不同。从源码结构可以推断这种设计的收益在于变更意图与其副作用事件生成、持久化翻译解耦。visitors/目录下的TableEventGeneratingSpecVisitor与TableSpecEventVisitor见packages/v2/core/src/domain/table/specs/visitors/正是以访问者方式在规格被接受时生成对应领域事件的实现佐证。6. TableSpecBuilder流式规格组合TableSpecBuilder.ts继承自共享层SpecBuilder为Table提供流式构建 API核心设计点有三个。6.1 BaseId 的隐式注入构造函数以baseId为可选参数若传入则includeBaseId默认为true并在build()时自动将TableByBaseIdSpec前置到规格列表build(): ResultISpecificationTable, ITableSpecVisitor, DomainError { const specs this.includeBaseId this.baseIdValue ? [TableByBaseIdSpec.create(this.baseIdValue), ...this.specs] : [...this.specs]; return this.buildFrom(specs); }这意味着table.specs().byName(Projects).build()天然限定在构建器所属的 Base 内。若未提供 baseId 或显式调用withoutBaseId()则includeBaseId置为false不再注入。byBaseId()方法则允许显式覆盖。6.2 查询方法byId(tableId)追加TableByIdSpecbyIds(tableIds)追加TableByIdsSpecbyIncomingReferenceToTable(tableId)追加TableByIncomingReferenceToTableSpec用于查询引用了指定表的表可跨 BasebyName(tableName)/byNameLike(tableName)精确/模糊名称匹配。6.3 组合方法andGroup(build)、orGroup(build)以回调式嵌套构建 AND/OR 子组not(build)先构建子规格再取反。createChild创建子构建器时继承baseIdValue但关闭includeBaseId避免子组重复注入 BaseId 条件。7. 测试验证组合行为与跨 Base 查询TableSpecBuilder.spec.tspackages/v2/core/src/domain/table/specs/TableSpecBuilder.spec.ts是原架构文档推荐的示例文件用 vitest 逐条验证了构建器的组合语义是理解该系统行为的最佳起点默认注入 BaseIdtable.specs().byName(Projects).build()对同 Base 同名表返回true对异 Base 同名表返回false显式排除 BaseIdwithoutBaseId().byName(...)对两个 Base 的同名表均返回true嵌套 OR 组orGroup(b b.byName(Projects).byName(Tasks))对两者均满足NOT 规格not(b b.byName(Projects))对 Projects 表返回false、对 Tasks 表返回true名称模糊匹配byNameLike(Pro)命中Projects、排除TasksID 列表withoutBaseId().byIds([table.id()])仅命中目标表跨 Base 反向引用Table.specs().byIncomingReferenceToTable(foreignTable.id())对持有指向 foreignTable 链接字段的 hostTable 返回true对 foreignTable 自身与无关表返回false测试中通过LinkFieldConfig.create({ relationship: manyMany, isOneWay: true, ... })构造引用关系。同时TableSpecs.spec.ts与__tests__/目录下的TableUpdateFieldConstraintsSpec.spec.ts、TableUpdateFieldNameAndTypeSpec.spec.ts、TableUpdateViewColumnMetaSpec.spec.ts、TableUpdateViewQueryDefaultsSpec.spec.ts以及field-updates/__tests__/中按字段类型的更新规格测试如UpdateLinkConfigSpec.spec.ts、UpdateRollupSpecs.spec.ts等共同构成对isSatisfiedBy与mutate语义的完整回归保障。8. 工作流串联从规格到持久化综合共享层与表级源码一条完整的数据流可以这样串联构建规格通过TableSpecBuilder或直接调用各 Spec 的create生成规格对象此时只是内存中的意图描述内存过滤内存仓储如MemoryTableRepository调用isSatisfiedBy(table)对聚合根逐一判定见packages/v2/core/src/ports/memory/MemoryTableRepository.ts持久化翻译持久化适配器实现ITableSpecVisitor将规格的accept分发翻译为 SQL where/update 负载ITableSpecVisitor注释明确其职责为query/update translation payloads变更执行变更类规格通过mutate驱动聚合根方法如Table.addField、Table.rename再由事件生成类访问者产出领域事件。这一流程使领域意图与基础设施翻译彻底解耦新增一种表操作只需新增规格类并在访问者接口补一个visit方法无需改动查询与存储管线。9. 小结domain/table/specs目录是 teable v2 领域层规格模式 访问者模式的表级落地查询规格负责内存筛选变更规格负责聚合根变更ITableSpecVisitor统一翻译出口TableSpecBuilder提供安全的 and/or/not 组合与 BaseId 隐式注入。想要进一步深入可以按以下路径继续阅读源码共享规格抽象packages/v2/core/src/domain/shared/specification/含ISpecification.ts、SpecBuilder.ts、AndSpec.ts、OrSpec.ts、NotSpec.ts、MutateOnlySpec.ts及对应架构说明ARCHITECTURE.md表规格全集packages/v2/core/src/domain/table/specs/含查询/变更规格、field-updates/与visitors/子目录访问者实现样例packages/v2/core/src/domain/table/specs/visitors/观察事件生成与字段更新语义的翻译逻辑测试基线TableSpecBuilder.spec.ts与TableSpecs.spec.ts。【免费下载链接】teable✨ AI Spreadsheet for Business项目地址: https://gitcode.com/GitHub_Trending/te/teable创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表