`
mondayw
  • 浏览: 139793 次
  • 性别: Icon_minigender_2
  • 来自: 广州
社区版块
存档分类
最新评论

[译文] 实现类Web应用灵活性的RESTful核心——第3部分——逻辑层面的编程

阅读更多

原文:A RESTful Core for Web-like Application Flexibility - Part 3 - Logical Level Programming

作者:Randy KahleTom Hicks

出处:http://www.theserverside.com/news/1363808/A-RESTful-Core-for-Web-like-Application-Flexibility-Part-3-Logical-Level-Programming

 

引言

在这一系列的头两篇文章[1][2]中,我们讨论了一种基于一组“REST风格的(RESTful)”核心原则的应用软件架构方法,这一面向资源的计算(Resource Oriented ComputingROC)方法是出于希望看看在万维网(World Wide Web)中发现的灵活性是否能够被纳入到应用软件中。

既然之前的文章已经研究了这种体系结构的物理层,因此本文研究物理层与逻辑层之间的界限的有关细节。我们的目的是展示物理层是如何支持逻辑层的,以及为在随后的文章中讨论逻辑层提供一个坚实的基础。在这一逻辑层面上,我们将会看到应用是如何灵活地由资源和小型的、低开销的、内部的服务组成的,这些服务也被称作微服务(micro-service)。

 

URI方案

如我们在前面的文章中讨论过的一样,客户端通过发送对由URI地址来标识的资源的请求来索取信息,URI地址由方案(scheme)和方案特定部分(scheme specific part[3]组成:

 

{方案名称} : {方案特定部分}

 

可以看到URI的语法允许非常容易地引入新的方案到这一系统中,每个新方案指定了一组逻辑地址,这些逻辑地址被映射到一个或多个物理端点上。

例如,假设我们要添加一个名为“var:”的新的URI方案,这一var:方案的URI指向代表了由一个临时存储服务管理的资源,为了把这些逻辑地址映射到物理端点上,我们可以这样来制定一个映射:

 

<map>

      <match>var:.*</match>

      <class>com.mycomp.endpoint.VARSchemeEndpoint</class>

</map>

 

这一映射在match这一元素中使用了一个正则表达式来把所有使用了新的var:方案的URI地址映射到某个Java端点类上,该类实现了临时存储服务。Java端点能够被编码成支持一整套请求动词(SOURCESINKNEWEXISTSDELETE等),并且能够使用任何适当的存储机制,例如在内存中的Java对象等来管理var:方案的资源。这样的实现使得客户端代码能够通过动词SINKvar:方案地址发送临时存储的请求,例如“var:tax-rate”。之后的客户则可以使用动词SOURCE发送请求到相同的地址来检索资源的表述。

另一种设计逻辑地址URI的方法的典型代表是active:方案,这是一个最初由惠普的研究人员[4]提出的多功能的、面向服务的URI方案,active:方案使用以下语法来编码对服务的一个调用,可以以URI地址的方式带零个或者多个命名参数:

 

active: {服务名称} ['+' {参数名称} @ {参数URI地址}] *

 

通过几个例子就可以清楚地说明active:方案及其用法,一个不需要任何参数的服务例子如下:

 

active:random

 

使用动词SOURCE向该地址发送请求的结果为获得01间的一个数值的表述。

active:方案的一个用到参数的服务例子是XSLT微服务,该服务需要两个参数,参数“operand”指向需要被转换的XML信息;参数“operator”指向定义转换的XSLT样式表。假定active:xslt这一地址空间已通过以下的映射绑定到了这一XSLT服务上:

 

<map>

      <match>active:xslt.*</match>

 

      <class>com.mycomp.endpoint.XSLTEndpoint</class>

</map>

 

那么一个使用动词SOURCE向该逻辑URI地址发送的请求:

 

active:xslt+operand@resource:/data.xml+operator@resource:/style.xsl

 

引发的最终结果是通过微内核调用XSLT服务,物理层的XSLT服务端点代码将按顺序向这一逻辑地址空间发回两个子请求(sub-request),一个是为了获得地址“resource:/data.xml”上的资源表述,另一个则是为了获得地址“resource:/style.xsl”上的资源表述。在XSLT转换完成后,XSLT服务会创建一个结果的表述,并把该表述返回给微内核,后者再把表述转发给那个发请求的客户端。

形成每个active:方案地址的文本必须要解析,以提取出服务的名称以及每个参数的名称和URI地址。虽然每个端点都能够做这一类的解析,但是让微内核来解释active:方案会使得端点更易于编写。利用微内核的解析支持,XSLT服务的端点类就可以用这样的代码来实现processRequest方法:

 

public void processRequest(Context context) throws Exception

     {

     Request req;

     Representation dataRep;

     Representation styleRep;

     String uri;

 

     XMLDOM domData;

     XMLDOM domStyle;

 

     //获得由operand参数指定的资源

     uri = context.getParameter("operand");

     req = context.createRequest(uri);

     req.setVerb(Request.SOURCE);

     req.setType(XMLDOM.class);

     dataRep = (XMLDOM)context.issueRequest(req);

 

     //获得由operator参数指定的资源

     uri = context.getParameter("operator");

     req = context.createRequest(uri);

     req.setVerb(Request.SOURCE);

     req.setType(XSMLDOM.class);

     styleRep = (XMLDOM)context.issueRequest(req);

 

     //operand指定的资源做XSLT转换

     resultRep = ...

 

     response = context.createResponseFrom(resultRep);

     response.setMimeType("text/xml");

     response.setCacheable();

     context.setResponse(response);

     }

 

我们希望本节内容能够说明,在软件构造方面使用逻辑URI寻址在ROC架构中并不是一个限制因素,相反,URI寻址有足够的灵活性,允许创建和使用服务特定的地址,并且允许自定义URI方案。

 

映射和地址的解析

逻辑地址的解析正好位于ROC架构的逻辑层与物理层的交界处,为了解析请求的逻辑地址,微内核通过搜索一组映射来查找第一个“匹配”的映射。可能会有各种各样不同的地址匹配方案,这样在控制匹配(进而映射)过程方面就会提供极大的灵活性。微内核在找到匹配的映射时,其使用映射信息来把请求绑定到服务端点上,并调度对请求的处理。

我们在前面已见到单个地址或者是多组地址是如何被映射到物理端点上的,不过地址解析也可以在逻辑层面上起作用。微内核在试图解析逻辑地址到物理端点的映射的时候,其亦可以利用逻辑到逻辑地址的映射,如果这些映射中的某个与当前请求的逻辑地址相匹配的话,那么微内核就会使用由映射指定的替代地址来代替当前请求的URI,以下的映射例子

 

<map>

      <match>resource:/customer/(.*)</match>

      <to>resource:/$1</to>

 

   </map>

 

使用了正则表达式来描述匹配和代替,用以把所有符合以“resource:/customer/”开始的逻辑地址转换成去掉“/customer”部分的逻辑地址。例如,微内核会使用地址“resource:/som”来代替URI地址“resource:/customer/som”,然后再继续使用新的地址来搜索物理端点。

这一功能允许应用设计者重定位整个地址空间。例如,Wiki应用的内部地址空间在Wiki模块内部可能是与“resource:/”绑定的,当Wiki被用在一个更大的应用套件中时,一个逻辑到逻辑的重写规则可能会把Wiki的地址空间置成,比如,为“resource:/public/application/wiki/.*”。

逻辑层的地址也可以映射到微服务上,比如以下的逻辑到逻辑地址的映射

 

<map>

      <match>resource:/customers</match>

      <to>active:sqlQuery+operand@resource:/sql/customersQuery.sql</to>

 

</map>

 

会导致对客户信息的请求被映射到active:sqlQuery服务上,该服务可以使用由参数“operand”指定的SQL查询来查找关系数据库,返回所有客户信息的表述[5]

 

模块和地址的解析

 

正如在上一篇文章中所提到的那样[2]ROC架构中的逻辑地址空间由各模块来定义、实现和管理。模块中的export语句声明了模块可接受的地址,因此,其宣称了对这一地址空间的责任[6],各模块还是之前描述的逻辑地址解析映射的容器。

由于某个模块可以引入其他的模块,而其他的模块依序又可以引入另一些模块,因此,地址解析过程必须遍历由各互相连接的模块定义的分层的地址空间和映射。在解析的过程中,微内核必须要确定是否需要“下入”到某个引入的模块中搜索端点映射,微内核检查当前请求的URI地址,并按照各个模块引入的顺序,比较URI地址和每个引入模块所声明其暴露的地址空间,如果有匹配的话,微内核则会向下进入到相应模块中继续解析搜索,然而一旦进入某个模块,解析过程就要有成功或者失败的结果,解析搜索不会退出已经进入的模块,又去搜索更高一层的其他模块(毕竟,只有在某个模块已经声明其可以处理请求地址的地址空间的情况下才会进入该模块)。

解析过程的影响是请求会沿着模块的树状结构“传送”,因此,传送贯穿了一组地址空间。例如,假设模块ACC暴露的地址空间是“resource:/accounting/.*”,模块HR暴露的地址空间是“resource:/hr/.*”,如果模块APP引入了模块ACCHR两者的话,那么发送到APP的地址空间的请求“resource:/accounting/post_to_journal”会被传送给“accounting”模块(ACC),而请求“resource:/hr/number_of_employees”则会被传送给“hr”模块(HR)。

 

逻辑层面编程

在能够做到指定资源的逻辑地址、创建服务调用、映射逻辑地址到逻辑地址以及定义模块化的引入/暴露的结构后,我们就具备了在逻辑层面结构化和创建应用所需的基础。

 

传输器和根请求

 

从根本上讲,面向资源的计算环境中的应用是请求-响应系统,当外部请求到达时,其必须以某种方式触发一个内部的对具体资源的请求,应用随后会通过返回内部生成的资源表述给外部客户端来响应该外部请求,从这一角度来看,外部请求的到达是应用所响应的事件。

我们的ROC体系包括了被称作传输器(transport)的外部事务检测器,他们跨越了外部事件环境与应用的内部请求处理之间的边界。一个传输器负责检查某种类型的外部事件并创建一个初始的请求,或者说是根请求,然后这一根请求被发送给微内核以解析并处理。

有许多适当的传输器类型,每种检测某一不同类型的事件,某些典型的传输器检测这样的一些事件:

Ÿ 预定事件Cron传输器能够发起位于可配置的任务调度表上的根请求,

Ÿ 文件系统的变化:当文件被添加到某个已被监控的目录中时,收件盘传输器能够发出通知,

Ÿ SMTP事件SMTP传输器能够监控电子邮件的收件箱的状态,并在邮件到达时发起一个处理邮件的内部请求,

Ÿ JMS消息JMS传输器能够监控进入JMS消息队列的消息,

Ÿ HTTP请求HTTP传输器能够监控到达某个TCP/IP端口的使用了HTTP协议的请求。

 

每种传输器负责处理某种类型的外部事件的细节,其中包括与这些事件相关的所有协议问题。通过把传输器置于ROC系统的边缘,并让其负责外部事件的细节处理,应用能够解耦于并独立于传输协议而构建。例如,诸如“resource:/customers/”的资源能够通过来自JMSCorn或者HTTP传输器中任一个的根请求来进行请求,有了传输器所提供的解耦功效,应用既无需关心请求来自哪里,也无需关心他们是如何到达的。

 

应用设计

 

ROC环境搭建的应用可以有许多不同的设计,不过他们往往有着一些共同的特点:

Ÿ 信息被建模成资源,他们通过URI地址来标识,

Ÿ 资源地址被组织到逻辑地址空间中,

Ÿ 逻辑地址空间包含了生产和/或接收资源表述的端点,

Ÿ 创建一些渠道,信息通过这些渠道流到外部客户端或者从外部客户端流入,

Ÿ 逻辑地址空间由实现了应用目标的各个层次组成。

 

现在做一个非常简单的说明,论坛应用的设计可能会以一些样板行为的标识以及相应于这些行为的请求URI作为开始:

 

行为                                                        请求URI

显示最新的帖子                                                        http://mycomp.com/forum/search/1

返回第一讨论区中的主题512                                  http://mycomp.com/forum/area/1/topic/512

使用HTTP POST的内容创建新的主题                   http://mycomp.com/forum/update/topic

 

一旦这些请求中的一个进入到论坛应用中,基于ROC的方法与传统面向对象系统之间的差异就立即变得明显起来,ROC应用将重点放在把请求传送给能够满足该请求的资源或者是内部的微服务,通过逻辑到逻辑的映射、映射服务和模块引入,所有的传送都在逻辑层完成。

返回给外部客户端的表述的准备工作也不同于传统的应用,其通过组合资源以及可以选择对结果进行格式转换来完成这些工作。例如,来自浏览器的显示论坛网页的请求会导致一个“聚合(mashup)”操作,该操作中的信息来自多个源,聚合服务能够由一个包含了被请求资源(菜单、子菜单、主题领域、主题摘要等)的逻辑地址的页面模板来驱动。这些资源中的每一个都由它的URI地址来标识,在页面的组装开始时,发送单独的逻辑地址空间来获取每一个构成部分。逻辑层与物理层的解耦意味着,页面组装过程并不知道被标识为“resource:/menus/menu1”的菜单是否是静态的、被存放在文件中的,还是由Java代码动态生成的,对于所有其他的模板请求资源来说同样如此。实际上,模板本身就是资源,它可以是基于静态的表述或者由另一个服务动态生成。

面向资源的计算体系结构有着另一个超越传统应用的巨大优势,即为每个请求缓存返回的表述和管理缓存以获得最佳性能的能力。这一能力是ROC应用的逻辑层特性的一个直接成果,这些特性是:逻辑层面的资源寻址、逻辑地址可以映射到别的逻辑地址上、响应由逻辑定位的资源组成,以及响应由逻辑定位的服务来进行格式转换等。

在这一论坛例子中,如果所有的资源都是在他们首次被请求的时候动态地生成的话,那么后续的请求运行起来会快得多,因为表述会从缓存中获取。如果某个论坛资源被更新了的话(比如新提交的帖子),那么所有受到这一变化影响或者是依赖这一变化的缓存资源都会自动地以原子方式从缓存中冲刷掉。下一次有论坛页面被请求的话,只有那些被置为无效的资源需要重新计算——其他的所有资源则依然可以从缓存中获取。

ROC架构中的缓存类似于memoization[7]的使用,除了其不需要开发者的额外工作,以及其会被自动地应用到整个系统中这两点之外。memoization通常只会被用于(如果真有的话)选定应用的某个小角落,针对某种特定的数据类型,以及只会用在某些人已经花费了额外的精力来实现它的地方。

 

结论

本文继续就使用面向资源的计算方法这样一种基于RESTful原则的灵活的、类web架构来构建应用软件方面进行了讨论,在讨论中,我们从物理代码层向上移动了一下,研究那些位于架构的物理层和逻辑层之间的交界处的功能,我们看到了ROC的功能,例如逻辑URI寻址、逻辑地址空间、地址映射、传输器以及各种模块等是如何支持简洁的、灵活的应用设计的。读者开始有希望见到人们如何可以利用这些ROC功能来在位于面向资源的计算环境内的逻辑层面上编程。

后续的文章将探讨在逻辑层面上变得可用的设计和架构模式,正如对象的关系和交互模式对面向对象的编程和设计来说很重要一样,逻辑层模式允许人们以一种强大并且简洁的方式来思考和理解系统设计。

 

参考资料 

[1]      实现类Web应用灵活性的RESTful核心——第1部分

[2]      实现类Web应用灵活性的RESTful核心——第2部分

[3]      RFC 2396:统一资源标识符(URI):通用语法

[4]      activeURI方案

[5]      在这个例子中,后面的active:sqlQuery到物理端点的映射会被用来建立到数据库的连接、执行查询并返回结果的表述。不需要在每个应用模块中都定义这一的一个映射,可以只是从一个一般都能获得的关系数据库查询模块中引入就行了。

[6]   由于模块只暴露逻辑地址,因此模块间的耦合是位于逻辑层面上的,模块可以自由地修改提供给暴露的逻辑地址使用的服务的内部实现。

[7]    memoization的维基百科入口

 

 

 

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics