随笔 - 19, 文章 - 93, 评论 - 17, 引用 - 0
数据加载中……

在面向服务的体系结构中管理状态

      一个有关 Web 服务的最常见误解是,它们只适合于支持基于同步请求/响应 SOAP(简单对象访问协议)的交互。 造成这种误解的主要原因之一是,许多 Web 服务是使用不可靠的无状态传输协议(如 HTTP)实现的。

  因此,许多组织正避免用 Web 服务来处理复杂的业务交互(在复杂业务交互中,服务使用者可能同时与同一服务进行多个交互,或同时与多个服务进行多个交互)。 OASIS(结构化信息标准促进组织)将此类交互称作 Web 服务会话,并将支持会话的 Web 服务称作会话式 Web 服务。 会话式 Web 服务是异步 Web 服务的基础,在实现持续长时间业务事务方面起着至关重要的作用。

  本文将介绍如何使用显式状态标识符支持会话式 Web 服务。 此外,还将概述如何使用 Oracle JDeveloper 10g 10.1.3 J2EE 开发人员预览版(为 J2EE 1.4 JAX-RPC 提供了内置的支持)实现该种方法。

  会话式 Web 服务简介

  服务使用者与服务提供者之间的大多数交互是 Web 服务会话,例如,某个服务端点可能同时从多个客户端收到多种类型的文档,处理这些文档并使用相应的响应联系使用者。 在这样的应用程序中,特别要求正确识别每个客户端的交互,这使得 Web 服务实现更加复杂。 为了避免这种复杂性,许多公司将 Web 服务实现局限于简单的单一请求/响应类型的交互(AuthorizeUser、CalculateTax、ConvertFunds 等)。

  但就其理想情形而言,Web 服务应支持服务使用者和支持提供者之间的单一交互和复杂交互。 无论传输协议如何,同步或异步 Web 服务都应能够代表单个客户端或在跨多个交互的特殊业务环境中保存状态或资源。

  长期以来,解决 Web 服务中的状态性问题的需要一直是标准组织和主要供应商的焦点。 为解决关键任务和冗长业务流程中的状态性问题,涌现了多个 Web 服务规范草案,如 WS-Addressing、WS-Resources 和 WS-Coordination。 最重要的是,业务流程执行语言 (BPEL) 1.0 规范已经整合了 WS-* 标准建议的许多特性来处理持续时间较长的业务事务。 但也应看到新标准和解决 Web 服务会话问题的相关供应商技术的广泛采用。

  总之,实现会话式 Web 服务仍是一个相当大的设计挑战。 当面临依赖 Web 服务的复杂应用程序时,应用程序开发人员别无选择,只能构建定制的解决方案。

  会话式 Web 服务的工作方式

  会话式 Web 服务(本质上是异步的)有两个重要的服务元素类型: 可调用方法和响应方法(通常称作回调)。 会话式 Web 服务中的每个可调用方法和回调方法确定该服务、其客户端以及该服务所使用的任何执行控件(例如,Java 控件)之间的通信行为。 可调用方法可以启动、继续或结束会话;而回调可以继续或停止会话。

  为更好地描述会话式 Web 服务,我将介绍一个场景,在本文通篇将以该场景为基础。 该示例采用两个会话: 一个是 Web 服务与它的客户端之间的会话,另一个是两个 Web 服务之间的会话(参见图 1)。

  在本示例中,BestTermInsurance Web 服务通过使用另一个服务 (InsuranceQuote) 代表客户查找各种保险提供商 。 当客户端请求 BestTermInsurance 服务提供报价时,将启动第一个会话;当 BestTermInsurance 服务调用 InsuranceQuote 服务请求报价时,将启动第二个会话。 当 InsuranceQuote 服务找到最低报价时,它将响应 BestTermInsurance 服务,第二个会话随即完成。 然后,BestTermInsurance 服务恢复与客户端的第一个会话。



