Web 服务版本控制

在任何分布式系统的设计中,API 版本控制是一个常见的问题。不幸的是,Web 服务也不例外。在本文中,Kyle Brown 和 Michael Ellis 将概述 Web 服务开发人员所面对的版本控制困难的范围,并且提供一些解决方案模板,讨论解决这个问题的体系结构和最佳实践。

正确处理 API 版本控制已经成为分布式系统开发人员所面临的最困难的问题之一。各种各样的方案被提出,从 CORBA 采用的自由放任方法到 DCOM 中所使用的更严格的方案。随着 Web 服务的出现,您可以利用一些新的特征,它们能够帮助缓解问题的严重程度。但是,一个残酷的事实是,版本控制并没有成为 Web 服务体系结构的一部分。现在来自于 IBM 和其他厂商的产品都没有直接地解决版本控制问题,开发人员需要通过模式的应用和最佳实践来解决这个问题。

理解 Web 服务 API 版本控制问题是很容易的。设想我们有一个简单的 WSDL 文档,其中包含一个 WSDL 操作,这个操作使用在 清单 1 中的代码片断进行定义。(这个操作来自 WSDL 规范中的一个示例——请参阅 参考资料。)


清单 1. GetLastTradePrice WSDL



<types>
       <schema targetNamespace="http://example.com/stockquote.xsd"
  xmlns="http://www.w3.org/2000/10/XMLSchema">
           <element name="TradePriceRequest">
              <complexType>
                  <all>
                      <element name="tickerSymbol" type="string"/>
                  </all>
              </complexType>
           </element>
           <element name="TradePriceResponse">
              <complexType>
                  <all>
                      <element name="price" type="float"/>
                  </all>
              </complexType>
           </element>
       </schema>
    </types>


    <message name="GetTradePriceInput">
        <part name="tickerSymbol"
 element="xsd1:TradePriceRequest"/>
    </message>

    <message name="GetTradePriceOutput">
        <part name="result" 
          element="xsd1:TradePriceResponse "/>
    </message>

    <portType name="StockQuotePortType">
        <operation name="GetLastTradePrice">
           <input message="tns:GetTradePriceInput"/>
           <output message="tns:GetTradePriceOutput"/>
        </operation>
    </portType>

现在,让我们假设您部署完这个 Web 服务,已经使用它回复了很多的请求者。然而,在代码审查中,您发现或许浮点数并不是表示钱数的最好的数据类型。毕竟,价格永远不会是 $2.33333333333;它总是 $2.33。您发现,由于系统中计算交易价格的方法,积累的舍入误差导致了逆差问题。结果,返回的钱数不时就会出错。所以,您需要将数据类型从浮点数变成整数,很明显整数的值表示价格中的分。您可以几乎不用修改直接采用 清单 2 中的方案来实现这个改变: 清单 2. 修改过 TradePriceResponse WSDL



<element name="TradePriceResponse">
   <complexType>
      <all>
         <element name="price" type="integer"/>
      </all>
   </complexType>
</element>

然而,现在您又面临一个新的问题。当所有现有的请求者还以为将收到浮点数而不是整数的时候,将会发生什么呢?这是一个微妙类型的问题中最糟糕的例子——请求者可能还会很好地继续处理作为 SOAP 信封中的输出结果的 XML,即便它们接收到的答案明显不是它们所期望的。在这种情况下,它们将接收到比它们所期望的大100倍的答案,并且没有小数部分。

问题在于,在 Web 服务定义中(更确切地说,在 WSDL 中)没有什么清楚地将这种不同传达给请求者。如果开发人员进行了上述改变,那么较老的请求者将会失败。这种失败 Web 服务的基础架构将是无法察觉的。在 WSDL 文档中,您需要能够仔细地描述那些将 不会破坏现有请求者的改变类型,同时也描述那些将 破坏现有请求者的改变类型。您同样需要一种机制来确保当 WSDL 改变太多以致将破坏现有的请求者时,没有发生一个较老的请求者调用改变了的 Web 服务的可能性。

理解改变类型

大致来说,在 WSDL 文档中有两种改变的类型 不能破坏现有的请求者和几个 能够破坏的改变类型。依照标准的行业术语,我们将它们分别称为 向后兼容的(backwards-compatible)改变和 非向后兼容的(non-backwards-compatible)改变。向后兼容的改变类型是:

  • 向现有的 WSDL 文档添加新的 WSDL 操作。 如果现有的请求者不知道新的操作,那么它们将不会由于新操作的引入而受到影响。
  • 在 WSDL 文档中添加先前存在类型中不包含的新的 XML Schema 类型。同样,即使一个新的操作需要一组新的复杂数据类型,只要那些数据类型不包括在任何先前存在的类型中(将依次需要为那些类型修改解析代码),那么这个改变类型将不会影响现有的请求者。

