内容


最佳实践和 Web 服务概要文件

Comments

第 1 章:关于本教程

本教程的目的

本教程是有关利用“用于 Web 服务的 IBM WebSphere SDK”(IBM WebSphere SDK for Web Services)V5.0(WSDK)构建 Web 服务系列教程的最后一篇。本系列教程的目的是向您介绍 Web 服务的概念和技术,演示如何使用 WSDK 实际应用它们,还探讨了 Web 服务技术的当前状态。如果学完了所有教程,那么您就会很好地理解 Web 服务和 WSDK 提供的好处。您还应该已经:

  • 构建从简单 Java 类、会话 Bean 和 WSDL 创建 Web 服务的应用程序。
  • 完成了一些项目,这些项目需要您将 Web 服务部署到 UDDI 注册中心和在 UDDI 注册中心内发现 Web 服务。
  • 创建和部署安全的 Web 服务。

Web 服务不同于传统的分布式计算方法,因为 Web 服务的目的不只是进程间通信,而且还进行完全无缝的相互操作,所以使得面向服务的体系结构(service-oriented architecture,SOA)成为可能。在 SOA 中,业务过程被公开为可动态发现(例如通过 UDDI)和执行的服务,这与组件截然不同。为了实现这个目的,人们已经建立了并且正在建立一些协议标准。您已经熟悉的一些比较常见的协议标准有:UDDI、SOAP、WSDL 和 WS-Security。

熟悉这些协议与深入了解 Web 服务框架的机理,以至深入了解构建一个 Web 服务的最佳方法是有区别的。这样汇总起来的深入研究的结果随着时间推移而逐渐发展成一套最佳实践。幸运的是,人们已经创立了 Web 服务互操作性组织(Web Services Interoperability Organization,WS-I)来定义和宣传 Web 服务最佳实践。本教程的目的是让您对已经出现的一些最佳实践有基本了解,并且向您介绍 WS-I 的一些成果。

要学习本教程,您需要了解哪些知识

本教程假定您已经学完了本系列前几篇教程:

如果您还未阅读上述教程,那么要学完本教程,您至少应当具备 UDDI、SOAP、WSDL、EJB 技术、Servlet 和 JSP 技术、Web 服务安全性以及 WSDK 的基本知识。

本教程涵盖了哪些内容

本教程研究了用于构建 Web 服务的一些体系结构级别(高级)和实现级别(低级)的最佳实践。它将研究下列主题、工具和技术:

  • WS-I 概要文件和方案
  • 使用哪个 SOAP 模型
  • WSDL 的重要性及其适用场合
  • UDDI 注册中心的用武之地
  • 维护和扩展的规划
  • 从 JSP 页面、Servlet 和 EJB 组件访问 Web 服务的最佳方法
  • 何时进行 SOAP 高速缓存是适当的。

第 2 章:WS-I 概要文件

元标准和概要文件

本系列的几篇教程已向您介绍了几种基于 XML 的协议:UDDI、WSDL 和 SOAP。协议不止这三种。在技术社区很容易诞生范例变化,这些变化使得标准建议象雨后春笋般地到处涌现。Web 服务的目标是互操作性,但是如果每个 Web 服务节点都任意实现自己对标准的彻底改变(或者是标准版本;例如 SOAP 1.1 和 SOAP 1.2),那么使用 Web 服务实际上将不可能实现互操作性。

请考虑一个示例。公司 X 决定使用 Web 服务公开其服务器端功能。他们已经选择使用 WSDL V1.2、SOAP V1.1 和 UDDI V1。公司 Y 也决定使用 Web 服务公开其服务器端功能。而他们选择使用 WSDL V1.1、SOAP V1.2 和 UDDI V2。两个组织都在使用 Web 服务,而客户机或注册中心需要了解两种不同的协议组合,以便与二者交互。这些协议本身不足以实现互操作性。如果将这样一种组合标准化,那么公司 X、公司 Y 及其客户机和注册中心就可以采用一组公共的协议和版本。在本示例中,不存在这样的情况。公司和客户机只能根据其自身特定的约束或需求集来选取它们认为合适的协议和版本,并且希望它们能够相互通信。

图

图

我们需要的是一个将一组协议及其版本组合到一个标题下的元标准。如果将 WSDL V1.2、SOAP V1.2 和 UDDI V2 的集合命名为 2003 WS 协议集(2003 WS Protocol Set),并且提升为正式的协议,那么通过选择 2003 WS 协议集而不是选择子协议集的任意组合,公司 X、公司 Y 及其客户机和注册中心在协议决策时会更为得心应手。

组建 WS-I 的目的是设法解决这类操作性问题。WS-I 概要文件倡议解决了公司 X 和公司 Y 面临的这个问题。概要文件(Profile)是在一个标题下组合一组 Web 服务协议及其版本。通过进行这样的组合,各种组织可以在更细的粒度级别上协商其协议需求。概要文件还对正式的协议集的数量进行了限制,从难以估计的数量限制为 WS-I 选择的任意有限数量。

图

图

目前 WS-I 只定义了一个概要文件,名为 Web 服务基本概要文件(Web Services Basic Profile)(也可称为 wsbasic 或 WS-I Basic,或者简称为基本概要文件)。该概要文件的当前版本是 V1.0,在 2003 年年中发布 1.1。WS-I Basic 包含许多协议及其版本。下面是其中的八个。

标准版本描述
XML1.0使用元素和属性来描述数据的语法
XML Schema 第 1 部分:结构1.0XML 协议中元素之间的嵌套关系
XML Schema 第 2 部分:数据类型1.0XML 协议的标量类型
RFC2616:超文本传输协议(HyperText Transfer Protocol)1.1使用头(header)发送给 Web 服务器的方法调用
SOAP1.1网络传输过程中的 XML 文档
WSDL1.1类型、网络传输数据、网络传输组、协议绑定和端点
UDDI2.0服务注册中心的数据类型和操作。
RFC2965:HTTP 状态管理机制3.0

WS-I 打算在其它类型的协议变得更稳定时将它们添加到概要文件中。以下是目前仍不太稳定的协议领域,WS-I 最终会将它们添加到概要文件中:

  • 二进制附件(Binary attachment)
  • 路由(Routing)
  • 相关性(Correlation)
  • 保证的消息交换(Guaranteed message exchange)
  • 事务(Transaction)
  • 过程流(Process flow)。

