翻译自<<Service Oriented Architecture with Java>>(使用Java开发面向服务的架构)一书之第二章
本章我们将详细讲述SOA的实现,并开始我们的Web服务实践之旅。在本章中,我们将会看到,为什么在企业交互的环境中,XML是消息交换的正确选择。接下来我们开始定义一个示例的Web服务,采用自顶向下的方法来开发我们第一个Web服务。然后我们再采用颇为通用的两种传输协议改进我们的程序。
通过对Web服务两种风格(RPC和Document)的比较,我们可以看到采用Document风格的Web服务更加完善,并被广泛使用。在本章的最后,我们将简单介绍一下最流行的几种Web服务实现框架。
SOA的实施方法
实施SOA的第一步其实很简单,即:确定您的应用所包含的业务功能。下面我们就仔细分析这句话的含义:
(1) 确定:就是要找到软件组成的独立模块,这些模块是自包含的,并且从功能上说,它们是不可分割的(具有功能上的原子性)。这就要求我们在设计时,要把您的问题域按照软件调用规范切分成良好定义的模块,同时也要确定这些模块的边界。对业务模块调用者而言,他们要按照契约式软件规范来正确调用这些模块。在软件设计时,请牢记一条,在多个环境(项目)中都能使用的一个模块就是软件开发中的金块。从某种意义上说,确定出软件中的业务功能,就是一种更高层次的抽象,它将我们以前对“接口”的抽象提高到业务层面上。
(2) 业务功能:这个词是指在SOA实践中,我们将专注于业务层(MVC概念中的M),而不是表示层和控制层(MVC模式中的V和C)。我们在这里只讨论服务,而良好设计的服务应该和表示层相互独立,服务对表现它们的表示层一无所知。
(3) 应用所包含的:应用程序可能包含许多软件层,但我们这里的应用程序强调的是多个而非仅仅一个应用程序。这是SOA实施方法的一个“大跃进”,它超越了我们现在正在进行的项目,我们只需做少量的工作,就可以让SOA中的业务组件超越单一项目,它们可以非常容易地使用在以后的应用程序中。
现在,假定我们按照上面的设计方法设计出了我们所需的服务,下一步我们该做什么呢,我们又如何实现它们呢?
我们不妨先举一个服务的例子,这个例子将返回所有的客户列表,它不需要输入参数,并返回一串对象列表。
服务的消费者(例如用户界面)如何能调用到该服务,并得到它所请求的对象列表呢?这可以通过多种方式实现,下面是其中最为流行的几种:
(1) 使用本地调用:就Java而言,本地调用可以通过RMI(远程方法调用)、Sockets、Servlets或JMS来实现;
(2) 使用分布式对象交换中间层:例如服务消费者可使用CORBA和DCOM来调用服务;
(3) 使用基于文本的交换协议来调用服务:服务消费者可以发送基于文本流的请求,然后可获得包含数据的应答文本。这是Web服务实现的基础。
第一种方法非常直接,当它有一些缺陷,它必须依赖于一种语言,服务及其消费者都必须使用同一种语言,比如Java、.Net等,并且,服务及其消费者交换对象的版本还必须相同;否则,对象传输就会失败。
分布式对象传输在相当长的时间内非常成功,尤其是CORBA的跨平台特性为后来的可互操作性提供了奠定了良好的基础。
第三种基于文本交换的实现方法表明,在客户端和服务器端都需要进行序列化和反序列化操作。但客户端发送请求时,需要将对象序列化成文本格式,然后发送;但服务器收到请求后,它需要将文本经过序列化转换成对象。同样的过程也发送在服务应答的流程中。序列化和反序列化貌似增加了对象交换的复杂性,其实不然,请想想这样做的优势:软件能完全独立于技术,实现了软件之间的松散耦合。
将数据嵌入到文本中最自然的方法莫过于通过XML来实现。
XML的优点和不足
XML语言由W3C于1998年为了数据交换的目的而设计的。对着时间的推移,该语言的魅力也开始显现。XML的主要优点有以下四个方面:
具有结构化特性
是可移动的
具可扩展性
具文本格式
XML的不足
以树型结构组织的XML语言优势可能有一些缺点。例如,XML在表示共享的对象应用方面有些不足,所以人们通常争论,XML是否是表示一个任意对象的最佳选择。想想我们前面举的例子,假设您在伦敦有许多客户,使用XML表示这些客户列表时,就会产生数据冗余。这是因为在客户这个XML实体中,其城市属性值都相同。这是人们所不能接受的。其实,这个例子恰恰反证了人们对于XML的误用。在上例中,城市这个属性应该算作一个实体而非属性。这个问题的更好的解决方法是,向关系数据库学习,把重复的数据搬移到主要对象之外,在主要对象中只应用这些重复对象的ID号即可。
客户端可以使用有状态方法(Stateful Approach),首先得到城市这个实体的列表。当它调用getAllCustomers服务时,服务器返回的客户列表的城市属性只需ID即可,而不需完整的城市名,返回的示例客户列表如清单1所示:
程序清单1—有状态方法(Stateful Approach)
<Customers>
<customer>
<id>4</id>
<name>Smith Ltd</name>
<location>
<address>39, Kensington Rd.</address>
<city>LND</city>
</location>
</customer>
<customer>
<id>7</id>
<name> Merkx & Co.</name>
<location>
<address>39, Venice Blvd.</address>
<city>LAX</city>
</location>
</customer> <id>7</id>
...
</Customers>
而且,我们还可以采用无状态方法(Stateless Approach),实现自包含的服务,即在我们的XML应答中,嵌入所需要的所有数据对象。示例代码如清单2所示:
程序清单2—无状态方法(Stateless Approach)
<Entireresponse>
<cities>
<city>
<id>LND</id>
<name>London</name>
<country>UK</country>
</city>
<city>
<id>LAX</id>
<name>Los Angeles</name>
<country>USA</country>
</city>
</cities>
<customers>
<customer>
<id>4</id>
...
<location>
...
<city>LND</city>
</location>
</customer>
<customer>
<id>7</id>
...
<location>
...
<city>LAX</city>
</location>
</customer>
...
</customers>
</Entireresponse>
Web服务、REST风格的服务及其它与XML传输相关的技术介绍
在上节中,我们讨论了使用XML实现SOA有哪些优势,但并没有说明SOA就是由Web服务构成的。SOA和Web服务有时在同一场合中被混用,因而人们对此产生了一些误解。SOA是一种方法,是架构设计中的一种选择,它与技术和语言无关。在面向服务的架构中,服务并不是Web服务的简称,而是一种宽泛意义上的服务。我们可以在不考虑任何具体技术实现的前提下,设计出某种获得所有商品列表的服务来。
按照SOA进行设计的意思为,针对某一特定的业务领域,设计出符合业务规则的高层接口。当然,但产品或项目进行到某一阶段时,我们必须要选择具体的实现方式。下面我们就讨论一下SOA有哪些实现方式,从比较简单的手工方法,到业界广泛采用的技术标准(SOAP)。
在进一步讨论之前,我们先了解一下本书中涉及到的几个术语。协议(Protocol)这个词在本书中的意思会依据上下文有所不同,其中一个关键是要区别“传输协议(亦称网络协议)(Transportation or layer protocol)”和“交换协议(Communication protocol)”。传输(网络)协议是指传输信息所使用的网络协议,它可以是广泛使用的HTTP协议,也可以是允许异步传输的SMTP协议或JMS协议;而交换协议需要考虑如何把消息放到XML文档中,如何把消息从从XML文档中提取出来。交换协议是我们本节讨论的重点。
首先,我们开始手动搭建一个简单的系统,在该系统中,XML请求和应答都通过HTTP协议传输,这将有助于我们从根本上理解消息交换传输的机制。然后,我们将使用REST来使我们的系统标准化。使用REST技术后的系统仍然是SOA的一个比较基本的实现,但系统将变得非常简洁,并使用了良好设计的信息交换协议。最后,我们将采用SOAP技术让这个系统成为具有可移植性、更加完全和灵活的解决方案。
SOA设计之基础---定义SOA服务的XML文档
SOA服务的设计过程是一个会产生许多成果的过程。首先,它将产生一系列的服务定义列表,我们有时也称之为“服务目录”,这些集合自然不是扁平的列表,而是以节或功能域的形式组织起来。比如,我们可能有商品功能域、订单功能域及客户功能域等等,在这些功能域下,我们可以定义一些具体的服务。例如在商品的功能域下,我们可以定义如下服务:
insertItem---新增商品服务
updateItem---修改商品服务
deleteItem---删除商品服务
findItemById---按照商品Id号发现商品的服务
findAllItems---找到所有商品的服务
findItemsByCriteria---按照某种规则找到商品的服务
再如,订单功能域的服务可能有:
createOrder
findOrderById
findAllOrdersByCustomer
我们通常还需要定义一些跨功能域的服务(正交服务)。有些服务可以共享一些诸于控制流或交易处理之类的通用过程。
一般说来,在软件设计的初始阶段,人们都专注于基本的业务域对象及对这些业务对象进行处理的基本操作上,这些操作包括增删改查等,亦称之为CRUD操作,即Create或 Insert、Read或select、Update和Delete。不管您使用什么语言,采用何种软件架构,您的项目或产品都需要处理这些增删改查操作。在我们这个例子中,基本业务域对象为商品实体,上面服务列表中的前4个定义即为该对实体的CRUD操作。
接下来,我们开始就商品这个业务对象的服务进行分析和设计。比如,insertItem服务可能具有如下格式:
创建
服务输入 | 服务名 | 服务输出 |
<Item> <id>0</id> <code>RX004</code> <description> Eth. Cable </description> </Item> | => insertItem => | <Result> <retCode> OK </retCode> <id>137</id> </Result> |
客户端如果想使用这个服务插入一个新的商品,它必须按照上面的输入格式提供一个XML消息,请注意在输入消息中,id属性值为0,这是因为我们假定Server会在XML应答中为这个商品分配一个id号。
对商品这个业务对象,其它的三个CRUD服务的XML定义可能如下:
读取
服务输入 | 服务名 | 服务输出 |
<ItemId> | => findItemById => | <Item> <id>137</id> <code> RX004 </code> <description> Eth. Cable 4 ft. </description> </Item> |
修改
服务输入 | 服务名 | 服务输出 |
<ItemId> | => findItemById => | <Item> <id>137</id> <code> RX004 </code> <description> Eth. Cable 4 ft. </description> </Item> |
删除
服务输入 | 服务名 | 服务输出 |
<ItemId> | => deleteItem => | <Result> OK </retCode> |
上面的设计就是一种我们可以采用的信息交换的例子。实际上,在上面的情况中,我们可以随便决定使用何种信息交换协议,因为它没有任何约束。例如,我们可能发现,采用单一的输入输出模式可能更好,我们就可以把所有的CRUD操作都包含在一个服务中。那么,此时的输入XML消息中就会含服务名称和服务对象,输出的XML中会包括返回值和商品对象,它们的定义如下图所示:
通用的CRUD操作
服务输入 | 服务名 | 服务输出 |
<ItemAction> <method> findById </method> <item> <id>137</id> <code></code> <description> </description> </item> </ItemAction> | => itemCrudService => | <ItemActionResponse> <retCode>OK</retCode> <item> <id>137</id> <code>RX004</code> <description> Eth. Cable 4 ft. </description> </item> </ItemActionResponse> |
这里我们只对CRUD操作定义了一个服务,但这是要付出代价的。当我们调用findById(通过id查找)和delete(删除)服务时,我们必须要为输入XML中提供商品的部分属性值,其实,在这两种情况下,id属性值就足够。只有在更新商品时,需要提供商品这个实体的所有属性值。另外,在服务输出的XML中,只有findById服务需要所有属性都被赋值的商品对象。
但是,在通常情况下,增删改查(CRUD)这四个服务是远远不够的。例如,对商品这个业务对象而言,我们需要定义一个返回所有商品的方法,或者至少能返回一部分商品的方法。我们可能为这个服务定义如下的规范:
非增删改查(CRUD)操作
服务输入 | 服务名 | 服务输出 |
void input |
=> findAllItems => | <Items> <item> <id>137</id> <code>RX004</code> <description> Eth. Cable 4 ft. </description> </item> ... </Items> |
现在,我们已经讨论了服务设计者在设计时可能采用的方法。您会注意到,消息交换的定义通常完全取决于您—服务设计者,没有什么可以遵循的规则,您只需要使用您的设计技巧来抽象化这些概念。
一旦您完成服务的消息交换定义(您的定义可能如上所示,也有可能和上面的有所不同),我们就需要考虑网络交换协议及其细节了。HTTP协议是一个实用而具有灵活性的协议:我们可以使用HTTP协议传输XML请求,这种方法也称之为POX-over-HTTP,这里POX指Plain OId XML的缩写(简单的旧XML格式)。
在实践中,我们只需要使用XML转换API库把我们程序语言中的对象转换为XML文档,然后再转化回对象即可,这样,我们就可以实现我们上面所定义的服务。我们甚至还可以使用不同的语言实现客户端和服务器端,知道它们都遵循我们上面的定义的消息交换规范。XML文档在客户服务器解耦过程中起了非常关键的作用,具体如下图所示:
Web服务的解耦过程