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> |
上面的设计就是一种我们可以采用的信息交换的例子。实际上,在上面的情况中,我们可以随便决定使用何种信息交换协议,因为它没有任何约束。例如,我们可能发现,采用单一的输入输出模式可能更好,我们就可以把所有的 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 服务的解耦过程