WSDK 在以后的版本中将支持 WS-I 概要文件。

子标准和概要文件

将不断发展的标准组合在一起形成更大的集合解决了一些互操作性问题。但是,还有一些其它问题。当您尝试将一组不相关的标准组合成为一个整体时,会出现一些难题。这些标准之间存在的重叠、含糊性和复杂性必须被解决。实际上,在基本概要文件 V1.0 中,标准协议的列表只是文档的一小部分,而大部分文档用来协调这些标准协议之间的不相容性。协调协议的工作是通过对不同的协议强制应用限制实现的,即使这些协议本身不包含这样的限制。

下面是出自 WS-I 概要文件标准的一个示例

R1011 soap:Envelope 中的消息,在 soap:Body 子元素后一定不能有任何子元素。

(该需求澄清了 SOAP 1.1 规范和 SOAP 1.1 XML Schema 之间的的不匹配。)

错误的:

                    <soap:Envelope xmlns:soap='http://schemas.xmlsoap.org/soap/envelope/'>
  <soap:Body>
    <p:Process xmlns:p='http://example.org/Operations' />
  </soap:Body>
  <m:Data xmlns:m='http://example.org/information' >
    Here is some data with the message
  </m:Data>
</soap:Envelope>

正确的:

                    <soap:Envelope xmlns:soap='http://schemas.xmlsoap.org/soap/envelope/'>
  <soap:Body>
    <p:Process xmlns:p='http://example.org/Operations' >
	 <m:Data xmlns:m='http://example.org/information' >
 	  Here is some data with the message
      </m:Data>
    </p:Process>
  </soap:Body>
</soap:Envelope>

WS-I 基本概要文件和 WSDL

对于我们中的大多数人而言,为了构建 Web 服务,只需阅读 WS-I 基本概要文件中的一小部分内容。该文档的主要读者是那些希望构建诸如 WSDK 之类的工具和 Web 服务实现的组织。正如前几篇教程指出的,WSDK 向您屏蔽了 SOAP 流序列化/反序列化以及读/写 HTTP 等低级细节。但是不能认为 WSDL 也属于这些范畴。必须为 Web 服务的客户机提供 WSDL。创建用于 Web 服务的 WSDL 有两种方法。一种方法就是让 WSDK 自动为组件生成适当的 WSDL。另一种方法是手工编写它,在这种情况下,WSDK 将根据 WSDL 文件生成框架和服务器(Bean、EJB 技术)代码。如果您希望用先定义 Web 服务应用程序的抽象接口,然后转向具体实现的方式来设计您的 Web 服务应用程序,那么这种方法可能有意义。请考虑下面两种方法。

  • 抽象-具体-抽象(Abstract-Concrete-Abstract):使用 UML 图进行高级设计(抽象),然后编写所有代码(具体),再从您的服务器代码生成 WSDL 文件(抽象)。
  • 抽象-抽象-具体(Abstract-Abstract-Concrete):使用 UML 进行高级设计(抽象),并使用 WSDL 描述相互关系(抽象)。然后使用 WSDK 生成您的服务器代码(具体)。

后一种方法比前一种具有更多优点。这些优点有:

  • WSDK 为您生成了管件代码。
  • 如果您编写了 WSDL,那么将不会把网络接口与实现束缚在一起。接口的这种稳定性减少了服务器实现的变化对 Web 服务客户机的影响。

许多人认为 WSDL 应当是编写任何代码之前的设计过程的一部分。使用 WSDL 定义您系统的界限,然后实现那些接口,这样做不仅保证接口(用于将您的系统公开给系统的客户机)的互操作性,而且还保证接口的稳定性。

一些行业正开始定义 WSDL 文件以记录其公共的数据类型和函数。随着可以使用这些 WSDL,问题就转变为某个组织是否应当采用已经由其行业所定义的 WSDL。在这种情形下,该组织只好从 WSDL 生成其实现,而别无它途。

另外,有可能的话您会希望手工开发自己的 WSDL。基本概要文件对 WSDL 协议设置了一些限制。下面是对 WSDL 设置的几个主要限制。

导入

在 WSDL 文档中可以使用的导入有两类:WSDL 导入和 XML Schema 导入。基本概要文件要求这两种导入类型不能既用于导入 WSDL 片段又用于导入 XML Schema 片段。在 WSDL 类型的元素中,只能使用 WSDL 导入来导入 WSDL 片段,并且只能使用 XML Schema 导入来导入 XML Schema 片段。如果您使用 WSDL 导入,那么它必须是 <definitions> 元素的第一个子元素。

<definitions name="DVDRetailer"
   targetNamespace="http://dvdretailer.com/definitions"
   xmlns:xsd1="http://dvdretailer.com/dvdretailer.xsd"
                    ...
   xmlns="http://schemas.xmlsoap.org/wsdl/">

	<!-- This is not allowed. 
	The WSDL import is being used to import XML Schema elements -->
   <import namespace="http://dvdretailer.com/schemas"
         location="http://dvdretailer.com/types/types.xsd"/>
         
   <message name="GetCatalogue">
        <part name="body" element="xsd1:DVDCatalogue"/>
    </message>
               ...
</definitions>
<definitions name="DVDRetailer"
   targetNamespace="http://dvdretailer.com/definitions"
   xmlns:xsd1="http://dvdretailer.com/dvdretailer.xsd"
                    ...
   xmlns="http://schemas.xmlsoap.org/wsdl/">

	<!-- This is correct. 
	The XML Schema import is being used to import XML Schema elements -->
   <types>
     <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
       <xsd:import namespace="http://dvdretailer.com/schemas" 
         schemaLocation="http://dvdretailer.com/types/types.xsd"/>
     </xsd:schema>
   </types>

         
   <message name="GetCatalogue">
        <part name="body" element="xsd1:DVDCatalogue"/>
    </message>
               ...
