REST Web 服务需要扩展以满足日益提高的性能要求。 具有负载平衡和故障转移功能、代理和网关的服务器集群通常以形成服务拓扑的方式进行组织,从而允许根据需要将请求从一个服务器路由到另一个服务器,以减少 Web 服务调用的总体响应时间。 要使用中间服务器扩大规模,REST Web 服务需要发送完整、独立的请求;也就是说,发送的请求包括所有需要满足的数据,以便中间服务器中的组件能够进行转发、路由和负载平衡,而不需要在请求之间在本地保存任何状态。
完整、独立的请求不要求服务器在处理请求时检索任何类型的应用程序上下文或状态。 REST Web 服务应用程序(或客户端)在 HTTP Header 和请求正文中包括服务器端组件生成响应所需要的所有参数、上下文和数据。 这种意义上的无状态可以改进 Web 服务性能,并简化服务器端组件的设计和实现,因为服务器上没有状态,从而消除了与外部应用程序同步会话数据的需要。
图 1 演示了一个有状态的服务,某个应用程序可能向其请求多页结果集中的下一个页面,并假设该服务跟踪应用程序在结果集中导航时的离开位置。 在这个有状态的设计中,该服务递增并在某个位置存储 previousPage 变量,以便能够响应针对下一个页面的请求。
图 1. 有状态的设计类似如此的有状态的服务变得复杂化了。 在 Java Platform, Enterprise Edition (Java EE) 环境中,有状态的服务需要大量的预先考虑,以高效地存储会话数据和支持整个 Java EE 容器集群中的会话数据同步。 在此类环境中,存在一个 Servlet/JavaServer Pages (JSP) 和 Enterprise JavaBeans (EJB) 开发人员非常熟悉的问题,他们经常在会话复制过程中艰难地查找引发 java.io.NotSerializableException 的根源。 无论该异常是由 Servlet 容器在 HttpSession 复制过程中引发的,还是由 EJB 容器在有状态的 EJB 复制过程中引发的,这都是个问题,会耗费开发人员几天的时间,尝试在构成服务器状态并且有时非常复杂的对象图表中查明没有实现 Serializable 的对象。 此外,会话同步增加了开销,从而影响服务器性能。
另一方面,无状态的服务器端组件不那么复杂,很容易跨进行负载平衡的服务器进行设计、编写和分布。 无状态的服务不仅性能更好,而且还将大部分状态维护职责转移给客户端应用程序。 在基于 REST 的 Web 服务中,服务器负责生成响应,并提供使客户端能够独自维护应用程序状态的接口。 例如,在针对多页结果集的请求中,客户端应该包括要检索的实际页编号,而不是简单地要求检索下一页(请参见图 2)。
图 2. 无状态的设计无状态的 Web 服务生成的响应链接到结果集中的下一个页编号,并允许客户端完成所需的相关工作以便保留此值。 可以作为大致的分离将基于 REST 的 Web 服务设计的这个方面划分为两组职责,以阐明如何维护无状态的服务:
服务器
客户端应用程序与服务之间的这种协作对于基于 REST 的 Web 服务中的无状态性极为重要。 它通过节省带宽和最小化服务器端应用程序状态改进了性能。
公开目录结构式的 URI从对资源寻址的客户端应用程序的角度看,URI 决定了 REST Web 服务将具有的直观程度,以及服务是否将以设计人员能够预测的方式被使用。 基于 REST 的 Web 服务的第三个特征完全与 URI 相关。
REST Web 服务 URI 的直观性应该达到很容易猜测的程度。 将 URI 看作是自身配备文档说明的接口,开发人员只需很少(如果有的话)的解释或参考资料即可了解它指向什么,并获得相关的资源。 为此,URI 的结构应该简单、可预测且易于理解。
实现这种级别的可用性的方法之一是定义目录结构式的 URI。 此类 URI 具有层次结构,其根为单个路径,从根开始分支的是公开服务的主要方面的子路径。 根据此定义,URI 并不只是斜杠分隔的字符串,而是具有在节点上连接在一起的下级和上级分支的树。 例如,在一个收集从 Java 到报纸的各种主题的讨论线程服务中,您可能定义类似如下的结构化 URI 集合:
{topic}
根 /discussion 之下有一个 /topics 节点。 该节点之下有一系列主题名称,例如闲谈、技术等等,每个主题名称指向某个讨论线程。 在此结构中,只需在 /topics/ 后面输入某个内容即可容易地收集讨论线程。
在某些情况下,指向资源的路径尤其适合于目录式结构。 例如,以按日期进行组织的资源为例,这种资源非常适合于使用层次结构语法。
此示例非常直观,因为它基于规则:
{topic}
第一个路径片段是四个数字的年份,第二个路径片断是两个数字的日期,第三个片段是两个数字的月份。 这样解释它可能有点愚蠢,但这就是我们追求的简单级别。 人类和计算机能够容易地生成类似如此的结构化 URI,因为这些 URI 基于规则。 在语法的空隙中填入路径部分就大功告成了,因为存在用于组合 URI 的明确模式:
{year}/{day}/{month}/{topic}
在考虑基于 REST 的 Web 服务的 URI 结构时,需要指出的一些附加指导原则包括:
URI 还应该是静态的,以便在资源发生更改或服务的实现发生更改时,链接保持不变。 这可以实现书签功能。 URI 中编码的资源之间的关系与在存储资源的位置表示资源关系的方式无关也是非常重要的。
传输 XML、JSON 或同时传输这两者资源表示形式通常反映了在客户端应用程序请求资源时的资源当前状态及其属性。 这种意义上的资源表示形式只是时间上的快照。 这可以像数据库中的记录表示形式一样简单,其中包括列名称与 XML 标记之间的映射,XML 中的元素值包含行值。 或者,如果系统具有数据模型,那么根据此定义,资源表示形式是系统的数据模型中的对象之一的属性快照。 这些对象就是您希望您的 REST Web 服务为客户端提供的资源。
基于 REST 的 Web 服务设计中的最后一组约束与应用程序和服务在请求/响应有效负载或 HTTP 正文中交换的数据的格式有关。 这是真正值得将一切保持简单、可读和连接在一起的方面。