Vibe Coding 数据模型规格:从业务不变量反推表与状态机
让 AI 直接“设计数据库”容易得到命名整齐却无法阻止非法状态的表。本文从身份、基数、状态转换、并发和删除规则出发,把业务不变量落实到约束与可验证事务。
本文要点
- 主题聚焦:AI、Vibe Coding、TypeScript
- 适合人群:独立开发者、全栈工程师、AI 应用开发者
- 核心价值:快速理解背景、实践路径、常见误区和工程建议
Prompt 写“为团队应用设计 User、Workspace、Member 和 Invitation 表”,AI 很快会给出四个模型和漂亮关系。真正决定正确性的却是它没有问的事:同一邮箱能否重复邀请?最后一名 owner 能否退出?邀请接受与撤销同时发生时谁赢?删除 workspace 是级联、软删除还是必须先导出?表名正确并不能阻止非法业务状态。
数据模型规格应从不变量开始,再反推实体、字段和索引。它承接目标用户与主路径中的角色与完成事件,也为API 合同与请求响应示例提供稳定语义。JSON Schema 能验证一个 JSON 实例的结构,数据库约束能保护持久化行;两者都不会自动发明业务规则。
先写任何时刻都必须成立的句子
选择主路径中的名词和动作,逐条回答身份、基数、唯一性、生命周期、所有权和时间。以 workspace 邀请为例,候选不变量可能是:
- 每个活跃 workspace 至少有一名活跃 owner;
- 一个用户在同一 workspace 最多有一个活跃 membership;
- 同一 workspace 与规范化邮箱最多有一个
PENDING邀请; - 邀请只能由
PENDING转为ACCEPTED、REVOKED或EXPIRED,终态不可再次接受; - 接受邀请创建 membership 与关闭 invitation 必须原子完成;
- 被撤销或过期的邀请 token 永远不能换取成员身份;
- 审计记录引用稳定主体身份,不因用户改显示名而改变。
这些只是示例,必须由具体产品负责人确认。为每条分配 INV-01 这样的稳定 ID、适用范围和反例。反例比正向描述更容易暴露歧义:两名 owner 同时退出会怎样?同一人用大小写不同的邮箱收到两份邀请是否允许?workspace 进入删除中时还能否接受邀请?无法回答就进入假设与未知问题账本,不要让迁移文件替团队决定。
决定哪一层执行哪条规则
能由单行或局部关系表达的规则尽量下沉数据库。NOT NULL 防止必要字段缺失,CHECK 限定枚举和值域,UNIQUE 或部分唯一索引保护业务键,FOREIGN KEY 维护引用存在。PostgreSQL 文档也提醒,跨行、跨表的条件不适合随意塞进 CHECK;这类规则通常需要事务内锁定、延迟约束、触发器或单一领域命令处理,并用并发测试证明。
把执行计划写成表:invariantId、数据库机制、应用前置校验、错误代码、并发策略、验证用例。例如 INV-03 使用对 (workspace_id, normalized_email) 且 status='PENDING' 的部分唯一索引;应用先查询是为了返回友好提示,但最终以数据库冲突为准。仅做“先查再插”会在两个并发请求间产生重复行。
规范化也是合同。邮箱是否大小写不敏感、Unicode 怎样处理、前后空格是否保留,要规定算法和版本,保存原始展示值与比较键。不要未经业务确认就声称所有邮箱本地部分都可转小写。外部 ID、幂等键和时间戳同样规定生成者、时区、精度及唯一范围。
把生命周期画成状态机
不要只生成一个字符串 status。为每个有生命周期的实体写允许转换、执行角色、前置条件、原子副作用和终态。PENDING -> ACCEPTED 要校验 token 摘要、未过期、邀请邮箱与当前身份的匹配规则,并在同一事务创建 membership;并发的 REVOKE 与 ACCEPT 只能有一个提交成功,失败方得到稳定的领域错误。
状态转换使用条件更新,例如匹配 id + expectedStatus + version,受影响行数为零即表示冲突或过期,再读取当前事实映射响应。不能先读状态、在内存判断、随后无条件更新。涉及“最后一名 owner”时,事务需锁定足以稳定计数的行或使用能序列化该决定的领域记录;具体隔离级别通过并发实验选择,不凭名称猜测。
删除规则逐关系决定 RESTRICT、CASCADE、匿名化或保留。审计与账单通常不能跟随用户删除而消失,临时缓存却可以。软删除不是免费的默认值:它改变唯一索引、查询过滤、恢复语义和外键行为。若采用,所有主路径、后台任务和唯一性规则都必须包含删除状态。
用示例和反例驱动生成
给 Agent 的模型任务应包含不变量清单、状态图、三组最小 fixture 和禁止方案。第一组是正常创建与接受;第二组覆盖重复邀请、过期 token、最后 owner 退出;第三组是并发时序。先让它给出“不变量到约束/事务/测试”的映射,再生成 schema 和迁移。任何不变量只能靠注释维持时,必须解释为何无法在更可信边界执行。
UI 状态矩阵引用领域错误而不是数据库异常文本;MVP 垂直切片只实现当前路径必要的模型,不提前生成计费、通知或多租户抽象。API 输入可用 JSON Schema/OpenAPI 描述形状,但“至少一名 owner”之类跨请求事实仍由领域事务保护。
迁移验收包含空库创建、现有样本升级、违反新规则的脏数据、回滚或前向修复策略。添加 NOT NULL 前先量化空值并规定 backfill 来源;增加唯一索引前列出冲突如何裁决,不能让 Agent任意保留最新一行。数据量和锁时长用目标环境副本测量,未知时在非功能预算中保留未决门。
最后运行属性与并发测试:随机操作序列后逐条检查不变量;让接受/撤销、退出/转移 owner 同时发生;故意绕过应用校验直接写库,确认可信约束仍生效。测试通过结果绑定迁移版本和数据库版本。
好的数据模型不是实体图看起来完整,而是无论请求怎样乱序、重试或并发,禁止状态都难以被写入;一旦无法判断,也会明确失败而非静默损坏。先写不变量,AI 才是在实现业务规则,而不是用熟悉的 ORM 形状替你发明规则。
Related