有效(valid)指文档在良构之外,还符合一份预先约定的结构契约(DTD 或 XSD):该有哪些元素、顺序如何、出现几次、取值什么类型。承上:3.1 已证明良构挡不住"qty 是二百件"这类业务错误;启下:本节讲清契约机制的全貌,DTD 与 XSD 两种契约语言随后各占一节。
两个企业系统对接,接收方真正的诉求是一份"输入说明书":book 元素必须有 title;title 之前不许出现 price;stock 必须是整数;isbn 若出现必须恰好一份。这份说明书如果只写在 Word 文档里,靠人眼核对,等而下之;写成机器可查的契约文档,接收方在解析前一键校验,违规直接退回——这才是"有效"两字的工程含义。
流程上的位置:收到报文 → 良构检查(解析器)→ 有效检查(校验器,对照契约)→ 才进入业务处理。校验失败的报文不进入业务代码,业务代码因此可以信任结构——契约是把防御前移到门口的手段。这四个环节在工程上对应四类组件:解析器几乎总在校验器内部复用;校验器可以是独立命令、应用内嵌的库或网关上的策略点;业务处理则是最终消费方。把校验放在链条哪个位置,取决于报文来源的可信度——完全可信的内部报文可以抽检,来自外网公开接口的报文应当全量校验,介于两者之间的走灰度策略。位置的选择本身也是契约治理的一部分。
背景:物流平台接收上游运单报文,未做契约校验,直接解析取值。某日上游发版,一个字段悄悄变了。操作与结果——上游报文:
<shipment> <id>S-88</id> <weight>12.5</weight> <weightUnit>KG</weightUnit> </shipment>
而新版本把重量拆成了两层:
<weight> <value>12.5</value> <unit>KG</unit> </weight>
解析器眼里两份都良构。平台的代码直接取 weight 的文本值,得到的是空白(新版 weight 里只有子元素没有文本)。计费模块把空白当 0 处理,一批运单按 0 公斤计费,隔天对账才发现。解读:这不是解析器的失职,是没有契约的失职——若平台发布了"weight 必须是十进制文本"的 XSD 并在入口校验,第二份报文会在门口被退回,附违规行号。变式:契约还应写死 minOccurs——weight 必须出现一次,缺字段同样拦下。
任何契约语言都要回答三类问题。结构上:哪些子元素允许出现、顺序是否强制;次数上:必选还是可选、最多几次;类型上:文本是十进制、日期还是枚举。用接近自然语言的伪契约示意:
元素 shipment: 子元素 id —— 必须恰好一次,类型为字符串 子元素 weight —— 必须恰好一次,类型为十进制数字 子元素 remark —— 可选,最多一次 顺序:id 在 weight 前,weight 在 remark 前
DTD 能完整表达结构与次数,类型只能"字符串 or 空";XSD 三类全覆盖,还支持自定义类型、命名空间。选择判据:对接老系统(HTML 时代的 SGML 遗产、早期 SOAP)多半绕不开 DTD;新建契约一律 XSD,这是行业默认。
⚠️ 一个反方向的事故:契约发布后无人维护,代码早就允许 remark 出现多次,契约还写着"最多一次"——校验器天天报错,最后被工程师注释掉。契约必须与代码同步演进,否则它比没有契约更危险(制造噪音,掩盖真报警)。
命令行校验器、IDE、构建流水线都能执行契约校验。成熟的用法是三层布防:联调环境入口校验(第一时间暴露双方理解偏差);持续集成里对样例报文跑校验(契约变更即刻可见);生产入口按策略校验(全量或抽样,视性能预算)。第 3.4 节会给出 XSD 的完整校验命令演示,第 7 章工具箱会盘点具体工具。
契约不是刻在石头上的。业务变化时契约要改版,改版的工程手法值得单独一节讨论——它比"写第一版"更容易出事故。成熟做法是契约即代码:把 XSD 纳入版本管理,每次变更走评审,变更类型分三级处理。宽松化变更(可选字段变可选、新增可选元素、放宽取值范围)对旧文档无破坏,直接发新版本;收紧化变更(新增必填、收窄枚举)有破坏性,必须提前通知所有生产方、约定切换窗口,并在窗口期"双契约校验"(新旧都试,新失败旧通过就告警);重命名与结构重组是最重的一级,通常只在大版本跳号时发生,配合 5.2 节的 XSLT 做"旧格式自动升格"的桥接层,让存量报文平滑迁移。
三级处理背后是一个原则:契约的稳定性是它的价值来源。频繁破坏性变更的契约会逼着所有接入方持币观望,最终契约名存实亡。所以发布前多问一句"这个字段三年后还在吗",比事后走三级流程便宜得多。第 7 章还会回到这个话题——把契约校验接进持续集成,正是让契约演进可控的基础设施。
再补一个团队协作视角:契约的所有权要在项目启动时定清。谁起草、谁评审、变更走什么流程、争议谁拍板——这四问没有答案的契约,演进时必然演成拉锯战。常见的有益位形是"消费方主导、生产方会签":接收报文的一方最清楚自己要什么,由它维护 XSD,生产方在评审桌上确认可行性。反向位形(生产方定义格式、消费方被动适应)也不是不行,但要配一条硬规则——契约变更必须提前一个发布周期通知。所有权的讨论看似与语法无关,却是契约能不能真正"机器可查"的前置条件:一份没有主的契约,最后总会退化成"以最后一份能跑通的报文为准"的口头契约。
还剩一个高频误区值得点破:校验通过不等于数据正确。契约管的是结构与类型,管不了语义——XSD 能保证 qty 是整数,保证不了它不是负数(除非你显式加范围约束),更保证不了库存数与实物一致。曾有团队以为"入口全量校验"就是数据质量的全部,结果负数数量穿透了只有类型约束的契约流进财务报表。教训是把契约当闸门而非质检员:闸门拦"形状不对的",语义与业务规则还得靠第二层校验逻辑或对账机制。分清契约的管辖范围,才不会对它抱错期待。
契约语言登场。第一代规程 DTD 语法最紧凑、存量最大——下一节先解剖它。