图 1: BestTermInsurance 服务

  Web 服务会话保存 Web 服务的状态;后者包括用于链接 Web 服务、它的客户端和其他资源之间的通信的相关数据以及在会话完成之前由该服务保存的任何数据。 该状态信息称作会话状态(也称作关联状态)。 在本示例中,BestTermInsurance 服务保存客户端的每个报价以及关联的消息属性,直到找到最低报价。

管理会话状态

  使用显式状态标识符是在 Web 服务交互中管理会话状态的最简单和最相当有效的方法。 尽管名称暗示了单个 ID,但完全受管理的会话需要更多的信息,尤其是当与服务请求关联时。 因此,状态标识符实际上是一个控制类,它保存以下一组针对每个请求-响应交互的与状态相关的信息:

  •   Web 服务客户端的唯一标识符
  •   为该客户端启动的 Web 服务会话的标识符
  •   该会话中服务请求的标识符
  •   服务请求者(客户端)与服务提供者之间的会话阶段指示器,服务使用该指示器进行正确的状态保持
  •   Web 服务基础架构的分发函数与调用接口之间的交互状态指示器(在使用 Web 服务接口的晚期绑定进行动态调用方面很有帮助)

  出于本文的需要,我采用静态 Web 服务调用,因此将不介绍第 5 种信息。

  一般说来,用于关联请求的技术解决方案是让服务请求者将状态标识符信息(服务请求的唯一标识符(请求 ID)、会话标识符(会话 ID)和会话阶段指示器(阶段)添加到请求消息中,并让服务提供者将标识符信息复制到通过回调传递的响应消息中,这样请求者可以将回复消息与请求消息相关联。

  有多种方法可以将状态标识符信息包含在消息交换中,具体包括:

  •   将标识符信息以额外的参数形式传入 Web 服务方法;可以将 Web 服务接口设计为不但接受 XML 文档消息,而且接受其他代表状态标识符信息的参数。
  •   将标识符信息作为 XML 文档的一部分传递 — XML 文档可以包嵌在其主体中的此种信息。
  •   在 SOAP 消息标头中传递标识符信息。

  但无论是通过添加额外的输入参数将标识符信息包含在服务接口中的第一种方法,还是将此类信息包含在 XML 文档本身中的第二种方法,事实上都会使标识符信息处理成为服务结构一部分,从而使代码更难以维护。 此外,将来随着新 Web 服务标准被更广泛地接受,可能不再需要自定义解决方案了,从而将难以从服务实现中“拆除”嵌入的、与标识符相关的逻辑。

  因此,第三种方法,即将标识符信息包含在 SOAP 消息标头中是最合理的方法。 使用此方法时,服务请求者将标识符信息作为一组新的子元素添加到消息的 SOAP 标头中。 服务提供者拦截该 SOAP 消息,然后提取标头中相应的状态标识符信息且不会破坏消息主体。

  简单地说,将标识符信息添加到 SOAP 标头中将使相关的代码与文档处理逻辑和关联的业务逻辑以及服务接口的具体实现相分离。

  实现显式的状态标识符 - 示例

  现在,我们将继续介绍示例场景 - BestTermInsurance 服务。 (请参见图 2 以获得该场景的说明。) 正如边条中描述的,Oracle 应用服务器提供了两个服务实现选项: 无状态 Java 类(可以部署到 Web 容器中)或无状态会话 EJB。 (该设计基于将 EJB 与一个服务 fa ade — BestTermInsuranceServlet 一起使用)。 建议您使用 fa?ade 将服务实现为 EJB,这是因为这样做不但具有更好的服务可用性和可伸缩性,还可以引入 SOAP JAX-RPC 处理程序(稍后将对其进行详细介绍)。


图 2: 示例场景

  下面我们将逐步介绍此示例场景并了解该控件。 当客户端通过调用 servlet 的 requestQuote 方法(传递有关客户年龄和健康状况的信息)提交搜索请求时,Web 服务交互将启动。 requestQuote() 方法包含一个 void 返回值,并立即返回,从而使客户端可以继续操作,而不必等待稍后将通过 onBestQuote() 回调发送的实际结果。 显而易见,应将客户端设计为从回调方法中接受消息。

      接下来,BestTermInsuranceServlet 调用 BestTermInsuranceEJB(用于保存客户年龄和健康状况指示器)和一个所获得的报价列表(与客户端 ID 和请求 ID 关联,作为与状态相关的数据)。 BestTermInsuranceEJB 随后调用 InsuranceQuote 服务的 obtainQuote() 方法以从参与的保险人那里获得报价。 (参见图 3,一个演示最重要的服务交互的序列图。)


图 3: 使用 JDeveloper Modeler 的示例 UML 序列图

  InsuranceQuote 服务在特定客户端作用域内运行,因此该服务需要访问客户端 ID。在会话跟踪方面,InsuranceQuote 服务使用它自己的会话和请求 ID。

  在根据给定的客户年龄和健康状况指示器收集所有保险报价时,InsuranceQuote 服务使用 onObtainQuoteComplete() 回调返回其结果。 此回调完成第二个转换,并清除由 InsuranceQuote 服务保存的所有状态数据。 但由 BestInsuranceQuoteServlet 控制的第一个会话仍继续执行: BestTermInsurance 服务可能从客户端获取其他请求,例如,根据特殊的保单条款(一年、十年等)提供最佳报价。 结果以消息形式通过 onBestQuoteCallback() 方法发送给客户端。

  编码技巧

  现在,我们来着重介绍以下实现状态标识符的具体编码技巧。 遗憾地是,由于篇幅有限,将只在代码中介绍与本文主题相关的元素。

  正如前面所介绍的,假设状态标识符信息已传入 SOAP 消息标头中。 用于基于 XML 的远程过程调用的 Java API (JAX-RPC) 非常适于处理 SOAP 消息标头 - 具体而言就是通过使用 JAX-RPC 的某个最有意义的特性: 消息处理程序。 消息处理程序为 Web 服务提供了附加的消息处理逻辑,以作为对这些服务的业务逻辑实现的扩展。 除了管理身份验证、加密和解密、日志记录和审计等外,处理程序还支持状态标识符的插入。 为此,可以创建多个处理程序来管理每个具体问题。 在 JAX-RPC 中,处理程序的这种用法称作处理程序链。

  处理程序链表示一组有序的处理程序。 使用有序组可以使应用程序开发人员能够定义处理程序调用策略 - 调用顺序、目标(“仅处理请求”或“仅处理响应”或两者都处理)等。 处理程序链继续处理处理程序,直到引发 SOAP 错误或调用策略指示显式停止点。

  因此,要使用 SOAP 标头内嵌的状态标识符实现 Web 服务,建议用三个步骤来进行开发:

  •   设计基本的服务组件并进行编码。
  •   开发 SOAP 消息处理程序。
  •   用已开发的 J2EE 组件构造 Web 服务(公开)。

  使用 Oracle JDeveloper 10g J2EE 开发人员预览版时,可以使用应用程序导航器下的类别“Business Tier”将已编译的 Java 类或 bean 公开为 Web 服务。 从该类别中,开发人员可以选择一个上下文菜单项“Java Web services”。 此菜单项包含两个选项“J2EE 1.4 (JAX-RPC) Web services”和“Oracle J2EE 1.3 Web services”。 选择“J2EE 1.4 (JAX-RPC) Web services”。
 以下代码清单显示了一个符合要求的 SOAP 消息格式,其中的标头包含 BestTermInsurance 服务的外部(客户端-服务)会话的状态标识符:

<SOAP-ENV:Envelope>
  <SOAP-ENV:Header>
          <ns:stateIdentifier xmlns:ns="http://xxxxx">
      <ns:ClientID>Client ID</ns:ClientID>
      <ns:ConversationID>Conversation ID</ns:ConversationID>
      <ns:RequestID>Request ID</ns:Request ID>
      <ns:Phase>Phase</ns:Phase>
</stateIdentifier
</SOAP-ENV:Header>
<SOAP-ENV:Body>
     <ns:insuranceParms xmlns:ns="http://yyyyyyy">
     <ns:Age>Age</ns:Age>
     <ns:Health>Health</ns:Health>
     <ns:Term>Term</ns:Term>
     </ns:insuranceParms
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>

但在此进程的开头,该 SOAP 消息定义不包含状态标识符信息,如下所示:
<SOAP-ENV:Envelope>
  <SOAP-ENV:Header>
</SOAP-ENV:Header>
<SOAP-ENV:Body>
     <ns:insuranceParms xmlns:ns="http://yyyyyyy">
     <ns:Age>Age</ns:Age>
     <ns:Health>Health</ns:Health>
     <ns:Term>Term</ns:Term>
     </ns:insuranceParms
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
这正是 SOAP 处理程序的用途所在: 对将状态限定符插入到标准 SOAP 消息定义进行处理。 在 JAX-RPC 服务中,处理程序由实现 handleRequest() 和 handleResponse() 方法(用于修改请求和响应消息)的处理程序类表示。 可以将处理程序类配置为在客户端或服务级别处理请求和响应。 处理程序使用 javax.xml.soap.SOAPMessage 类来处理 SOAP 消息。 SOAPMessage 对象包含一个 SOAPPart 对象,该对象包含实际的 SOAP XML 文档和一个被分解为访问 SOAP 主体和标头的 SOAPEnvelope 对象。

  以下代码清单演示了所描述的方法:

package exp.oracle.jaxrrpc.headers;

.............

import javax.xml.rpc.*;
import javax.xml.soap.*;

public class BestTermInsuranceRequestHandler implements javax.xml.rpc.handler.Handler
{

...........................

 /*
 *The handleRequest method processes the request message.
  */
try {
SOAPMessageContext msg = (SOAPMessageContext) context;
SOAPEnvelope env = msg.getMessage().getSOAPPart().getEnvelope();
SOAPHeader hdr = env.getHeader();
exit=processRequestHeader(hdr);

       }
catch (Exception ex) {
ex.printStackTrace();
 }

 return exit;
 }


 /*
 * This version of the process header method inserts the state identifier information.
 */
  private boolean processRequestHeader (SOAPHeader hdr) {
  boolean exit = false;
 
  SOAPFactory sFactory = SOAPFactory.newInstance(); 
try {
SOAPElement he1 = sFactory.createElement("stateIdentifier",PREFIX,URI);
SOAPElement che11 = sFactory.createElement("ClientID",PREFIX,URI);
            che11.addTextNode("Cust1");
            ............................... 
            he1.addChildElement(che11);
            he1.addChildElement(che12);
            he1.addChildElement(che13);
           
            // add the state identifier information to the SOAP header object
            hdr.addChildElement(he1);
            exit = true;      

       }
 catch (Exception ex) {
     ex.printStackTrace();
 }

 return exit;
    }
开发 SOAP 处理程序后,可以使用 WSA 在相应的 webservices.xml 文件中配置它们。 然后,可以继续创建部署描述文件并将 Web 服务安装到 OC4J 中。

  结论

  显而易见,将单个请求-响应交互与无状态的 Web 服务结合使用是最简单的实现方法。 但伴随简单性而来的是一个很大的缺点: 无法实现用于处理复杂和长期运行的业务事务的 Web 服务。

  本文介绍的状态管理方法所基于的策略以更出色的方法对长期运行的业务活动进行建模。 具体而言,在本方法中,服务“注册”到良好控制的会话(将特定的工作单元表示为业务活动进度)。 此功能对于连接企业内部以及跨企业的、支持 Web 服务的应用程序很重要,并包含一组支持对连接的应用程序之间的交互进行管理和监控的特性。


http://www.oracle.com/technology/global/cn/pub/articles/davydov_soa.html

posted on 2006-12-02 13:09 BPM 阅读(246) 评论(0)  编辑  收藏 所属分类: web services


只有注册用户登录后才能发表评论。


网站导航: