内容


通过 Web 服务向后兼容性向前发展

管理和实现 Web 服务中的变更

Comments

作为集成计算系统组件的一种现代方法,面向服务的体系结构(Service-Oriented Architecture,SOA)的价值得到了广泛的认可(请参阅参考资料)。Web 服务为这些系统组件间的通信提供了公共的开发标准基础。Web 服务协议既适合企业内部集成,也适合企业间的集成。SOA 的一个关键目标就是组件间的松散耦合。通过松散耦合,组件可以变更各自的行为,而不会对其他组件造成影响。虽然这个目标非常有价值,但在实际中,仍然会存在单个组件中的很多变更要求对其他组件进行更改的情况。

本文提供了一个有效的过程,用于标识那些将会影响 Web 服务使用者的变更类型和不会带来影响的变更类型。文中讨论了一系列策略,以处理对使用者造成影响的变更,并提供了用于选择最合适的解决方案的标准。其中很重要的一个标准就是解决方案选项的成本,建议在将解决方案实际应用到特定业务场景前对其好处和成本进行仔细评估。

本文的大部分都在详细讨论有关如何使用 Rational® Application Developer 来实现对 Web 服务的变更,以帮助减少由此对服务提供者和服务使用者双方的影响。

当前的 Web 服务标准对变更方面的支持很少(甚至没有)。本文的目的在于提供一系列 Web 服务的变更管理最佳实践。

关键概念

向后兼容性 指硬件和软件系统可以使用该系统的早期版本的接口和数据。在 Web 服务中,向后兼容性考虑的是对接口的变更如何影响接口的现有用户(也称为服务使用者)。如果现有用户不受影响,则变更就是向后兼容的。如果现有用户受到影响,则变更不向后兼容,将需要使用策略来管理变更的影响。

向后兼容性的概念与变更变更管理 的概念紧密相关。无论我们如何悉心地设计 Web 服务,都将出现需要对接口进行变更的情况。需要有计划地、系统地进行变更,需要根据分阶段的方法进行操作。Web 服务变更管理中的典型阶段有:

  1. 评估变更是否向后兼容
  2. 如果不兼容,则评估是否支持所需的旧接口
  3. 如果支持,对变更进行设计和实现,以便同时支持旧接口和新接口
  4. 预定义的过渡期结束后,使旧接口退役

第 1 阶段(向后兼容性评估)将在下一部分中进行讨论。

第 2 阶段的评估属于业务管理领域。直接完全采用新服务或使旧接口退役(即使有过渡期)可能会导致严重的业务中断。由于这个决策过程是由业务管理部门进行,因此不在本文的讨论范围之内,本文的主要目标读者是开发团队。

本文的剩下部分将研究一些用于同时支持旧接口和新接口的版本控制技术(第 3 阶段)和一种用于使现有接口退役的方法(第 4 阶段)。在最后的这部分中,我们将使用弃用 的概念。当要在特定时期后使 Web 服务退役时,我们将声明此 Web 服务已弃用。

评估变更

已有多篇有关向后兼容的变更与不向后兼容的变更的分类主题的论文发表(请参阅参考资料)。本部分的目的是对这些早期的讨论进行整合和补充。

有些变更可以通过对使用现有接口的服务使用者完全透明的方式进行。让我们看看下面的示例接口,并在稍后讨论可能出现的一些变更:

  • Web 服务 URL:
    • http://bigbank.com/BigBank/services/AccountService
  • 定义的操作如下(使用 output_message operation(input_message) 格式):
    • accountNumber create(name, street, suburb, postcode, state)
    • amount deposit(accountNumber, amount)
    • amount withdraw(accountNumber, amount)
    • amount balance(accountNumber)

