开发contract-first的web service最重要的就是定义contract,我们需要定义发送给服务和从服务接收的消息,不用考虑服务被实现的方式和消息被处理的方式。
虽然这里主要讨论Spring-WS,但其实这和Spring是无关的。因为定义一个web service的contract与服务的实现细节是独立的。它只关注于它应该有什么而不是它如何去拥有什么。Web service的contract-first强调与服务通讯的消息。所以,定义contract首先就要确定这些消息的格式。我们将创建一段样本XML消息来定义service的contract。
创建样本XML消息
前面的扑克牌服务以五张扑克牌作为输入,产生一个扑克牌名字作为输出。为服务编写一个样吧输入消息的XML代码如下:
<EvaluateHandRequest
xmlns="http://www.springinaction.com/poker/schemas">
<card>
<suit>HEARTS</suit>
<face>TEN</face>
</card>
<card>
<suit>SPARES</suit>
<face>KING</face>
</card>
<card>
<suit>HEARTS</suit>
<face>KING</face>
</card>
<card>
<suit>DIAMONDS</suit>
<face>TEN</face>
</card>
<card>
<suit>CLUBS</suit>
<face>TEN</face>
</card>
</EvaluateHandRequest>
这段XML很直观,总共有5个<card>元素,每一个都有一个<suite>和<face>。这很好地描述了这个扑克牌服务。所有的<card>元素都封装在<EvaluateHandRequest>元素中——这就是我们要发送给service的消息。
与输入消息类似,输出消息如下:
<EvaluateHandResponse
xmlns="http://www.springinaction.com/poker/schemas">
<handName>Full House</handName>
</EvaluateHandResponse>
EvaluateHandResponse消息只包含了一个handName元素,handName保存了输出信息。这些样本消息就是contract的基础。现在我们已经完成了最困难的部分。
创建数据contract
现在我们开始创建服务contract。在此之前,我们先把contract从概念上划分几个概念:
·data contract:定义与服务通讯的消息。在本例中,这包括EvaluateHandRequest和EvaluateHandResponse消息的schema定义。
·operational contract:定义服务执行的操作。值得注意的是,一个SOAP操作并不需要关联于服务API中的一个方法。
一般地,这两个contract部分都被定义在一个单个的WSDL文件中。WSDL文件通常都包含一个内嵌的XML Schema,该Schema定义了data contract。另外,WSDL文件定义了operational contract,在<wsdl:binding>元素中包含一个或多个<wsdl:operation>元素。
data contract是被定义在XML Schema(XSD)中的,XSD允许我们准确地定义一个消息。我们不但可以定义消息中的元素,还可以指定消息类型和为消息设置限定条件。
有很多XSD inference工具可供使用,例如Trang。Trang以XML作为输入产生一个XSD文件作为输出。Trang是基于Java实现的,所以可以应用于任何JVM上。Trang可以产生RELAX NG(一种schema风格)的schema,也可以产生XSD文件。对于Spring-WS来说,我们可以使用Trang来产生XSD,执行下面的命令即可得到xsd文件:
java –jar trang.jar request.xml response.xml PokerTypes.xsd
Trang将会产生一个名为PokerTypes.xsd的文件,但XSD并不是完美的。因为Trang可以产生XSD,所以Trang对于XML中的数据类型做了一些假设。大多数情况下这些假设没有问题,但是有时候我们还是需要对产生的XSD进行修改调整。
例如,Trang假设<suit>和<face>元素的值应该被定义为noncolonized name(xs:NCName)。一个noncolonized name不带有命名空间的前缀。我们实际想要的元素只是一个简单的字符串(xs:string)。所以,我们需要把<suit>和<face>定义为string类型:
<xs:element name=”suit” type=”xs:string” />
<xs:element name=”face” type=”xs:string” />
我们还知道对于<suit>元素只有4个可能的值,所以我们加上限定条件:
<xs:element name="suit" type="schemas:Suit" />
<xs:simpleType name="Suit">
<xsd:restriction base="xs:string">
<xsd:enumeration value="SPADES" />
<xsd:enumeration value="CLUBS" />
<xsd:enumeration value="HEARTS" />
<xsd:enumeration value="DIAMONDS" />
</xsd:restriction>
</xs:simpleType>
同样地,<face>元素只有13个合法值,这里就不再一一列出其定义了。另外值得注意地是,Trang错误地假定<EvaluateHandRequest>包含一个未限制的<card>元素(maxOccurs=”unbounded”),但是对于本例来说它包含5张卡片。因此,我们需要调整它的定义为:
<xs:element name="EvaluateHandRequest">
<xs:complexType>
<xs:sequence>
<xs:element minOccurs="5" maxOccurs="5" ref="schemas:card"/>
</xs:sequence>
</xs:complexType>
</xs:element>
就<EvaluateHandResponse>来说,Trang生成的代码还可以,我们当然也可以为其加上限定值,但这不是必须得。
现在我们已经有了poker hand服务的data contract,但operational contract呢?我们需要WSDL的帮忙。毕竟,WSDL是定义web service的标准。我们当然可以手工编写WSDL,但是这太容易,没有多大乐趣。
首先要做的就是创建一个服务的端点(endpoint)。contract只定义了服务通讯的消息,没有定义它们是如何被处理的。下面来看在Spring-WS中如何创建消息的endpoint,以及Spring-WS如何处理来自服务客户端的消息。