</definitions>
<definitions name="DVDRetailer"
   targetNamespace="http://dvdretailer.com/definitions"
   xmlns:xsd1="http://dvdretailer.com/dvdretailer.xsd"
                    ...
   xmlns="http://schemas.xmlsoap.org/wsdl/">

	<!-- This is also correct. 
	The WSDL impot is being used to import a WSDL fragment -->
   <import namespace="http://dvdretailer.com/schemas"
        location="http://dvdretailer.com/types/types.wsdl"/>
        
   <message name="GetCatalogue">
        <part name="body" element="xsd1:DVDCatalogue"/>
    </message>
               ...
</definitions>

use 属性值

use 属性不允许为 encoded,必须为 literal。use 属性决定了 SOAP 头或主体中的数据在可以被转换成 XML 节点树(类似 DOM)之前是否需要进行译码。该属性有两个可能的值:encodedliteral。目前它基本上都是 literal。如果 useliteral,那么就假定没有对该数据进行过编码。有一个特殊的编码方案,它被定义成了 SOAP 早期版本的一部分,该方案允许某个进程确切地知道如何将数据转换成特定于平台的类型。现在已不再使用这个方案了,因为进程可以通过向定义数据类型的 WSDL 咨询来决定如何处理 SOAP 文档中的数据。

<definitions ...>
	...
		<!-- This is not allowed. 
		The use attribute has been given the value of 'encoded'. -->
	
	<wsdl:binding name="SoapDVDRenting" type="tns:DVDRenting">
		<soap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http"/>
		<wsdl:operation name="SubscribeToSpecialsAlertList">
			<soap:operation soapAction=""/>
			<wsdl:input>
				<soap:body namespace="http://dvdretailer.com" use="encoded"/>
			</wsdl:input>
		</wsdl:operation>
		<wsdl:operation name="RentDVD">
			<soap:operation soapAction=""/>
			<wsdl:input>
				<soap:body namespace="http://dvdretailer.com" use="encoded"/>
			</wsdl:input>
			<wsdl:output>
				<soap:body namespace="http://dvdretailer.com" use="encoded"/>
			</wsdl:output>
			<wsdl:fault name="tns:RentFault">
				<soap:fault namespace="http://dvdretailer.com" use="encoded"/>
			</wsdl:fault>
		</wsdl:operation>
	</wsdl:binding>
  ...
</definitions>
<definitions ...>
	...
		<!-- This is correct. -->
	
	<wsdl:binding name="SoapDVDRenting" type="tns:DVDRenting">
		<soap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http"/>
		<wsdl:operation name="SubscribeToSpecialsAlertList">
			<soap:operation soapAction=""/>
			<wsdl:input>
				<soap:body namespace="http://dvdretailer.com" use="literal"/>
			</wsdl:input>
		</wsdl:operation>
		<wsdl:operation name="RentDVD">
			<soap:operation soapAction=""/>
			<wsdl:input>
				<soap:body namespace="http://dvdretailer.com" use="literal"/>
			</wsdl:input>
			<wsdl:output>
				<soap:body namespace="http://dvdretailer.com" use="literal"/>
			</wsdl:output>
			<wsdl:fault name="tns:RentFault">
				<soap:fault namespace="http://dvdretailer.com" use="literal"/>
			</wsdl:fault>
		</wsdl:operation>
	</wsdl:binding>
  ...
</definitions>

操作重载

跟大多数编程语言一样,WSDL 允许对操作进行重载。通过在 portType 中定义多个具有相同名称的操作,可以做到这一点。然后 Web 服务实现可以根据参数的数据类型和顺序来决定调用哪个函数。基本概要文件限制了这种功能。基本概要文件中的所有操作都必须具有唯一的名称。

<wsdl:definitions
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
...>
	...
	<!-- This is not allowed. 
	The two operations need to have unique names. -->
	<wsdl:portType name="DVDRenting">
		<wsdl:operation name="SubscribeToSpecialsAlertList">
			<wsdl:input message="tns:Subscribe" />
		</wsdl:operation>
		
		<wsdl:operation name="SubscribeToSpecialsAlertList">
			<wsdl:input message="tns:SubscribeWithEndDate" />
		</wsdl:operation>
	</wsdl:portType>	
	...
</wsdl:definitions>

返回类型

RPC 样式的操作的返回类型是在操作的 <message> 元素中定义的。该消息元素包含了 <part> 子元素。基本概要文件要求为 RPC 样式的操作返回的 <message> 元素不包含 <part>,或者只包含 1 个 <part>。这意味着必须用在 <types> 元素中定义的某种简单或复杂类型完全地描述某个操作的返回值。这很有意义,因为函数只返回一种类型的结果。如果返回消息可能带有多个 <part>,那么这意味着它公开的服务器函数必须具有多个返回值。

<wsdl:definitions
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
...>
	
	<!-- 
	This is not allowed. The PurchaseResponse contains 2 parts. -->

	...
	
	<wsdl:message name="PurchaseResponse">
		<wsdl:part name="paymentResponse" 
		type="tns:ResponseConfirmationType" />
		<wsdl:part name="purchaseDate" type="xsd:date" />
	</wsdl:message>
	
	<wsdl:message name="PurchaseRequest">
		<wsdl:part name="purchaseRequest" type="tns:DVDPurchaseType" />
	</wsdl:message>

	...
	
	<wsdl:operation name="RentDVD">
		<wsdl:input message="tns:RentalRequest" />
		<wsdl:output message="tns:PurchaseResponse" />
	</wsdl:operation>	
	...
</wsdl:definitions>
<wsdl:definitions
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
...>
	
	<!-- This is correct. (
This assumes that ResponseConfirmation has been redefined 
to include both the confirmation type and the purchase date) -->

	...
	
	<wsdl:message name="PurchaseResponse">
		<wsdl:part name="paymentResponse" type="tns:ResponseConfirmation" />
	</wsdl:message>
	
	<wsdl:message name="PurchaseRequest">
		<wsdl:part name="purchaseRequest" type="tns:DVDPurchaseType" />
	</wsdl:message>

	...
	
	<wsdl:operation name="RentDVD">
		<wsdl:input message="tns:RentalRequest" />
		<wsdl:output message="tns:PurchaseResponse" />
	</wsdl:operation>	
	...
</wsdl:definitions>