对接口进行的很多变更可能不会影响现有用户,如下所述:

  • 添加新操作
    • 现有使用者将继续调用现有操作,而同时可向新使用者提供新操作。现有使用者不会受到影响。
    • 示例:添加操作 transfer 是向后兼容变更
      • amount transfer(fromAccountNumber, toAccountNumber, amount)
  • 向输入消息添加新的可选数据结构
    • 现有使用者不会受到影响,因为它们将不会注意到新数据结构。
    • 示例:向 create 操作添加可选字段 country,如下所示
      • accountNumber create(name, street, suburb, postcode, state, country=null)
  • 将现有输入数据结构的基数性由强制更改为可选
    • 现有使用者将继续把输入数据结构当作强制内容处理。现有使用者不会受到影响。
  • 对接口没有实质性影响的服务提供者实现变更
    • 只要对接口没有实质性的修改,就可以对服务实现进行修改或完全重写。即,仍然完全支持现有接口规范。

不向后兼容的变更

一系列变更会立即中断现有使用者的使用:

  • 删除操作
    • 旧接口不再是新接口的固有子集。使用已删除的操作的现有使用者将会受到影响。
    • 示例:删除操作 amount transfer(fromAccountNumber, toAccountNumber, amount) 将中断使用它的使用者。这个变更不是向后兼容的。
  • 变更现有输出数据结构的基数性
    • 对输出消息中的字段的基数性进行更改(如将强制字段更改为了可选字段)将中断现有使用者。
  • 变更数据类型定义
    • 对输入或输出消息中的数据类型的大部分变更都不是向后兼容的。
    • 示例:简单地将小数更改为浮点数之类的操作都将中断现有使用者。
  • 业务规则或业务流程变更
    • 有时候,Web 服务操作之间具有依赖关系。这个依赖关系通常是由于必须按照指定的顺序调用操作而造成的。假如基础业务规则或业务流程发生了变更,依赖关系就发生了变化,当接口保持不变,或按照向后兼容的方式发生了变更,现有客户机就不在能够正确地操作了。
    • 示例:假定 Big Bank 决定,只有之前进行了授权,才能进行提款。自然地,我们必须引入一个与以下类似的新操作:boolean authorize(accountNumber, amount)。根据我们前面的讨论,这个变更将是向后兼容的。不过,在这种情况下,这个变更并不向后兼容,因为使用 withdraw 操作的现有用户由于不会调用 authorize 而被断开了。

特殊注意事项

在有些情况下,变更并不会影响现有使用者,但可能带来一些负面效应。建议服务提供者参考其向后兼容性对此类变更进行逐个评估。

  • 服务质量 (QoS) 中的变更
    • 如果服务提供者实现的变更并不会影响接口,却给服务质量(如响应时间和可用性)造成负面影响,则会对现有使用者带来负面的影响。
  • 变更输出消息中的值的预定值域
    • 使用者可能对响应消息中的特定字段的值的值域有个预期值。如果提供者开始返回预定值域之外的值,则可能对现有使用者造成负面的影响。
    • 示例:我们将 AccountNumber 限制为 <xsd:pattern value="[0-9]{12}"/>,即帐户号为 12 位数字组织的字符串。允许前导零,但原始实现从来不会返回前导零。如果希望变更此实现,使得创建的新帐户号前面带有前导零,则可能对某些使用者造成有害的影响。

Web 服务版本控制方法

如果必须对服务进行向后不兼容的变更,但又必须同时提供旧版本的服务和新版本的服务,则需要使用某种同时支持新旧两个接口的机制。

此处所描述的所有版本控制方法允许向 Web 服务引入新接口,且不要求删除现有接口。这两个接口具有不同的影响,会影响其在特定场景中的适用性。在所有版本控制方法中,值得一试的是更改模式类型的命名空间,以防止不同版本间的类型混淆。

按操作进行版本控制

如果需要在服务中引入现有使用者无法使用的新版本操作,请考虑根据操作进行版本控制。可以通过基于旧操作创建新操作,并同时保持两个接口来保持向后兼容性。您能继续提供早期的操作版本。这样就对 Web 服务进行了向后兼容的变更,允许旧版本操作和新版本操作并存。

例如,可能需要对 Account.create 操作进行变更,以允许其接受 country 作为强制输入。这可以通过添加以下新操作来完成,而不会影响现有操作:

  • Account.createV2(name, street, suburb, postcode, state, country)

只要原始 create 操作保持不变,则同时可以使用旧版本的操作和新版本的操作。

