容忍读者

2011年5月9日

使用 Web 服务的优势之一是它有助于解耦系统的各个部分。人们可以在一定程度的分离下处理不同的代码库。虽然你获得了一些解耦,但你无法完全消除耦合,因为服务仍然必须通过它们的接口相互通信。令人遗憾的是,许多团队使这种耦合比应有的要糟糕得多。

解耦协作的指导原则应该是 Postel 定律

对自己所做的事情要保守,对来自他人的事情要宽容。

-- Jon Postel

在协作服务的情况下,最棘手的问题之一是演化。虽然有些人认为你应该第一次就把你的服务定义做好,这样你就永远不需要更改它们,但我经常阅读的读者不会感到意外地发现我缺席了他们的聚会。为了能够演化服务,你需要确保提供者可以进行更改以支持新的需求,同时对现有客户端造成最小的破坏。

自从撰写这篇博客条目以来,Rob Daigneau 在 服务设计模式 中发布了对这种模式的完整描述。

一种常见的搞砸方法是使用某种类型的模式驱动的服务端点绑定。一个例子是从 XSD 定义生成代码的 C# 类。这被吹捧为一个节省时间的特性 - 服务提供者发布其服务的 XSD 定义,消费者获取一份副本并生成一个类。看,妈妈,不用编程。它工作得很好,直到提供者需要对接口进行任何更改,例如添加一个字段。像这样向接口添加字段不应该对任何人来说都是一个破坏性更改 - 但通常会破坏这些方案。

我的建议是在读取来自服务的数据时尽可能地容忍。如果你正在使用 XML 文件,那么只获取你需要的元素,忽略任何你不需要的元素。此外,对正在使用的 XML 结构做出最少的假设。不要使用像 /order-history/order-list/order 这样的 XPath 搜索,而是使用 //order。你的目标应该是允许提供者进行任何不应破坏你的代码的更改。一组 XPath 查询是为 XML 负载执行此操作的绝佳方法,但你也可以将相同的原则应用于其他事物。

最重要的是,确保只有一段代码读取像这样的数据负载。数据传输对象 的目的之一是将数据负载包装在一个方便的对象后面,这样系统中的其他部分就可以简单地执行 anOrderHistory.orders 并不受会破坏容忍读者的更改的影响。

即使你的数据传输协议是二进制的,也值得牢记这一原则。想象一下,你在连接的两端都有 Java 程序,并且想要使用二进制传输来减小消息大小。在这种情况下,大多数人都会使用 Java 的内置序列化机制直接序列化对象,但如果一方添加了一个字段,传输就会中断。你可以通过首先将数据放入通用集合(列表和映射)中,然后序列化这些集合来轻松避免这种情况。如果你向映射添加了一个额外的字段,它仍然会在另一端反序列化,并且容忍读者可以轻松地忽略它。

为了帮助服务提供者演化他们的服务,你可以告诉他们你正在读取通信的哪些部分。一个好方法是将读者及其测试发送给他们,以便他们可以在构建过程中使用它们来检测潜在的破坏。你们中的一些人可能认识到这是 消费者驱动契约 的下一步。

进一步阅读

服务设计模式 中对这种模式进行了完整的描述。

我的同事 Ian Cartwright 几年前发布了一组关于此主题的有用博客文章。他指出 模式验证提供了一种虚假的安全感,并且 序列化存在危险,无论是总体上还是 特别是对于域对象

Saleem Siddiqui 描述了容忍读者如何与 宽容的编写者 很好地协同工作。