W07 · S43—S49 · 总 Day 133—139
协议集成:JSON 相同,含义未必相同
把两个版本的金额契约转换为明确的内部语义。
作者准备的学习示例 · 不计真实学习进度 · 不代表生产 / GPU / 真机结果
核心问题
工具从 amount: 12.34 改成 amount: 1234,两个数都能通过 number 类型检查,却可能差一百倍。协议集成最难的部分常常不是把 JSON 传过去,而是保证双方对单位、币种、身份、动作与版本有相同理解。
对应 S43~S49。本周用金额转换建立语义兼容直觉,不实现 MCP 或 A2A 服务。
1. 外部版本与内部表示分开
本例规定 v1 接受美元字符串 12.34,v2 接受整数最小单位 1234 并显式声明 USD。二者进入内部后都表示 {amountMinor:1234,currency:'USD'}。
金额用字符串拆分再转换为整数,避免直接用二进制浮点数乘 100 产生不必要误差。本例限定非负金额、恰好两位小数和 JavaScript 安全整数范围;这不是通用货币库。不同币种的小数位、舍入、负数退款和超大数额需要另外定义。
版本字段不是装饰。未知版本应停止转换或走明确迁移流程,而不是猜测。内部 canonical representation 使业务代码不用同时理解所有外部格式;adapter 负责在边界把差异显式处理。
2. 运行与阅读拒绝原因
npm run learning:p2 -- w07
前两个输入分别来自 v1、v2,应归一为相同对象。v2 中的 12.34 因为不是整数最小单位被拒绝;版本 99 被拒绝。错误是预期的学习输出,不意味着脚本运行失败。
这份函数真正执行了运行时校验,而不是仅写 TypeScript interface。网络、模型输出和历史事件都不会因为接口声明就自动可信。不过它也不是完整 JSON Schema validator:这里手写了一个狭窄合同,允许额外字段但转换时只保留约定字段。
3. 兼容性要看谁在读
| 场景 | 应检查的问题 |
|---|---|
| 新 producer → 旧 consumer | 新字段被忽略还是导致拒绝?旧端是否误读单位? |
| 旧事件 → 新 consumer | 历史数据能否通过版本 adapter 重放? |
| 新 consumer 回滚 | 已写入的新状态能否被旧版读取? |
| 重复投递 | 是同一个业务事件,还是新的操作?幂等键是否保持语义? |
增加可选字段通常比改变字段含义更容易兼容,但仍取决于读取器是否严格、默认值如何解释。把字段改名而不提供迁移,也可能让旧数据在回放时失效。
MCP 关注模型应用与工具/资源集成,A2A 关注 Agent 之间的任务协作,OpenAPI 描述 HTTP API,AsyncAPI 描述异步接口。它们不能自动推导你的金额单位、授权责任或业务幂等规则。应先确定业务契约,再选择承载方式。
4. 轻量练习
把 v1 输入改为 12.3 或 12.345,判断为什么这里选择拒绝而不是猜测舍入。也可以试 currency:'EUR',验证固定 USD 边界。只选一个即可。
深入问题:若外部系统把 amountMinor 放到字符串中以支持超大整数,内部要怎样改变?可能需要 BigInt 或十进制库,同时重新定义 JSON 序列化。这是迁移决策,不是简单扩大一个 number 类型。
5. 专业材料与未来连接
- OpenAPI 3.1.1 Specification:选 Schema Object 与版本语义,理解结构描述和业务含义的差别。本例固定引用此版本,不宣称它是最新版本。
- MIT 6.5840:结合 RPC 与状态章节,思考旧请求重试和历史状态读取的问题。
未来 Agent 互操作需要显式任务和权限合同;具身系统会把同样的单位问题放大为米/毫米、弧度/角度、世界坐标/机体坐标。这个金额 adapter 不能直接用于机器人,只是训练“类型正确不代表物理语义正确”的习惯。