基本概要文件还对 WSDL 文件设置了其它限制。请参阅参考资料一章以获取到基本概要文件的链接。

第 3 章:WS-I 方案

简介

WS-I 概要文件将一组协议和标准组合到一个标题下,这样就可以将它们看成一个单一的实体。这对开发人员而言是有益的,这样他们在讨论 Web 服务时就可以使用一个标题而不必在每次出现协议和标准时遍历一列协议和标准。WS-I 使用方案(WS-I Usage Scenario)将标题(titles)应用于交互序列。但是,除了标题以外,WS-I 使用方案还定义了每个交互序列类的角色和职责。

使用方案是进程怎样通过 Web 服务进行交互的通用描述。目前的使用方案非常简单。可通过许多不同途径对这些简单方案进行扩展,当 WS-I 接受其它一些更复杂的方案作为可扩展的子类型时,还可能会添加这些复杂方案。

通过标准化原本要在无指导的情况下发展的工作,WS-I 希望保证 Web 服务保持其互操作性的目标。这是概要文件和使用方案背后的基本动力。

接下来将首先描述使用方案的构成(WS-I 称为使用方案分类(Usage Scenario Taxonomy)),然后将分别描述 WS-I 已经定义的三个使用方案,先从最简单的方案开始介绍,然后介绍更复杂的方案。

使用方案分类

使用方案分类由用于定义和描述使用方案的术语和概念构成。这些概念归为两类:Web 服务堆栈(Web Service Stack)和活动(Activity)。Web 服务堆栈列出了调用 Web 服务所涉及的不同通信层。有三个层:

数据层(Data Layer)― 该层负责 Web 服务通信中的数据。特别地,它负责将模型的数据转换成数据流,以及从数据流转换成模型的数据,该数据流遵从目标 Web 服务的 WSDL 的类型和消息元素中的 Web 服务类型定义。

SOAP 消息层(SOAP Message Layer)― 该层负责生成和处理 SOAP 消息,这些消息包含了来自数据层的应用程序数据。将请求分派给正确的应用程序或方法也是该层的职责。该层映射到 wsdl:portTypes 和 wsdl:bindings 定义上。

传输层(Transport Layer)― 该层仅由 HTTP 服务器和客户机构成,它负责发送和接收 HTTP 消息。该层映射到 <wsdl:binding><wsdl:port> 定义上。

图

图

活动列出了每层可以执行的操作。

活动
数据
  • 编写 XML
  • 处理 XML
SOAP 消息
  • 编写 SOAP 信封
  • 处理 SOAP 信封
  • 编写 SOAP 主体
  • 处理 SOAP 主体
  • 编写 SOAP 头
  • 处理 SOAP 头
传输
  • 发送 HTTP
  • 接收 HTTP

通过使用 Web 服务堆栈及其相关活动作为构件,WS-I 能够定义使用方案。目前只有三种使用方案:单向(One-Way)、同步双向(Synchronous Two-Way)和基本回调(Basic Callback)。它们都可以在另一个基础上构建。

使用方案

单向

这种使用方案描述了如何从 Web 服务消费者(Consumer)向 Web 服务提供者(Provider)传达请求。当提供者接收到来自消费者的消息时,它就负责处理该消息。正如其标题所指出的,这种通信只能单向进行。传输层之上的其它层不会对所接收的消息作任何确认(例如 HTTP 响应状态码)。由于该需求,WS-I 强调:该方案描述的是一种认为“信息丢失无关紧要”的通信,因为提供者没有消息层方法来将通信失败的消息传回给消费者。

图

图

同步双向

同步双向方案与单向方案几乎相同,但是双向的。在本方案中,消费者向提供者发送一条消息。提供者接收该消息并执行任何所需的服务器端处理,然后向消费者发回一条消息。如果在满足消费者请求的过程中出现任何问题,提供者会向消费者返回一个描述该问题的 SOAP 错误。

图

图

基本回调

基本回调是由两个同步双向构成的。消费者将带有其连接信息的消息发送给提供者。提供者发回一个确认响应。然后关闭消费者和提供者之间的连接。只要提供者处理完消费者消息中的数据,提供者就使用消费者在初始请求中发送的连接信息来建立与消费者之间的连接。提供者然后将处理结果发送给消费者,并等待消费者的确认响应。

图

图

在可以实现该使用方案之前,消费者和提供者必须就格式化客户机连接信息的协议以及标识对话的方法达成一致。有一些新兴的标准描述了应当如何完成该工作:

  • Web Service Choreography Interface(WSCI)
  • 用于 Web 服务的业务过程执行语言(Business Process Execution Language for Web Services,BPEL4WS),作为业务过程的可执行规范
  • WS-协调(WS-Coordination,WS-C)。

第 4 章:Web 服务设计最佳实践

简介

用于 Web 服务的常见最佳实践并不很多。尽管很多公司都正在研究如何使用 Web 服务,但只有少数公司拥有可用的产品。在率先使用高级的体系结构方法实现 Web 服务的方面,IBM 是为数不多的几家公司之一。

IBM 的电子商务模式(Patterns for e-business)向开发人员提供了有关如何创建企业应用程序的非常不错的理念。电子商务模式有两个方面:设计模式本身和该模式可以满足的业务需求。电子商务模式记录了需求和模式之间的连接。这使开发人员在评估了业务需求后可以立即决定他或她将需要利用哪种设计模式。

最近,IBM 发表了一系列有关建立 Web 服务架构的最佳实践的文章(请参阅参考资料)。通过吸取 IBM 与其客户一起工作的实际经验,这些文章考虑了与 Web 服务有关的电子商务模式和业务-需求之间的映射。本章的其余部分总结了那些文章中的建议和理念,因为它们与高级 Web 服务基础结构体系结构是相关的。

电子商务模式

IBM 电子商务模式在最高级别上描述了企业系统中实体之间存在的不同类型的关系。这些业务模式的抽象性使得理解它们比较困难。它们除了描述基于 Web 的应用程序结构和交互以外,并无有关实现这些模式的假设。它们只打算为您提供一个“基本字典”,您可以用它来构造和描述关系。

