在网络上传输 JSON 是专业失职
Source: Dev.to
我已经预感到这篇文章会惹恼一些人。对我来说没关系。
JSON 的问题
在传输过程中使用人类可读的数据是一个已经存在了数十年的错误,我们出于习惯不断为之辩护。并不是因为它正确,也不是因为它高效,而是因为它熟悉。JSON 是其中最熟悉的,这正是问题所在:我们把熟悉当作了保证。
JSON 没有类型安全。 它没有模式,没有强制的契约,没有规范的表示形式。它只是一串字节,双方礼貌地约定以相同方式解释……直到有一方不再遵守。
你可以在文档里说明 age 是整数。于是客户端因为 JavaScript 发送 "age": "27",而你因为静默的强制转换而没有注意到。直到你不再这样做。或者服务器在客户端期待数字的地方返回 null。又或者服务器在一次重构中“贴心地”把 snake_case 改成了 camelCase。又或者有人添加了一个同名但意义不同的字段,因为它“反正没用”。又或者后端在失败时返回 "status": "ok",因为错误路径被临时拼凑,API 网关在为它撒谎。所有这些都是合法的 JSON。这才是重点。JSON 会愉快地序列化你的错误。JSON 并不在乎。“它本来就应该简单”。
不使用模式而依赖“灵活性”意味着你的契约始终是隐式的、社交的,由好心情来强制执行。你不能指望在交叉的手指、牵着手、赞美选择之神的情况下构建一个严肃的系统。
“这只会验证它”并不是对 JSON 的辩护。这是一种承认——你需要第二套系统,而你最终会在每个客户端、每个服务、每种语言中重复实现这种验证——一次又一次,永无止境。
我并不是在攻击验证库的开发者;无论使用何种协议,都需要强大的验证逻辑。问题在于把 JSON 本身当作契约。
为什么基于模式的格式有帮助
使用基于模式的格式时,你仍然可能出错,但你拥有存在于部落记忆之外的防护栏:
- 模式定义了契约。
- 规范的编码消除了歧义。
- 兼容性规则是机械的,而非解释性的。
你不可能“意外”地在应该是数字的地方发送字符串而不被某处的某个人提前喊出。
除非你使用像 JavaScript 这样根本没有类型的语言。
字节同样重要。文本在乏味的方式上成本高:字段名重复、引号重复、转义字符重复、解析器做了本不该做的工作、在负载下会产生成倍增长的分配。为了什么?只为了让某人可以抓起调试的安慰毯。如果 JSON 最好的防御是“我喜欢用肉眼阅读”,那你就是在为错误的目标进行优化。调试应该使用理解契约的工具来完成,而不是用肉眼盯着一堆二进制数据祈祷自己没有漏掉逗号。我们拥有比“复制为 cURL,管道到 jq,然后眯眼”更好的契约驱动协议工具。
推荐
使用基于模式的格式——例如 Protocol Buffers、Avro 或其他模式驱动系统——作为机器之间的规范表示。仅在真正合适的情况下才使用 JSON。
如果你仍然对此感到冒犯:很好。你应该如此。只是不该冒犯我!而是应该对我们集体决定“一个装满无类型字符串加上好心情的袋子”可以作为严肃系统的基础感到冒犯。如果你愿意,可以继续把 JSON 当作你的胶带,但别把它称作架构。更不要一本正经地说它是“类型安全”的,同时假装你可爱的 GraphQL 模式已经足够。
我不会告诉你放下一切去熟悉 Protocol Buffers 和 gRPC,但我也不会阻止你这么做。