尽管这样的版本控制方法非常易于实现,且能够将版本控制的影响隔离到单个操作中,但这个方法有几个缺点。它将 Web 服务更为紧密地耦合到实现代码(因为这两个操作现在都必须在相同的 Web 服务中实现),从而使得以后的操作删除工作变得更为复杂。如果引入更多的版本,这可能导致相同的 Web 服务中存在大量操作。另外,如果要求进行影响 Web 服务中的多个操作的变更(如对多个操作共享的数据类型进行更改),按操作进行版本控制可能很快导致整个服务变得非常笨重。

按 Web 服务名称进行版本控制

按 Web 服务名称进行版本控制比按操作进行版本控制的影响大,因为它会影响整个 Web 服务,而不是单个操作。

例如,假定有一个现有 Web 服务 AccountService,则可能采用不同的名称定义新服务,例如 AccountServiceV2。可以在 AccountServiceV2 实现新功能或经过更改的功能,而不会影响现有的 AccountService Web 服务。AccountServiceV2 还将实现未变更的操作,以便在不需要旧的 AccountService 的时候将其删除。

通过引入新 Web 服务来提供版本控制的优势在于,可将变更从原始实现隔离出来。如果在某个时间需要删除早期版本,可以删除整个 Web 服务,而不必按操作删除代码。

该方法的一个缺点是,可能会在生成的实现代码中出现受版本控制的 Web 服务名称,具体取决于所使用的 Web 服务开发工具包。这意味着,对于每个新版本,可能会创建一组命名方式不同的对象集,从而要求使用者进行额外的工作将其集成到现有代码中。

按 URL 进行版本控制

当根据 URL 或端点执行版本控制时,端点的 URL 将发生变更,而操作或 Web 服务名称不会改变。

例如,如果现有 Web 服务位于以下 URL:

  • http://bigbank.com/BigBank/services/AccountService

可以在以下 URL 引入新版本,而不会影响现有 Web 服务:

  • http://bigbank.com/BigBank1/services/AccountService

新版本包含完整操作集,并具有所需的变更,因此使用者将仅使用两个 URL 之一来访问 Web 服务。使用这种版本控制方法时,Web 服务和操作的名称将保持不变,为了使用新版本而对使用者进行更新的必需变更可能会更少些。对生成的代码的影响将比其他方法要少些。

由于变更出现在上下文根的位置,因此新接口同样独立于现有接口。它要求创建独立的 Web 模块来处理新 URL 上的请求。按操作或 Web 服务名称进行版本控制并不要求使用独立的 Web 模块。

使旧接口退役:时间和原因

维护相同服务的多个版本可能是一项开销非常大的建议。表 1 对进行向后兼容变更和进行需要采用版本控制的不向后兼容的变更所需的成本进行了比较。

表 1. 向后兼容与采用版本控制的不向后兼容的成本比较
成本
服务提供者活动向后兼容变更使用版本控制进行缓解的不向后兼容变更
设计和开发两个场景中类似——成本将取决于变更的复杂性
新接口的递进测试两个场景类似
旧接口的回归测试
增量式部署少量额外成本(多个版本通常将带来额外的部署工作)
维护(例如,查找和修复缺陷)较低(仅需要对缺陷进行一次修改)较高(需要在两个或更多版本中对缺陷进行修复)
旧接口的退役小(删除旧版本)
服务使用者活动
为采用新接口而进行的设计和开发工作少量额外成本(将取决于变更的复杂性)
新接口的递进测试
部署少量额外成本

通过上表可以看出,采用版本控制的不向后兼容变更比向后兼容变更的开销更大。而且,随着支持的版本数量的增加,此成本会按比例增加。

另一方面,如果引入无版本控制的不向后兼容变更,现有使用者将不能再工作。依赖于该服务的组织则面临着业务中断、潜在的收入损失和/或处罚。

显然,需要在不支持旧接口和始终支持旧接口之间找到一个平衡点。建议采用以下方法:

  • 服务的新版本可用后,将旧版本声明为已弃用,表明在一段时间后将使其退役。
  • 必须通知使用者服务被弃用,以便他们有时间对其代码进行修改和测试。旧版本服务的退役要求有合理的通知期,通常为三个月或更长时间。
  • 在任何时候,可操作的服务版本不应超过两个。支持两个以上的版本不仅会给服务提供者带来很大的开销,同时也延迟了最初提出变更时主张的业务优势的实现。
  • 在同时提供两个版本时,在其间进行切换。

