在本文中,我们打算展示一下如何使用简单的技术加上以文档为中心的方式带来有价值的业务服务,而无须使用专有的中间件,也不必引入 Web 服务栈的复杂性。我们的灵感来自于 REST 的架构风格,以及把 XML 移到 HTTP 协议之上的能力。
Web 服务的方式
介绍我们这个方式的最好办法就是将它和一个简单的 Web 服务例子相对比。假设有一个简单的天气服务,暴露出一个名为“WeatherQuery”的 Web 方法,这个方法返回一个对象,包含温度和气压值。在通常情况下,人们拿现成代码,使用工具来暴露方法,并生成 WSDL。
如果你相信这个骗局,那么要做的无非就是找到一个 WSDL 在 Java 下的等价工具,然后生成存根(Stub)代码。
不巧的是,事情并没有那么简单,WSDL 是一个概括性的标准,而且实际上范围到可以让人自由诠释。在我们的例子里,我们发现.NET 强制使用基于文档的方法,而 Java 工具则采用了相反的 RPC 方法。此外,我们还发现以下方面存在问题:命名空间混淆,Schema 的包含,以及工具将 WSDL 切分成若干独立部分。简而言之,这项技术已经开始把注意力放在我们试图解决的实际问题之外了。
除了这些问题,我们还发现 Web 服务的工具之间存在不一致性。例如,对于自动发布的 WSDL 文档,不同版本的 Internet Information Server 和 Web Services Enhancements 之间,还有它们的 Java 等价产品,彼此之间只能部分兼容。
有些东西今天七拼八凑起来可以工作,但到了明天,如果服务的后续版本需要更复杂的 Web 方法时就得抛锚。这些东西真是令我们倍感厌烦。
更 RESTful 的风格
上面的方法里存在两个关键性假设:首先,仅暴露一个已有方法调用就足以给我们带来一个有意义的服务;其次,使用工具能使通过 Web 服务访问到这个服务的工作变成小菜一碟。
我们可以把请求看做一个包含请求的类型还有相应参数的文档,而不是考虑请求的参数和返回的类型。把这个文档当成对试图建模的业务过程中契约的一部分的描述。如果我们以相同的 WeatherQuery 方法为例,用常见的 XML 来描述它,那么就可以得到类似下面的东西——
同样,作为文档,返回类型看起来会像这样——
现在我们可以定义一个简单的类,用于表示文档中的相同字段——
关于这两个文档,有意思的是它们在.NET 和 Java 中都比较容易序列化和反序列化。对于.NET 来说,内建的 XML 序列化机制(带标注 /Annotations)可以很轻松地完成这项工作。对于同样一个类,C#代码如下:
Java 开发人员就没那么幸运了,因为 Java 并不提供内建的 XML 序列化工具:不过有一个名为 XStream 的开源项目( http://xstream.codehaus.org ),能帮我们完美地完成这项工作:
我们的 Weather 服务目前仅需负责文档的交换,而作为结果,我们就可以使用简单的 HTTP 而不用使用 Web 服务了。
有了这样的转变,我们就可以考虑让多个文件类型使用同一个进入系统的入口点,从而为我们带来一个更可扩展的方法。
编写 Java 服务器的代码
用于此服务 Java 实现的技术包括 XStream 序列化类库,以及任意一个 Servlet 容器,因为我们选择用 Servlet 提供这些服务。XStream 类库的源码是开放的,而 Servlet 容器可以是 WebLogic、WebSphere、JBoss、Geronimo 或者 Orion。如果对 J2EE 中的 E(企业特性)要求没有那么高,那么甚至只用 Resin、Tomcat 或者 Jetty 就可以了。
我们的 Servlet 应当实现doPost()
方法,而不是doGet()
方法。对于 XML,我们没有使用名称 / 键值对:我们仅仅使用完整的 POST 内容主体,这有点超出 HTTP 规范涵盖的范围了。不过,这只是个人喜好的问题——只要在服务器端和客户端相同就可以了。
在请求传入时,我们使用 XStream 将 XML 反序列化成一个命令对象,然后合理地进行处理就可以了。
编写.NET 服务器的代码
用于.NET 服务器端的代码要简单得多——我们仅需使用 Internet Information Server(IIS)和.NET 的内建 XML 序列化机制就可以了。内建的序列化机制要求 C#属性将字段标记成 XML 元素而不是 XML 属性。当然,还存在着 XStream 的一个.NET 移植版本,不过我们还没有拿它做过实验,而我们也听说过开源社区还将有另外一个移植版本发布。
编写 Java 客户端的代码
对于 Java 客户端,除了 XStream 以外,我们还得使用 Apache 的 HttpClient 类库(以及相应的依赖类库)。
请参见 http://jakarta.apache.org/commons/httpclient 。
HttpClient 类库用起来很简单,并且允许 POST 操作在执行之前被以编程的方式创建。记住,POST 内容主体完全是用 XStream 生成的 XML,形式可以是名称 / 键值对,或者是除去 HTTP 构造的完整请求。如果你希望为服务创建一个测试 Web 表单,那么前者可能更有吸引力一些。
编写.NET 客户端的代码
对于.NET 客户端,只要有.NET 框架就没问题了,不管是 1.1 或者 2.0 都可以。
对于 POST 操作,可以使用内建的 WebRequest 类和内建的序列化机制。
提升服务的可扩展性和兼容性
简单 POST 表达命令的 XML 的好处在于,我们可以在稍后加入更多的命令,而不需要改变我们的消息实现方式。服务器可以在运行期决定是否可以处理相应的 XML:
要处理今后可能出现的多种类型,好的解决方案是为每个类型注册一个处理器,如此一来就可以避免大量的 if/else 或者 switch/case 代码块了:
在使用 XML 是,人们面临着过分依赖于相关 Schema 的诱惑,我们必须仔细了解 Schema 验证和客户实际所需信息之间的差别。
如果 Schema 验证在运行期执行,会带给开发人员不恰当的安全感。错误仍然可能发生——可能错误的 XML 还是会被发出。但这时候出现了什么?答案是会有一个 Schema 无效的异常信息被抛出。使用把 XML 映射到类定义的方法,可能存在两种情况:要么得到一个正确的对象,要么该对象会丢失某些字段。在这种情况下,可以抛出一个带有真实原因的真正异常,它可以很容易地被转换回一条清晰的 XML 回复信息提供给客户。关键的区别在于,异常只在必要信息缺失的情况下抛出——其它东西都可以有变更。
这种设计所固有的可能性在于,XML 中可以加入新的元素(对应于类中的字段),从而使得让 API 在表义性上更上一层楼成为可能。经过仔细的测试之后,服务将可以支持客户发送旧版本的请求文档。一般意义上的 API 变更,以及“额外字段”级别上的变更,也会变得更加意义深远:
不过,要是把版本号也加入 URL 就更好了: http://x.com/weather/WeatherQuery/2.0
以 Web 服务的形式包装服务
如果你要在同一台服务器上放上另外一个服务,用于使刚才的服务处理标准的 Web 服务请求,那么没有什么可以阻止你这样去做。你所需要的,不过是 WSDL 指定的方法而已,就像这样:
在工具上的选择,包括:.NET 平台下的 WSE 2.0/3.0,Java 平台下是 Glue、AXIS、JAX-WS 或者 J2EE 容器内建的适配器。进行 SOAP 编码的方法可以简单交给为纯 REST 实现开发的反序列化 - 执行 - 序列化的代码来完成。这样一来,你就可以避免负责公司标准监控的人来找麻烦了。
将服务扩展至消息传送(Messaging)
在使用了 TIBCO Rendezvous 之后,我们就可以将请求的相同 XML 表现形式发送到隐式的异步服务执行了。我们还没有尝试过 MQ 系列,不过应该没有什么问题。
就我们的范例来说,这意味着针对天气详情的请求不会马上得到回复。相反,在等待片刻之后才会收到响应。这是一个巨大的变化,可能意味着你不得不改变或者放弃对 API 的某些简单设计。例如,下面的 Façade 方法可能派不上用场了:
取而代之的是两个接口,每一个都带了单独的方法,这样会更合适些:
关于消息传送的模式,有许多值得学习,不过在这里我们不打算深入探讨。然而,值得一提的是,存在两个常用的模式值得我们考虑一下。第一种设计是队列,在这个设计中,你发送的请求只能接受返回给你的请求。第二种概念是多播(Multicast),其中线上的事件被发送到多个订阅者处(出版者 / 订阅者模式)。此外,一个没有明确指出的事实,你可以设计出一个队列,让它持续不断地发送修正的信息。如果你熟悉 JMS 的话,那么 Rendezvous 在这方面和它只有些许不同。
WSDL 到哪儿去了?
我们编码完成的生产者 / 消费者设计以简单的形式交换 XML。但是,在正式的 Web 服务设计中发现阶段所固有的协议检查在我们这个例子里是不存在的。
对此,我们的建议是,这并不是必要的。相反,全面遵循敏捷思维,设计出一套全面符合持续集成(Continuous Integration)的集成测试。这样,在部署上线之前,服务的不兼容问题就可以事先发现。因此,你可以把一套复杂的 WSDL 规范丢在一旁,取而代之拥有的是一系列从提供者和使用者角度断言描述服务的单元测试。不管怎么说,WSDL 只能在运行期提示不兼容问题,而这时候恢复已经不是一件容易的事情了。这么说来,我们是不是错为它建了神龛了?
要以平台无关的方式创建这类测试,Schematron 是可选方案之一。
要辅助调试和文档,你还得准备一些范例文档:
此外,你还可以使用 XML Schema(XSD)来控制文档格式:
同时静态提供 XSD 和范例 XML 是个不错的注意(碰巧作者们在 XSD 方面意见不统一)。在这里“静态提供”指的是,API 可以由人们通过 Web 浏览器手动请求调用——
http://x.com/weather/xsd/WeatherQuer
http://x.com/weather/sample/WeatherQuery
记住,XSD 和范例文档都只是可选方案,也可以很容易地生成。
本文要点回溯
本例中的法宝是使用 Codehaus 的 XStream,通过 HTTP-POST 而不是 GET 与.NET 进行文档交换。其它的所有内容早就有人在博客上撰文详述或者写过相应白皮书了。选用 XStream 意味着消息本身的“规范”存在与 Java 和 / 或 C#的代码当中,而不存在于基于 XML、常常让你不得不面对 WS-* 规范的设计中。
此外,传统的 REST 金科玉律推荐使用 HTTP-GET,因为它更适合对命令进行编码……
http://x.com/weather/WeatherQuery?locn=Chicago
……在结果可以被 Web 服务器缓存的情况下尤其如此。或许我们这种风格最适用于通讯的场景,在这种情况下,试图进行缓存是没有意义的。在 GET 方案中存在着另外一些优势,这些优势是我们的方案所不具备的。GET 方案更优雅,并且在可测试性上更突出,人们可以使用浏览器通过完整的 URL 完成测试。然而,它所或缺的,是 POST 方法所带来多功能性,对于参数格式上不仅局限于名称 / 键值对;而且 XML 的有效载荷的复杂程度也可以任意指定。当然,在更大型的解决方案中,GET 和 POST 都拥有一席之地。
在这篇论文撰写过程中,我们向 XStream 团队提了一项建议(用补丁的形式),让 XStream 在合适的地方对属性的支持更加游刃有余。XStream 开发团队实现了必要的功能,而 XStream 今后的版本将可以支持 XML 属性,以便与.NET 实现更加无缝的互操作。
我们还提到,这个方法对于异步传输,如 Tibco RV,也同样适用。在这方面,目前 WS-* 规范的工具基本上没有提供支持。
在文章末尾,我们不能肯定这是否是 POST-REST(PREST),或者只是传统的 REST,不带缓存和 URL 简化功能,也没有新造的名词。
评论