从用户故事到数据库模式:AI 指导的方法
学习一种实用方法,将用户故事、实体和工作流转成清晰的数据库模式,并了解 AI 推理如何帮助检查缺失项与规则。

你要构建的东西:与真实工作相匹配的模式
一个 数据库模式 是应用如何记住事物的计划。实操上,它包括:
- 表(Tables):信息的“桶”(Customers、Orders、Tickets)
- 字段(列):你为每个对象存储的细节(customer_name、order_date)
- 关系:这些桶如何相连(一个 Order 属于一个 Customer;一个 Customer 可以有多个 Orders)
当模式与真实工作匹配时,它反映人们实际做的事——创建、审核、批准、排程、分配、取消——而不是白板上听起来整洁的概念。
为什么从用户故事开始?
用户故事和验收标准用通俗语言描述真实需求:谁在做什么,以及“完成”意味着什么。如果以这些为来源,模式不太可能漏掉关键细节(比如“我们必须记录谁批准了退款”或“一次预订可以被多次重新安排”)。
从故事出发还能让你对范围保持诚实。如果故事(或工作流)里没有它,就把它当作可选,而不是偷偷构建复杂模型“以防万一”。
AI 在这里能做和不能做的事
AI 可以帮助你更快地推进:
- 抽出候选实体(故事中重要的“东西”)
- 建议验收标准隐含的字段(时间戳、状态、引用)
- 发现可能的关系和遗漏(“提到了审批但没有存储审批人”)
AI 不能可靠地做到:
- 知道你没有写下的隐藏业务规则或边缘情况
- 在没有权衡的情况下选择“正确”的细节层级(简单 vs 灵活)
- 保证模式满足你的报表、安全或合规需求
把 AI 当作强力助理,而非最终决策者。
如果你想把这个助理变成执行力,像 Koder.ai 这样的 vibe-coding 平台可以帮助你更快地把模式决策变成一个可运行的 React + Go + PostgreSQL 应用——同时让你保持对模型、约束和迁移的控制。
设定预期:迭代式,而非一次到位
模式设计是一个循环:草案 → 针对故事测试 → 发现缺失数据 → 细化。目标不是一次性完美输出,而是能将每张表追溯到特定用户故事并自信地说:“是的,我们可以存储这个工作流所需的一切——并且能解释每张表存在的理由。”
输入:用户故事、验收标准和真实示例
在把需求转成表之前,先弄清你在建模什么。一个好的模式很少从空白页开始——它始于人们实际做的具体工作,以及你以后需要的证明(屏幕、输出和边缘情况)。
希望集中在一处的典型输入
用户故事是主线,但单靠它们不够。收集:
- 用户故事 + 角色(谁在做什么、为什么)
- 验收标准(“必须为真”的规则)
- 表单/屏幕(用户输入、选择或看到的字段)
- 报表/导出(哪些内容需要汇总、分组、过滤)
- 真实示例(样例订单、发票、工单、日历——任何具有代表性的东西)
如果你使用 AI,这些输入能让模型不过度发明结构。AI 可以快速提出实体和字段,但需要真实工件来避免与产品不匹配的设计。
验收标准:隐藏的约束来源
验收标准通常包含最重要的数据库规则,即使它们没有显式提到数据。寻找类似的陈述:
- “电子邮件必须唯一”(唯一性)
- “状态可以是 Draft、Submitted、Approved”(允许值)
- “只有经理可以批准”(权限,可能需要审计字段)
- “不能删除有付款的发票”(参照完整性规则)
早期要修正的常见陷阱
模糊的故事(“作为用户,我可以管理项目”)常常隐藏多个实体和工作流。另一个常见缺口是漏掉边缘情况,如取消、重试、部分退款或重新分配。
建模前的快速故事质量清单
- 行为者/角色是否明确。
- 对象是否具体(不是“数据”或“东西”)。
- 是否至少有一个真实示例。
- 验收标准是否包括验证和边界。
- 错误和“如果发生 X”情况是否被提及(或明确延后)。
第 1 步 — 从故事中提取实体(名词)
在考虑表或图表之前,读一遍用户故事并标出名词。在需求写作中,名词通常指向系统必须记住的“东西”——这些通常会成为你的实体。
一个简单的心理模型:名词成为实体,而动词成为操作或工作流。例如故事说 “A manager assigns a technician to a job”,很有可能的实体是 manager, technician, 和 job——而“assigns”暗示你以后要建模的关系。
如何判断一个名词是否是真正的实体
并非每个名词都值得独立成表。当一个名词满足以下条件时,它就是实体的强候选:
- 有自己的身份:你可以指向一个特定实例(Job #1042、Customer A)。
- 随时间变化:它有生命周期(一个 job 从 scheduled → completed)。
- 在多处使用:多个故事引用它,或多个工作流程会接触到它。
如果一个名词只出现一次,或只是描述别的东西(“红色按钮”、“星期五”),它可能不是实体。
属性 vs 独立实体(“地址”和“标签”的测试)
一个常见错误是把每个细节都做成表。用下面的经验法则:
- 如果它是描述某物的单个值,通常是属性(例如 Customer.phone_number)。
- 如果它是可重复、可共享或有结构的,通常是独立实体。
两个经典示例:
- Address(地址):如果你需要保存发货和账单地址、保留历史,或在客户/地点间重用地址,Address 可能是独立实体。如果你只需要一个邮寄地址且不会重用,地址可以作为属性。
- Tag(标签):标签几乎总是做成独立实体,因为它们是可重复的并且是多对多关系(一个 Job 有多个 Tags;一个 Tag 适用于多个 Job)。
谨慎使用 AI 建议候选实体
AI 可以通过扫描故事来加速实体发现并返回按主题分组的候选名词(人员、工作项、文档、地点)。一个有用的提示是:“提取必须存储的数据的名词,并合并重复/同义词。”
把输出当作起点,不是最终答案。继续问:
- “这些中哪些有生命周期或需要独立 ID?”
- “哪些实际上是状态、类别或属性?”
- “有没有同义词(例如 ‘client’ vs ‘customer’)?”
第 1 步的目标是得出一个简短、干净的实体列表,并能通过指向真实故事来为其辩护。
第 2 步 — 将细节变成字段(你必须存储的提醒)
一旦你命名了实体(如 Order, Customer, Ticket),下一步是捕捉以后会用到的细节。在数据库里,这些细节是字段(也叫属性)——系统不能忘记的提醒。
如何在不猜测的情况下选择字段
从用户故事开始,然后把验收标准当作必须存储的清单来读。
如果需求说 “Users can filter orders by delivery date”,那么 delivery_date 就不是可选的——它必须存在为字段(或能可靠地从其他存储的数据推导)。如果需求说 “Show who approved the request and when”,你可能需要 approved_by 和 approved_at。
一个实用测试:"有人需要用来显示、搜索、排序、审计或计算这个值吗?" 如果需要,它很可能应该是字段。
干净字段的简单规则
- 保持原子性:如果你会按名字搜索或排序,就把“名”和“姓”分开。避免把多个值塞进一个字段(例如 “red, blue”)。
- 使用一致类型:日期用日期类型,货币用小数,布尔值用 true/false——不要混用格式如 “$10”, “10 USD”, “10”。
- 避免重复文本:不要把客户地址复制到每一行订单项。把它放在恰当的位置并引用它。
受控词汇:状态、类型和分类
许多故事包含“status”、“type”或“priority”之类的词。把它们当作受控词汇——一组允许的有限值。
如果集合很小且稳定,简单的枚举(enum)字段可以胜任。若可能扩展、需要标签或需要权限(例如管理员管理的分类),使用单独的查找表(如 status_codes)并存储引用。
这是把故事转成可被信任字段的方式——可搜索、可报表、且不易输错。
第 3 步 — 用关系连接实体
列出实体(User、Order、Invoice、Comment 等)并起草它们的字段后,下一步是把它们连接起来。关系是故事暗含的“这些东西如何交互”的那一层。
三种关系形态(通俗)
一对一(1:1) 意味着“一个东西恰好有另一个东西的一个实例”。
- 故事表述:“每个用户有一个个人资料。”
- 建模思路:
User↔Profile(通常可以合并,除非有分离的理由)。
一对多(1:N) 意味着“一个东西可以有很多另一种东西”。这是最常见的。
- 故事表述:“一个用户可以有很多订单。”
- 建模思路:
User→Order(在Order上存user_id)。
多对多(M:N) 意味着“多个东西可以互相关联”。需要额外的表。
- 故事表述:“一个订单可以包含很多产品,且一个产品可以出现在很多订单中。”
多对多:连接表技巧
数据库不能把“产品 ID 列表”整齐地存在 Order 里而不造成后患(查询、更新、报表难办)。相反,要建一个连接表来表示关系本身。
示例:
OrderProductOrderItem(连接表)
OrderItem 通常包含:
order_idproduct_id- 故事中出现的额外细节,如
quantity,unit_price,discount
注意故事的细节(“数量”)往往属于关系本身,而不是任一实体。
必需 vs 可选(不用术语)
故事还会告诉你某个连接是必须的还是有时不存在。
- “一个订单必须属于一个用户” → 每个
Order需要user_id(不可为空)。 - “一个用户可以有电话号码” →
phone可以为空。 - “一个订单可以有一个运送地址(如果是实物)” →
shipping_address_id对数字产品可能为空。
快速检查:如果故事暗示在创建记录时必须有这个关联,就把它设为必需;如果故事里用到“可以”/“可能”等词,就把它设为可选。
把故事句子改写为关系句子
阅读故事时,把它改写为简单的配对句:
- “用户可以留下许多评论” →
User1:NComment - “一个评论属于一个用户” →
CommentN:1User
为故事中每个交互做这件事。到最后,你会得到一个在建任何 ER 图工具前就与工作流程相匹配的连通模型。
第 4 步 — 用工作流找出状态、事件和遗漏
用户故事告诉你人们想要什么。工作流则展示工作如何一步步流转。把工作流转成数据是发现“我们忘记存储某项信息”的最快方法之一——在你动手建之前就能发现问题。
从简单工作流开始
把工作流写成动作和状态变化的序列。例如:
- Create request → Draft
- Submit request → Submitted
- Manager reviews → Approved or Rejected
- If approved, work is scheduled → In progress
- Completed → Done
这些加粗词通常会成为一个 status 字段(或一个小的“状态”表),包含明确的允许值。
工作流会暴露缺失字段
在逐步梳理每一步时,问:“以后我们需要知道什么?”工作流通常会揭示以下字段:
- 时间戳:
submitted_at,approved_at,completed_at - 所有权:
created_by,assigned_to,approved_by - 原因/上下文:
rejection_reason,approval_note - 排序:多步骤流程的
sequence
如果工作流包括等待、升级或交接,通常至少需要一个时间戳和一个“当前由谁持有”的字段。
工作流会暴露缺失的表
有些工作步骤不只是字段——它们是独立的数据结构:
- 审计日志 / 历史:记录“谁何时更改了状态”
- 审批:用于多审批人或条件性审批规则
- 附件:用户在某步骤上传文件时
- 评论:当讨论是流程的一部份
用 AI 做交叉检查找遗漏
同时给 AI 两类资料:(1)用户故事与验收标准;(2)工作流步骤。让它列出每一步需要的数据(状态、执行者、时间戳、输出),然后高亮当前字段/表无法支持的需求。
在像 Koder.ai 这样的工具里,这种“差距检查”尤其实用,因为你可以快速迭代:调整模式假设、重新生成脚手架,并快速继续,而不是陷入大量手工样板代码中。
键、唯一性与基础约束(通俗)
把用户故事转成表时,你不仅是在列字段——你还在决定数据如何在时间里保持可识别和一致。
主键:每行的稳定“身份证”
主键 唯一标识一条记录——把它想成该行的永久身份证。
为什么每行都需要一个:故事里暗示会有更新、引用和历史。如果故事说 “支持可以查看订单并发起退款”,你需要一个稳定的方式指向该订单——即便客户改了邮箱、地址被编辑或订单状态改变。
在实践中,这通常是一个内部的 id(数字或 UUID),且永不改变。
外键:表间的指针
外键 是一张表安全地指向另一张表的方式。如果 orders.customer_id 引用 customers.id,数据库可以强制每个订单都属于真实存在的客户。
这对应类似“作为用户,我可以看到我的发票”的故事。发票不是漂浮的;它挂在客户(常常也挂在订单或订阅)下面。
唯一性规则:把“必须唯一”变成约束
用户故事经常包含隐藏的唯一性要求:
- “用户用邮箱注册” → 在
email上强制唯一(或在多租户场景下按租户唯一)。 - “财务按发票号查询” → 强制
invoice_number唯一。
这些规则能防止日后出现令人困惑的重复数据。
索引(高层):让常用查询更快
索引能加速“按邮箱查找客户”或“按客户列出订单”等查询。先从与你最常用查询和唯一性规则一致的索引开始。
需要推迟的:为罕见报表或推测性过滤加重索引。把这些需求记在故事里,先验证模式,再基于真实使用和慢查询证据做优化。
保持数据一致:实用范式检查清单
范式的唯一目标是避免冲突性重复。如果同一事实能被保存在两个地方,迟早会出现不一致(两个拼写、两种价格、两个“当前”地址)。规范化的模式把每个事实只存一次,然后引用它。
对任何草案模式都可以运行的快速清单
- 注意重复组
如果你看到像“Phone1、Phone2、Phone3”或“ItemA、ItemB、ItemC”这样的模式,那就是应该拆成单独表(例如 CustomerPhones, OrderItems)。重复组让搜索、验证和扩展都变难。
- 不要把同一名称/细节复制到多表
如果 CustomerName 出现在 Orders, Invoices, 和 Shipments,你就出了多个真相来源。把客户细节放在 Customers,其他地方只存 customer_id。
- 避免“为同一件事设置多列”
像 billing_address, shipping_address, home_address 在概念上可以,但如果你实际在建模“多地址及其类型”,就用 Addresses 表并加 type 字段。
- 把查找表和自由文本分开
如果用户从已知集合中选择(状态、类别、角色),保持一致建模:要么受限的枚举,要么查找表。这样能避免 “Pending” vs “pending” vs “PENDING”。
- 检查每个非 ID 字段依赖的是正确的对象
一个有用的直觉检验:在一张表里,如果某列描述的不是该表的主要实体,它很可能属于别的表。示例:除非它表示“下单时的价格快照”,否则 Orders 不应存 product_price。
何时可接受反规范化(作为后续选择)
有时你会故意保存重复:
- 报表/性能:预聚合总计或汇总表。
- 缓存:存储计算值以避免昂贵重算。
- 审计/历史:保存“购买时的名称”以保留过去的真实情况。
关键是要有意为之:记录哪个字段是事实源,以及副本如何更新。
AI 能帮忙的地方——和需要人来决定的地方
AI 能标出可疑重复(重复列、相似字段名、不一致的“status”字段)并建议拆成表。人类则根据产品的实际使用场景在简洁性、灵活性和性能间做取舍。
存储 vs 计算:什么该放数据库里
一个有用的规则:存储那些你无法可靠重建的事实;其他的都计算。
存储 vs 计算(派生)数据
存储数据 是事实源:单行项目、时间戳、状态变更、谁做了什么。 计算数据 是从这些事实推导出的结果:总计、计数、像“is overdue”这样的标记,以及像“当前库存”的汇总。
如果两个值可以从相同的底层事实计算出来,优先存事实并计算其余部分。否则你会遇到矛盾。
为什么存派生值会导致不匹配
派生值会在其输入改变时变化。如果你同时保存输入和派生结果,就必须在每条工作流和边界情况下保持它们同步(编辑、退款、部分发货、回溯更改)。一次遗漏更新,数据库就会讲两个不同的故事。
示例:既存 order_total 又存 order_items。如果有人改了数量或应用折扣而总价没被完美更新,财务看到一个数字,购物车显示另一个。
用工作流决定什么必须被存储(历史和快照)
工作流会显示你何时需要历史真相,而不仅仅是“当前真相”。如果用户需要知道某个值“当时是什么”,就要存快照。
对于订单,你可能会存:
- 明细行和价格(事实)
- 在结账时捕获的
order_total(快照),因为税费、折扣和定价规则以后可能改变
对于库存,“库存水平”通常由变动(入库、销售、调整)计算而来。但如果你需要审计轨迹,就存变动记录并可选地定期存储快照以提高报表速度。
对于登录追踪,保存 last_login_at 作为事实(事件时间戳)是合理的。“过去 30 天是否活跃?”这类问题仍然作为计算得出。
实战示例:从 5 条用户故事到 ER 模型
我们用一个熟悉的支持工单应用为例。从五条用户故事出发,得到一个简单的 ER 模型(实体 + 字段 + 关系),然后把它与一个工作流做对照。
5 条用户故事 → 名词 → 实体
- 作为客户,我可以创建一个带有主题、描述和分类的支持工单。
- 作为客服,我可以把工单指派给自己或其他客服。
- 作为客服,我可以对工单添加内部备注和公开回复。
- 作为客户,我可以看到我的工单何时更新以及何时关闭。
- 作为经理,我可以追踪工单开放多久以及谁关闭了它们。
由这些名词我们得出核心实体:
- User(客户、客服、经理)
- Ticket(工单)
- Message(公开回复 + 内部备注)
- Category(分类)
- TicketEvent(审计/历史)
字段与关系(简洁 ER 模型)
- User: id, name, email, role
- Category: id, name
- Ticket: id, subject, description, status, created_at, updated_at, closed_at
- 关系: Ticket.category_id → Category.id
- 关系: Ticket.requester_id → User.id(客户)
- 关系: Ticket.assignee_id → User.id(客服,nullable)
- Message: id, ticket_id, author_id, body, is_internal, created_at
- 关系: Message.ticket_id → Ticket.id
- 关系: Message.author_id → User.id
- TicketEvent: id, ticket_id, actor_id, type, from_status, to_status, created_at
工作流映射:创建 → 更新 → 关闭
- 创建:插入 Ticket(status = “open”,created_at),插入 TicketEvent(type = “created”)。
- 更新(指派、回复):插入 Message 或更新 Ticket.assignee_id,并插入 TicketEvent(type = “assigned”/“replied”,updated_at)。
- 关闭:更新 Ticket.status = “closed”,设置 closed_at,并插入 TicketEvent(type = “closed”,actor_id = closer)。
“前后对比”:AI 抓到的缺失约束
之前(常见遗漏):Ticket 有 assignee_id,但我们忘了确保只有客服(agent)可以被指派。
之后:AI 报告该问题,你可以增加一条实际规则:assignee 必须是 role = “agent” 的 User(通过应用层验证或数据库约束/策略来实现,视你的技术栈而定)。这能防止“把工单指派给客户”这类会破坏报表的数据。
验证模式:把每个故事追溯回去
只有当每条用户故事都能用你真实能存储并查询的数据回答时,模式才算“完成”。最简单的验证步骤是逐条拿起故事并问:
“我们能从数据库可靠地回答这个问题吗?如果不能,模型就有缺口。”
把每条故事改写成数据库问题
把每条用户故事改写为一个或多个测试问题——你期望报表、屏幕或 API 会提出的问题。例如:
- 报表:"按客户显示最近 30 天的所有未完成订单及其总计。"
- 权限:"哪些用户被允许为该店铺批准退款?"
- 边缘情况:"一个订单可以没有运送地址吗?数字商品如何处理?"
- 删除:"如果删除客户,订单、发票和备注会怎样?"
如果你无法把故事表达为清晰问题,说明故事本身不清楚。如果能表达但模式无法回答,则缺少字段、关系、状态/事件或约束。
用示例数据做快速健查
创建一个小数据集(关键表每张表 5–20 行),包含正常情况和棘手情况(重复、缺失、取消)。然后用这些数据“演练”故事。你会很快发现诸如“我们无法区分购买时使用的是哪个地址”或“我们没有地方存谁批准了变更”之类的问题。
让 AI 帮你找未处理的情况
让 AI 为每条故事生成验证问题(包括边缘情况和删除场景),并列出回答这些问题所需的数据。把这个列表与你的模式对照:任何不匹配都是一个具体的待办项,而不是一种模糊的“不对劲”感觉。
安全使用 AI 并保持模式可维护
AI 能加快建模,但也会带来泄露敏感信息或把糟糕假设硬编码进去的风险。把它当作非常迅速的助理:有用,但需要护栏。
可以与 AI 分享什么(以及应避免什么)
分享足够真实以便建模、但已脱敏以保证安全的输入:
- 已脱敏的用户故事(把客户、产品、地点改名)
- 验收标准 和边缘情况(“14 天内退款”、“每账号一个生效订阅”)
- 示例字段与伪数据(例如
invoice_total: 129.50,status: "paid") - 现有 CSV 表头 / 现有表结构(结构通常是安全的;内容通常不是)
避免任何能识别个人或泄露机密运营的信息:
- 真实姓名、邮箱、电话、地址
- 真实订单历史、工单、内部备注
- API 密钥、数据库凭证、含私人数据的截图
如果需要真实性,生成符合格式和范围的合成样本——绝不要复制生产行。
在模式旁记录假设
模式失败多数源于“每个人都默认了不同的理解”。在 ER 图旁(或同一仓库里)保留一个简短的决策日志:
- 定义(“什么算作‘活跃’账户?”)
- 约束(“用户可以属于多个组织”)
- 权衡(“我们在每张发票上存货币代码以便审计”)
这会把 AI 输出变成团队知识,而不是一次性的产物。
为变更做计划:版本与迁移
模式会随新故事演进。用下列方式保证安全:
- 为模式变更版本化(把迁移文件放在 Git)
- 尽量写可回滚的迁移
- 更新种子数据和示例查询,让变更可测试
- 像审查普通代码一样审查 AI 生成的迁移
如果你用像 Koder.ai 这样的工具,利用快照与回滚等护栏来迭代模式变更,并在需要更深层次定制或传统审查时导出源码。
一个简单可复用的流程
- 脱敏用户故事 + 生成 5–10 个合成示例。
- 让 AI 建议实体、字段、关系与约束。
- 团队评审;在旁边记录假设。
- 实施迁移;运行小规模“故事追溯”测试(每条故事都能被模型满足)。
- 在故事变化时重复此流程;保持模式与决策记录同步。
常见问题
How do I extract database entities from user stories?
从故事开始,找出那些表示系统必须记住的名词(例如 Ticket, User, Category)。
当一个名词满足下列条件时,可以提升为实体:
- 需要自己的 ID
- 会随时间变化(有生命周期/状态)
- 在多个故事中出现
保持一个可以通过指向具体故事句子来证明的简短实体清单。
When should something be a field vs. its own table?
使用“属性 vs 实体”的判别法:
- 如果它是描述单条记录的单个值(例如
customer.phone_number),把它作为字段。 - 如果它是可重复的、可共享的、结构化的,或需要历史记录(例如 多个地址、标签、附件),把它做成独立表。
一个快速提示:如果你可能需要“多个这样的项”,通常就需要另一张表。
How do acceptance criteria translate into fields and constraints?
把验收条件当作存储清单。如果某个需求需要过滤/排序/展示/审计某个内容,你必须存储它(或能可靠地从已有数据推导出来)。
示例:
- “显示谁批准以及何时” →
approved_by,approved_at - “按交付日期过滤” →
delivery_date - “电子邮件必须唯一” → 在
email上添加唯一约束/索引
How do I turn story text into table relationships (1:1, 1:N, M:N)?
把故事句子改写成关系句子:
- “一个客户可以有很多订单” → 1:N(在
orders上放customer_id) - “一个订单包含许多产品” → M:N(加一个连接表,如
order_items)
如果关系本身包含数据(数量、价格、角色),这些数据应该放在连接表上。
What’s the right way to model many-to-many relationships?
用一个连接表来建模 M:N,并在里面存两张表的外键以及关系专属字段。
典型模式:
ordersproductsorder_items,包含order_id,product_id,quantity,unit_price
避免把“ID 列表”存成单个列——查询、更新和完整性维护会变得棘手。
How do workflows help me find missing tables or fields?
逐步演练工作流并问:“后来我们要证明这一步发生过,需要什么信息?”
常见添加项:
- 时间戳:
submitted_at,closed_at - 执行者:
created_by,assigned_to,closed_by - 原因/备注:
rejection_reason
如果你需要“谁何时更改了什么”,加一个事件/审计表,而不是覆盖单一字段。
Which constraints should I add first (keys, uniqueness, indexes)?
先从以下开始:
- 每张表一个稳定的主键(
id) - 为关系添加外键(
orders.customer_id → customers.id) - 从需求中提取唯一性规则(邮箱、发票号)
然后为常用查找添加索引(如 email, customer_id, status + created_at)。把推测性的索引留到看见真实查询模式后再做。
How do I know if my schema is normalized enough without overdoing it?
运行一个快速的一致性检查:
- 如果看到重复组(如
Phone1/Phone2),拆成子表。 - 如果同一事实出现在多张表里,选定一个事实源并引用它。
- 如果某列描述的不是该表的主要实体,把它移到合适的表。
只有在有明确理由(性能、报表、审计快照)时再去反规范化,并记录哪个字段是权威来源。
What should be stored vs. calculated in the database?
把那些无法可靠重建的事实存下来;其他的都计算得出。
适合存储的:
- 事件和时间戳
- 明细行和历史价格
- 审计用的“谁做了什么”
适合计算的:
- 从明细计算出的总计
- 类似“是否逾期”的标记(从日期计算)
如果决定存派生值(比如 order_total),要明确如何保持同步,并测试退款/编辑/部分发货等边界情况。
How can I use AI safely to speed up schema design without making bad assumptions?
把 AI 当作起草和交叉检查的工具,然后根据你的工件验证结果。
实用提示:
- “从这些故事中提取候选实体和同义词。”
- “列出验收条件暗示的字段(时间戳、执行者、状态)。”
- “给定这个工作流,每一步需要哪些数据?”
防护措施:
- 清理输入(不要包含真实 PII、凭证或生产行)
- 在 schema 旁边保留决策记录
- 把 AI 生成的迁移当作普通代码来审查并在 Git 中版本化