LabHub
学习 学习路径 课程

生产级后端 API 综合项目

schema 是最后一道防线

在 LabHub 中继续学习

一句话总结

生产环境的数据契约不能止步于应用程序里的 if,而应通过 PostgreSQL 约束一直守到底。对于订单 API,数据库必须亲自保证金额为正、所有者与组织存在、幂等键在组织范围内唯一,并记录创建时间。

概念图: 一句话总结 · 为什么只有应用程序检查还不够 · 在实际项目中如何验证 · 实际工作的判断标准

为什么只有应用程序检查还不够

写入路径一开始可能只有一条,但很快就会增加批处理、管理员工具、恢复脚本和新服务。即使最初的 API 检查金额,其他路径仍可能写入负数,数据届时已经被污染。NOT NULLCHECKUNIQUE 与外键,无论由哪个客户端访问,都会应用同一套规则。尤其要注意,幂等键的唯一范围应是组织,而不是全局,这样不同客户使用同一个键时才不会冲突。因此,UNIQUE (org_id, idempotency_key) 能更准确地表达业务边界。

PostgreSQL 16 的 GENERATED ALWAYS AS IDENTITY 是真实生产语法,不同于 SQLite 的整数主键。TIMESTAMPTZ 让保存的时刻可以在统一时间轴上比较。索引应跟随查询模式,按照 org_idowner_idcreated_at 的顺序设计。迁移要在 BEGINCOMMIT 之间执行,避免暴露中间状态。

在实际项目中如何验证

不能只检查 DDL 文本里是否出现某个单词。应把迁移真正应用到空的隔离 schema,插入正常记录,再确认同一组织、同一键的第二条记录是否触发 unique_violation,负数金额是否触发 check_violation。测试时创建临时 schema,结束后删除,避免与生产数据混在一起。同一套验证必须能在 CI 中反复执行,才能把“在我的电脑上能运行”变成部署证据。

实际工作的判断标准

约束不是拖延错误的障碍,而是防止错误状态被写入的契约。API 可以把错误码转换成有意义的 409 或 400,但不能因此移除数据库约束。回滚策略也需要考虑,不过首先要证明前向迁移同时满足全新空数据库与现有数据的条件。下一课会把这些不变量连接到事务与重试语义。