然而,还有很多其他的非向后兼容的改变类型。这些类型包括:

  • 删除一个操作
  • 重新命名一个操作
  • 改变一个操作的参数(数据类型或者顺序)
  • 改变一个复杂的数据类型的结构。

所以,从广泛的意义上来说,您能够使用两种不同的策略来处理可能发生的不同的改变类型。

对于向后兼容的改变,可以简单地在存储库(请求者正是通过这个存储库获得 WSDL 文档)中更新 WSDL 文档,并且可以更新现有的 Web 服务。我们推荐将 WSDL 文档的每个新版本都存储在一个版本控制系统里,并且使用 XML 注释指出惟一的版本 ID 或者版本的历史记录。然而,这纯粹是为了 Web 服务提供者的方便,Web 服务请求者的实现人员并不需要。

对于非向后兼容的改变,您需要采用另一种方法。为了解决这个问题,首先使用 XML 名称空间来清晰地描述一个文档的兼容版本。完成这个的机制依赖于 SOAP 绑定是否完成,绑定利用在 WSDL 中文字- 或者 SOAP-编码氖褂梅绞健T谖淖值陌蠖ㄖ校?名称空间作为 XML schema 名称空间定义的一部分在消息定义中指定的;在 SOAP 编码中,它可能在 SOAP 绑定元素里被指定。不管选择哪种机制,一个特定的名称空间值随着每个 SOAP 消息和结果被发送。这使得 Web 服务能够实现基于名称空间值来正确地决定如何处理传入消息。

所以,如果一个 WSDL 文档需要进行非向后兼容的改变,那么您需要做的的第一步就是确保由那个文档产生的 XML 元素的名称空间是惟一的。为了确保 WSDL 文档的不同的版本是惟一的,我们推荐一个简单的命名方案,就是在名称空间定义的末尾附上日期或者版本戳。这遵循的是 W3C 为 XML 名称空间定义给出的一般指导原则。因此,假设您正使用一个文字的使用方式的参数,在我们已经讨论的 WSDL 样本 中,您的 XML 类型定义看起来可能像下面这样:

清单3.使用惟一的名称空间的修改过的 WSDL


<types>
       <schema targetNamespace="http://example.com/2003/10/15/stockquote.xsd"
              xmlns="http://www.w3.org/2000/10/XMLSchema">
           <element name="TradePriceRequest">
              <complexType>
                  <all>
                      <element name="tickerSymbol" type="string"/>
                  </all>
              </complexType>
           </element>
           <element name="TradePriceResponse">
              <complexType>
                  <all>
                      <element name="price" type="float"/>
                  </all>
              </complexType>
           </element>
       </schema>
    </types>

注意,在这个示例中,我们用如下的值重新定义了名称空间:



http://example.com/2003/10/15/stockquote.xsd

这个示例使用的命名约定是基于 W3C 在识别方案中所遵循的标准约定。它包括公司名称,后面是一个日期戳、再后面是语义上(并且明确地)表示特定名称空间的一个(或多个)标识符。我们这里提出一个对最常用方法的改变:虽然大部分 W3C 名称空间在公司名称后面只有年和月,但是我们建议在月的部分同样添加日是值得的,考虑到改变的发生可能超过一月一次。

所以,一旦您已经决定改变名称空间,您就不得不决定如何处理老的请求者。一种选择是如果接收到一个对老的名称空间的请求,就在服务器端生成一个失败。另一种用于处理这个问题的常见选择是使用一个 Web 服务中介(例如一个路由器),由它决定如何处理进入任何特定名称空间的 Web 服务请求。该路由器可以检查名称空间里的日期戳,然后将来自较老的名称空间的请求路由到 Web 服务的一个较老的版本,同时将来自较新的名称空间的请求路由到 Web 服务的一个较新的版本。然而,这种方法也有不利的方面。首先,您将不得不实现这个路由器中介(尽管存在能够实现这项功能的商业产品,像 TalkingBlocks)。然而,或许更重要的是,您将不得不为每个 Web 服务部署两次(至少在过渡时期),直到所有较老的请求者让位于新的 WSDL。





