[译文] 实现类Web应用灵活性的RESTful核心——第4部分——模式(上)
原文:A RESTful Core for Web-like Application Flexibility - Part 4 - Patterns
作者:Randy Kahle和Tom Hicks
引言
在这一系列的头三篇文章[1][2][3]中,我们讨论了一种基于一组“REST风格的(RESTful)”核心原则的应用软件架构方法,这一面向资源的计算(Resource Oriented Computing,ROC)方法是出于希望看看在万维网(World Wide Web)中发现的灵活性是否能够被纳入到应用软件中。
之前的文章研究了这种体系结构的物理层以及物理层与逻辑层之间的交界,本文则介绍在逻辑层面上的编程,并讨论一些可用于ROC应用设计的逻辑层面的设计模式。
在逻辑层面上的编程
Hello World
在展示需要用来创建响应客户端请求的应用的最小结构方面,Hello World”这一有代表性的程序是很有用的,就我们所描述的ROC实现来说,以下这些组件必须准备就绪:
检测请求“Hello World”这一资源的外部事件的传输器(transport),
“Hello World”资源,
包含“Hello World”资源的模块,
把传输器的内部请求路由到将会返回资源表述的端点的映射定义。
我们在前面的文章中已经介绍了这些基本的组成部分,因此在这里我们就致力于解释这些组件如何一起工作,构建出“Hello World”这一应用。
我们应该记得传输器是驻留在系统的边缘地带的事件检测器,其负责检测和处理那些隐藏了各种细节的外部事件,这些细节包括传输协议和所有协议特有的问题等。我们故意先不明确定义该例子的传输器,然后假定选择的传输器检测到了外部的请求事件,并发起一个内部的根请求,要求获取位于逻辑地址“resource:/helloworld”上的资源。
传输器通常在其自己的模块内实现,并会从应用中导入一个或多个希望通过该传输器来接收请求的模块。系统内部的每个模块都有一个唯一的URI标识符[4],该标识符被用在导入语句中,可以在导入语句中指定模块的版本号(最小,最大),如果没有指定的话,则导入可用的最新模块版本。
<import>
<uri>urn:com:mycomp:demo:helloworld</uri>
<version-min>2.0</version-min>
</import>
使用逻辑标识符和模块版本号的作用是很重大的,因为模块关系是通过逻辑地址来定义的,为逻辑URI地址定位物理端点的解析过程同样也用来解析模块间的关系,这意味着在应用运行的时候可以添加、更新和回滚模块,一个模块的不同版本也可以同时在单个系统内运作而不会产生冲突。
举个例子来说,假设某个现有模块的1.0.1版本显露出了某个缺陷,因此需要部署更新后的模块(版本1.0.2),那么模块部署管理器能够上传这一模块的更新后的JAR文件并执行一个“热重启”。这一做法支持微内核动态地加载新的模块到内存中,然后其在这之后解析的所有相应请求都指向该模块。如果需要的话,那么该模块的初始版本甚至可以继续与已更新后的版本并行运作,不过,这就要求到最初版本的请求通过包含期望的版本号(1.0.1)来特别地指定为最初的模块。
为了暴露“Hello World”资源,我们的“Hello World”模块必须要导出资源的地址:
<export>
<uri>resource:/helloworld</uri>
</export>
其还需要把逻辑资源地址映射到返回资源表述的物理端点上,其在自己的内部私有地址空间实现这一点,如下:
<map>
<match>resource:/helloworld</match>
<class>com.mycomp.HelloWorldEndpoint</class>
</map>
有了为“Hello World”例子设定的这一配置,我们就已经做好了执行一个请求/响应周期流程的准备。当外部的客户端请求到达某个传输器时,该传输器检测该事件并使用地址“resource:/helloworld”发起一个对资源的根请求。微内核试图在传输器模块的地址空间内部解析该请求,并找到定义了我们的“Hello World”模块的导入语句。由于传输器已经导入“Hello World”应用模块,因此解析过程会找到模块的导入语句,并且会遇到一个匹配,然后其进入该模块继续解析的搜索。在我们的模块中,微内核发现了一个到物理端点的映射,于是其把端点的处理过程调度到一个工作者线程上,在端点返回表述的时候,微内核会把表述传回给传输器,而传输器,依次,又把表述往回传递给外部发送请求的客户端。
聚合
作为把地址“resource:/helloworld”直接映射到物理端点上(正如我们之前所做的那样)的一个代替,我们可以把它映射到一个模板化的微服务(micro-service)上,微服务可以构造或者是“聚合(mashup)”表述。
<map>
<match>resource:/helloworld</match>
<to>active:template-service+operand@resource:/template.html</to>
</map>
该模板微服务需要一个参数:模板资源的逻辑地址,而模板资源又相应地会包含用来组成最后的资源表述的资源的逻辑地址:
<html>
<body>
<template:include uri="resource:/menu.xml"/>
...
<template:include uri="resource:/helloworld.xml"/>
...
<template:include uri="resource:/footer.xml"/>
</body>
</html>
模板的微服务在处理该模板时,其向逻辑地址空间发回一个子请求以获得组成部分的资源的表述,每个子请求都被发送给微内核,然后从提供这一模板服务的模块的地址空间开始进行解析。如果某个具体的资源之前已经被请求过的话,那么它的表述有可能会在系统缓存中,在这种情况下微内核返回缓存的值。实际上,顶层请求(“resource:/helloworld”)的资源表述也有可能已经存放在缓存中,如果是这样的话,则模板化的微服务就完全不会被调用(除非其依赖的资源之一发生变化或者是已到期)。
需要注意的是,从事模板这一工作的开发者(或者图片设计者)只需要了解所需资源的逻辑地址就可以了,API、数据类型、对象、协议以及物理位置等方面的知识是无需知道的,因为这些细节被隐藏到了应用的物理层之下。
我们的“聚合”操作还能够组合位于互联网上的任何可寻址的位置上的远程资源,通过使用实现了http:方案的端点,我们能够使用HTTP协议来对位于整个互联网中的资源发出请求。例如,简单地把URI修改成常见到的http:方案URL:
<html>
<body>
<template:include uri="resource:/menu.xml"/>
...
<template:include uri="http://www.1060.org/upload/readme.xml"/>
...
<template:include uri="resource:/footer.xml"/>
</body>
</html>
第二个逻辑地址的内容将会从一个远端的网站[5]上取得,可以注意到,唯一的不同只是修改了资源的逻辑地址,这一模板化服务不需知道某个资源是位于远端的。
更好的应用设计甚至会在某个较低的逻辑层内部隔离远端地址信息,为了创建这样的一个分层,我们使用了从一层到另一层的一组映射。在我们的例子中,以下的配置把逻辑资源地址映射到远端的逻辑地址上:
<map>
<match>resource:/helloworld.xml</match>
<to>http://www.1060.org/upload/readme.xml</to>
</map>
这种逻辑到逻辑的映射创建出了一个层次,在这个层面上,聚合组件都有自己的名称,这对那些创建聚合应用的组件来说很有作用;而且其允许在不影响聚合层面的情况下重新定位资源的地址(本地的、远端的等等)
层次和映射器
好的系统设计往往依赖于多层次来分离不同的概念层,以及把设计和实现的细节隔离成(几乎)不相交的集合。保持了“关注点分离”的层次的运用不仅只是在设计良好的应用中才会见到,其也会出现在诸如硬件芯片设计以及操作系统等其他许多复杂的系统中。通过使用我们已经讨论过的ROC组件(模块、逻辑到逻辑的映射、微服务等等),在面向资源的计算体系结构中创建分层的逻辑系统是很容易的。
借助于ROC,应用设计中的每个层次都可以被赋予一个地址空间,每个这样的地址空间都可以由一个模块来定义和管理。如果设计要求层次A驻留在层次B之上的话,那么层次A(较高那层)的模块会导入层次B(较低那层)的模块。逻辑到逻辑的映射通常被定义来连接他们的地址空间,映射定义了较高那层内部的请求子集,这些请求子集会被传送到较低一层进行处理。一种常见的ROC应用设计使用了三个分层:表述层、领域层和信息访问及整合层。
[译文] 实现类Web应用灵活性的RESTful核心——第4部分——模式(下)