存下来的形状和吐出去的形状不一样
一句话总结
存储的数据形态与对外输出的形态并不相同。 许多后端事故,都是因为没有守住这一条原则。
为什么需要它——事故往往悄无声息
假设你给订单表新增了一个 internal_memo 字段,处理程序没有改动,return order 这一行也保持原样,测试仍然全部通过。但从这一刻开始,API 响应会把内部备注一起发送出去。
没有人看到错误,日志也完全正常。没有人意识到,新增一个字段本身就是一次 API 变更。
在一个地方定义边界
需要修改的不是处理程序,而是把允许输出的字段集中定义,只让这些字段通过。
const publicItem = ({ id, name, qty }) => ({ id, name, qty });
关键在于它是允许列表。如果采用 delete item.secret 这种排除方式,下次再新增字段时,数据仍会泄露。不要逐个统计应当删除的字段,而要明确列出允许输出的字段。
在实际项目中
Nest 的 ClassSerializerInterceptor 与 FastAPI 的 response_model 做的正是这件事。名称不同,判断标准却相同——即使不修改处理程序代码,字段也会被过滤。
代码审查时也只需关注一个问题:新增字段的 PR 是否同时明确修改了输出形态,还是任由它自动流出。如果是后者,那么这本来就是一次 API 变更,只是没有人这样称呼它。