BT

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

重新考虑“代码优先”的Web服务

| 作者 Dennis Sosnoski 关注 0 他的粉丝 ,译者 胡键 关注 0 他的粉丝 发布于 2007年9月26日. 估计阅读时间: 28 分钟 | 都知道硅谷人工智能做的好,你知道 硅谷的运维技术 也值得参考吗?QCon上海带你探索其中的奥义

你开始开发SOAP Web服务了吗?如果是的话,那么你有两种开发风格可选择。第一种被称为“由WSDL开始(start-from-WSDL)”,或是“契约优先(contract first)”,牵涉构建一个WSDL服务描述,并直接关联用于数据交换的XML模式。第二种被称为“由代码开始(start-from-code)”,或是“代码优先(code first)”,牵涉将例子服务代码插入你选择的框架,并由那个代码产生WSDL+模式(schema)。

不论使用哪种开发风格,最终目标都是相同的——你想要你的服务有一个稳定的WSDL+模式定义。当你正工作在一个SOA环境时,这个目标尤其重要。SOA要求服务松耦合,服务间的接口是固定的,且与实现相分离。XML Web服务为实现SOA创建了一个重要的基础,这很大程度上是因为WSDL和模式可以让你以平台中立的方式去指定被一个Web服务使用的XML消息交换。如果WSDL和模式不是稳定的,服务就只能被由服务提供者直接或间接控制的客户端使用——这可不是SOA。

“由代码开始”的问题

“由代码开始”开发Web服务的想法被许多Web服务和SOA领域的权威人士反对。他们觉得“由代码开始”将XML消息结构绑定到了一个特定的实现,这废弃了使用WSDL和模式的整个目的。对于“由代码开始”的最初形式——SOAP编码模式(SOAP encoding scheme)——的确是这样,它被广泛使用以支持rpc/encoded。使用SOAP编码,XML模式直接由服务提供者应用数据结构产生,客户端代码使用这些产生的数据结构副本进行工作。这种数据模型和XML之间自动转换的特性使得rpc/encoded在早期的SOAP中流行——但是它也是这种风格后来被废止的一个重要原因。它意味着,每次你的服务数据结构的改变都会引起模式的改变,客户端将需要使用新的模式重新生成它们的代码。

图1. “由代码开始”的SOAP编码方式

图1. “由代码开始”的SOAP编码方式

使用SOAP编码除了产生紧耦合,在XML数据表示和模式定义方面它也有一些缺点。SOAP编码是用于对象图的XML序列化算法,以编程语言独立的方式定义。既然它是一个序列化算法,因此就最后产生的XML结构来说没有灵活性——你应用这个算法到你的数据结构上,你所得到的就是用于那些结构的SOAP编码。不幸的是,所使用的序列化规则导致了XML模式对于其他非rpc/encoded消息交换几乎没有任何用处(包括文档校验)。序列化格式同样还导致较差的效率,这是因为引用结构的花销、过多的运行时打印,以及为所有组件使用子元素(而不是在合适的地方使用属性)。

大多数这些问题适用于那些只是在数据结构和XML间序列化的技术。但是“由代码开始”并不意味着必须通过直接序列化来暴露数据模型。使用某种形式的数据绑定,当前的Web服务栈通常都支持数据模型和XML间的灵活转换。使用数据绑定,你可保持对数据的XML表示的控制。那种控制意味着你的模式定义至少可以与实际的数据模型间稍有隔离,并且你可以选择适合你数据的XML表示。使用数据绑定方法,大多数与SOAP编码有关的问题就不再会有了。

“由WSDL开始”的问题

与使用代码工作相比,使用“由WSDL开始”的最大问题就是使用WSDL和模式定义工作的麻烦本性。现代IDE一般都配备了“智能”编辑器和令代码变更容易的强大的重构工具。目前还没有用于WSDL和模式的等价工具。即便是最基础的模式重构,如转换局部定义到全局定义,也不被主流WSDL和模式工具所支持。

