(源自:http://www.infoq.com/cn/articles/REST-INTEROP;jsessionid=E8D432D42B6F4B8903046C1A8A0D5197)
作者 Paul Hammant, Ian Cartwright, ThoughtWorks, Inc译者 Jason Lai 发布于 2008年1月17日 下午8时0分
本文是一个关于REST的讨论,也是一套在Java和.NET之间进行连线互操作的简单低依赖性解决方案。
在本文中,我们打算展示一下如何使用简单的技术加上以文档为中心的方式带来有价值的业务服务,而无须使用专有的中间件,也不必引入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简化功能,也没有新造的名词。
查看英文原文:Simple Java and .NET SOA interoperability