《架构之美》摘录四
第4章 数据增长:Facebook平台的架构
给我看你的流程图而隐藏你的表,我仍然莫名其妙。如果给我看你的表,那么我将不再需要你的流程图,因为它们太明显了。
——《The Mythical Man-Month》(人月神话)
4.1 简介
(1).在任何情况下,Web呈现的几乎所有面对用户的功能归根结底都是提供一个界面,访问站点专有的一组核心数据。这些信息构成了几乎所有网站的核心价值,不论它是由顶级员工研究团队创建的还是由世界各地的用户创建的。数据推动了用户喜欢的产品,所以架构师围绕数据创建了其余的传统“N层”软件栈(逻辑层与显示层)。
(2).这个故事讲的是Facebook的数据,以及它如何与Facebook平台的创建一起发展。 Facebook是一个很有用的围绕数据建立架构的例子,这个社会关系网站就是紧紧地围绕数据,使用标准的N层栈,将数据读出呈现出来。利用局域架构中心的用户社会关系数据,该平台开发了一组web服务(Facebook平台应用编程接口,或Facebook API),一门查询语言(Facebook查询语言,或FQL),以及一种数据驱动的标记语言(Facebook标记语言,或FBML),目的是将应用开发者的系统与Facebook的系统结合在一起。
(3).随着某些数据集越来越广泛的提供出来,而且用户要求跨越多个网和桌面应用来统一使用他们的数据,Facebook以一种受控的方式向外界开放数据,跟随数据演进的每一步的架构选择,以及调和数据开放与渗透在社会关系系统中特定的隐私需求的过程。它包括:
<1>.促进这些类型的集成。
<2>.将数据功能从内部栈调用移到外部可见的Web服务器上(Facebook API)。
<3>.授权访问这个Web服务,注意保持这个社会关系系统的隐私性。
<4>.创建一种数据查询语言,减轻这个Web服务的新客户端的负担(Facebook FQL)。
<5>.创建一种数据驱动的标记语言,将应用的显示集成回Facebook,同时也支持使用其他方式不能访问的数据(Facebook FBML)。 当我们将应用的架构从分离的栈进行了足够的演进之后:
<6>.创建一些技术来弥补Facebook体验与外部应用体验之间的差异。
4.1.1 某些应用核心数据
Web应用,即使是不提供也不使用任何的数据平台,基本上仍然是由它们内部的数据来驱动的。以http://fettermansbooks.com为例,它是个假想的网站,提供书籍方面的信息。这个站点的功能可能包括可查找的库存索引、关于每件产品的基本信息,以及用户每本书作出的评论。访问这些具体的信息构成了这个应用的核心,驱动了架构的其它部分。该站点可能使用Flsh和AJAX技术,支持通过移动设备来访问,并提供一个一流的用户界面。
4.1.2 一些Facebook核心数据
随着所谓“Web 2.0”的网络技术逐渐流行,数据在系统中的地位就变得更明显了。Web 2.0展现的核心主题就是它们数据驱动的,用户本身提供了绝大部分的数据。
4.1.3 Facebook的应用平台
(1).在一般的n层架构中,应用将输入(对于Web来说,就是GET、POST和Cookie信息的集合)映射为对原始数据的请求,这些原始数据可能存在于数据库中。它们被转换为内存中的数据,并通过一些业务逻辑进行智能化处理。输出模块将针对这些数据对象进行转换,变成HTML\JavaScript\CSS等。
(2).Facebook平台的技术通过在社会关系网络和数据架构方面的一系列改进,实现了这一点:
<1>.应用可以通过Facebook平台的数据服务来访问有用的社会关系数据,为外部的Web应用、桌面操作系统应用和其他设备上的应用提供社会关系上下文。
<2>.应用可以通过一种名为FBML的数据驱动标记语言来实现显示,在http://facebook.com的页面上集成他们的应用体验。
<3>.通过FBML所要求的架构改变,开发者可以使用Facebook平台的cookie和Facebook JavaScript(FBJS),从而让应用出现在http://facebook.com上所需的改动最小。
<4>.最后,应用可以获得这些功能,同时不必牺牲隐私,也不必放弃对于Facebook为用户数据和显示提供的用户体验的期望。
(3).最后一点是最有趣的。Facebook平台的架构并非一直是美丽的——它主要被看成是社会关系平台领域的先行者。大多数的架构考虑是为了创建统一可用的社会关系上下文,它体现了这样的阴阳关系:数据可获得性和用户隐私。
4.2 创建一个社会关系Web服务
<1>.回过头来看一看像http://fettremansbooks.com这样一个简单的例子,我们就很清楚大多数因特网应用都会因为在数据显示时添加社会关系上下文而受益。但是,我们会遇到一个实际的问题:这种数据的可获得性。
<2>.实际问题:应用可以利用在Facebook上的用户社会关系数据,但这种数据是不可访问的。数据解决方案:通过一个外部可以访问的Web服务来提供Facebook数据。 为Facebook架构添加了Facebook API,就开始通过Facebook平台为外部应用和Facebook建立了关系,本质上为外部应用栈添加了Facebook数据。对于Facebook用户,当他显式的授权外部应用可以代表他获得社会关系数据时,这种集成就开始了。
<3>.让我们来看看Facebook的API如何支持这一点。首先,我们会简单浏览一下Web服务包装Facebook数据的技术,这是通过使用合适的元数据以及名为Thrift的灵活的代码生成器生成的。开发者可以使用下一节中提到的这些技术,有效的创建各种Web服务,不论开发者手中的数据是公有的还是私有的。 但是请注意,Facebook的用户并不认为他们的Facebook数据全部是公有的。所以在技术概述之后,我们会探讨Facebook层面的隐私,这是通过平台API中的主要认证方式来实现的,即用户会话。
4.2.1 数据:创建一个XML Web服务
(1).为了能够在一个示例应用中提供基本的社会关系上下文,我们已经建立了两个远程方法调用,即frids.get和users.getinfo。访问这些数据的内部功能可能存在于Facebook代码树的某个库中,为Facebook站点上的类似请求提供服务。
(2).创建一个简单的Web服务,将通过HTTP的GET和POST输入转换成对内部栈的调用,以XML的格式输出结果。在Facebook平台中,目标方法的名称以及它的参数是在HTTP请求中传递的,还包括一些与调用应用相关的证书(称为“api key”),与用户-应用对相关的证书(称为“用户会话key”),与请求实例本身相关的证书(称为请求“签名”)。要服务一个针对http://api.facebook.com的请求,其大致过程如下:
<1>.检查传递的证书,验证调用应用程序的身份,用户当前在该应用中的授权,以及请求的可信度。
<2>.将进入的GET\POST请求解释为带有相应参数的方法调用。
<3>.对内部方法进行单次调用,将结果保存为内存中的数据结构。
<4>.将这些数据结构转换成已知的输出格式(如XML或JSON)并返回。 创建外部可使用的接口,难度主要在于第2步和第4步,为外部使用者提供这些数据接口的一致维护、同步和文档是很重要的,手工打造一个代码架构来确保这种一致性则是一项无人赞赏而又耗时的工作。另外,我们可能需要将这些数据提供给多种语言编写的内部服务来使用,或者以不同的Web协议将结果提供给外部开发者,如XML\JSON或SOAP。 那么这里的优美解决方案,就是利用元数据来封装数据类型和描述API的方法签名。Facebook的工程师创建开源的跨语言进程间通信(IPC)系统,名为Thrift(http://developers.facebook.com/thrift)
(3).Thrift生成类似的代码来声明RPC函数调用、序列化成已知的输出格式,并将内部的异常转化成外部错误代码。其他像XML-RPC或SOAP这样的工具集也提供这样一些好处,但可能需要更多的CPU和带宽开销。使用像Thrift这样的漂亮工具有以下好处:
<1>.自动化类型同步
在user类型中添加“favorite_records”,或将uidz转换成i64需要字所有使用或生成这些类型的方法中进行;
<2>.自动化绑定生成
所有读写类型的麻烦工作都不需要了,转换函数用生成XML的RPC方法要求函数声明、类型检查和错误处理,这些都由Thrift自动完成;
<3>.自动化文档
Thrift生成公开的XML Schema文档,它将作为外界看到的无二义的文档,通常比在“手册”上看到的文档要好得多。这种文档也可以直接在一些外部工具中使用,生成客户端的绑定。
<4>.跨语言同步 这个服务可以由外部的XML客户端或JSON客户端调用,内部是通过各种语言(PHP\JAVA\C++\Python\Ruby\C#等)写的服务程序通过套接口来通信的。这要求基于元数据的代码生成,这样服务的设计就不必在每次小改动时花时间更新这些代码。
4.2.2 简单的Web服务认证握手
(1).一个简单的认证策略让我们能够在尊重Facebook用户的隐私观点的前提下访问这些数据。用户对Facebook系统的数据有某种特定的视图,这取决于用户是谁、用户的隐私设定,以及与也会有关系的人的隐私设定。用户可以授权单个应用来继承这一视图。用户通过某个应用可以看到的信息,是用户通过Facebook可以看到的信息中有意义的一部分(但不会超出通过Facebook可以看到的信息)。
(2).Web服务的客户端只要在每次请求时发送session_key,让Web服务知道这代表的哪个用户的请求执行。如果用户(或Facebook)禁用了这个应用,或者他从未用过这个应用,安全检查就会通不过,会返回一个错误。否则,外部应用站点会把这个会话键记入它自己的用户记录,或者放到该用户的cookie中。 但是最开始如何得到这个会话键呢?在http://fettermansbooks.com应用代码中的eatablish_facebook_session是一个占位符,为这个过程保留的。每个应用都有它自己特有的“应有键”(也称为api_key),开始应用认证流程:
<1>.用户通过一个已知的api_key重定向到Facebook登陆界面;
<2>.用户在Facebook上输入口令,对这个应用授权;
<3>.用户带着会话键和用户ID重定向到已知的应用;
<4>.应用现在获得了授权,可以代表用户调用API方法(除非会话超时或被删除)。
(3).有些应用不容易适应这种第二步“重定向”的方式。“桌面”风格的应用、基于设备的应用(如手机应用),或浏览器内建的应用有时候也相当有用。在这种情况下,我们采用一种稍微不同的机制来使用第二次认证令牌。令牌是应用通过API请求得到的,在第一次登陆时传递给Facebook,然后在现场用户认证之后,应用换到一个会话键和会话专有的一些秘密信息。
4.3 创建社会关系数据查询服务
<1>.通过一个带有用户控制的认证握手的Web服务,我们已经将我们的内部库扩展到外部世界。通过这个简单的改变,Facebook的社会关系数据现在可以驱动其用户决定认证的任何其他应用程序,通过普遍关注的社会关系上下文,在应用的数据中创建新的关系。随着用户渐渐了解这些数据交换的无缝性,使用这些平台API的开发者知道这些数据集是很独特的。开发者访问自己的数据的模式与访问Facebook数据的模式有着很大的不同。
<2>.实际问题:从Facebook平台API获取数据要比获取内部数据的开销大很多。 数据解决方案:类似内部数据采用的模式,实现外部数据访问模式:一种查询服务。 Facebook的解决方案称为FQL,FQL很像SQL,但它将平台数据转换成字段和表,而不是简单松散的定义为XML Schema中的对象。这让开发者能够在Facebook的数据上使用标准的数据查询语义,这种方式可能与他们取得自己数据的方式一样。同时,将计算推到平台一端的好处与将操作通过SQL推到数据层的好处是相似的。在这两种情况下,开发者有意识的避免了在应用逻辑中进行这种处理的代价。 FQL代表了基于Facebook的内部数据的另一项数据架构改进,是标准的黑盒Web服务的进步。但是首先,我们先来看一种容易而明显的方法,它让开发者能够消除多次数据请求的来回开销,同时我们也要说明为什么这是不够的。
4.3.1 批量方法调用
<1>.对于负载问题最简单的解决方案,就是类似于Facebook的batch.runAPI方法。这消除了多次通过HTTP栈对http://api.facebook.com进行调用的来回开销,一批接受多个方法调用的输入,一次返回输出的多棵XML树。
<2>.在Facebook平台的PHP5客户端库中,end_batch实际上是向平台服务器发起请求,取得所有结果,并针对每个结果更新引用的变量。这里我们从一次用户会话中批量获取了用户数据。通常,人们用批量查询机制将许多设置操作归为一组,如大量的Facebook个人描述更新,或大量突发的用户通知。
<3>.这些批量操作很有效,但这也揭示了这种批量操作的主要问题。问题是,每次调用必须与其他调用的结果无关。对多个不同用户的批量操作通常具备这种特点,但有一种常见的情况仍然不能处理,即使用一次调用的结果作为下次调用的输入。
4.3.2 FQL
(1).FQL是一种简单的查询语言,它包装了Facebook的内部数据。输出的格式通常与Facebook平台API的输出格式一样,但输入超出了简单的RPC库的模型,变成了SQL的查询模型:命名的表和字段,包含已知的关系。像SQL一样,这种技术添加了选择实例或范围的能力,从数据行中选择字段子集的能力,并通过嵌套查询将更多的工作推到数据服务器端,避免了通过RPC栈进行多次调用。
(2).我们在概念上将users_getInfo引用的数据是为一个表,它基于一个索引(uid),包含一些可选择的字段。如果正确的扩展,这种新的语法可以支持一些新的数据访问能力:
<1>.限定范围查询;
<2>.嵌套查询;
<3>.结果集大小限制和排序。
(3).FQL架构
<1>.开发者通过fql_query API来调用FQL。问题的要点是在FQL的命名“表”和“字段”中,统一外部API的命名“对象”和“属性”。我们仍然继承了标准API的流程:通过内部方法取得数据,应用跟这个方法的API调用相关的规则,然后根据第4.2.1节介绍的Thrift系统,转换到输出。对于每个数据读取API方法,在FQL中都有一个对应的“表”,代表了这次查询背后的数据抽象。
<2>.例如,API方法users_getInfo,它提供给定用户ID的姓名、照片、书籍和当前位置等字段,在FQL中它就表现为用户表和对应的字段。fql_query的输出实际上也符合标准API的输出(如果修改XSD来允许省略对象小的字段),所以在用户表上调用fql_query返回的输出与相应的users_getInfo调用是等价的。事实上,像user_getInfo这样的调用在Facebook的服务器端通常是实现为FQL调用的。
<3>.FQL还有其他一些精妙之处,但这个总体流程说明了已有的内部数据访问和隐私规则实现与全新的查询模型的结合。这让开发者能够更快的处理它的请求,能够以比API更好的粒度来访问数据,同时又保持了SQL类似的语法。
4.4 创建一个社会关系Web门户:FBML
(1).前面讨论的服务让外部的应用栈能够在它们的系统中包含社会关系平台的数据,这是很大的进步。这些数据架构实现了让社会关系平台数据更开放的承诺:外部应用和数据平台的共同用户可以共享信息,每个新的社会关系应用就不需要一个新的社会关系网络。但是,即使有了这些新的能力,这些应用还是不能享受Facebook这样的社会关系网站的全部强大的功能。应用还需要让许多用户发现,才会变得有价值。而且,并不是所有支持社会关系平台的内部数据都可以提供给这些外部的应用栈。
(2).实际问题:对于社会关系应用来说,要获得引人注目的关键性用户数,支持它的社会关系网络上的用户必须要能注意到其他用户在利用这些应用进行交互。这意味着应用与社会关系网站更深层次上的集成。
这个问题在早期的软件中就存在:我们难以让数据、产品或系统得到广泛使用。缺少用户成功Web 2.0空间中特别值得一提的困难,因为如果没用户使用而且(特别是)生成内容,我们的系统什么时候才有用呢? Facebook支持大量的用户,他们对在社会联系之间共享信息感兴趣,而且Facebook的特点就是把应用的内容和它自己的内容等同视之,应用都需要在Facebook站点上有独特的显示展现。
(3).实际问题:外部应用不能够使用Facebook没有通过Web服务暴露出来的那些核心数据元素。在Facebook提供网站(http://facebook.com)的内容时,Facebook为它的用户提供了大量的数据。隐私信息本身就是一个很好的例子,不能被Facebook站点的用户显式的看到。但是强制实现Facebook用户的这种隐私设置是所有良好集成的应用的特点,也是对社会关系系统上用户期望的支持。Facebook为了保护用户的隐私,不能通过数据服务将这些数据开放出来。开发者怎样才能利用这些数据呢? 对这些问题的最优雅解决方案就是结合Facebook的数据和外部应用的数据、逻辑和显示,同时让用户在一个受信任的环境下操作。
数据解决方案:开发者通过一种数据驱动的标记语言,在社会关系站点上创建应用执行和显示的内容,与Facebook交互。
(4).我们必须同时记得我们的资产和约束。一方面,我们有一个访问频率很高的社会关系系统,让用户能发现外部的内容,并有大量的社会关系数据来增强这种社会关系应用。另一方面,请求需要从社会关系站点(Facebook)上发起,将应用作为服务来使用,然后将内容渲染成HTML、JavaScript和CSS,并且不违反Facebook用户的隐私或期望。
4.4.1 Facebook上的应用:直接渲染HTML、CSS和JS
<1>.对http://apps.facebook.com/fettermansbooks/...的请求于是简单的取出应用服务器上的HTML\JS和CSS等内容,并在Facebook上的页面主内容区域进行显示。这基本上是将外部站点作为一个HTML Web服务来渲染的。
<2>.这对应用的n层模型进行了重要改变。以前,应用栈会通过数据服务来使用Facebook的内容,这个数据服务是直接服务于对http://fettermansbooks.com的请求的。现在,应用在它的Web根下维护了一个树形结构,它自己提供HTML服务。Facebook通过在线请求这个新应用服务(该服务又可能用到Facebook的数据服务)而取得内容,将它包装成一般的Facebook站点导航元素,显示给用户。
<3>.但是如果Facebook直接在它的页面中渲染一个应用的HTML、JavaSacript或CSS,这就会允许应用完全违反用户对http://facebook.com上受控体验的期望,让站点和用户暴露在各种安全攻击下。允许外部用户直接订制标记语言和脚本几乎从来都不是好主意。实际上,代码或脚本注入通常是攻击者的目标,所以这并不是一个很好的特征。而且没有新数据!尽管这为应用栈的改变奠定了基础,但这个解决方案没有完全解决前面的两个实际问题。
4.4.2 Facebook上的应用:iframe
<1>.还有一种更安全的显示应用内容的方法,可以显示另一个站点的可视化上下文和界面流转,这种方法已包含在浏览器中,即irame。为了复用前一节中提到的映射,对http://apps.facebook.com/fettermansbooks/PATH?QUERY_STRING的请求将导致输出这样的HTML:<iframe src="http://fettermansbooks.com/fbapp/PATH?GET_STRIGN"></iframe>这个URL的内容将显示在Facebook页面的一个帧中,在它自己的沙盒环境中可以包含任何类型的Web技术:HTML\JS\AJAX\Flsh等。
<2>.这实际上是让浏览器成为请求代理者,而不是由Facebook作为请求代理者。这比前一节中的模型有改进,浏览器也维护所得页面中其他元素的安全性,所以开发者可以在这个帧中随意创建他们想要的用户体验。对于某些应用,如果开发者希望花最小的代价将他们的代码从他们的站点移到平台上,那么iframe的方式也是有意义的。实际上,Facebook继续支持完整页面生成的iframe模型。虽然基于iframe的请求流程可以确保安全,但除了API服务暴露出来的数据之外,这些开发者并不能利用其他的新数据。
4.4.3 Facebook上的应用:FBML是数据驱动的执行标记语言
(1).HTML的解决方案采用了直观的方法,将应用本身变成Web服务,将触点带回到Facebook上显示。iframe方式的好处在于将开发者的应用内容放在一个独立的(安全的)执行沙盒中。最佳解决方案将保留“应用即服务”的模型和iframe的安全和可信,同时又让开发者能够使用更多的社会关系数据。 问题是,为了让社会关系应用提供特定的用户体验,开发者必须通过他们自己的应用栈来提供数据、逻辑和展示。但是,生成这些输出必须用到那些不能离开Facebook的用户数据。
(2).解决方案:不是发回HTML,而是一种特定的标记语言,其中定义了足够的标记来表现应用的逻辑和显示,也包含对受保护数据的请求,完全让Facebook在受信任的服务器环境中渲染它!这就是FBML的前提。 在这个流程中,对http://apps.facebook.com的请求同样被转换成对应的请求,应用栈会使用Facebook的数据服务。但是,开发者不会让应用返回HTML,而是返回FBML。
FBML中包含了许多HTML元素,而且添加了Facebook特别定义的标签,FBML解释器将这段标记语言转换成它自己的数据、执行和显示实例,生成应用页面。用户就会收到一个页面,其中包含了Facebook页面的一般Web元素,而且也包含了应用的数据、逻辑和观感,它能在技术上确保Facebook强制实现其隐私理念和良好的用户体验元素。
(3).FBML是XML的一个特例,它包含了许多熟悉的HTML标签,增加了在Facebook上显示的平台专有的标签。FBML同样体现了FQL的高级模式:修改已知的标准(HTML,对FQL来说就是SQL),将执行和决定延迟到Facebook平台服务器上进行。FBML解释器让开发者通过FBML数据,自己能够控制在Facebook服务器上执行的逻辑和显示。这是数据处于执行中心的绝妙例子:FBML只是声明式的执行,而不是必须服从的控制流。 FBML是一个XML实例,所以它由标签、属性和内容组成。标签可以在概念上分成以下几类:
<1>.直接的HTML标签
如果FBML服务返回标签<p/>,Facebook将在输出页上直接渲染为<p/>。作为Web展现的基石,大多数HTML标签都是支持的,少数违反Facebook层面的信任或设计期望的标签除外。
<2>.数据显示标签
这是体现数据威力的地方。假定个人简历照片不能转到其他站点。通过指定<fb:profile-pic uid="8055">,开发者就可以在他们的应用中显示更多的Facebook用户信息,同时不要求用户完全信任开发者,将这部分信息交给开发者处理。请注意,即使信息受到了保护,这些内容也不会返回到应用栈中,只是显示给用户看。在容器端执行使这些数据可以查看,但不要求将它们交给应用程序。
<3>.数据执行标签
作为使用隐藏数据的一个更好的例子,用户的隐私限制只能通过内部的can_see方法来访问,它是应用体验的一个重要部分,但不能通过数据服务从外面进行访问。利用<fb:-if-can-see>标签和其他类似的标签,应用可以通过属性来指定一个目标用户,这样只有当查看者能够看到目标用户的特定内容时,那些子元素才会渲染出来。
因此,隐私数据本身不会暴露给应用,同时应拥有能满足强制实现的隐私设置。 从这个角度来说,FBML是一个受信任的声明式执行环境,与C或PHP这样的必须服从的执行环境不同。严格来说,FBML不像这些语言那样是“图灵完备”的。像HTML一样,除了树状遍历所隐含的状态外,在执行时不保存任何状态。但是,通过让受信任系统中的用户获得数据,FBML提供了大量功能,这些功能正是大多数开发者希望提供给他们的用户的。FBML实际上有助于定义执行应用的逻辑和显示,同时又让应用可以在应用服务器上显示独特的内容。
<4>.只面向设计的标签
Facebook因其设计标准而受到称赞,许多开发者都选择以某种方式复用Facebook的设计元素,保持Facebook的观感。通常,他们是通过利用http://facebook.com的JavaScript和CSS来实现的,但FBML提供了类似“设计宏”的库,以更可控的方式来满足这种需求。
<5>.替代HTML标签
HTML造成了一些信任风险,但没有暴露数据,所以FBML中的替换标签只是修改或限制一组参数,如Flash自动播放。这不是所有显示平台都严格要求的,它们只是强制应用满足容器站点的默认显示行为。但是,随着多个应用发展成为一个生态系统,它们都反映容器站点的观感,这种修改就变得很重要了。
<6>.“功能包”标签
某些Facebook FBML标签包含了整套的常见Facebook应用功能。<fb:friend-selector>创建了类型的前置的朋友选择器软件包,常见于许多Facebook页面,包括Facebook数据(朋友、主要网络)、CSS样式和针对键盘动作的JavaScript。像这样的标签让容器站点可以推广某些设计模式和应用间的公有元素,也让开发者能够快速实现他们想要的功能。
4.4.4 FBML架构
(1).将开发者提供的FBML翻译成显示在http://facebook.com上的HTML,需要一些技术和概念综合作用:将输入字符串解析成一棵句法树,将这棵树中的标签转换成内部方法调用,应用FBML语法规则,保持容器站点的约束。像FQL一样,这里将关注点主要放在FBML与平台数据的交互上,对其他的技术则不作详细探讨。FBML处理了一个复杂的问题,FBML的全部实现细节是相当多的——这里省略的内容包括FBML的错误日志、为后来的渲染事先缓存内容的能力、表单提交结果的安全性签名等。
(2).解析FBML的底层问题。在继承了浏览器的某些角色的同时,Facebook也继承了它的一些问题。为了方便开发者,不要求提供的输入可以通过schema验证,甚至不要求是结构良好的XML——不封闭的HTML标签,打破了输入必须作为真正的XML进行解析的假定。因为这一点,需要一种方法将输入的FBML字符串先转换成结构良好的句法树,包含标签、属性和内容。为了做到这一点,采用了一个开放源代码浏览器的一些代码,假定在接收到FBML并经过这样的处理流程后,得到名为FBMLNode的树状结构,它让能够查询生成的句法树中任何节点的标签、属性键值对和原是内容,并能够递归查询子元素。
(3).从最高的层面上看,会注意到FBML出现在Facebook站点的所有地方:应用“画布”页面、新闻信号源的故事内容、个人简介等等。每种上下文中或每种“风味”的FBML都定义了对输入的约束,因为FBML维护数据隐私的方式与API类似,所以执行上下文中必须包含查看用户ID和生成核对内容的应用ID。所以在真正开始使用FBML之前,先看看环境的规则,它是由FBMLFlavor类来封装的。
这种风味类的设计很简单:它包含了隐私上下文(用户和应用),实现了检查方法,为稍后将展示的FBMLImplementation类中包含的丰富逻辑建立了规则。与平台API的实现层很像,这个实现类为服务提供了实际的逻辑数据访问,其他的代码为这些方法提供了访问入口。每个Facebook特有的标签,将有一个对应的实现方法,每个实现方法都接收一个来自FBML解析器的FBMLNode,以字符串的方式返回输出的HTML。下面是具体实现的程序清单:
<1>.在FBML中实现直接的HTML标签
例如<img>标签的内部FBML实现,是将图像标签的实现包含更多的逻辑,有时候需要将图像源的URL重写到Facebook服务器上图像缓存的URL。这体现了FBML的强大:应用栈可以返回与HTML非常相似的标记语言,支持它自己的站点,而Facebook可以通过纯技术的手段强制实现平台所要求的行为。
<2>.在FBML中实现数据显示标签
通过FBML使用Facebook数据的例子。<fb:profile-pic>用到了uid、size和title属性,将它们结合起来,根据内部数据产生HTML输出,并符合Facebook的标准。在这个例子中,输出是指定用户名的个人简单照片,链接到用户的个人间接页面,只在当查看能看到这部分内容时才显示。这个功能也存在于FBMLImplementtation类中。
<3>.FBML中的数据执行标签
FBML解析的递归本质使得<fb:if-can-see>标签就像是标准的必须服从的控制流中的if语句一样,它是FBML实际控制执行的一个例子。这是FBML实现类中的另一个方法。如果对“观察者-目标”通过了can-see检查,引擎就会递归的渲染<fb:if-can-see>节点的子节点。否则,就会渲染可选标签<fb:else>子节点下的内容。请注意fb_if_can_see直接访问<fb:else>子节点的方式;如果<fb:else>出现在这样的一个“if风格”的FBML标签之外,标签和它的子标签就不会返回任何内容。所以,FBML不仅仅是一个简单的转换式例程,它会注意到文档的结构,因此可以包含条件控制流的元素。
<4>.结合在一起
前面讨论的每个功能,都需要注册为一个回调,在解析输入的FBML时使用。在Facebook这个“黑盒”解析器是用C写的PHP扩展,每个回调都存在于PHP树中。要完成这种高层控制流,我们必须向FBML解析引擎声明这些标签。 FBML利用回调扩展了浏览器的解析技术,包装了由Facebook创建和管理的数据、执行和展现宏。这个简单的思想实现了应用的完全集成,支持使用通过API暴露出来的内部数据,同时保持安全性方面的用户体验。FBML本身几乎就是一种编程语言,它也是充分发展后的数据:外部提供的声明式执行,安全的控制了Facebook上的数据、执行和显示。
4.5 系统的支持功能
<1>.现在,开发者创建的软件运行在Facebook的服务之上,不仅是结合了界面组件,而是全部的应用。在这个过程中,创造了一个社会关系网络应用的完全不同的概念。从一个典型的Web应用的独立数据、逻辑和显示的标准设置开始,不考虑所有社会关系数据,只是让用户可以确信能够做出贡献。现在,取得从分的进展,应用使用了Facebook的社会关系数据服务,同时它自己又成为一个FBML服务,完全集成到容器站点之中。
<2>.Facebook数据也获得了长足的发展,不再仅仅是第一章讨论的内部库。但是,仍有一些重要的、常见的Web使用场景和技术,目前平台未支持。通过将应用变成一个返回FBML的服务,而不是直接由浏览器解读的HTML\CSS\JS,我们接触到了关于现代Web应用的一些重要假定。
4.5.1 平台cookie
<1>.应用的新Web架构排除了浏览器内建的一些技术,许多Web应用栈可能依赖于这些技术。可能其中最重要的一点是,过去浏览器用于保存用户与应用栈交互信息的cookie不再刻意得到了,因为应用的目标消费者不再是浏览器,而是Facebook平台。cookie信息属于该应用领域所提供的用户体验。
<2>.解决方案是什么?让Facebook具有浏览器的职责,在Facebook自己的存储库中复制这种cookie功能。如果应用的FBML服务送回请求头,试图设置浏览器cookie,Facebook就保存这个cookie信息,以(user,application_id)对为主键。Facebook然后“重新创建”这些cookie,就像用户向这个应用栈发出后续请求时浏览器所做的一样。注意当用户决定在这个应用提供的HTML栈上导航时,这种信息是不能使用的。另一方面,它可以有效的分离用户的Facebook上的应用体验和在应用的HTML站点上的应用体验。
4.5.2 FBJS
(1).当应用栈作为一个FBML服务被使用,而不是直接由用户的浏览器来使用,Facebook就没有机会执行浏览器端的脚本。直接返回未修改过的开发者提供的内容可以解决这个问题,但它违背了Facebook在显示体验上所加的约束。例如,当价值用户的简介页面时,Facebook不希望在加载事件上触发一个弹出窗口。但是,限制所有的JavaScript会排除许多有用的功能,如AJAX或在不重新加载的情况下动态操作页面的内容。
(2).相反,FBML在解释开发者提供的<script>树和其他页面元素的内容时会考虑到这些约束。在此之上,Facebook提供了一些JS库,让这些场景容易实现,同时又受到控制。这些修改共同构成了Facebook平台JS仿真套件,称为FBJS,它通过以下几点,让应用既动态又安全:
<1>.重写FBML属性,确保实现虚拟文档范围;
<2>.延迟激活脚本内容,直到用户在页面或元素上发起动作时;
<3>.提供一些Facebook库,以受控的方式来实现常见的脚本使用场景。 很清楚,不是所有的实现自有平台的容器站点都需要这些修改,但FBJS向我们展示了几种解决方案,这样的新Web架构需要这些解决方案来绕过一些困难。这里只展示解决方案的一般思想,与FBML和扩展的专有JS库进行融合。
(3).JS通常可以访问包含它的文档的整个文档对象模型(DOM)树。但是在平台画布页面中,Facebook包含了许多它自己的元素,开发者不允许对它们进行修改。解决方案是什么?在用户提供的HTML元素和JS符号之前加上前缀,即应用的ID。通过这种方式,在开发者的JS中如果试图调用不允许调用的alert()函数,就会调用未定义的函数,并且只有开发者自己提供的那部分文档的HTML可以被document.getElementById这样的JS代码访问。
(4).对于FBML和FBJS中的NOTE注释的解释:
<1>.NOTE1
Facebook需要包含它自己的特殊JS,包括fbjs_sandbox的定义,目的是渲染开发者的脚本。
<2>.NOTE2
FBM会重写初始化流程中的属性,变成Facebook特有的功能,这实际上是FBJS的一部分。所以这里的onclick会激活这个页面的其他元素,这些元素在用户执行这个动作之前是非激活的。
<3>.NOTE3
注意在HTML和脚本中的元素如何加上了该应用的应用ID作为前缀的。这意味着开发者对alert()的调用将变成对app12345_alert()的调用。如果Facebook的后台JS在这个上下文中允许这个方法,它将最终转向执行alert()。如果不允许,这将是未定义的调用。类似的,这种加前缀的方式时机尚未DOM树提供了命名空间忙所以对该文档某些部分的改变只限于开发者定义的那些部分。类似的沙盒技术也允许开发者提供限制范围的CSS。
<4>.NOTE4
Facebook提供了一些专门的JS对象,如AJAX和Dialog,目的是支持常见的使用场景。例如,通过AJAX()对象发出的请求实际上能获得FBML作为结果,所以它们被重定向到Facebook域的一个代理上,在这里Facebook完全在线的FBML到HTML的转换。 支持FBJS需要对FBML进行改动、专门的JS和AJAX代理这样的服务器端组件,才能够绕过应用Web架构的一些限制,但结果是很强大的。开发者因此可以享受绝大多数的JS功能,而且平台确保了应用内容提供了用户在Facebook上期望的受控体验,这完全是通过技术手段来实现的。
4.5.3 服务改进小结
解决了新的n层社会关系应用的概念带来的剩下一些问题之后,我们又改进了服务架构,添加了cookie和FBJS等项。随着开发者的社会关系应用越来越成为Facebook使用的一项集成服务,而不是由浏览器使用的外部站点,我们已经重新创建或重新设计了浏览器的某些功能(通过平台的cookie、FBJS等)。在试图改变或重建“应用”的概念时,这是必须的两个重要修改的例子。Facebook平台包括类似的其他一些架构上的巧妙设计,这里只是简单介绍,还有一些包括数据存储API和浏览器端Web服务客户端。
4.6 总结
Facebook的用户贡献的社会关系有效的提高了http://facebook.com上几乎所有页面的效果。而且,这种数据非常通用,所以当它与外部开发者的应用栈结合在一起时,它的最佳使用就出现了,这都是通过Facebook平台的Web服务、数据查询服务和FBML等技术来实现的。 从取得用户的朋友或简介信息的简单内容API开始,有介绍了一些全部改进展示了如何协调不断扩展的数据访问方法和容器网站的预期,特别是对数据隐私和站点体验集成方面的要求。每次对数据架构的新改动都发现了Web架构的一些新问题,又通过对数据 访问模式的更强改进来解决这些问题。
虽然关注重点在Facebook的社会关系数据平台的应用的潜力和约束上,但像这样的新型数据服务不一定局限于社会关系信息。随着用户贡献和使用的信息越来越多,这些信息在许多容器站点上都很有用,各式各样的平台提供者可以应用Facebook平台特有的数据和Web架构背后的这些思想,并从中获益。