百积木文档
开发指南后端应用开发

数据库设计规范

Hosted Service 后端按第三范式设计关系模型,通过 ID 引用权威数据,并对专门设计的冗余存储逐项确认和记录原因。

Hosted Service 后端的关系型数据库设计默认必须符合第三范式(3NF)。能够通过已保存的 ID 或稳定引用 查询得到的同一业务事实,不要在引用方重复持久化。 这项要求适用于业务表、关联表和数据库 JSON 字段, 不因使用 PostgreSQL、其他关系型数据库或不同 ORM 而改变。

先明确业务事实与归属

设计表之前,先说明每类业务事实由哪个服务负责维护、如何唯一标识,以及哪些服务只是使用它。

  • 数据所有者保存并更新权威事实;引用方保存必要的 ID 或稳定业务引用。
  • 同一数据所有者内,通过关联查询取得被引用对象的属性;跨服务通过数据所有者提供的正式接口查询, 不直接读写另一个服务的数据库。
  • 查询结果可以组合成响应 DTO;为展示而组合字段不等于需要把组合结果再次存入数据库。
  • 关联字段、唯一约束和适用的外键应表达真实业务不变量。跨服务引用不暴露对方内部数据库主键。

新增数据库字段时,要回答“这是当前对象自己的事实,还是可以沿已有引用查询的另一个对象的事实”。 不能为了少写一次关联查询,把后一种事实复制到当前对象中。

第三范式与 ID 引用

评审必须识别候选键和函数依赖,不能只检查每张表是否有一个 id:

  1. 满足第一范式,按字段语义保存单一值,不用重复列或分隔字符串表达关系。
  2. 满足第二范式,非主属性依赖完整的候选键,不能只依赖组合候选键的一部分。
  3. 满足第三范式,避免非主属性通过其他非主属性传递依赖于键。严格地说,每个非平凡函数依赖 X → A 中,X 应是超键,或 A 是某个候选键的组成属性。

增加自增 ID 不会消除原有业务属性间的依赖。平台还要求跨表、跨服务保持同一事实的唯一权威来源, 因此不能把“各表单独满足第三范式”当作复制其他服务数据的理由。

例如,一项任务引用客户,客户名称表示客户的当前名称:

customers(id, name)
tasks(id, customer_id, title)

任务保存 customer_id,读取时解析客户名称。不要再增加 tasks.customer_name 保存同一个当前名称, 否则客户改名后,两处数据就可能不一致。把它改名为 customer_label、放进 JSON 或单独建一张复制表, 都不会改变冗余的业务语义。

性能问题先验证查询计划、索引、批量查询和正式接口能力。不能仅凭“减少 JOIN”或“减少一次服务调用” 就增加冗余字段。

冗余存储必须单独确认

确有业务或性能证据需要冗余时,必须把它作为一项专门设计,在实现、迁移或发布前向当前用户说明 具体字段或数据集、目的、代价和替代方案,并取得针对该设计的单独明确确认。

普通功能批准、发布批准或“优化性能”的笼统要求都不等于批准冗余存储。缓存、读模型、物化投影、 快照和审计也不是自动豁免理由;涉及重复保存业务事实时,同样需要确认和文档记录。

历史事实与当前事实必须区分。例如“成交时确认的价格”和“商品当前目录价格”语义不同,不能通过更新 商品价格重写历史成交记录。设计时先明确事实发生时点及其所有者;如果需要复制目录内容形成快照, 仍须明确复制范围、冻结语义并完成上述确认,不能仅凭字段名带有 snapshot 就认定获准。

在项目文档中记录设计原因

每项获准的冗余设计,都必须记录在所属后端项目的架构文档或架构决策记录(ADR)中,随项目源码维护, 让后续开发者能够理解和复核:

记录项必须说明的内容
事实与归属权威数据源、负责维护的服务、引用键,以及当前事实或历史事实的语义
冗余范围具体表、字段、JSON 属性、缓存或投影,以及使用者
设计原因业务要求或实测性能证据,不能只写“方便查询”或“性能优化”
替代方案不冗余的方案,以及未采用的原因和代价
一致性责任谁负责写入,何时更新或失效,允许的滞后和冲突处理方式
生命周期创建、删除、保留期限、失败恢复、重建和回滚;快照还需记录采集时点、版本及冻结语义
确认记录确认人、日期、明确批准的范围及可追溯的确认依据

未获得确认,或缺少上述设计记录时,不得实施冗余存储。扩大字段范围、改变用途或一致性语义时, 须重新确认并更新记录,不能把一次批准当成整个项目的永久豁免。

变更与评审

  • 新增或修改表、字段、持久化 JSON 时,检查候选键、函数依赖、权威来源和已有 ID 引用。
  • 对每项冗余检查专门设计、用户确认和 ADR 是否一致;自动检查不能代替业务语义评审。
  • 触及存量冗余时,先盘点真实读写者和迁移影响,明确最终模型。历史上已经存在不代表已经获准, 也不能直接删除仍被使用的字段或数据。
  • 结构和数据变更遵守数据库迁移,验证在线版本、 回滚版本、并发写入和迁移恢复。临时双写或复制同样需要上述确认,并记录退出条件。

资源申请与环境绑定见 Project、Environment、Slot、Endpoint 与数据库; 数据库 Record 与领域模型的代码边界见 Rust 服务设计约束。

本页内容