这篇文章(作者是 IBM 的 Mike Edwards)讨论了在使用 SOA 构建应用时所需的异步服务。异步服务的构建很复杂,但是使用服务组件架构(SCA)则构建相对直接。本文描述了使用 SCA 创建异步服务和异步服务客户端所涉及的步骤。
对 SCA 一般性的介绍,参见 [ 1 ]。
业务过程和异步服务的需要
尽管我们都认为要是事情能马上搞定就好了,但是到目前为止,在现实生活中事情常常要花一段时间才会发生,涉及到一系列有顺序的步骤。
设想在线订购一些书籍。先是搜索你想要的书籍,接着你可能会去检查当前的数量,指定发货日期和可供选择的交付方式。一旦订单寄出并付了款,书不会立马拿到:
- 首先,书商会发送订单的确认,以及期望的发货日期。你也可能会得到一个某种类型的订单号,这样你就可以检查订单的状态。
- 在随后的某个时间,书商会给你发送一个你订单的发货确认,以及一个跟踪号,你可用它来检查运货公司的运货状态。
- 最后,如果幸运的话,你就拿到书了。
很多类型的业务过程有这些特性。当我们试图构建支持这些业务过程的应用时,典型的“调用 - 返回”风格的服务很明显不能很好地工作。
调用 - 返回,也称同步处理,即一个服务的客户端调用服务,然后停止工作,在客户端代码继续工作之前等待服务完成它的任务。如果客户端代码没有什么可做的,这就给运行客户端的系统增加了一个负担,因为它必须使代码保持在内存中,同时止步不前。如果系统中只有客户端代码运行,这不是个问题——但是当系统在一分钟内处理成千上万条单个客户端的请求时,麻烦很快就来了。
另一个情况是客户端代码能在等待被调用服务完成的同时能干些别的事情。安排假期的客户端代码可能需要调用服务来安排航班、酒店、出租车。比起一次调用一个服务然后在继续下一个之前等待每一个的完成来说,并行处理这些请求会快得多。
在这些情况下,它们被称为异步处理。你可以把这想象成客户端向一个服务发送一个请求消息,然后继续一些新工作。被调用服务开始它的工作,当工作完成了,它再给客户端发回一个响应消息。或者它可能会给客户端发回多个响应消息,正如你在订书过程中看到的,其中一个消息确认订单,另一个消息指明订单的派发。
构建异步服务的工具
假使存在有异步服务的需求,那么有什么工具帮助我们来构建这些服务呢——以及构建异步服务的客户端?
绝大多数编程模型和框架都很好地提供了编写同步服务的客户端的工具。对于编写同步服务也是如此。同步的“调用 - 返回”模型是用绝大多数编程语言编写代码的标准形式。结果是,编写同步服务或同步服务客户端通常不用对标准的编程模型进行多少扩展。例如,在 Java 中,这通常意味着一个服务被实现为一个类中的一个方法,而这个服务的客户端就是对这个类方法的调用。
编写异步服务和异步服务客户端的工具并没有得到很好的发展。Java 是众多编程语言的典型,异步编程在其中成为一等公民还是最近的事情——随着在 Java 5 中并发类的出现。
还有些异步处理工具可以通过一些协议相关的编程模型 [ 2 ] 来使用。举个例子,JAX-WS [ 5 ] 提供了编写同步服务的异步客户端的方法,使用 Web 服务作为客户端和服务间的通信方法。
这种情况中,假设同步服务调用的客户端代码如下: this:
OrderResponse placeOrder( OrderRequest oRequest );
例 1:简单的同步 placeOrder 操作
等价的异步服务调用如下:
Future placeOrderAsync( OrderRequest oRequest, <br></br> AsyncHandler responseHandler );
例 2:placeOrder 请求的异步形式
对调用的异步响应经由声明如下的异步响应处理程序发回给客户端:
class OrderResponseHandler implements AsyncHandler {<p> public void handleResponse( <span lang="EN-GB">Response</span> theResponse ){</p><p> OrderResponse orderResponse = theResponse.get();</p><br></br> }<p>}</p>
例 3:在 placeOrder 完成之后处理程序方法会被调用
JAX_WS 不支持编写异步服务。它也只支持一个特定请求刚好一个响应的情况。
一个支持异步风格客户端和异步服务的 Java 框架是 JMS API [ 6 ]。当客户端和服务之间使用支持 JMS 的消息系统进行通信时就可以使用 JMS API。在 Java EE 框架中,可以通过消息驱动 Bean[ 7 ] 使用 JMS。尽管 JMS 方法支持一些异步服务,但是 JMS 将客户端和服务代码束缚到了某些消息基础设施的使用之上。
最后,支持所能想到的异步业务过程的一个整体语言都已以被开发出来了。这就是业务过程执行语言(简称 BPEL)[ 8 ]。BPEL 特别擅长协调一组服务,包括同步的和异步的,但是它可能不擅长编写涉及大量数据操纵的服务或客户端。
SCA 和异步服务
SCA 对创建异步服务和异步服务客户端提供了直接支持。SCA 支持具有简单的交互模式(每个请求消息一个响应消息)的异步服务,也支持具有更复杂的交互模式(每个请求 0 个、1 个或多个响应消息)的异步服务——记住,我们的“简单”订书过程为每条订书请求产生两条响应消息。
SCA 支持使用一个范围广泛的编程语言中的任何一种编写的异步服务,例如包括 Java 和 BPEL。此外,SCA 还能够选择客户端和提供者间的通信方法——它可能使用消息基础设施(如 JMS 所使用的),但它也可能选择使用 Web 服务基础设施。SCA 不会把客户端或服务提供者代码束缚到任何特殊的通信基础设施上。
SCA 使用以下概念给服务客户端和服务提供者之间的通信建模:
- 服务,包含在一个接口内的一个或多个操作的集合,服务提供者实现这个接口。
- 引用,客户端使用它调用服务提供者的操作。引用一般是一个代理对象,它实现了服务提供者实现的相同接口。
在同步服务情况中,接口的每个操作有一个“调用 - 返回”风格,其中消息作为操作的参数被发送给服务提供者,响应则是操作的返回值:
OrderResponse placeOrder( OrderRequest oRequest );
例 4:简单的同步 placeOrder 操作
对于异步服务,SCA 有一个回调(callback)概念。一个回调暗示了通信是双向且异步的。客户端通过服务提供者上的操作调用提供者——提供者相关的响应使用一个单独的服务回调接口回调客户端。尽管服务提供者实现了服务接口,但是客户端必须实现回调接口。
在例 4 中显示的同步操作的等价异步回调包含如下一个请求操作:
void placeOrder( OrderRequest oRequest );
例 5:异步 placeOrder 服务请求
……结合如下的回调接口中的一个操作:
void placeOrderResponse( OrderReponse oResponse );
例 6:placeOrder 响应的回调操作
SCA 方式的异步服务的一个重要方面就是:在初始客户端调用服务提供者之后,服务提供者可以通过回调接口在任意时间回调客户端。服务提供者可以使用定义在回调接口中的任何操作,在对来自客户端单个请求的响应中,提供者可能调用回调接口中一个或多个或零个的操作。最简单的情形是让提供者对于每个由客户端所调用的服务操作只调用回调接口中的一个操作。
一个简单的例子
让我们看看被一个客户端使用的一个异步服务的简单例子——在这个例子中,客户端和服务都使用普通的 Java 类编写。这是一个对某些物品下订单的服务的简单版本,其中服务是异步的,通过一个回调接口通知客户端进度信息。
首先,服务和回调接口如下:
public interface OrderService {<br></br> public void placeOrder( OrderRequest oRequest ); <br></br>}
例 7:OrderService 接口
public interface OrderCallback {<br></br> public void placeOrderResponse( OrderResponse oResponse );<br></br>}
例 8:OrderCallback 接口
接下来是 OrderService(即服务提供者)的实现代码:
import org.osoa.sca.annotations.*; <p>public class OrderServiceImpl implements OrderService { </p><p> // A field for the callback reference object </p><br></br> private OrderCallback callbackReference;<p> // The place order operation itself </p><br></br> public void placeOrder( OrderRequest oRequest ) {<br></br> // …do the work to process the order…<br></br> // …which may take some time…<p> // when ready to respond…</p><p> OrderResponse theResponse = new OrderResponse();</p><p> callbackReference.placeOrderResponse( theResponse );</p><br></br> }<p> // A setter method for the callback reference</p><p> @Callback</p><br></br> public void setCallbackReference( OrderCallback theCallback ) {<br></br> callbackReference = theCallback;<br></br> }<br></br>}
例 9:OrderService 实现
最后是 OrderService 客户端的代码,它提供了 OrderCallback 接口的实现:
import org.osoa.sca.annotations.*;<p>public class OrderServiceClient implements OrderCallback {</p><p> // A field to hold the reference to the order service</p><p> private OrderService orderService;</p><p> public void doSomeOrdering() {</p><p> OrderRequest oRequest = new OrderRequest(); </p><p> //… fill in the details of the order …</p><p> orderService.placeOrder( oRequest ); </p><p> // …the client code can continue to do processing</p><br></br> }<p> public void placeOrderResponse( OrderResponse oResponse ) {</p><p> // …handle the response as needed </p><br></br> } <p> // A setter method for the order service reference </p><p> @Reference </p><br></br> public void setOrderService( OrderService theService ) { <br></br> orderService = theService; <br></br> }<br></br>}
例 10:OrderService 的客户端
服务实现
首先,让我们检查例 9 中显示的异步服务代码。
那儿有一个叫 setCallBackReference 的 setter 方法,它使用 @Callback 注解。@Callback 注解指示 SCA 运行时注入一个表示客户端回调接口的引用对象——这在服务收到 placeOrder 操作请求的时候完成,这样服务实现手头就有了一种回调客户端的方法
服务实现自己无需关心客户端的身份或位置——这由 SCA 运行时挑选,所有这些信息包含在所提供的 callbackReference 对象内。
服务操作 placeOrder 只需接收 OrderRequest 消息,然后开始处理订单的工作。这可能花费任何所需要的时间去完成——无论何时 placeOrder 操作准备对客户端进行响应,可能是几秒之内或几天之内,然后它只需使用 callbackReference 即可,其提供了回调客户端的 placeOrderResponse 操作。
如果需要,OrderService 实现可以保存一份 callbackReference 拷贝,然后在需要的时候再把它取回。这可能是正确的方法,例如,如果一个人工任务(如打包订单)是过程的一部分,那么在这儿 OrderService 需要等待人来提供一些输入以使其能继续工作。
服务客户端
OrderService 客户端的代码显示在例 10 中。
这有两个主要部分——doSomeOrdering 方法调用 placeOrder 服务,placeOrderResponse 处理来自 placeOrder 请求的回调。这儿同样有一个 orderService 引用的 setter 方法。注意,这个方法使用 @Reference 注解标记,它指示 SCA 运行时在客户端开始工作之前提供一个 OrderService 的引用对象。这个引用对象提供 OrderService 的业务接口,所有关于服务位置和调用使用的通信方法信息都包含在这个引用对象中。
客户端在 doSomeOrdering 方法中准备它的 OrderRequest。当它准备好了,它通过调用 orderService 引用对象上的方法调用 OrderService 的 placeOrder 操作。这就把请求发送了给 OrderService。客户端代码立即从 placeOrder 服务返回,不等待来自 OrderService 的任何响应。客户端代码可以继续前进处理其他事情,或者它可以简单的返回,完成它的初始工作。
当 OrderService 给初始 placeOrder 请求发送它的响应时,OrderResponse 消息被路由到客户端的 placeOrderResponse 方法。
placeOrderResponse 方法接下来就可做处理响应所需的任何工作——如,它可能把它记录到一个数据库中。
placeOrderResponse 方法可能在初始 placeOrder 请求发送后的很长时间才被调用。有些情况下,OrderReponse 消息包含可以用来找回初始 OrderRequest 消息的信息(如订单号),在这种情形下,响应可能包含客户端用来将响应和初始请求关联起来全部必须的信息。
但是,有些情况下 OrderResponse 消息不能如此简单地关联到初始请求。这时,客户端代码能给初始请求贴上一个 callbackID,它将随响应消息一起返回。callbackID 是一个简单的 serializable 对象,如字符串,用于唯一的标记初始请求。这样,这个 ID 就可用来使客户找回那些标识初始请求的状态数据。
客户端和服务间的连接
你可能要问的一个问题是——“客户端和服务间的连接是如何定义的呢?”客户端代码和服务代码看起来就像使用了某些魔法,通过来自 SCA 运行时注入得到了服务和传入的回调的引用。
SCA 定义了一个装配模型 [ 3 ],它定义了那些被配置和彼此连接构成一个解决方案的组件的分组方式。定义服务位置、客户端位置和它们如何连接一起的所有所需信息来自组合(composite),它把它们装配成为一个解决方案。SCA 运行时从组合(composite)中读出信息,使用它来给客户端和服务待提供在运行时所使用的引用和回调代理。
图 1:简单客户端和服务的 SCA 装配图
SCA 可以使用图来表示组合(composite)——一图胜千言!图 1 显示的图可以是一个用来描述 OrderClient 和 OrderService 装配的图。在 OrderClient 侧,它有一个名为“OrderService”的引用。在 OrderService 侧有一个名为“OrderService”的服务。引用通过连线(wire)连接到服务,意即客户端引用被连接到了服务上。事实上,涉及回调的连接通过描述服务的接口来定义——这个接口是“双重接口”,同时具有 OrderService 接口和 OrderCallback 接口。
内部实现中,SCA 将组合(composite)表示成 XML 元数据,它也可以包含其他信息,如链接引用和服务所使用的通信方法。与上图相对应的 XML 如下:
xml version=“1.0” encoding=“UTF-8”?>
<composite name=“orderComposite”
xmlns:sca=“ http://www.osoa.org/xmlns/sca/1.0 ”>
<implementation.java class=“OrderServiceClient”/>
<reference name=“OrderService”
target=“OrderService/OrderService”>
<binding.ws/>
reference>
component>
<implementation.java class=“OrderServiceImpl”/>
<interface.java interface=“OrderService”
callbackinterface=“OrderCallback”/>
<binding.ws/>
service>
component>
composite>
例 11:链接 OrderClient 和 OrderService 的组合
在这个例子中,通过
总结
异步服务,其中的响应在初始请求发送很长时间后才返回,仅仅是生活的现实——不是所有事情都是立即发生的!
使用服务组件架构,使得提供异步服务实现和异步服务客户端的工作更简单。SCA 为异步服务提供了一个后跟回调响应的请求的模型,结合使用 callbackID 将回调响应关联到初始请求上。
SCA 支持客户端和服务间不同的底层通信方法,这使代码无需依赖复杂的中间 API。
如果你自己想在现实项目中尝试 SCA,并亲眼看看它是如何简化面向服务应用编程的,你可以在以下链接的后续页面找到一些公司的商业产品:
<a href="http://www.osoa.org/display/Main/Implementation+Examples+and+Tools">http://www.osoa.org/display/Main/Implementation+Examples+and+Tools</a>
你还可以使用 SCA 的开源实现:
Apache Tuscany: <a href="http://cwiki.apache.org/TUSCANY/">http://cwiki.apache.org/TUSCANY/</a><br></br>Fabric3: <a href="http://fabric3.codehaus.org/">http://fabric3.codehaus.org/</a>
## References
[1] “Setting out for SCA” http://www.infoq.com/articles/setting-out-for-sca
[2] “Develop asynchronous Web services with Axis2” http://www.ibm.com/developerworks/webservices/library/ws-axis2
[3] SCA Assembly Model Specification http://www.osoa.org/download/attachments/35/SCA_AssemblyModel_V100.pdf
[4] SCA Java Common Annotations and APIs Specification http://www.osoa.org/download/attachments/35/SCA_JavaAnnotationsAndAPIs_V100.pdf
[5] JAX-WS Specification http://jcp.org/aboutJava/communityprocess/mrel/jsr224/index2.html
[6] JMS Specification http://java.sun.com/products/jms/docs.html
[7] Message Driven Beans in EJB 3.0 Specification http://jcp.org/aboutJava/communityprocess/final/jsr244/index.html
[8] Business Process Execution Language (BPEL) http://docs.oasis-open.org/wsbpel/2.0/wsbpel-v2.0.pdf ## 作者简介
Mike Edwards 是位于英格兰的 Winchester 附近的 IBM Hursley 实验室的新兴技术战略师。他目前正从事 SOA 方面的工作,并参与了服务组件架构规范的工作。他还是 OASIS SCA Assembly 技术委员会的联合主席,以及 Apache Tuscany 项目的提交者。
查看英文原文: http://www.infoq.com/articles/async-sca ““Can I call you back about that?” Building Asynchronous Services using Service Component Architecture”">“Can I call you back about that?” Building Asynchronous Services using Service Component Architecture
评论