返回专题首页

Node.js 专题

数据校验与契约协作:Schema 校验、序列化与输入输出模型

API 能收到数据不代表接口就是稳定的。真正可靠的服务端项目,一定会认真区分“外部输入是否合法”“内部业务是否允许”“最终响应应该长什么样”。这一篇会把 Schema 校验、序列化和输入输出模型放到一起讨论。

Node.js 专题第 29 篇 / 40 篇5 分钟

API 能收到数据不代表接口就是稳定的。真正可靠的服务端项目,一定会认真区分“外部输入是否合法”“内部业务是否允许”“最终响应应该长什么样”。这一篇会把 Schema 校验、序列化和输入输出模型放到一起讨论。

为什么不能把校验都压给数据库?

数据库当然可以帮助你兜住某些约束,但它并不能替代服务端输入边界的职责。因为一个请求在真正写入数据库之前,通常还需要回答很多问题:

  • 字段格式对不对;
  • 必填项有没有缺失;
  • 类型是否合法;
  • 某个值是否超出合理范围;
  • 某种业务前提是否满足。

如果这些都等到数据库报错才知道,接口层的错误表达就会非常粗糙,也不利于调用方理解问题。

而且数据库擅长的是约束存储正确性,不是描述接口语义。比如“这个字段是字符串”与“这个字段当前业务阶段不允许为空”,虽然都可能导致请求失败,但它们对应的是不同层级的问题。把所有错误都推迟到数据库,服务端就失去了第一道清晰的输入边界。

输入模型、内部模型和输出模型为什么不该完全共用?

这几类模型都在描述数据,但它们描述的是不同视角:

  • 输入模型关心外部请求传了什么;
  • 内部模型关心业务逻辑如何理解和处理数据;
  • 输出模型关心对外暴露什么结构和字段。

如果你把它们完全混成同一份结构,短期看起来省事,长期则会导致:

  • 对外契约和内部实现强绑定;
  • 某些内部字段被意外暴露;
  • 接口变更会牵动业务内部实现。

所以,模型分层不是为了显得复杂,而是为了让外部契约和内部实现保持适度隔离。

这背后其实是在保护两件事:

  • 对外接口的稳定性;
  • 对内实现的可调整空间。

只要这两者完全绑死,每次改一边都会拖动另一边。短期当然省事,长期却很难演进。

序列化为什么不只是“转成 JSON”?

很多人会把序列化理解成“最后把对象转成 JSON 发出去”,但在服务端里,它其实同时承担了对外结果裁剪和控制的职责。比如:

  • 哪些字段应该暴露;
  • 哪些字段应该隐藏;
  • 某些内部枚举和状态是否要转换成更适合调用方理解的形式;
  • 默认值和空值要如何表达。

也就是说,序列化是接口输出契约的一部分,而不是单纯的技术细节。

很多隐蔽问题其实都发生在输出阶段。比如数据库里有内部状态码、敏感字段、时间对象或级联关系,如果没有统一的输出模型和序列化规则,很容易一不小心就把不该公开的信息直接吐给调用方。输出边界不清楚,接口安全性和可维护性都会一起下降。

Schema 校验为什么会变成协作契约?

一旦输入和输出模型稳定下来,Schema 就不再只是“校验规则”,它还会变成团队协作中很重要的契约表达。前端、服务端、测试,甚至文档和自动化工具,都可以围绕这套结构形成一致理解。

这也是为什么很多现代服务端框架和工具会很强调 Schema:因为它帮助大家在真正运行代码之前,就先对“什么是合法输入”“什么是稳定输出”达成一致。

从这个角度看,Schema 最珍贵的地方不是“校验代码能复用”,而是它让沟通变得更具体。前端知道哪些字段必填、测试知道边界值怎么构造、后端知道哪些字段进入业务层前已经被清洗过。契约一旦清楚,很多联调争议其实会明显减少。

校验体系里最容易混淆的两件事是什么?

最容易混在一起的,通常是:

  • 结构合法性校验;
  • 业务规则校验。

前者关注的是“这份输入长得对不对”,比如字段类型、是否缺失、格式是否合理;后者关注的是“这份输入在当前业务上下文里能不能被接受”,比如用户名是否已存在、库存是否足够、当前状态是否允许修改。

把这两者分开看很重要。因为结构校验更适合靠近接口边界,业务校验则应该进入服务层判断。只有这样,错误来源和返回语义才会更清楚。

一个稳定接口契约通常要守住哪些点?

如果把这一篇收拢成可执行的判断标准,一个更成熟的服务端契约体系通常会包含:

  • 输入有明确的 Schema 和基础错误表达;
  • 内部业务处理不直接暴露外部模型;
  • 输出经过统一裁剪和序列化;
  • 结构校验与业务校验职责分开;
  • 文档、测试和前后端联调围绕同一套契约协作。

总结

这一篇我们把服务端输入输出边界真正拉清楚了:数据库不是第一道校验线,输入模型、内部模型和输出模型不该完全混用,而序列化和 Schema 又共同把接口契约变得更稳定、更可协作。

下一篇我们会继续进入服务端的持久化层,看 Node.js 如何和数据库与事务边界协作。