版本切换

所有版本控制方法都提供了同时支持一个或多个 Web 服务的多个定义的机制。从理论上说,可以使用上述的任何方法来支持任意数量的 Web 服务版本。不过,由于上面刚刚说明的原因,通常建议任何时间仅维护相同服务的两个版本。

要维护服务的两个实例,我们建议在引入新版本时在两个版本之间进行切换。进行切换需要使用按 URL 的版本控制。

例如,假定有同一个服务的两个版本。当前版本均在以下基础 URL 下提供:

  • http://bigbank.com/BigBank/services/

我们可能具有以下的 Web 服务集:

  • http://bigbank.com/BigBank/services/AccountService
  • http://bigbank.com/BigBank/services/CustomerService
  • http://bigbank.com/BigBank/services/EmployeeService

如果某个版本中要求使用 AccountService 的新版本,则通过使用按 URL 的版本控制,我们现在可能还有以下这个服务:

  • http://bigbank.com/BigBank1/services/AccountService

但也要保留 Web 服务的上一版本,将为由两个版本组成的 Web 服务完整集提供 URL:

  • http://bigbank.com/BigBank/services/AccountService <- Old version
  • http://bigbank.com/BigBank/services/CustomerService
  • http://bigbank.com/BigBank/services/EmployeeService
  • http://bigbank.com/BigBank1/services/AccountService <- New version

仍然在原始 URL 处提供原始服务。新服务在新 URL 上提供。

到目前为止,这与简单的按 URL 控制完全相同:引入了 Web 服务的新版本,并因此而创建了一个新 URL。不过,让我们考虑一下需要进行进一步变更的版本中的情况。假定现在需要对 AccountService 进行另一个变更,还要对 CustomerService 进行变更。我们不为第二个新版本的 AccountService 引入新的 URL,而将通过使用切换来切换回原始的 URL。发布了下一个版本后的新 Web 服务集将为:

  • http://bigbank.com/BigBank/services/AccountService <- New version
  • http://bigbank.com/BigBank/services/CustomerService <- Old version
  • http://bigbank.com/BigBank/services/EmployeeService
  • http://bigbank.com/BigBank1/services/AccountService <- Old version
  • http://bigbank.com/BigBank1/services/CustomerService <- New version

由于在任何时候都只需要 Web 服务的两个版本,因此可以重新使用原始 URL。这就限制了所需的 Web 模块数量。我们不会丢弃基础 URL(上下文根)或版本号,所有现有版本都包含在两个 URL 中。

上面的示例允许使用 Web 服务的两个受支持版本。在例外的情况下(如强制的法规性变更或错误修复变更),可以对此概念进行扩展,通过扩展有效上下文根集(BigBank2、BigBank3 等)来允许任意数量的并发版本。还可以考虑使用其他上下文根命名规则,如 BigBankA、BigBankB。

此方法的一个明显之处是,必须通知服务使用者哪个上下文根中包含特定 Web 服务的最新版本。这些细节通常在发行说明中捕获。这样还限制了旧 Web 服务的可用性窗口,因此在下一版本推出前,会强制 Web 服务使用者升级到最新版本。

对于本文中所使用的 Big Bank 示例,我们使用了按 URL 的版本控制和切换。这就允许将新接口的实现从现有接口隔离开来,从而提供了较少所需 Web 模块数量的好处。本文的下一部分将提供示例实现的详细信息。

实现版本控制

在以下部分中,我们将演示如实现按 URL 的版本控制。我们将使用的是前面描述的 AccountService Web 服务。以下 WSDL 和 XSD 文件对 AccountService Web 服务的接口进行了描述:

  • Types.xsd
  • Account.xsd
  • BankFault.xsd
  • AccountService.wsdl