电子商务模式被分成了两类:简单的高级的。简单的业务模式比高级的模式抽象,并且描述了四种不同的基本关系。高级模式所处的级别比四个简单的基本模式低。一种解决方案就是聚集使用高级模式的解决方案以实现简单的模式。

图

高级模式有两类:集成模式(Integration patterns)复合模式(Composite patterns)。本教程将只考虑简单业务模式和集成模式。

简单业务模式

  • 自助服务(Self-Service)(或称为用户对商家,User-to-Business)― 该模式描述了用户对某项业务执行事务的情形。用户可以是任何内部雇员或外部客户。在这种情形下,用户始终是一个人,而不是另一个过程。
  • 协作(Collaboration)(或称为用户对用户,User-to-User)― 用户通过网络进行交互的例子有很多。电子邮件、即时消息传递和 Usenet 等都是该行为的示例。该模式允许您谈论这样的交互时不必涉及实现。
  • 信息聚集(Information Aggregation)(或称为用户对数据,User-to-Data)― 类似于自助服务模式,信息聚集模式描述了用户如何与商家进行交互。但是,在这种情况下,公开的是业务数据而不是业务过程。
  • 扩展企业(Extended Enterprise)(或称为商家对商家,Business-to-Business)― 该模式描述了不同业务过程之间的交互和协作。与前三种模式不同,该模式不涉及人类用户。所有交互都是自动的。

集成模式

  • 访问集成(Access Integration)― 访问集成模式描述了如何以一种不会对系统产生较大影响的方式,将用户和业务过程与由全异的子系统构成的系统集成在一起。该模式的一些示例包括单点登录(single sign-on)和多设备访问(multi-device access)。
  • 应用程序集成(Application Integration)― 一个系统可能会从多个数据库、ERP 系统和平面文件等获取数据。该模式描述了在这样的系统内,如何对用户和业务过程隐藏数据源的不同性。该模式的一个示例是聚集器(Aggregator),或称为虚包(facade)过程,插在客户机和数据存储之间。适当拥有这样一个过程,客户机将只需直接与虚包交互,而不必知道数据存储。

具备了这些模式的知识之后,就可以绘制一个表,将抽象业务需求与可用于满足那些需求的模式连接起来。下表是在“用于 Web 服务的最佳实践”系列中列出的。

业务驱动力自助服务协作信息聚集扩展企业访问集成应用程序集成
最终用户和客户需要与业务过程和/或数据直接交互。XXX.X.
需要将业务过程与现有的业务系统和信息进行集成。X....X
需要将业务过程与现存于合作伙伴组织中的过程和信息进行集成。...X.X
业务活动需要聚集、组织和提供来自组织内外各种来源的信息。..X.XX
业务过程必须能够通过多种传递渠道以一种常见的、一致的和简化的方式获得。X...X.
业务活动要求并推动在其参与者之间进行协作和共享信息。.X....

诸如此类的方法可作为设计过程的有用起点。首先,列出用户和过程交互业务需求。其次,将每个业务模式作为一列列出。有了该矩阵,您的团队就可以针对满足业务需求讨论每个模式的可行性,并且还可以讨论可能使用哪些模式的组合。

第 5 章:UDDI 最佳实践

我何时需要它?

真正的互操作性的最大优点之一就是,它允许创建发现机制。请参考教程 Publishing your services: UDDIDiscovering Web services: UDDI 以了解有关 Web 服务注册中心的影响(请参阅参考资料)。UDDI 是由基本概要文件认可的 Web 服务注册中心协议,并且也是 WSDK 目前支持的一个协议。

由于 Web 服务的出现,技术经理们正面临着作出许多新决定。其中的一个决定涉及是否应当将其 Web 服务定义和/或连接信息张贴在注册中心。这正在转变为这样一种您不能“保持中立”的问题。如果不通过注册中心公开其 Web 服务信息,一些公司从长远来看可能会亏本。而另外一些公司可能从未因注册 Web 服务信息获得投资回报。那么考虑一下什么类型的公司将从注册其 Web 服务而获利,这是很有用的。

发布的标准/非唯一的公司

那些具有共同业务协议的公司从 UDDI 获益最大。这些公司通常涉及了所有行业。例如,鞋业公司可能走到一起并且发布一个 WSDL,它描述了制鞋行业中使用的数据类型和各类业务功能的特征符,这些公司可能希望彼此执行这些业务功能。有了鞋业 WSDL,希望参与制鞋行业的分布式社区的任何组织只需使用鞋业 WSDL 文档中描述的语法公开其业务过程就可以了。然后 UDDI 注册中心就会发挥象鞋业从业者的“聚会点”一样的作用。由于他们就 WSDL 文档中定义的相同协议达成了一致,因此他们都可以进行交流。

上述方案成为现实的例子很少见,因为需要就安全性、服务质量、对话和其它协议达成一致。但是,最终结果是,将自己当成是较大业界社区的竞争者的任何组织可能会很快开始询问是否为其所从事的行业创建了 WSDL。如果不仅是为了不落在其竞争对手之后,这类正考虑使用 Web 服务的公司还应该在注册中心注册自己及其服务。

拥有不同的全异开发部门的大型公司

许多公司正在从私有的 UDDI 注册中心获益。拥有多个开发部门的较大型公司通常会竭尽全力提高重用和凝聚力程度,同时还要允许其不同部门的各自发展。Web 服务考虑到将组件重用为服务,UDDI 使得开发人员可能了解其组织是否已经开发了必需的组件。因此,Web 服务和 UDDI 促进了重用,同时允许不同部门使用它们希望的任何平台。在这种情形下,注册中心成了已部署组件的资源库,组织中所有人都可以使用这些公开为服务的组件。该方案更为常见,因为涉及信任或服务质量的问题很少,而信任和服务质量在与外部实体进行通信时是必需的。

将我的 WSDL 放在网站上怎么样?

与将 WSDL 的链接张贴到 UDDI 注册中心相比,该方法在许多方面都更容易,但是两者各有利弊。

