Enterprise REST = Customize, Invent and Standardize Media Types

Enterprise REST = Customize, Invent and Standardize Media Types

这几天有点弄清楚了一直困扰我的一个问题:在 Machine-to-machine 的 REST 应用中, Client 怎么可能在没有任何预先知识的情况下就能follow server端返回的超文本来自适应的将应用程序逻辑进行下去?
答案是不可能. Client端必须提前了解足够的server端返回的超文本的知识, 才能follow其中的instruction将逻辑进行下去. 那跟SOAP, WSDL之类的又有啥区别? 答案在于 REST 要求的“足够知识”要远远弱于WSDL对服务接口定义的约束, 其灵活性则高于WSDL. 毕竟, REST 架构风格着眼于生命周期较长的, 不断演化的, 跨组织边界的应用程序. 可以用来满足HATEOAS这个REST约束的武器就是Media Type: 扩展已有的Media Type, 发明新的Media Type, 并在合适的范围内标准化
来看两个例子.
Web的成功与其说是HTTP的成功, 不如说是HTML的成功, 或者说text/html这种Media Type的成功. HTML定义了一组有限的, 标准化的, 特定领域的标签. 所有的HTML客户端都能理解并按照自己的能力渲染出Web服务器返回的任何合法的HTML. 从全功能的Chrome/FireFox/Safari/IE, 到只是显示文本的lynx, 到资源受限环境下如手机中的各种浏览器, 都能将多姿多彩的Web呈现给最终用户, 并引导他们进行下一步交互
最经常被拿来作为 machine-to-machine REST 应用成功案例的则是 RSS/ATOM. 这族标准定义了新的Media Type, application/rss+xml, application/atom+xml. 不出意外的, 这组Media Types定义了一组有限的, 标准化的, 特定领域的标签. 每一个RSS/ATOM客户端应用都可以从某个资源的application/rss(atom)+xml的表述开始, 顺藤摸瓜的遍历每个感兴趣的资源. 这些客户端都理解每一个RSS/ATOM定义的标签. 服务端可以自由的改变新资源的URI/URL Template而不必担心破坏现有的Client, 因为这些Client并不依赖于预先设定的URL Template, 而仅仅依赖于一个Root的表述, 及标准的link relations
回到我们的问题. 那么REST要求的“足够知识”要弱到什么程度, 才能既使客户端能理解服务器给出的线索, 又不致于耦合的太紧以致服务器和客户端无法独立演化? 观察上面两个例子, 它们有以下共同特点:
1. Response是Well-formed,可以被客户端解析. 这是废话, 除了AI应用, 绝大部分网络应用都得有明确定义的协议. 但这意味着 REST 应用中也必须明确定义客户端和服务器数据交换的格式
2. Response中包含了当前上下文(或资源)相关的信息, 但对我们这个问题来说更重要的是包含了对其它资源进行访问的线索. 是的, 它是用最最基本, 最最普通的 link 来实现的.足够弱. 那语义是否足够清晰到客户端能理解每一个link, 能够选择正确的link并知道如何访问呢? 我们来看看link上都能承载啥语义(from http://www.ietf.org/rfc/rfc5988.txt):
 
  Link           = "Link" ":" #link-value
  link-value     = "<" URI-Reference ">" *( ";" link-param )
  link-param     = ( ( "rel" "=" relation-types )
                 | ( "anchor" "=" <"> URI-Reference <"> )
                 | ( "rev" "=" relation-types )
                 | ( "hreflang" "=" Language-Tag )
                 | ( "media" "=" ( MediaDesc | ( <"> MediaDesc <"> ) ) )
                 | ( "title" "=" quoted-string )
                 | ( "title*" "=" ext-value )
                 | ( "type" "=" ( media-type | quoted-mt ) )
                 | ( link-extension ) )
  link-extension = ( parmname [ "=" ( ptoken | quoted-string ) ] )
                 | ( ext-name-star "=" ext-value )
  ext-name-star  = parmname "*" ; reserved for RFC2231-profiled
                                ; extensions.  Whitespace NOT
                                ; allowed in between.
  ptoken         = 1*ptokenchar
  ptokenchar     = "!" | "#" | "$" | "%" | "&" | "'" | "("
                 | ")" | "*" | "+" | "-" | "." | "/" | DIGIT
                 | ":" | "<" | "=" | ">" | "?" | "@" | ALPHA
                 | "[" | "]" | "^" | "_" | "`" | "{" | "|"
                 | "}" | "~"
  media-type     = type-name "/" subtype-name
  quoted-mt      = <"> media-type <">
  relation-types = relation-type
                 | <"> relation-type *( 1*SP relation-type ) <">
  relation-type  = reg-rel-type | ext-rel-type
  reg-rel-type   = LOALPHA *( LOALPHA | DIGIT | "." | "-" )
  ext-rel-type   = URI
 
这里我们重点考察一下 rel 和 media-type 属性
在ATOM(http://www.ietf.org/rfc/rfc4287.txt)里, 缺省定义了5种link relations, 分别是alternate, related, self, enclosure和via. 标准赋予它们明确的语义, 比如alternate表示link所指向的目标是当前资源的备用版本. 客户端因此可以理解每个link的含义, 自动或引导用户完成后面的操作. 如何完成?则牵扯到media-type. 比如如果media-type定义的是audio/mp4,客户端则可以显示播放选项或自动播放或干脆忽略.
这里link以及media-type等属性帮助形成了一个递归的过程. 我们从某个link开始, 知晓它的media-type(所谓知晓它的media-type, 就是能理解这种media-type定义的每一个元素的语义), 我们就能够得到这个link所指向的资源以这种media-type表现出来的一种表述, 其中包含了其它可用的link以及media-type, 可以让这个过程一直继续下去, 直到客户端觉得可以了或者服务端返回了不包含任何其它link的表述.
然而初始的media-type和link relations总是有限的, 我们如何应对现有media-type表达不了的语义?这就是HTML和RSS/ATOM例子包含的第三个要素
3. 定义领域相关的media-type. HTML的问题域是如何表达各种显示效果, RSS/ATOM则是如何发布信息. media-type以及link relations等都是可扩展的. 我们要做的就是为我们的特定领域的应用定义扩展, 如果现存的被标准化的元素不够用的话. 这里有一个问题, 就是REST应用的范围的问题. 如果我们的应用是面向internet的, 面向无数已知的未知的客户端应用的,则意味着我们必须尽可能标准化我们扩展的media-type, 或者至少让它广泛接受. 而对于企业内部以REST架构的应用, 我们同样面临标准化的问题, 只不过范围可能小一点, 至少是企业内部需要有共同接受的media-type
前面我们看到了良好定义的media-type是如何帮助客户应用理解server端的response而又不致紧密耦合服务端的实现的. 我们需要一个企业开发的例子来验证一下我们的理解. 比如要开发一个企业内部不同应用之间共享用户信息的应用, 包括权限信息, 当前可以执行的操作, 可以访问的应用, 以及应用之间共享的Preference设置等. 我们可以定义一种叫application/vnd.tw.account+xml 的media-type 可以有以下的片段
 
这样无论是交互式客户端像浏览器还是自动化的后台应用, 都可以follow这段media的指示进行自己感兴趣的操作

URL Template Considered Harmful

如果这些都实现的话, 是否可以有一个推论:URL Template不是必须的,甚至是有害的. 它限制了服务器端的变化. 客户应用应总是从Root Resource开始, 在特定Media Type的引导下解析出其它的URI, 给予服务程序演化的灵活性而不是按照URL Template这种预先的知识来推算.
Xu Hao 对Url template也有同样的看法:"URL template在我看来是有害的,它是一种隐含的服务器和客户端约定。此外还有一点,由于大多数URL template是用来表达state transfer的URL的,比如/xxx/approve之类的,使得客户端必须了解服务器的状态转移的细节,这在我看来是一个更大的问题。这使得客户端和服务器的耦合变得更加紧密,同时这种风格极度鼓励服务器借由客户端来维持状态的一致性" 
之前感觉有点被误导, 很多REST相关的文章都把大量的篇幅给了HTTP, 比如HTTP Verbs POST/GET/PUT/DELETE, HTTP Status Code等, 而media type着墨不多. Xu Hao曾很多次的提起自定义Media Type, 当时不太明白, 现在感觉扩展和标准化更多的media type才能更多的发挥REST的潜力
http://www.iana.org/assignments/link-relations/link-relations.xml
http://microformats.org/wiki/existing-rel-values
http://www.w3.org/TR/html401/types.html#type-links
http://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven
 
Tags: 

延伸阅读

最新评论

发表评论