Types.xsd 包含 AddressDollarAmountAccountNumber 的底层类型定义。Account.xsd 包含 AccountService Web 服务操作的请求和响应模式定义。BankFault.xsd 包含所有操作的异常类型,AccountService.wsdl 包含 Web 服务定义。

Big Bank 希望在其他国家(地区)拓展业务,但 Account.create 操作的 Web 服务定义并不允许在请求的 Address 类型中包含国家(地区)。他们还希望提供向后兼容性窗口,以便现有 Web 服务客户机可以将其应用程序迁移到新 Web 服务定义,因此也必须同时支持现有 Web 服务定义。

在此示例中,我们使用 IBM Rational Application Developer Version 6.0.1 来创建新 Web 服务,并同时保持现有服务不变。

Web 服务操作 Account.create 需要一个新的强制性参数 country,因此我们对 Address 类型进行修改,以添加这个新字段。我们还将创建一个新命名空间来承载此类型,以避免与现有定义冲突,消除 Address 的旧版本和新版本间的混淆。我们将年份和月份添加到该命名空间,因为这种做法是命名空间版本控制的事实标准。

我们创建一个新版本的 Types.xsd,如清单 1 中所示,其中的变更以粗体显示。

清单 1. 支持国家(地区)名的新版本 Types.xsd
<?xml version = "1.0" encoding = "UTF-8"?>
<xsd:schema attributeFormDefault="qualified"
 elementFormDefault="qualified"
 xmlns:xsd="http://www.w3.org/2001/XMLSchema"
 xmlns="http://parts.bigbank.com/2006/03"
 targetNamespace="http://parts.bigbank.com/2006/03">
	
   <xsd:complexType name="Address">
	  <xsd:sequence>
		<xsd:element maxOccurs="1" minOccurs="0" name="street" type="xsd:string"/>
		<xsd:element maxOccurs="1" minOccurs="1" name="suburb" type="xsd:string"/>
		<xsd:element maxOccurs="1" minOccurs="1" name="postcode" type="xsd:string"/>
		<xsd:element maxOccurs="1" minOccurs="0" name="state" type="xsd:string"/>
		<xsd:element maxOccurs="1" minOccurs="1" name="country" type="xsd:string"/>
 	  </xsd:sequence>
	</xsd:complexType>

    <xsd:simpleType name="DollarAmount">
	  <xsd:restriction base="xsd:float">
		<xsd:pattern value="[0-9].[0-9]{2}"/>
	  </xsd:restriction>
   	</xsd:simpleType>
	
   	<xsd:simpleType name="AccountNumber">
	  <xsd:restriction base="xsd:string">
		<xsd:pattern value="[0-9]{12}"/>
	  </xsd:restriction>
    </xsd:simpleType>		
</xsd:schema>

现在我们需要创建一个新版本的 Account.xsd,以使用此版本的 Types.xsd。因此我们要修改 Account.xsd 文件中的命名空间,并将所有类型定义保留在该文件中。之所以要这样做,是因为请求和响应消息是特定于 Web 服务的,而我们在创建整个 Web 服务的全新版本,因此有必要创建所有请求和响应消息的新版本。

清单 2 显示了 Account.xsd 文件的一个代码片段,其中的变更使用粗体突出显示。

清单 2. 支持国家(地区)名的新版本 Account.xsd
...
<xsd:schema attributeFormDefault="qualified"
	elementFormDefault="qualified"
	xmlns:xsd="http://www.w3.org/2001/XMLSchema"
	xmlns="http://parts.bigbank.com/2006/03"
	targetNamespace="http://parts.bigbank.com/2006/03">
	
	<xsd:include schemaLocation="Types.xsd"/>
...

最后,我们创建 WSDL 文件的新版本。我们使用日期版本修改 Web 服务的命名空间,并对端点进行修改。这个端点是 Web 服务使用者将用于访问新版本 Web 服务的端点。清单 3清单 4 提供了两个说明 WSDL 变更的代码片段,同样也是以粗体突出显示。