回页首


服务版本控制与服务绑定

为了进一步检查版本控制问题,提出下面两个问题是有帮助的:

  • 在 Web 服务的术语中,接口版本是什么?
  • 不同的接口版本和完全不同的接口之间的差别重要吗?

可以这样认为,当一个服务的接口以非向后兼容的方式改变的时候,实际上是创造了一个全新的服务。在这种情况下,除非第一个接口的实现继续存在,否则,先前存在的服务实际上就已经停止了。从客户端的角度看,一个服务只是一个接口和它可能声称显示的一些非功能性的品质(例如信任和 QoS 属性);因而,如果一个服务的接口以非向后兼容的方式改变,它将表示最初的服务不再是的一个实例,而是一个全新的服务。

从这个观点来说,一个接口版本总是和先前存在的接口向后兼容的。这意味着,或者在维护所有先前的操作的同时添加新的操作,或者以与最初的接口相兼容的方式改变现有的操作签名(这种改变的机会是有限的)。更进一步地,认为一个服务的较早的版本不会向前兼容后面的版本是合理的。因此,如果一个服务的接口被以非向后兼容的方式改变,那么它就不再是较早服务的一个版本了;它是一个新的服务。

正如我们已经建议的,用惟一的名称空间识别每个服务接口提供了一种保证它们各不相同的方法。因为向后兼容性是每个版本的一个必要条件,所以一种区别相同接口的较早和稍后版本的方法同样也是必需的。为了理解表示一个版本的最佳方式,对于绑定机制的理解是必须的,我们马上就要进一步讨论这个问题。

我们的第二个问题是,“不同的接口版本和完全不同的接口之间的差别重要吗?”现在可以回答了——答案就是“是的”。这是因为绑定机制需要能够识别兼容的接口和与之相对的不兼容的接口,在这里,兼容的接口在它们的名称空间里提供通用组件(它正是绑定者(binder)正在寻找的)和一个版本标识符,这个标识符和客户端的标识符相同或者比客户端的更新。





回页首


服务绑定

接受这个服务的观点导致了另一个问题。应该使用什么方法将客户端绑定到服务上,以便能够对服务进行版本控制同时又不破坏绑定?显而易见地,为了减轻服务的版本控制,您必须消除所有客户端与接口易变的方面的耦合。可能改变的方面有:

  • 由服务接口提供的特殊的操作集合
  • 服务的 WSDL 的 URI
  • 服务接口的版本号
  • 服务端点。

服务端点可能随时改变来满足服务提供者的部署需求。通常,绑定机制不应该假设一个静态的端点。

到现在为止,一个绑定方法的重要特征正在开始成形。一个灵活的绑定方法应该:

  • 间接地获得一个服务端点
  • 避免对服务的任何方面是静态的假设(除了兼容的接口)。
  • 根据接口和它们显示的接口版本来识别兼容的服务。




回页首


使用 UDDI 作为理解版本的服务注册中心

存在一些正在发展的最佳实践,它们与 UDDI 和 WSDL 的联合使用相关联。它们以发布标准的 Web 服务这个需求为中心,以便服务能够使用这些标准公布灵活性和兼容性。

目前,最佳实践针对的是接口的单一版本,并没有促进用于服务版本控制的最佳实践。然而,UDDI 数据模型足够的丰富,以至于能够增强当前的最佳实践来包含服务版本控制。

当前的最佳实践是基于这样一个先决条件—— wsdl:portType 的特定版本应该由惟一的 tModel 来表示。那个 portType 版本的客户端能够通过 UDDI 的 绿页(green page)搜索(一个简单的惟一关键字的搜索)来查找服务,这个服务通过将它们自己与 tModel 联系在一起来公布灵活性。这种关系如 图 1所示(根据 UDDI 版本 2)。


将 WSDL 与服务关联起来

考虑到当前的最佳实践,这样的搜索结果将不会包括兼容以后的接口版本的服务,因为最佳实践的用例暗示了一个与 tModelKey 精确的匹配,而最佳实践要求不同的接口具有不同的 tModel

UDDI 版本控制方法 1:公布与几个接口的兼容


然而,在最佳实践(或者数据模型)中没有什么能够限制一个特定的服务可以公布的接口的个数。与最佳实践相一致,同时与一个接口的较早版本和较新版本相兼容的服务能够为 tModelInstanceDetails 集合中的每一个元素引用 tModel