为什么不在网站上使用链接?

  • 网站没有发现协议,该协议可以允许应用程序搜索和下载您的 WSDL。
  • 有了 UDDI,都遵从同一个 WSDL 协议的 Web 服务可以引用同一个 tModel(Web 服务定义)。由于这个由多个相似的 Web 服务组成的集合,所以随着 Web 服务的逐渐流行,消费者或许可以转到 UDDI 注册中心以弄清楚他们应当连接哪种 Web 服务实现。如果到您的 WSDL 的链接位于网站上的某个地方,那么您的 Web 服务实现可能从不会被考虑。
  • 通过网站上的 WSDL 的链接来查找 Web 服务不仅当前无法以编程方式实现,而且也不具有 UDDI 注册中心所具有的良好的分类程度。这种分类机制允许您快速找到您正在寻找的 Web 服务类型。

为什么在网站上使用链接?

  • 易于完成。
  • 如果您是一个独一无二的组织,那么您可能只需要在网站上张贴一个 WSDL 的链接。首先,您的组织决不会与其它任何组织共享一个 WSDL,因此您无需与其它组织围绕同一个 UDDI tModel 进行集合。其次,您的消费者可能已经了解了您的组织,因此根本不需要动态地发现您。此类组织的一个示例是 IRS。只有一个 IRS。如果 IRS 希望通过 Web 服务公开其过程,那么它无需将其 WSDL 的链接放入 UDDI 注册中心。只要将该链接放在其网站上的某个地方就可以了。
  • 如果您只拥有规模较小,准静态的一组客户,那么您只需通过电子邮件的方式将 WSDL 发送给相应需要它的人。

第 6 章:SOAP 最佳实践

RPC vs 文档

基于 SOAP 的 Web 服务有两种:RPC 样式和文档样式。RPC 样式的 Web 服务旨在使网络和实现区别在客户机调用服务器函数的过程中是透明的。SOAP 消息包含了 XML 文档,用于描述方法的参数、返回值和名称等。在这种情形下,数据类型被认为直接映射到服务器函数的参数和返回值上。该映射可在 WSDL 文档中定义,然后由客户机使用以生成调用服务器所必需的存根代码。文档样式的 Web 服务用于发送带有任何元数据的 XML 文档,这些元数据可能是在网络中传输 XML 文档所必需的。

如果认真考虑一下,您会发现 RPC 样式是文档样式的子集。在 RPC 样式中,被传输的 XML 文档描述了一个方法调用。在文档样式中,XML 文档是任意的;它可能描述任何东西。

将一个抽象解决方案与一个较具体的解决方案进行比较时,会出现同样的优缺点。对于开发人员而言,抽象的解决方案更灵活但涉及了更多的工作,而较具体的解决方案更易于立即使用。比较文档样式(抽象,通用)与 RPC 样式(更具体,特定)时,会出现同样的优缺点。RPC 样式具有一个定义得比较详细的目的(例如方法调用),它更易于服务器开发人员短期使用。有一些工具可以脱离服务器接口而生成支持 SOAP 的框架代码和 WSDL 文件。在非常短的时间内您就可以构造一个实现,如果客户机碰巧拥有可以脱离 WSDL 而生成存根代码的工具,那么在构建客户机存根代码时所花费的工作可能比较少。但是,当接口发生变化时就会出现问题。请考虑下面的示例。公司 X 销售 DVD。它们创建了一个 RPC 样式的 Web 服务来公开其购买 DVD 的功能。其 SOAP 消息请求和响应的主体看上去可能如下所示:

请求 SOAP 主体

	<dvd:purchaseDVDs xmlns:dvd="urn:DVDService">
            <dvd:arg1>10</dvd:arg1>
            <dvd:arg2>346534</dvd:arg2>
            <dvd:arg3>jim_smith@nowhere.com</dvd:arg3>
        </dvd:purchaseDVDs>

响应 SOAP 主体

	<dvd:purchaseDVDsResponse xmlns:dvd="urn:DVDService">
            <dvd:arg1>2</dvd:arg1>
            <dvd:arg2>e67-rtt5-gf34</dvd:arg2>
        </dvd:purchaseDVDsResponse>

如果它们将该服务作为一个文档样式的服务实现,那么请求和响应看上去可能如下所示:

请求 SOAP 主体

	<dvd:dvdOrder xmlns:dvd="urn:DVDService">
            <dvd:quantity>10</dvd:quantity>
            <dvd:productID>346534</dvd:productID>
            <dvd:contact>jim_smith@nowhere.com</dvd:contact>
        </dvd:dvdOrder>

响应 SOAP 主体

	<dvd:dvdReceiptConfirmation xmlns:dvd="urn:DVDService">
            <dvd:status>2</dvd:status>
            <dvd:confirmationID>e67-rtt5-gf34</dvd:confirmationID>
        </dvd:dvdReceiptConfirmation>

这两组 SOAP 消息之间有一些区别。RPC 样式的文档的描述性不是很强。它们无需具备这种能力。调用是在单个套接字上发生的。客户机进程知道如何解释 <purchaseDVDResponse>,因为该进程知道它与自己发送的 <purchaseDVD> 文档是关联的。文档样式的服务元素描述得十分详细。它们是独立的文档,不是描述某个函数调用,而是描述业务实体。如果第一个接口发生变化,那么服务器和客户机都会遭到破坏,因为数据的含义与任何一个文档都是无关联的。对于文档样式,由于元素具有有意义的名称,因此可以添加元素而不会影响当前元素的名称或含义。此外,在 RPC 样式的文档中,协议是和函数名密切相关的。如果函数名发生了变化,要么协议名必须更改(导致客户机问题),要么函数名和协议名必须保持不同(这导致了原先打算联合一致的函数/协议发生分裂)。对于文档样式,根本没有函数名。如何处理文档取决于接收文档的应用程序。

RPC 样式的另一个局限性是,RPC 样式的传输不能作为单向消息。在 RPC 模型中,请求始终都希望有一个响应,即便响应是空的。这在单向消息传递模型中是行不通的。最终目标不会做出响应,因为一旦发送完消息则断开连接,并且预期目标可能永远不需要知道是谁向它发送该消息的。

出于所有这些原因,RPC 样式的 SOAP 在支持只有文档样式的 Web 服务方面似乎是无力的。幸运的是,WSDK 可以对您隐藏围绕该转换所出现问题。WSDK 工具生成的存根/框架/助手代码可以透明地处理文档样式的 SOAP 和 RPC 样式的 SOAP。