清单 3. 新版本 AccountService.wsdl——命名空间变更
...
<wsdl:definitions name="AccountService" 
	targetNamespace="http://account.services.bigbank.com/2006/03" 
	xmlns="http://schemas.xmlsoap.org/wsdl/" 
	xmlns:apachesoap="http://xml.apache.org/xml-soap" 
	xmlns:impl="http://account.services.bigbank.com/2006/03" 
	xmlns:intf="http://account.services.bigbank.com/2006/03" 
	xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" 
	xmlns:tns1="http://parts.bigbank.com/2006/03" 
	xmlns:tnsf="http://faults.bigbank.com/2006/03" 
	xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" 
	xmlns:wsdlsoap="http://schemas.xmlsoap.org/wsdl/soap/" 
	xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  
	<wsdl:types>
		<xsd:schema xmlns="http://www.w3.org/2001/XMLSchema">
		<import namespace="http://faults.bigbank.com/2006/03" schemaLocation="BankFault.xsd"/>
		<import namespace="http://parts.bigbank.com/2006/03" schemaLocation="Account.xsd"/>
		</xsd:schema>
 	</wsdl:types>
...
清单 4. 新版本 AccountService.wsdl——端点变更
...
   <wsdl:service name="AccountService">
      <wsdl:port binding="impl:AccountSoapBinding" name="AccountService">
         <wsdlsoap:address location="http://bigbank.com/BigBank1/services/AccountService"/>
      </wsdl:port>
   </wsdl:service>
...

既然已经完成了 WSDL 的新版本,接下来就必须确定如何包装应用程序,以使 Web 服务的新旧版本可以同时存在。让我们假定现有版本的包装方式如图 1 中所示。

图 1. Big Bank 示例应用程序的现有版本的包装
Big Bank 示例应用程序的现有版本的包装
Big Bank 示例应用程序的现有版本的包装

BigBankClient 是第三方客户机,要使用 Big Bank Web 服务,BigBankWeb 组件包含生成的 Web 服务代码,其上下文根设置为 BigBank。最后,BigBankEJB 组件包含 Account EJB,其中包含着 AccountService Web 服务的业务逻辑。

用于访问新版本 AccountService Web 服务的端点具有一个新上下文根,因此需要创建第二个 Web 项目,使用上下文根 BigBank1。新 Web 项目将调用现有 EJB 的一个新函数,该函数将实现 Web 服务的新逻辑。那么,新包装应与图 2 中所示类似。

图 2. 新版本 Big Bank 示例应用程序的包装
新版本 Big Bank 示例应用程序的包装
新版本 Big Bank 示例应用程序的包装

生成新服务提供者 Web 服务组件

完成了概略设计,并对 WSDL 和 XSD 文件进行了更改后,生成新 Web 服务提供者组件就相当简单了。

Rational Application Developer Web 服务向导通常可以对 Web 服务部署模块中的 WSDL 和 XSD 文件进行一些较小的修改,因此最好将这些文件保存在独立的项目中。图图 3 中所示,我们已将我们已更新的 WSDL 和 XSD 文件与原始文件一起放入了 Build/wsdl/schema200603 下名为 Build 的项目中。

图 3. Rational Application Developer Project Explorer,显示 Build 项目组件
Rational Application Developer Project Explorer,显示 Build 项目组件
Rational Application Developer Project Explorer,显示 Build 项目组件

正如前面所讨论的,我们需要使用 BigBank1 作为上下文根的 Web 模块来承载新版本的 Web 服务。在我们的例子中,这是首次对 Web 服务进行版本控制,因此要创建一个具有所需上下文根的名为 BigBank1Web 的新 Web 模块。下次对 Web 服务进行版本控制的时候,可以切换回原始的上下文根 BigBank。在这种情况下,我们可以使用类似的过程在 BigBankWeb 中生成 Web 服务提供者组件。

要创建 Web 服务提供者组件,请在 Build/wsdl/schema200603/AccountService.wsdl 位置选择 WSDL 文件,并调用 New Web Service 向导。我们从 BigBank1Web 项目中的 WSDL 生成了一个框架 Java Bean Web 服务。一个不错的做法是使用向导选项 Define custom mapping for namespace to package。我们创建了一个映射文件 Account-NStoPkg.properties,以将命名空间年份和月份包含在 Java 包名称中。此方法可帮助避免从不同命名空间版本生成的代码间的混淆。可以在图 4 中看到生成的 Java 包。