这种方法的优点如下:

  • 它不需要版本号
  • 不需要改变“绿页”查询的执行方式。

然而,这种方法也有一些缺点:

  • 由于一个接口经历几个版本,版本集合的维护可能将变得比较麻烦。
  • 如果存在几个服务的实现,那么就必须维护数个 tModel 集合。

UDDI 版本控制方法 2:引入一个版本号来限制接口 tModel


按照最佳实践,UDDI bindingTemplates 使用称作 tModelInstanceInfo 的结构来引用标准接口 tModeltModelInstanceInfo 结构可以有选择地包含一组 instanceDetails 结构,正如下面所定义的:

清单 4. 用于 UDDI instanceDetails 的 XSD


<xsd:complexType name="instanceDetails">
   <xsd:sequence>
      <xsd:element ref="uddi:description" minOccurs="0" maxOccurs="unbounded"/>
           <xsd:element ref="uddi:overviewDoc" minOccurs="0"/>
           <xsd:element ref="uddi:instanceParms" minOccurs="0"/>
     </xsd:sequence>
</xsd:complexType>>
    

引用 UDDI 规范中的一段话,“当需要特定于 tModel 引用的设置或其他描述性信息,来描述服务描述特定于 tModel 的组件或者支持需要附加技术数据支持的服务时,可以使用这一元素。” 在我们的例子中,特定于引用的设置就是接口 tModel 的版本号。

通过在 instanceDetails 中引入版本号和改变“绿页”搜索来包括对于接口版本的考虑,“绿页”查询变成了理解版本的。

然而,单单一个版本号是不够的。当前的 UDDI 最佳实践将接口 WSDL 和接口 tModel 联系在一起是基于这样的假设,所有声明符合规范的服务将共享一个通用的 WSDL 定义。版本控制服务接口没有共享一个通用的定义,所以这种假设在接口版本控制的上下文中是不成立的。我们所要寻找的是一种将 WSDL 与服务 tModel 分离开来并且将它与接口的版本号相管理的方法。

这种必需的关联可以通过将 instanceDetails 结构(前面已提到过)中的 overviewDoc URL 值设置成接口 WSDL 的位置来实现。这个版本号和 WSDL URL 可以被看作是 bindingTemplate 和标准 tModel 之间的引用的链接属性。对当前的最佳实践的这两个改变提供了实现 UDDI 中的接口版本控制的必要方法。

扼要重述:如果想使用版本号来限定 UDDI 中的 Web 服务已发布的接口,则需要在 tModelInstanceInfo 中为标准 tModel 引用(它是当前标准服务类型的 UDDI 最佳实践所需要的)引入一个携带版本号的 instanceDetails 结构和相关的 WSDL 文档的 URL。

这种方法的一个重要的好处就是:

  • 在实现版本控制的同时不需要服务注册携带所有那些与其兼容的接口的 tModel 引用。

然而,同样也有一些不利的方面:

  • 总的说来,在服务接口发展的每一特定的时刻,所有的服务实现都必须就它的版本号达成一致意见。这是因为版本号并不是一种可以在几个实现之间共享的结构。这将导致这样一种情况,那就是一个服务列出一个特定的接口为版本 n,而另外一个服务却公布相同的接口为版本 m。
  • 必须改变执行一个典型的“绿页”查询必需的步骤来包含对 instanceDetails 结构中的版本标识符的考虑。
  • 这种方法与 OASIS 发布的最佳实践相冲突。

您应该使用哪种版本控制方法呢?


在大多数的情况下,您应该使用第一种方法。第二种方法存在有很多问题:

  • 所有的客户端都必须使用 UDDI “绿页”查询,这与已发布的最佳实践相冲突。
  • 在所有的实现中,可能会出现不一致地使用与接口相关联的版本号的现象。
  • 注册一个服务的需求更复杂。

从性能的角度来看,第一种方法支持版本兼容的搜索的时候,只要使用一个 tModel 关键字就够了;而第二种方法则需要一个查询分析数个 UDDI 结构,这可能会减缓查询的速度。





回页首


总结

在本文中,我们分析了在 Web 服务中处理服务版本控制问题的一些最常用的技术。您所选择的方法将取决于很多因素,但是对于大多数的问题,有足够的选择供您找到一种有效的解决方案。特别地,我们分析了使用惟一版本空间和在 UDDI 中使用版本号来缓解这个问题的严重程度。您能够利用我们在这里所列举出来的优点和缺点来确定在您的环境中最有效的技术。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值