BT

如何利用碎片时间提升技术认知与能力? 点击获取答案

“我能以后再调用你吗?”使用SCA开发异步服务

| 作者 Mark Edwards 关注 0 他的粉丝 ,译者 胡键 关注 0 他的粉丝 发布于 2008年3月11日. 估计阅读时间: 28 分钟 | GMTC大前端的下一站,PWA、Web框架、Node等最新最热的大前端话题邀你一起共同探讨。
这篇文章(作者是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,                            
AsyncHandler responseHandler );

例 2:placeOrder请求的异步形式

对调用的异步响应经由声明如下的异步响应处理程序发回给客户端:

class OrderResponseHandler implements AsyncHandler {

public void handleResponse( Response theResponse ){

OrderResponse orderResponse = theResponse.get();
}

}

例 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 {
public void placeOrder( OrderRequest oRequest );
}

例 7:OrderService接口

public interface OrderCallback {
public void placeOrderResponse( OrderResponse oResponse );
}

例 8:OrderCallback接口

接下来是OrderService(即服务提供者)的实现代码:

import org.osoa.sca.annotations.*;  

public class OrderServiceImpl implements OrderService {

// A field for the callback reference object
private OrderCallback callbackReference;

// The place order operation itself
public void placeOrder( OrderRequest oRequest ) {
// …do the work to process the order…
  // …which may take some time…

  // when ready to respond…

  OrderResponse theResponse = new OrderResponse();

 callbackReference.placeOrderResponse( theResponse );
}

// A setter method for the callback reference

@Callback
public void setCallbackReference( OrderCallback theCallback ) {
callbackReference = theCallback;
  }
}

例 9:OrderService实现

最后是OrderService客户端的代码,它提供了OrderCallback接口的实现:

import org.osoa.sca.annotations.*;

public class OrderServiceClient implements OrderCallback {

// A field to hold the reference to the order service

 private OrderService orderService;

 public void doSomeOrdering() {

 OrderRequest oRequest = new OrderRequest();

//… fill in the details of the order …

 orderService.placeOrder( oRequest );

// …the client code can continue to do processing
 }

 public void placeOrderResponse( OrderResponse oResponse ) {

 // …handle the response as needed
}

// A setter method for the order service reference

@Reference
public void setOrderService( OrderService theService ) {
orderService = theService;
}
}

例 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)中读出信息,使用它来给客户端和服务待提供在运行时所使用的引用和回调代理。

SCA Assembly diagram for simple Client and Service

图 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">

 

  <component name="OrderClient">

      <implementation.java class="OrderServiceClient"/>

      <reference name="OrderService"

                 target="OrderService/OrderService">

            <binding.ws/>

      reference>

  component>

 

  <component name="OrderService">

      <implementation.java class="OrderServiceImpl"/>

      <service name="OrderService">

            <interface.java interface="OrderService"

                  callbackinterface="OrderCallback"/>

            <binding.ws/>

      service>

  component> 

composite>

 

例 11:链接OrderClient和OrderService的组合

在这个例子中,通过<reference>元素的@target属性,OrderClient将其OrderService引用连线到了OrderService组件的OrderService服务。在OrderClient的<reference>和OrderService服务中都出现的<BINDING.WS>元素,指明Web服务是它们之间通信的方法。

总结

异步服务,其中的响应在初始请求发送很长时间后才返回,仅仅是生活的现实——不是所有事情都是立即发生的!

使用服务组件架构,使得提供异步服务实现和异步服务客户端的工作更简单。SCA为异步服务提供了一个后跟回调响应的请求的模型,结合使用callbackID将回调响应关联到初始请求上。

SCA支持客户端和服务间不同的底层通信方法,这使代码无需依赖复杂的中间API。

如果你自己想在现实项目中尝试SCA,并亲眼看看它是如何简化面向服务应用编程的,你可以在以下链接的后续页面找到一些公司的商业产品:

http://www.osoa.org/display/Main/Implementation+Examples+and+Tools 

你还可以使用SCA的开源实现:

Apache Tuscany: http://cwiki.apache.org/TUSCANY/
Fabric3: http://fabric3.codehaus.org/

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项目的提交者。

查看英文原文:"Can I call you back about that?" Building Asynchronous Services using Service Component Architecture

评价本文

专业度
风格

您好,朋友!

您需要 注册一个InfoQ账号 或者 才能进行评论。在您完成注册后还需要进行一些设置。

获得来自InfoQ的更多体验。

告诉我们您的想法

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我
社区评论

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

讨论

登陆InfoQ,与你最关心的话题互动。


找回密码....

Follow

关注你最喜爱的话题和作者

快速浏览网站内你所感兴趣话题的精选内容。

Like

内容自由定制

选择想要阅读的主题和喜爱的作者定制自己的新闻源。

Notifications

获取更新

设置通知机制以获取内容更新对您而言是否重要

BT