图 4. Rational Application Developer 所生成的 Java 包
Rational Application Developer 所生成的 Java 包
Rational Application Developer 所生成的 Java 包

从生成的 Web 服务框架调用业务逻辑

AccountSoapPort.java 是生成的服务端点接口,而 AccountSoapBindingImpl.java 是生成的 Java Bean Web 服务框架。我们需要向生成的框架添加代码,以调用 Account EJB 中所需的业务逻辑。由于仅在 Account.create 方法中添加了一个额外的参数,因此新 AccountSoapBindingImpl.java 与旧版本间的差异很小。通过定义所有生成的类的导入,可帮助将包名称变更(由于命名空间变更引起的)隔离到 AccountSoapBindingImpl.java 的导入部分,如清单 5 中所示。

清单 5. 新版本 AccountSoapBindingImpl.java——命名空间变更
...
import com.bigbank.faults_2006_03.BankException;
import com.bigbank.parts_2006_03.ACreateRequest;
import com.bigbank.parts_2006_03.ACreateResponse;
import com.bigbank.parts_2006_03.AWithdrawRequest;
import com.bigbank.parts_2006_03.AWithdrawResponse;
import com.bigbank.parts_2006_03.ADepositRequest;
import com.bigbank.parts_2006_03.ADepositResponse;
import com.bigbank.parts_2006_03.ABalanceRequest;
import com.bigbank.parts_2006_03.ABalanceResponse;
...

另一个变更是更新对 Account.create 的调用,以在 Web 服务请求中包含 country 字段,如清单 6 中所示。

清单 6. 新版本 AccountSoapBindingImpl.java——Account.create 变更
...
public ACreateResponse create(ACreateRequest request)
		throws java.rmi.RemoteException, BankException {
	AccountLocal account = createAccountLocal();
	ACreateResponse response = new ACreateResponse();
	response.setAccountNumber(account.create(
		request.getCustomerName(),
		request.getAddress().getStreet(),
		request.getAddress().getSuburb(),
		request.getAddress().getState(),
		request.getAddress().getPostcode(),
		request.getAddress().getCountry()));
	return response;
}
...

我们使用 Rational Application Developer Snippets 视图来生成对 Account EJB 的本地接口的调用。通过 Call an EJB create method 代码片段,可以非常方便地调用 EJB。

当然,同样也需要实现新 Web 服务所需的业务逻辑变更。正如前面所述,我们通过向 Account EJB 添加新 create 方法来实现业务逻辑变更,该方法将接收添加的参数 country,并执行所需的处理。这样,仍然提供旧 Web 服务的旧 create 方法,新旧 Web 服务版本都使用相同的 Account EJB 方法来访问所有未变更的业务逻辑。实现工作的重点是接口变更和业务逻辑变更,从而最大化地对未变更逻辑进行重用。

此时,我们就同时为服务使用者提供了两个版本的 AccountService Web 服务。正如前面所述,旧版本的 AccountService 仍然可以不加更改地使用,但已向服务使用者发送了通知,告知旧版本已弃用,将会在指定的时间后(通常为计划推出下一个新版本的时间)退役。服务使用者需要更新其 Web 服务客户机应用程序,以在旧版本退役前开始使用新版本的 AccountService。让我们看看为了更新客户机应用程序来使用新版本的 AccountService,服务使用者将需要进行哪些工作。

更新客户机应用程序,以使用新 Web 服务版本

和很多当前 Web 服务实现一样,我们的场景中并不使用 UDDI。在此情况下,服务提供者通常会向服务使用者提供一个网站链接,可以从此处访问新 WSDL 和 XSD 文件。我们的 Web 服务的新 WSDL 和 XSD 可以使用以下链接在 Rational Application Developer 测试环境中进行访问:

  • http://localhost:9080/BigBank1/services/AccountService/wsdl.

对于我们的场景,我们直接使用 Rational Application Developer Web Service Client 向导来在 BigBankClientWeb 模块中为旧 Web 服务创建测试客户机应用程序。要更新此模块,以使用新版本的 AccountService,请首先打开 BigBankClientWeb 的 Web 部署描述符,并删除旧 AccountService 的 ServiceRef,如图 5 中所示。