因为工具软弱,“由WSDL开始”还需要扎实理解WSDL和模式,这样才能获得良好的效果。如果可供开发者使用的工具没有立足于标准之上,那么最终的WSDL和模式常常是丑陋的大杂烩,它使得服务和数据的结构更加模糊,而不是更清晰。就WSDL部分而言,要有效地理解它还不太困难,但是模式这一部分就不同了。W3C XML模式推荐(“模式”的全称)至少与大多数编程语言一样复杂,要精通它需要花很多工夫。拥有专门架构团队的大型组织可以负担得起雇用或培训模式专家,但是对于更小的组织而言,模式的复杂性是“由WSDL开始”的服务规格的真正障碍。

即使在开发出WSDL和模式定义的初始集合之后,这些“使用的方便性”问题仍然适用。服务综合集合的开发总是一个迭代过程,反复经过规格、原型和测试周期。在每一阶段,功能蹩脚工具所带来的工作不便都会是开发的障碍。

使“由代码开始”起作用

目前,针对“由代码开始”完成服务规格的可用工具已经远远超越令这种开发类型声名狼藉的SOAP编码模型。它们提供了灵活性和可扩展性,使得它能与相当复杂的数据结构一起工作。最重要的是,它们在代码定义的数据结构与相应的XML表示之间增加了一个解耦层。

微软的.NET框架和Sun的JAX-WS 2.0/JAXB 2.0是两个流行的例子。两者都使用内嵌于源代码中的配置信息(在.Net中是属性,在JAX-WS/JAXB中是标注)来控制数据结构与XML间的相互转换。由这种内嵌配置提供的控制是有限的,而且通常等于只需详列出不同于缺省序列化选择的差异。那意味着XML并不必需与数据结构的细节相隔离——例如,如果你给对象增加一个域,它将自动成为XML表示的一部分,除非你显式列出要包含的域——但是它要比一个纯粹的序列化方式要好得多。

图 2. “由代码开始”的.NET和JAX-WS 2.0/JAXB 2.0方式

图 2. “由代码开始”的.NET和JAX-WS 2.0/JAXB 2.0方式

使用JiBX解耦

