使用SOA构建业务解决方案主要的优势之一就在于其能按照业务需求的变化和革新快速组装新的解决方案。解决方案组装的关键是包含现有的应用和功能的能力,而不是什么都从头开始。的确,只有尽可能地复用现有的功能,才能完成快速开发的目标。
SCA一种是使用SOA的业务解决方案的编程模型。SCA提供了这么一个特性,使得将已存在的功能组装成新的解决方案尽可能的简单。该文档检验了这些特性中的一些。
本文档举例说明通过对用EJB技术实现的业务应用的复用从而组合出新的解决方案。
集成已存在应用的方式
构建自依靠松耦合服务接口连接的一系列分隔的组件的业务解决方案术语称为“composite application”(译为:构件应用),因为他们包含了将这些片段组合形成解决方案的能力。
SCA提供了各种将已存在应用连接成为一个新的构件应用的方法。每种方法都提供了特定的功能集合,当然不可能一种方法会适合所有的应用环境。部分地,要使用的方法依赖于已存在应用的特征,也依赖于为应用所使用的运行时基础设施的功能。
来自已存在应用的必须保证的东西就是可调用接口的某种形式。通过该接口形式,可以访问应用的功能。它可以有多种形式—也许是通过如Web service或CORBA IIOP的标准协议访问的开放接口,也可以是私有访问方式。SCA被设计来处理广泛的协议,并能适应多种接口方式。
如果已存在应用确实有一个可调用接口,那么有三种主要方式来将应用功能连接成构件服务的解决方案。
l 通过绑定,在绑定下,应用被看作是存在于构件应用外部的”external service”(外部服务)。
l 通过将已存在应用看成是单一的提供了一个或多个服务的服务组件,并对其进行建模。
l 通过建模应用,把应用看成是服务组件集,而每个组件都提供了一个或多个指定的服务。
下节依次考察这每一个选项,描述他们的特征、优点和缺点。
通过绑定集成
也许构建一个使用了已存在应用提供功能的新的构件应用最简单的方式就是将已存在应用看作是构件中的组件,好象它是某种外部提供的服务。这种方式,构件自身并不建模已存在的应用—换言之,在代表已存在应用的构件中不存在组件或组件集。构件中已存在应用的功能使用用reference(引用)来描述。通过使用协议和由应用所使用的endpoint,引用被配置为指向应用的可调用接口中的某一个。来自构件中的不同地方的已存在应用可以做成多引用。然而,因为感知不同的引用实际上是指向同一个目标的唯一方法就是仔细地比较不同引用的配置信息,所以这种情形的使用并不明显。
图1:通过绑定集成已存在的应用
图1 展示了订单处理的构件应用。左下方是描述应用的SCA构件—OrderProcessingComposite。应用提供了Order Processing service(订单处理服务),该服务由OrderProcessing组件实现。OrderProcessing组件依次引用了如下服务:
l 处理客户信息的Customer service
l 处理库存和派发的Warehouse service
l 处理支付和计价的AccountSystem
在该案例中,warehouse服务是由构件中的组件提供的,customer服务也如此。Accounts System就是提供服务的已存在应用,但它并未用SCA实现。而,SCA构件通过右上角的OrderProcessing构件中的AccoutSystem reference来引用Accouts System的服务。
OrderProcessingComposite关心的所有关于Accounts System的相关信息在AccoutSystem引用配置中维护。该相关信息中包含了绑定使用的细节,比如EJB绑定,也包含了Accouts System提供服务的endpoint地址的详细信息。
这种访问已存在应用的简单方法的好处:其一,应用如前,不需要任何改动。其二,运行已存在应用的系统根本不需要知道SCA任何信息。该方法的缺点:其一,可能很难通过构件应用的不同部分计算出到底有多少已存在的应用使用了。其二,已存在应用的服务信息,包括通讯协议和endpoint地址,都必须从应用中以某种方式人工解出,然后插入构件中,并且如果这些信息发生变化,必须人工维护。
作为服务组件来建模完整的应用
作为可选方案,将已存在应用连接成为一个新的构件的解决方案的更复杂的解决方式就是将整个已存在应用作为单个SCA的服务组件来建模。
在这里,已存在应用作为SCA服务组件出现。该应用提供的服务作为服务组件上的服务出现。如果已存在应用依赖于业务中别处提供的服务,那么这些依赖就作为服务组件上的引用展现。应用的服务和引用的建模下一步就是通过绑定集成。潜在地,允许应用的引用可以重连线到不同的目标服务上。
OrderProcessing构件中Accounts System application是如何出现的在图2中展现了。
图2:已存在应用建模为组件的集成
在这个案例中,Accounts System application作为AccountsSystem组件展现。它表示单一的服务,尽管在更现实的情形下,应用可能会提供一系列不同的服务。
用这种方法,OrderProcessing component被直接连线到AccountsSystem组件,并且在构件中,对于AccountsSystem没有引用。
用这种方式看待已存在应用,的确要求已存在应用所用到的运行时的特定功能。运行时必须是SCA特性的,并知晓如何用SCA术语来呈现应用的功能。这么一个运行时,可能是原运行时的增强版。
作为例子,设想下Accouts System是某个Java企业级应用,由一系列EJB编码而来。该应用编码时,对SCA一无所知—并且原先的JEE运行时是不知晓SCA的应用服务器。然而,应用服务器的更新的版本是感知SCA的,并能将相同的未做改变的应用“渲染”为SCA组件。
用这种方法,使用SCA组合新的解决方案的优点是完全有效的,并且已存在应用是如何被用于系统中其他的组件的都非常清晰。SCA可以被用来配置到应用的连线—也就意味着,基础设施服务,如安全、事务等可以作为构件的组成部分声明性地使用。如果已存在应用依赖于构件中其他组件提供的服务,那么这些也能在构件中被建模。
这种方法的主要缺点就是要求已存在应用的运行环境提供基本的SCA特性支持。这也许就意味着需要升级当前的运行时。
将已存在应用作为SCA
构件来描述
对于更复杂的应用运行时和相当结构化的应用,可能可以在将已存在应用处理为SCA构件时走得更远。这当然也依赖于支持用SCA集成的更复杂形式的应用运行时环境。
对于这种方式,对于已存在应用,将使用某种已存在的编程模型,并且该编程模型支持将其(这里指应用)功能分割为独立的单元或组件是非常必要的。许多已存在应用模型和框架都能完成此项工作。该文档中用到的案例是Java企业版。在该版本中,应用被划分为一系列session EJB,它们都运行于作为容器和运行时的J2EE应用服务器中。
当已存在应用用这种方式构造,就有可能将该应用建模成SCA构件。在此构件中,每个应用的子单元被建模为SCA组件,连接在一起形成提供整个应用功能的构件。在JEE应用的情况下,每个EJB都作为组件来建模,且EJB之间的联系(如果有的话),都建模为SCA的连线(wire)。为其他应用所提供的应用功能描述为该构件一系列的服务,依赖关系被建模为该构件的引用。
该方式如图3所示:
图3:将已存在应用作为构件来建模的集成
这种方式允许装配器重做已存在应用的装配—应用可以被复用,但也可能重新连线应用的某些内部细节。这样使得更适应新业务的使用。案例可以通过连接到某个由外部业务合作者提供的等效服务,用AccoutSystemComposite的component B替代掉component C。
所以,这种方式的优点就是在已存在应用的功能上拥有附加的更有条理的控制,使用由某处提供的新功能替代已存在应用的某些方面的能力。
这种方式的缺点在于它要求更复杂的应用运行时环境,这与将应用作为单个组件要求运行时环境具有与SCA更深度集成的特性。另外,重新连线应用的内部实现,实际上可能会难得多,并且还有不稳定性的危险。这就意味着在新的组合应用之前有更多的验证和测试的花费会带到产品中。
总结
SCA支持将要集成的已存在应用的重要用例形成新的构件业务解决方案。做集成有很多种方法,每种都有自己的特点和特性,但必须依靠应用所使用的运行时基础设施的增强功能。最简单的情况下,已存在应用可以不用修改,继续使用,只要它通过某种可调用接口的形式来开放其功能。更复杂的运行时可以提供更紧密的集成,当然要对已存在应用所使用的服务进行些许改造。
创建构件业务解决方案是SCA的核心功能。SCA利用功能强大的模型支持了SOA的灵活性。该模型给出了解决方案组件的清晰描述和它们的连接方式。在构件中应用基础设施的功能,如安全性和事务性,同时不需要修改代码的这种能力,以最小的成本提供了产品级的灵活的环境来应对复杂业务的需求。
附录:SCA构件案例
该文章的主要章节故意避免show出SCA XML的描述信息在这里描述。这样做是为了避免描述混乱,为初识SCA的读者保持相对的简洁性。该附录目标就是为更专业的读者提供信息—包括SCA XML描述。对于不熟悉SCA XML的读者,最好在读该附录之前阅读些基础资料。
一个好的去处就是去访问SCA 装配规范(本人blog中已经翻译了)。
这些例子中,记住已存在的AccoutsSystem应用假定是用Java企业版实现的—特别的,它的服务是通过一个或多个Session Bean提供的。这些Session Bean可以使用RMI-IIOP协议远程访问,该协议在SCA中称为”EJB binding”。
案例1
:通过绑定组合
第一个案例情形对应于图1中的装配图。这种情形下,没有SCA代表AccoutsSystem应用的组件—对应用的使用是通过OrderProcessing构件处理的。该构件引用了指向AccountsSystem应用通过EJB binding所提供的服务。
<?
xml
version
=
"1.0"
encoding
=
"UTF-8"
?>
<
composite
name
=
"OrderProcessingComposite"
xmlns:sca
=
"http://www.osoa.org/xmlns/sca/1.0"
xmlns:xsi
=
"http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation
=
"http://www.osoa.org/xmlns/sca/1.0
../../SCA_Specifications/xsds/sca-core.xsd "
>
<!-- The Order Processing Service , offered as a Web service -->
<
service
name
=
"OrderProcessing"
promote
=
"OrderProcessing"
>
<
interface.wsdl
interface
=
"http://www.foo.com/OrderProcessing#
wsdl.interface(OrderProcessingExternal)"
/>
<
binding.ws
/>
</
service
>
<!-- The Order Processing Component -->
<
component
name
=
"OrderProcessing"
>
<
implementation.java
class
=
"com.foo.OrderProcess"
/>
<!-- Wires for the references of the component -->
<
reference
name
=
"Warehouse"
target
=
"WarehouseBroker"
/>
<
reference
name
=
"Customer"
target
=
"Customer"
/>
<
reference
name
=
"Accounts"
/>
</
component
>
<!-- The Warehouse Broker component -->
<
component
name
=
"WarehouseBroker"
>
<
implementation.java
class
=
"com.foo.WarehouseBroker"
/>
<!-- Wires to both the internal Warehouse component -->
<!-- and to the Reference to the external Warehouse -->
<
reference
name
=
"Warehouse1"
target
=
"Warehouse"
/>
<
reference
name
=
"Warehouse2"
/>
</
component
>
<!-- The internal Warehouse component -->
<
component
name
=
"Warehouse"
>
<
implementation.java
class
=
"com.foo.Warehouse"
/>
</
component
>
<!-- The Customer information component -->
<
component
name
=
"Customer"
>
<
implementation.java
class
=
"com.foo.CustomerSystem"
/>
</
component
>
<!-- The Reference to the external Warehouse service -->
<!-- accessed via a Web service binding -->
<
reference
name
=
"ExternalWarehouse"
promote
=
"WarehouseBroker/Warehouse2"
>
<
interface.wsdl
interface
=
"http://www.foo.com/Warehouse#
wsdl.interface(ExternalWarehouse)"
/>
<
binding.ws
uri
=
"http://www.bigwarehouse.com/WarehouseService"
/>
</
reference
>
<!-- The Reference to the existing AccountsSystem application -->
<!-- handled via an EJB binding to the address at which the -->
<!-- application offers its accounts service via Session Bean -->
<
reference
name
=
"AccountsSystem"
promote
=
"OrderProcessing/Accounts"
>
<
interface.java
interface
=
"com.foo.AccountSystem"
/>
<
binding.ejb
uri
=
"corbaname:rir:AccountsHost:2809#
ejb/AccountSystemHome"
/>
</
reference
>
</
composite
>
案例2
:作为单个服务组件建模应用
第二个案例情形对应于图2中的装配图。这里,已存在的AccountsSystem应用被作为 SCA构件的单一组件建模。OrderProcessing component直接连线到AccountsSystem component提供的服务。OrderProcessing组件不在拥有图1中AccountsSystem引用。注:构件没有AccountsSystem应用的内部细节信息—这些内部信息并没有被建模在SCA构件中,这些仅仅是运行时感知的。在运行时环境中,AccountsSystem执行并仅仅用JEE编程模型的术语描述。
<?
xml
version
=
"1.0"
encoding
=
"UTF-8"
?>
<
composite
name
=
"OrderProcessingComposite"
xmlns:sca
=
"http://www.osoa.org/xmlns/sca/1.0"
xmlns:xsi
=
"http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation
=
"http://www.osoa.org/xmlns/sca/1.0
../../SCA_Specifications/xsds/sca-core.xsd "
>
<!-- The Order Processing Service , offered as a Web service -->
<
service
name
=
"OrderProcessing"
promote
=
"OrderProcessing"
>
<
interface.wsdl
interface
=
"http://www.foo.com/OrderProcessing#
wsdl.interface(OrderProcessingExternal)"
/>
<
binding.ws
/>
</
service
>
<!-- The Order Processing Component -->
<
component
name
=
"OrderProcessing"
>
<
implementation.java
class
=
"com.foo.OrderProcess"
/>
<!-- Wires for the references of the component -->
<
reference
name
=
"Warehouse"
target
=
"WarehouseBroker"
/>
<
reference
name
=
"Customer"
target
=
"Customer"
/>
<
reference
name
=
"Accounts"
target
=
"AccountsSystem"
/>
</
component
>
<!-- The Warehouse Broker component -->
<
component
name
=
"WarehouseBroker"
>
<
implementation.java
class
=
"com.foo.WarehouseBroker"
/>
<!-- Wires to both the internal Warehouse component -->
<!-- and to the Reference to the external Warehouse -->
<
reference
name
=
"Warehouse1"
target
=
"Warehouse"
/>
<
reference
name
=
"Warehouse2"
/>
</
component
>
<!-- The internal Warehouse component -->
<
component
name
=
"Warehouse"
>
<
implementation.java
class
=
"com.foo.Warehouse"
/>
</
component
>
<!-- The Customer information component -->
<
component
name
=
"Customer"
>
<
implementation.java
class
=
"com.foo.CustomerSystem"
/>
</
component
>
<!-- The AccountsSystem application is modeled as -->
<!-- a single component, implemented by its entire -->
<!-- EJB module - any of its services can be used -->
<
component
name
=
"AccountsSystem"
>
<
implementation.ejbjar
module
=
"AccountsSystemEJB.jar"
/>
</
component
>
<!-- The Reference to the external Warehouse service -->
<!-- accessed via a Web service binding -->
<
reference
name
=
"ExternalWarehouse"
promote
=
"WarehouseBroker/Warehouse2"
>
<
interface.wsdl
interface
=
"http://www.foo.com/Warehouse#
wsdl.interface(ExternalWarehouse)"
/>
<
binding.ws
uri
=
"http://www.bigwarehouse.com/WarehouseService"
/>
</
reference
>
</
composite
>
案例3
:作为SCA
构件建模的应用
第三个案例情形对应于图3中的装配图。这里,已存在的AccountsSystem应用被作为 SCA构件的构件来建模。AccountsSystem composite作为OrderProcessing composite中的单个组件来使用—基本上,和案例2中的OrderProcessing composite一样,除了实现类型现在是”composite”而不是”ejbjar”了。
在这个案例中,有第二个构件—AccountsSystem composite。这里,组成AccountsSystem应用的各个EJB每个都被建模为SCA component,用连线来连接它们。外部来说,由应用提供的服务变成了构件的服务。实际上可能的是:该构件通过对原EJB jar文件运行某个工具来构建。
首先,OrderProcessing composite:
<?
xml
version
=
"1.0"
encoding
=
"UTF-8"
?>
<
composite
name
=
"OrderProcessingComposite"
xmlns:sca
=
"http://www.osoa.org/xmlns/sca/1.0"
xmlns:xsi
=
"http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation
=
"http://www.osoa.org/xmlns/sca/1.0
../../SCA_Specifications/xsds/sca-core.xsd "
>
<!-- The Order Processing Service , offered as a Web service -->
<
service
name
=
"OrderProcessing"
promote
=
"OrderProcessing"
>
<
interface.wsdl
interface
=
"http://www.foo.com/OrderProcessing#
wsdl.interface(OrderProcessingExternal)"
/>
<
binding.ws
/>
</
service
>
<!-- The Order Processing Component -->
<
component
name
=
"OrderProcessing"
>
<
implementation.java
class
=
"com.foo.OrderProcess"
/>
<!-- Wires for the references of the component -->
<
reference
name
=
"Warehouse"
target
=
"WarehouseBroker"
/>
<
reference
name
=
"Customer"
target
=
"Customer"
/>
<
reference
name
=
"Accounts"
target
=
"AccountsSystem"
/>
</
component
>
<!-- The Warehouse Broker component -->
<
component
name
=
"WarehouseBroker"
>
<
implementation.java
class
=
"com.foo.WarehouseBroker"
/>
<!-- Wires to both the internal Warehouse component -->
<!-- and to the Reference to the external Warehouse -->
<
reference
name
=
"Warehouse1"
target
=
"Warehouse"
/>
<
reference
name
=
"Warehouse2"
/>
</
component
>
<!-- The internal Warehouse component -->
<
component
name
=
"Warehouse"
>
<
implementation.java
class
=
"com.foo.Warehouse"
/>
</
component
>
<!-- The Customer information component -->
<
component
name
=
"Customer"
>
<
implementation.java
class
=
"com.foo.CustomerSystem"
/>
</
component
>
<!-- The AccountsSystem application is modeled as -->
<!-- a single component, implemented by its entire -->
<!-- EJB module - any of its services can be used -->
<
component
name
=
"AccountsSystem"
>
<
implementation.composite
name
=
"AccountsSystemComposite"
/>
</
component
>
<!-- The Reference to the external Warehouse service -->
<!-- accessed via a Web service binding -->
<
reference
name
=
"ExternalWarehouse"
promote
=
"WarehouseBroker/Warehouse2"
>
<
interface.wsdl
interface
=
"http://www.foo.com/Warehouse#
wsdl.interface(ExternalWarehouse)"
/>
<
binding.ws
uri
=
"http://www.bigwarehouse.com/WarehouseService"
/>
</
reference
>
</
composite
>
以下是AccountsSystem构建,构建自实现了AccountsSystem的原始EJB jar
<?
xml
version
=
"1.0"
encoding
=
"UTF-8"
?>
<
sca:composite
name
=
"AccountsSystemComposite"
xmlns:sca
=
"http://www.osoa.org/xmlns/sca/1.0"
xmlns:xsi
=
"http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation
=
"http://www.osoa.org/xmlns/sca/1.0
../../SCA_Specifications/xsds/sca.xsd "
>
<!-- The service offered by the Accounts System application -->
<!-- offered via an EJB binding -->
<
sca:service
name
=
"AccountsService"
promote
=
"ComponentA"
>
<
sca:interface.java
interface
=
"com.foo.AccountSystem"
/>
<
sca:binding.ejb
uri
=
"corbaname:rir:AccountsHost:2809#
ejb/AccountSystemHome"
homeInterface
=
"com.foo.AccountSystemHome"
/>
</
sca:service
>
<!-- The EJBs of the application, each modeled as an SCA -->
<!-- component -->
<
sca:component
name
=
"ComponentA"
>
<
sca:implementation.ejb
ejb-link
=
"AccountsSystemEJB.jar#
AccountsBeanA"
/>
<
sca:reference
name
=
"ServiceB"
target
=
"ComponentB"
/>
</
sca:component
>
<
sca:component
name
=
"ComponentB"
>
<
sca:implementation.ejb
ejb-link
=
"AccountsSystemEJB.jar#
AccountsBeanB"
/>
<
sca:reference
name
=
"ServiceC"
target
=
"ComponentC"
/>
</
sca:component
>
<
sca:component
name
=
"ComponentC"
>
<
sca:implementation.ejb
ejb-link
=
"AccountsSystemEJB.jar#
AccountsBeanC"
/>
</
sca:component
>
</
sca:composite
>