第 6 章 以 REST 方式集成业务伙伴
6.2 把服务开放给外部客户
在进行这个项目之前,我向我们的用户组和关于 SOAP(简单对象访问协议,Simple Object Access Protocol)和 Web 服务架构的会议做了一些技术报告。因此,当客户电话打
来的时候,似乎我所讲述的东西正是这个客户正在寻找的解决方案。然而,在理解了他们的 真正需求之后,我认为更好的方式是通过 HTTP 上简单的 GET 和 POST 请求把一组服务开放出 去,并且在服务中交换描述这些请求和响应的 XML 数据。不过我在当时并不知道,这种架构 形式现在通常叫做 REST,或者叫做具象状态传输(Representational State Transfer)。
我如何决定在 SOAP 上使用 REST?下面是一些在选择 Web 服务架构时需要考虑的决策因 素:
有多少不同的系统需要访问这些服务,并且在当时有多少系统是已知的?
虽然这个制造商知道目前只有一个销售商需要访问这个系统,但它也承认其他的销售商 在将来也可能需要访问。
是否有一些终端用户需要预先了解这些服务,或者这些服务是否需要是自描述的,以便 于让匿名用户自动进行连接?
因为在制造商及其所有的销售商之间存在一个确定的关系,所以需要保证每个潜在的终 端用户都要预先知道如何访问制造商的系统。
在单个事务中需要维护什么样的状态?一个请求是否依赖于前一个请求的结果?
在我们的情况中,每个事务都包含一个请求和一个不依赖于其他任何信息的结果。
在这个项目中对上述问题的回答使我得出了显而易见的决策:在 HTTP 协议上开放一组 已知的服务,并且使用在双方系统中都能够理解的标准电子商务协议来交换数据。如果制造 商希望匿名用户也能够查询产品的现货供应能力,那么我可以选择完整的 SOAP 解决方案,
因为这将使系统能够发现这些服务以及可编程的接口而无需预先了解系统。
我目前从事生物信息领域,在这个领域中明确需要 SOAP 形式的 Web 服务架构。我们利 用了一个叫作 BioMoby(http://www.biomoby.org)的项目来定义 Web 服务并且把它们发布 到一个中央存储仓库,以使得其他小组能够正确地把我们的服务应用在构建数据管道的工作 流中,从而帮助生物学家们集成不同集合的数据并且对结果进行不同的分析。这是一个完美 的示例,它很好地说明了为什么有人选择在 REST 上的 SOAP。匿名用户可以访问我们的数据 和工具而甚至无需预先知道它们的存在。
6.2.1 定义服务接口
在确定如何实现这个软件时,首先要确定的就是用户如何发出请求和接受响应。在和这 个销售商(主要用户)的一位技术代表讨论之后,我了解到他们的新系统可以通过 HTTP POST 请求发送一个 XML 文档,并且把结果作为一个 XML 文档来分析。XML 必须遵循 Rosettanet 电子商务协议(后面还将做更详细的讨论)的格式,但就目前来说,只要知道这个系统能够 通过发送 XML 格式的请求和响应在 HTTP 上进行通信就足够了。在图 6-1 中说明了各个系统 之间的常见交互。
图 6-1 后端系统的服务接口
这个制造商最近被一个更大的公司收购了,这个公司在整个组织内都使用 IBM 的产品。
因此,我已经知道了所使用的是什么样的应用服务器及相关技术。我把这个服务实现为一个 运行在 IBM WebSphere 上的 Java Servlet。由于我知道这个软件将需要使用基于 Java API 来访问运行在 AS/400 服务器上的函数,因此很容易做出这个决策。
下面是在 web.xml 文件中的代码,这个文件描述是将为用户提供必要接口的 servlet:
<servlet>
<servlet-name>HotKeyService</servlet-name>
<display-name>HotKeyService</display-name>
<servlet-class>com.xxxxxxxxxxxx.hotkey.Service</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>HotKeyService</servlet-name>
<url-pattern>/HotKeyService</url-pattern>
</servlet-mapping>
servlet 本身仅处理 POST 请求,这是通过重载 Servelet 接口的 doPost 方法并且提供 了标准生命周期方法的默认实现来完成的。在下面的代码中给出了这个服务的完整实现,但 当我最初对问题进行分解并设计一个解决方案时,我首先在代码中写下了一系列的注释作为 占位符用来表示将要插入代码的位置。然后我将逐步用代码来代替每个伪码注释,直至最终 得出可以运行的实现为止。这将有助于我把注意力放在每段代码如何关联到整个解决方案 上:
public class Service extends HttpServlet implements Servlet {
public void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//读取请求数据并且保存在 StringBuffer 中 BufferedReader in = req.getReader( );
StringBuffer sb = new StringBuffer( );
String line;
while ((line = in.readLine( ))!= null) { sb.append(line);
}
HotkeyAdaptor hotkey = null;
if (sb.toString( ).indexOf("Pip3A2PriceAndAvailabilityRequest") > 0) {
else if (sb.toString( ).indexOf("Pip3A5PurchaseOrderStatusQuery ") > 0) {
仔细阅读这段代码,你可以看到它首先读取请求数据并且将其保存起来以用于后面的操 作。然后它将在这个数据中进行查找以确定其是哪种类型的请求:是价格和现货供应信息请 求,还是订单状态查询请求。在确定了请求的类型后,将会创建相应的辅助对象。注意,我 使用接口 HotkeyAdaptor 来获得多个实现而无需为每种类型的请求编写大段重复的代码。
这个方法的其他功能包括解析 XML 请求数据,在 AS/400 系统上执行合适的查询,创建
public interface HotkeyAdaptor {
public void setXML(String _xml);
if (sb.toString( ).indexOf("Pip3A2PriceAndAvailabilityRequest") > 0) { //价格和现货供应信息请求
hotkey = HotkeyAdaptorFactory.getAdaptor(
HotkeyAdaptorFactory.ROSETTANET,
HotkeyAdaptorFactory.PRODUCTAVAILABILITY);
}
else if (sb.toString( ).indexOf("Pip3A5PurchaseOrderStatusQuery ") > 0) {