我何时应当使用高速缓存?

服务器包含和生成用于其客户机的数据。某些数据是频繁请求的,或者需要花费相对较长的时间或许多资源来生成。某些服务器具备高速缓存这类数据的能力。高速缓存数据意味着可以快捷地使用数据,这样,生成或检索数据的进程出现的频率就会降低,因为不需要在每次请求时都生成数据。请考虑时钟。人们对时钟进行“高速缓存”。他们把时钟挂在墙上,把表带在手腕上。这是因为人们时时刻刻都希望知道现在是什么时间。如果没有时钟,那么将很难弄清楚大约一个小时内的具体时间。Web 服务器利用了高速缓存。流行的图片和 html 文件等以映射方式存储在内存中,这样当对它们进行请求时,则可以快速获取它们。

高速缓存还可以用于改进 SOAP 应用程序的性能。与其它 Web 任务(例如检索图像或 html 文件)相比,生成 SOAP 消息涉及了更多开销。服务器必须:

  1. 解析 SOAP 消息 ― 涉及了获取解析器,并且可能要在内存中创建 DOM 树并遍历它
  2. 执行满足客户机请求所必需的业务逻辑 ― 可能涉及与 EJB、CORBA 对象和数据库的通信
  3. 生成 SOAP 响应 ― 可能在内存中创建 DOM 树,并且将其转换成送回客户机的流。

高速缓存 SOAP 消息并不象把它放入 HashMap 那样简单。您需要确定什么使两个 SOAP 请求相同(创建某个 SOAP 消息类)。只有那样高速缓存的框架才能确定何时向客户机发送一个经过高速缓存的响应,而不是让请求进一步传播进系统。因此,高速缓存 SOAP 消息的难点在于弄清楚什么构成了两个 SOAP 消息请求之间的一致性。它是 SOAP 主体中 XML 文档的数据?它是 SOAP 主体和 SOAP 头值的某种组合?

<soap:Envelope xmlns:soap='http://schemas.xmlsoap.org/soap/envelope/'>
  <soap:Body>
    <p:SomeData xmlns:p='http://someData.org'>
	 <p:DataValueOne>
		A very important wad of data.
      </p:DataValueOne>
	 <p:DataValueTwo>
 	  An incidental wad of data.
      </p:DataValueTwo>
    </p:SomeData>
  </soap:Body>
</soap:Envelope>

在上述情形中,可能会认为具有相同 <DataValueOne> 的请求是相同的,因为出于某些原因,DataValueTwo 对于应用程序的处理而言是无关紧要的。必须单独仔细检查每个 Web 服务,以确定标识符集是什么。

图

图

一旦确定了标识符集,则有可能创建一个使用 HashMap 和 Thread(它定期清空高速缓存)的高速缓存框架。HashMap 的关键在于用于那类 SOAP 消息的所有标识该信息的组合。其值是以 Strings 形式表示的整个 SOAP 消息响应,这样就可以轻易地将这些值附加到一组响应头,并且发送回客户机。通过实现象这样的高速缓存,服务器就无须使用任何中间层组件,也无须生成 XML 流。

请参阅本文结尾列出的参考资料,以获取有关 SOAP 高速缓存的文章。

对于某些 Web 服务而言可以实现高速缓存,而对于另一些而言,不可能实现高速缓存,因为 SOAP 响应包含了基于系统状态、当前时间或者其它变量(它们都与 SOAP 请求数据无关)。请考虑以下二者之间的区别:

  • 返回该营业日产品报价信息的 Web 服务
  • 返回当前股票价格的 Web 服务。

产品报价信息可能会每天发生变化,但是不会随意变化。如果是这样,那么 SOAP 请求的标识信息可以是产品标识和请求日期的组合。每天都必须清空高速缓存,因为里面的所有数据都是旧的。

不能利用高速缓存实现当前股票价格应用呈现。因为股票价格不断在变化,因此没有请求标识符集可以用来预测响应中将发送什么数据。

SOAP 高速缓存可以显著地减少 Web 服务的开销。如果 Web 服务的请求可根据标识符分组成多个类别,并且如果 Web 服务的响应不包含根据系统状态或时间而任意变化的信息,那么就可以对 Web 服务进行高速缓存。

优化消息大小

对于任何分布式通信,都会由于使用网络而带来巨大的处理开销。与进程内交互不同,网络调用要求系统打开一个套接字(带有其所有的握手等);通过套接字序列化数据(这一过程涉及了构造分组、递归地通过对象树移动并格式化其状态);并等待也必须要进行处理的响应,以便想得到该数据的应用程序对该响应进行处理。减少网络交互的数量可以提高性能。

请考虑一个允许您根据标识来获取产品信息的 Web 服务。您的初始设计可能要求客户机在 SOAP 消息的主体中发送一个产品标识。

<soap:Envelope xmlns:soap='http://schemas.xmlsoap.org/soap/envelope/'>
	<soap:Body>
		<p:ProductInfo xmlns:p='http://productInfo.org'>
	 <p:Id>10</p:Id>
		</p:ProductInfo>
	</soap:Body>
</soap:Envelope>

如果通信是本地的,那么上面就是可能的实现方式。就功能而言,该解决方案是好的。它满足了允许客户机获取产品信息的要求。但是,在性能方面,它可能不是最佳的解决方案。如果客户机需要 100 个产品的产品信息,那么客户机将必须调用 100 次您的 Web 服务。每次都会产生同样的网络开销。如果可以为客户机提供一种一次传递 100 个标识,而不是一次传递一个标识的方法,那么将省去一些开销。稍做考虑,或许更应该编写这个一次传递多个标识的 Web 服务,这样它就可以接收 XML 文档中的标识列表,然后在一个大型的文档中返回每个标识对应的产品信息。

<soap:Envelope xmlns:soap='http://schemas.xmlsoap.org/soap/envelope/'>
  <soap:Body>
    <p:ProductInfo xmlns:p='http://productInfo.org'>
	 <p:IdList>
	 	<p:Id>10</p:Id>
	 	<p:Id>34</p:Id>
	 	...
	 </p:IdList>
    </p:ProductInfo>
  </soap:Body>
