从何时选择移动 Web 服务到总体设计指导原则再到用于移动 Web 服务的值类型,本文提出了在设计用于移动设备的 Web 服务时需要考虑的许多设计事项。文中还介绍了许多设计移动 Web 服务方面的最佳实践。从本文中,您可以了解如何决定何时使用 Web 服务、在设计 Web 服务时需要考虑什么事项,以及在规划移动 Web 服务时必须谨记哪些问题。
Web 服务是一种集成技术。在集成异构系统时 Web 服务的价值可以得到最好的证明,因为其支持许多类型的编程语言、运行时环境和网络。当需要从不兼容的环境连接应用程序时,Web 服务就有了用武之地。通过 Web 服务,您可以将业务应用程序从 Java? 2 Platform Enterprise Edition (J2EE) 连接到 .NET.您还可以使用某个运行在 Linux? 中的应用程序将一个应用程序集成在 Windows? 操作环境中。在本文中,我提供了一些针对移动 Web 服务的重要设计考虑事项,并且向您介绍了一些与之有关的最佳实践。
首先,我将讨论在开始之前 需要考虑哪些事项。
开始之前
在开始设计整个系统的体系结构之前,您必须做出如下决定——何时使用移动 Web 服务以及何时不使用移动 Web 服务。
对于移动设备,Web 服务是利用工作站的强大计算功能的一种最佳方式。Java Specification Request 172 (JSR-172) 定义了用于 Java 2 Platform Micro Edition (J2ME) 平台的 Web 服务 API.由于移动服务主要从客户端的角度进行编程并且是服务使用者,因此本文只需要介绍一部分远程服务调用 API (JAX-RPC) 和 JAXP (Java API for XML Parsing)。
设计移动 Web 服务的主要目的在于使嵌入式设备能够使用由服务器提供的服务,换句话说,移动 Web 服务是从 Web 服务使用者的角度进行设计的,目的在于支持轻量级设备共享服务器的计算功能和数据库。
移动 Web 服务无缝地集成了运行在不同平台上的两种不同的应用程序,并且提供了它们之间的互操作性。通常,在考虑移动设备的参与时,有三种类型的集成技术可以运用:
套接字通信
Web 服务
消息传递技术(如 WebSphere? MQ Everyplace)
与套接字通信和消息传递技术相比,Web 服务有一些突出的优势。Web 服务使用可扩展标记语言 (XML) 来传输消息(包括结构良好的数据信息),使用简单对象访问协议 (SOAP) 来传输对象。如果您使用的是套接字通信,则必须完全负责定义要传输的数据结构。而且,如果客户端和服务器是用不同的编程语言编写的(例如 C++ 和 Java 编程语言),则您的工作量将大大增加——您必须负责数据传输和 C++ 和 Java 编程中的编码细节。
消息传递软件可能是一种解决方案,但如果您所关注的是性能,并且不担心事务和安全级别,则消息传递软件真的不是一个非常好的选择。如果使用消息传递软件,您将花大量的时间和精力解决安全问题,并且您的客户很有可能站在您的门口问:“为什么这么慢?”
我不准备告诉您正确的选择应该是什么,而给出一些理由来说明为什么 Web 服务可能是一个好的选择。
其中的一个理由可能是服务器端编程。即使是像 Web 服务这样好的机制,也仍然由于 XML 处理以及传输和接收 SOAP 消息的开销的原因而不能满足严格的实时处理需求。因此在设计时需要考虑两个问题:
1.与普通的 HTTP 访问和专用的消息传递方法相比,每次 Web 服务调用的开销都比较大,所以当您主要考虑性能时,您可能需要首先选择另一项技术。
2.由于开销的原因,如果您只需要在应用程序的各层之间进行通信,就不必选择 Web 服务。例如,不要将 Web 服务放在应用程序的视图层和控制器层之间。
现在,我假定您已经决定使用 Web 服务。那么,在总体设计方面需要考虑哪些问题?
在决定使用 Web 服务之后
您需要考虑的下一个问题是 Web 服务的总体设计。可以运用下列通用设计指导原则:
1.管理 Web 服务的粒度。
2.首先定义 Web 服务接口,然后加以实现。
3.使用 Document/literal 作为编码样式。
4.优先选择 JavaBean 组件而不是 Enterprise JavaBean (EJB) 组件作为服务提供者。
5.避免 XML 元素嵌套太深,因为这可能大大延长解析、封送处理和取消封送处理的时间。
下面详细介绍这些设计考虑事项。
管理 Web 服务粒度
关于粒度管理需要谨记以下几点:
1.始终优先考虑粗粒度的 Web 服务;决不要在分布式系统之间使用细粒度 Web 服务调用。
2.Web 服务是一个很好的工具集,但是当您以细粒度的方式使用 Web 服务时,它可能对应用程序的性能有很大的影响,因为 XML 解析、序列化和反序列化的开销很高;这种开销可能占处理时间的百分之三十。
在本文中,我以 Task Management 系统中的登录功能为例。驱动程序首先登录到系统,如果登录成功,则有两列任务显示在屏幕上。
对于此登录问题有两种解决方案:
1.一种解决方案是首先调用登录方法,然后当该方法调用返回 true 时,调用一个方法来获取相应的任务。
2.其他的事情将在一步中完成。如果驱动程序登录到 Task Management 系统,则返回驱动程序的角色以及分配的任务和启动的任务。如果登录失败,则返回指示驱动程序未登录到该系统的信息。
对于第一种解决方案,它是细粒度登录系统,而显示任务列表需要两次调用 Web 服务;这可能带来很大的延迟。第二种解决方案是粗粒度登录系统,它返回您在一次调用中需要的所有信息,并确保由于网络延迟和系统 I/O 带来的影响最小。
首先定义 Web 服务接口,然后进行实现。
这不仅从移动 Web 服务的角度来看是适用的,而且从 Web 服务设计和(更高的层次)面向对象的软件设计来看也是适用的。正如您所知道的,接口是客户端和服务提供程序之间的契约,而且保持稳定非常重要(因为正如您所知道的,实现容易改变)。
定义功能接口非常直接和直观——只需考虑:
1.您的目标
2.您需要获取什么信息
3.您需要将何种结果返回到客户端
然后,您应该将该接口的相关要点写下来,并在这些要点的指导下完成所有实现细节。
这是一条简单的设计指导原则。了解您的目标非常重要,目标可以驱动您编写测试用例,并且指导您编写功能实现,这也是为什么测试驱动开发 (TDD) 广泛地应用于各种开发技术的原因。
使用 Document/literal 作为编码样式
目前,JAX-RPC 支持三种操作模式:
1.RPC/encoded.其优点在于简单,接收方可以轻松地将消息发送到操作的实现。其缺点在于类型编码信息的开销较大,这会降低吞吐量性能。
2.RPC/literal.其优点与 RPC/encoded 相同,而且 遵循 WS-I 组织制定的规范(请参阅参考资料)。
3.Document/literal.其优点在于没有类型编码信息,任何 XML 验证器都可以验证消息。其缺点在于 SOAP 消息中缺少操作名,所以发送消息很难甚至不可能。
在一些资料中,您还可能发现名为 Document/encoded(使用这种模式的人不多)和 Document/literal wrapped(由 Microsoft 定义,但是没有相关规范,其缺点在于比其他模式复杂)的模式。对这些操作模式的详细解释可以在参考资料中的“Which style of WSDL should I use?”内找到。
在这些模式中,WS-I 标准仅支持 RPC/literal 和 Document/literal.对于移动 Web 服务,JAX-RPC 实现必须使用 Document/literal 将基于 Web 服务描述语言 (WSDL) 的服务描述映射到相应的 Java 表示形式。因此,如果您只使用 Document/literal 作为编码样式,则您是最安全的。
优先选择 JavaBeanser 组件而不是 EJB 组件作为服务提供程序
在使用 Java 编程语言公开服务时,需要考虑两种类型的服务提供程序:
1.从 JavaBean 组件生成 Web 服务
2.使用无状态会话 EJB 组件
虽然在某些情况下,EJB 组件非常有用,但是 JavaBeans 组件常常是更好的选择,特别是在开发移动 Web 服务时。实现使用 JavaBean 组件生成的服务提供程序比较简单和容易,而且与相应的会话 EJB 组件相比,JavaBean 组件更稳定。但是,如果您需要从使用 EJB 组件开发的现有 J2EE 应用程序公开 Web 服务,则请使用 EJB 组件。
避免 XML 元素嵌套太深
如果数组的数组、复杂类型的数组或包含另一个自定义复杂类型的复杂类型等嵌套太深,则将大大影响 Web 服务的性能。清单 1 显示一个 XML 描述示例——一个自定义数据类型 Task 类的数组:
清单 1. 自定义数据类型
import java.io.Serializable; public class Task implements Serializable { /** * The id of the task */ private int taskID = 0; /** * Owner name of the task */ private String ownerName; /** * public default non-argument constructor * */ public Task() { } /** * Constructor of the Task class * * @param taskID * id of the task * @param ownerName * owner name of the task */ public Task(int taskID, String ownerName) { this.taskID = taskID; this.ownerName = ownerName; } /** * @return Returns the ownerName. */ public String getOwnerName() { return ownerName; } /** * @param ownerName * The ownerName to set. */ public void setOwnerName(String ownerName) { this.ownerName = ownerName; } /** * @return Returns the taskID. */ public int getTaskID() { return taskID; } /** * @param taskID The taskID to set. */ public void setTaskID(int taskID) { this.taskID = taskID; } } |
清单 2. 返回自定义数据类型的数组的方法
public Task[] getTasks(String name){ Task[] tasks = new Task[5]; for(int i=0; i<5; i++){ tasks[i] = new Task(i, name); } return tasks; } |
当使用 getTasks()(如清单 3 所示)所属的 JavaBean 组件公开 Web 服务时,Task 类映射到其中包含 Task 类的名称空间的 tn2:Task。
清单 3. WSDL 定义中的 XML 数据类型
<complexType name="Task"> <sequence <element name="ownerName" nillable="true" type="xsd:string"/> <element name="taskID" type="xsd:int"/> </sequence> </complexType> |
同时,数据类型 Task[] 映射到 ArrayOf_tn2_Task;ArrayOf_tn2_Task 的 XML 描述如清单 4 所示:
清单 4. ArrayOf_tn2_Task 的 XML 描述
<complexType name="ArrayOf_tns2_Task"> <sequence> <element maxOccurs="unbounded" minOccurs="0" name="Task" nillable="true" type="tns2:Task"/> </sequence> </complexType> |
如清单 4 所示,为单个自定义复杂类型数组生成的 XML 描述很长。相反,Java 语言中的单个 String 类型映射到 xsd:string,而没有生成 complexType 元素;诸如 boolean、int 和 byte 这样的基元类型分别映射到 xsd:boolean、xsd:int 和 xsd:byte。
您可能已经注意到 XML 元素的嵌套(避免嵌套太深)和粒度考虑(使用粗粒度)之间的冲突。在实际运用中,嵌套和粒度之间应该有一个平衡。如果您更关注应用程序的性能,则应该仔细地权衡这两个考虑事项,以获得一个更好的解决方案。