在将XML表示从应用数据模型解耦方面,笔者自己的用于Java(它可用于使用Apache Axis2、XFire和JiBX/WS栈的Web服务)的JiBX(http://www.jibx.org)数据绑定框架甚至走得更远。JiBX使用与源代码分离的绑定定义,要求每个想包含到XML表示中的项目要显式地在绑定中命名。数据模型和XML表示之间的结构性区别可在绑定中处理,即使数据模型随着时间而改变,XML表示通常也可以得到保护。JiBX还允许多个绑定应用于相同的代码,允许使用单一数据模型支持许多模式类型的版本标定变更。

相关的Jibx2Wsdl工具(http://www.sosnoski.com/jibx-wiki/space/axis2-jibx/jibx2wsdl)示范了使用“由代码开始”方式的潜在好处。它产生WSDL和模式,以及对应的JiBX绑定定义,确保所有的元件匹配。它还从Java源代码中导出JavaDoc文档,放入所产生的WSDL和模式中,这样服务描述被全部文档化,而不需要任何手工编辑。Jibx2Wsdl使用一个合理的缺省算法产生用于数据模型类的绑定,但是算法可以在任何层级上通过提供XML文档形式的自定义而被修改。这些自定义有和.NET属性、JAX-WS/JAXB标注相同的效果,且不需要内嵌在源代码中。

图3. 由代码开始的JiBX/Jibx2Wsdl方式

图3. 由代码开始的JiBX/Jibx2Wsdl方式

在效果上,Jibx2Wsdl将“由代码开始”分成两步。在产生步骤,你使用Jibx2Wsdl创建实际的WSDL+模式定义,以及相应的JiBX绑定定义。在部署步骤,你使用JiBX将产生的绑定定义应用到你的Java类。

在初始开发中,可结合这两个步骤以简化创建和精炼原型服务。一旦一个稳定的服务定义最终完成,产生步骤就不再是必须的了——JiBX绑定定义可以被作为稳定元件对待,并直接用于部署,只要没有影响绑定数据(如缺少域,或改变类结构)的数据模型的变更。如果存在这样的变更,JiBX绑定编译器将报告错误,部署步骤将失败。此时,你要么恢复绑定所期望的数据模型,要么修改绑定以匹配修改的数据模型(同时保护模式定义的XML格式——尽管目前这还不是由JiBX强制的)。

 package com.sosnoski.infoq.ex1;

/**
* Interface for placing orders and checking status.
*/
public interface StoreService
{
/**
* Submit a new order.
*
* @param order
* @return id
*/
public String placeOrder(Order order);

/**
* Retrieve order information.
*
* @param id order identifier
* @return order information
*/
public Order retrieveOrder(String id);

/**
* Cancel order. This can only be used for orders which have not been shipped.
*
* @param id order identifier
* @return <code>true</code> if order cancelled, <code>false</code> if already shipped
*/
public boolean cancelOrder(String id);
}

/**
* Order information.
*/
public class Order
{
/** Unique identifier for this order. This is added to the order information by the service. */
private String orderId;

/** Customer identifier code. */
private String customerId;

/** Customer name. */
private String customerName;

/** Billing address information. */
private Address billTo;

/** Shipping address information. If missing, the billing address is also used as the shipping address. */
private Address shipTo;

/** Line items in order. */
private List items;

/** Date order was placed with server. This is added to the order information by the service. */
private Date orderDate;

/** Date order was shipped. This is added to the order information by the service. */
private Date shipDate;
...
}

清单1. 服务代码和数据模型例程(部分)

清单1给出了一个服务接口和根数据模型类的简单例子。清单2显示了用于Jibx2Wsdl的自定义文件,它增加了没有在清单1源代码中描述的额外信息。在本例中,被增加的信息包括指定在WSDL和模式中使用的命名空间,列出在每个数据中哪些值是必须的,并且哪些应该使用属性而不是子元素表示(在值名字前以@开始),指明被包含在集合中的项目类型。

 <custom force-classes="true" namespace="http://ws.sosnoski.com/order/data"
namespace-style="fixed">
<wsdl namespace="http://ws.sosnoski.com/order/wsdl"
wsdl-namespace="http://ws.sosnoski.com/order/wsdl"/>
<package name="com.sosnoski.infoq.ex1">
<class name="Order" requireds="@customerId customerName billTo items"
optionals="orderId orderDate shipDate">
<collection-field field="items" item-type="com.sosnoski.infoq.ex1.Item"/>
</class>
<class name="Address" requireds="street1 city @state @zip"/>
<class name="Item" requireds="@id @quantity @price"/>
</package>
</custom>

清单2. Jibx2Wsdl自定义文件

清单3显示由Jibx2Wsdl产生的WSDL和模式的所选的一部分。你可以看到从源代码中抽出的JavaDocs,在模式中以<xsd:annotation>/<xsd:documentation>组件形式,在WSDL中以<wsdl:documentation>元素出现。这些被产生的元件可能或不会用于最终的部署——首先,一些被加入的空格和格式化可能是帮助使文档更易让人读——但是至少它们的确是最终版的一个非常好的起点。

<wsdl:definitions ... targetNamespace="http://ws.sosnoski.com/order/wsdl/StoreService">
<wsdl:types>
<xsd:schema ... targetNamespace="http://ws.sosnoski.com/order/wsdl/StoreService">
<xsd:import namespace="http://ws.sosnoski.com/order/data"
schemaLocation="data.xsd"/>
<xsd:element name="placeOrder">
<xsd:complexType>
<xsd:sequence>
<xsd:element type="ns1:order" name="order" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
<xsd:element name="placeOrderResponse">
<xsd:complexType>
<xsd:sequence>
<xsd:element type="xsd:string" name="string" minOccurs="0">
<xsd:annotation>
<xsd:documentation>assigned order identifier</xsd:documentation>
</xsd:annotation>
</xsd:element>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
...
</xsd:schema>
</wsdl:types>
...
<wsdl:portType name="StoreServicePortType">
<wsdl:documentation>Interface for placing orders and checking status.</wsdl:documentation>
<wsdl:operation name="placeOrder">
<wsdl:documentation>Submit a new order.</wsdl:documentation>
<wsdl:input message="tns:placeOrderMessage"/>
<wsdl:output message="tns:placeOrderResponseMessage"/>
</wsdl:operation>
...
</wsdl:portType>
</wsdl:definitions>

<xsd:schema ... targetNamespace="http://ws.sosnoski.com/order/data">
<xsd:complexType name="order">
<xsd:annotation>
<xsd:documentation>Order information.</xsd:documentation>
</xsd:annotation>
<xsd:sequence>
<xsd:element type="xsd:string" name="orderId" minOccurs="0">
<xsd:annotation>
<xsd:documentation>Unique identifier for this order. This is added to the order information by the service.</xsd:documentation>
</xsd:annotation>
</xsd:element>
...
<xsd:element ref="tns:address" minOccurs="0">
<xsd:annotation>
<xsd:documentation>Shipping address information. If missing, the billing address is also used as the shipping address.</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="item" minOccurs="0" maxOccurs="unbounded">
<xsd:complexType>
<xsd:sequence/>
<xsd:attribute type="xsd:string" use="required" name="id"/>
<xsd:attribute type="xsd:int" use="required" name="quantity"/>
<xsd:attribute type="xsd:float" use="required" name="price"/>
</xsd:complexType>
</xsd:element>
</xsd:sequence>
...
</xsd:complexType>
</xsd:schema>

清单3. WSDL和模式(部分)

一旦你有了服务构造的基本例程,那么在其中填入更多的细节和转换数据模型以适合不同受益者的需要将是非常容易的事情。被产生的绑定可与WSDL和模式的组合,使用Apache Axis2 Web服务框架(或使用XFire,或即将发布的JiBX/WS)部署你的服务,并且相同的数据模型可被Java客户端直接使用。你自然也可使用其它框架和其它客户端——因为产生的WSDL和模式定义了服务接口——但是对于最初的开发,使用代码的单一版本进行工作提供了真正的方便,并且Jibx2Wsdl使你能方便地完成这些。

总结

已经被SOA社区广泛接受的“由WSDL开始”总是正确的方法,但是需要指出的是,现实世界的选择要比这个简单的判断要复杂得多。“由WSDL开始”要求一个高层的投资,包括关于学习WSDL和模式,以及使用通常乏味的支持这些格式的工具。这存在着许多预先努力,但不保证结果恰好适合你的需要,更别说清晰和结构良好了。

“由代码开始”也有其自身潜在的毛病,包括可能你会无意间将你的服务描述与特定实现捆绑在一起。但是现代数据绑定框架允许你从实际的XML表示中隔离数据模型的细节,从实用角度来看,开发者使用代码工作总是较使用WSDL和模式工作更具生产率。在很多情况下,Web服务开发实际总是开始于现有代码,使用某种老技术实现服务。因此,不论专家给出哪些意见,从长远来看,“由代码开始”可能依旧是Web服务开发的重要组成部分。

不管使用哪种类型的数据绑定和Web服务框架,使用"由代码开始"作为快速跟进一个工作服务的做法都是可行的。一旦你使你的服务功能正确且根据你的用例进行了测试,你总是可以选择完全地破坏这种捆绑——只要采用所产生的WSDL和模式定义作为新的起点,并且如果必须,你可修改它们以清除任何不符合你组织需要的XML部分。接着,使用这个“最终的”WSDL和模式来在你选择的框架中产生新的服务提供者代码,并转换你的服务器应用到那个代码上工作。

关于作者

Dennis Sosnoski是基于Java的SOA和Web服务方面的专业咨询师和培训促进者。他从事职业软件开发已超过30年,在过去9年,他关注于服务器端XML和Java技术。Dennis是开源的JiBX XML数据绑定框架和相关的Jibx2Wsdl工具主要开发者,也是Apache Axis2 Web服务框架的提交者。他还是JAX-WS 2.0和JAXB 2.0规范专家组的成员之一。要获得更多信息,请访问他的网站或给他发email enquiry@sosnoski.com

查看英文原文:"Code First" Web Services Reconsidered

评价本文

专业度
风格

您好,朋友!

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

获得来自InfoQ的更多体验。

告诉我们您的想法

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

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

test by borball borball

WSDL和模式的固定对于服务提供者和使用者来说都有比较大的好处,否则服务提供者频繁更新服务而没有通知使用者很可能造成双方的数据和信息交换失败。但是WSDL和模式固定并不一定要从WSDL开始,也可以从代码从在服务端和客户端传输的数据结构的设计开始。

允许的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通知我

1 讨论

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


找回密码....

Follow

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

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

Like

内容自由定制

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

Notifications

获取更新

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

BT