</soap:Envelope>

带有 100 个产品信息的响应始终要比 100 个单独的产品信息响应的总和要小。

通过简单地调整描述产品信息 Web 服务的 WSDL,并且利用 WSDL2WebService 重新生成服务器代码,可以轻易地用 WSDK 实现这一点。

WSDL 将以下内容:

... WSDL ...
<xsd:types>	
 <xsd:element name="ProductInfo">
  <xsd:complexType>
   <xsd:sequence>
    <xsd:element name="Id" />
   </xsd:sequence>
  </xsd:complextType>
 </xsd:element>
</xsd:types>
... WSDL ...

更改为以下内容:

... WSDL ...
<xsd:types>	
 <xsd:element name="ProductInfo">
  <xsd:complexType>
   <xsd:sequence>
    <xsd:element name="IdList" >
     <xsd:complexType>
      <xsd:sequence>
       <xsd:element name="Id" minOccurs="1" maxOccurs="unbounded"/>
      </xsd:sequence>
     </xsd:complextType>
    </xsd:element>
   </xsd:sequence>
  </xsd:complextType>
 </xsd:element>
</xsd:types>
... WSDL ...

第 7 章:通过 J2EE 访问 WS

简介

J2EE 使得访问 Web 服务非常容易。与获取对 EJB 组件的远程接口存根的引用类似,它是一个包含了三个步骤的过程。

EJB 组件Web 服务
查找 Home查找服务
调用 create...(...) 以获取对业务方法存根的引用调用 getPort(...) 以获取对业务方法存根的引用
调用业务方法调用业务方法

由于两种调用机制之间的这种相似性,因此您可以将用于获取对 EJB 组件引用的任何访问模式应用于您的 Web 服务。应用访问模式的预期效果是对客户机隐藏获取存根的需求。这样做的话,如果获取存根的过程发生了变化,那么客户机将不必更改。这样客户机也无须乏味地创建 InitialContext 等。

有许多模式可用来松散地将一个对象与它希望调用的 Web 服务耦合在一起。但是,在众多的模式中,EJBHomeCacher 模式不仅可以对对象隐藏如何获取存根的机制,它还可以高速缓存 Port 对象(象 EJBObject),这样在每次需要该对象时就无须从 JNDI 上下文中进行检索了。

PortCacher

PortCacher (请参阅简介第一页上的 EJBHomeCacher)是一个带有两个静态字段 InitialContextHashtable 的简单类。通过 InitialContext 的实例,您可以获取各种 javax.xml.rpc.Service 存根。通过 Hashtable 的实例可以存储 Port 存根以便于进行检索。PortCacher 包含两个方法:getCachedPort()fetchAndCachePort()

import java.util.Hashtable;
import javax.naming.InitialContext;
import javax.xml.rpc.Service;
import java.rmi.*;

public class portCacher {

 private static Hashtable portCacher = null;
 private static InitialContext ctx = null;
 private static final String ENC_PREF = "java:comp/env/service/";
 static {
  try {
   serviceCacher = new Hashtable();
   ctx = new InitialContext();
  } catch (Exception ex) { }
 }
 
 public static Remote getCachedPort(String serviceName, Class serviceEndpointInterface) {
  Remote port = null;
port = (Remote)portCacher.get(serviceName);
  if (port == null) {
   port = fetchAndCacheService(serviceName, serviceEndpointInterface);
  }
  return port;
 }

 public static Remote fetchAndCachePort(String serviceName, Class serviceEndpointInterface) {
  Remote port = null;
  Service serviceHome=null;
  try {
   serviceHome = (Service)ctx.lookup(ENC_PREF+serviceName);
  } catch (Exception ex) {
   serviceHome = null;
  }
  if (serviceHome != null) {
	port = serviceHome.getPort(serviceEndpointInterface);
   serviceCacher.put(serviceName, port);
  }
  return port;
 }
}

Servlet、JSP 文件或 EJB 组件只需要知道在 JNDI 环境命名上下文(JNDI Environment Naming Context)中所需 Port 绑定到的 String。客户机首先调用 getCachedPort()。PortCacher 无法知道服务存根是否已经无效。如果存根已经无效,那么则需要获取另一个存根。如果存根是无效的,则存根会抛出一个 Exception。客户机然后需要某种途径通知 PortCacher 它需要刷新高速缓存中的无效存根。这就是 fetchAndCachePort() 方法的用途。如果客户机发现存根是无效的,那么它会调用 fetchAndCachePort()。这将使 PortCacher 再次查找Service,并为调用者返回新的 Port 引用。

public class ServiceClient {
 public void doSomething() {
  try {
   SomeServicePort ss = (SomeServicePort)
           PortCacher.getCachedPort("SomeService");
   ss.businessMethod(...);
   ...
  } catch(Exception e) {
   //try again in case the stub was invalid...
   SomeServicePort ss = (SomeServicePort)
           PortCacher.fetchAndCachePort("SomeService");
   ss.businessMethod(...);
   ...      
  }
 }
}

第 8 章:结束语

回顾

在本教程中,您已经学习了下列知识:

  • WS-I 概要文件
  • WS-I 基本概要文件
  • WS-I 使用方案
  • IBM 的电子商务模式以及如何将其用于 Web 服务
  • 何时使用 UDDI
  • 何时使用 SOAP 高速缓存
  • 何时使用 RPC 样式和文档样式的 Web 服务
  • 如何使 SOAP 传输有效率
  • 通过 J2EE 访问 Web 服务的方法。

给定技术的最佳实践主题从来就不是能够轻松讨论的主题,特别是当该技术(如 Web 服务)相对较新时。通过标准化和文档化,WS-I 和 IBM 已经开始奠定 Web 服务最佳实践的基础。我们鼓励您定期访问 WS-I 和 IBM 的网站,以便于您随时了解这些组织在 Web 服务方面的进展(请参阅参考资料)。

反馈

欢迎您就本教程提供您的反馈。期盼收到您的意见!


相关主题


评论

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

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=SOA and web services
ArticleID=85048
ArticleTitle=最佳实践和 Web 服务概要文件
publish-date=09222003