图 5. BigBankClientWeb 模块的 Web 部署描述符
BigBankClientWeb 模块的 Web 部署描述符
BigBankClientWeb 模块的 Web 部署描述符

然后需要删除为旧 Web 服务使用者生成的组件。这些组件包括旧 WSDL 和 XSD 文件、Web 服务绑定文件和生成的 Web 服务代理类,如图 6 中所示。

图 6. 要从 Web 服务使用者中删除的组件
要从 Web 服务使用者中删除的组件
要从 Web 服务使用者中删除的组件

正如前面讨论使用 Rational Application Developer 的服务提供者时提到的,服务使用者也应考虑创建一个独立的项目来保存下载的 WSDL 和 XSD 文件,以在构建时使用。我们使用 Build/wsdl/schema200603/AccountService.wsdl 调用 New Web service client 向导来在 BigBankClientWeb 项目中生成一个 Java 代理。和处理服务提供者时一样,使用 Define custom mapping for namespace to package 向导选项来将命名空间年份和月份包含在 Java 包名称中。

最后一步是修改客户机应用程序,以使用生成的新 Web 服务代理。对于服务使用者,要对客户机应用程序进行更改,以使用由于命名空间变更而出现的新 Web 服务代理类新包名称。还需要修改客户机应用程序来使用新接口。在我们的场景中,客户机应用程序需要修改为,在为 AccountService.create 构建请求消息时包括 country

已弃用 Web 服务的退役

弃用时间结束后,Web 服务版本控制过程的最后一阶段就可以完成了。可以采用任何方法来退役和删除已弃用的 Web 服务接口。禁用 Web 服务的相对简单的方法是直接在 Web 部署描述符 (BigBankWeb/WebContent/WEB-INF/web.xml) 中将其 Servlet 定义注释掉,如清单 7 中所示。

清单 7. 注释掉 Web 服务 Servlet 定义
...
<!-- <servlet>
	<servlet-name>com_bigbank_services_account_AccountSoapBindingImpl</servlet-name>
	<servlet-class>
	com.bigbank.services.account.AccountSoapBindingImpl</servlet-class>
	<load-on-startup>1</load-on-startup>
</servlet> -->
...

不过,为了从弃用和删除获得完全的维护好处,将要进行重构,以将 Web 服务完全删除。在我们的场景中,已弃用的 WSDL、XSD 文件、生成的 Java Web 服务框架类和 Web 服务部署描述符(如图 7 中所示)都需要从 BigBank 应用程序的下一版本中删除。

图 7. 使 BigBank 退役时要删除的组件
使 BigBank 退役时要删除的组件
使 BigBank 退役时要删除的组件

如果您的 Web 模块中包含其他 Web 服务,则注意不要删除其他 Web 服务所需的文件或 Web 服务部署描述符项。

业务逻辑接口可能也需要进行清理。在我们的场景中,Account EJB 上的旧 create 方法将不再使用,因此可以将其删除。

通过使用上述方法,您的 Web 服务客户机将可以方便地完成一个发布周期,以对其应用程序进行所需的任何更改。作为服务提供者,该方法允许您以可持续的方式支持那些不向后兼容的变更,且不会无限期地支持旧接口。

结束语

本文所讨论的管理 Web 服务变更的技术和指导方针可作为任意 SOA 控制流程的基础信息。这些技术和指导方针表明,开发团队可以在实现变更策略时扮演重要角色,可在对服务进行变更的同时最小化此变更对提供者和使用者的影响,从而提供持续的企业灵活性。能提供可预测性且认识到服务变更需要由业务部门进行管理的策略能更清楚地对变更及其潜在影响进行说明,并能提高相关代码的完整性和易维护性。

致谢

感谢 Peter Chilcott、Paul Dreyfus 和 Charles Ray 对本文进行了审阅并提出了宝贵的建议。


下载资源


相关主题


评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=SOA and web services, Rational, Java technology
ArticleID=158038
ArticleTitle=通过 Web 服务向后兼容性向前发展
publish-date=09072006