这几日忙里偸闲,在图灵社区翻译了Architecture of Open Source Applications第二卷Moodle一章。这算是我第一次做技术翻译,三天翻译完成之后又经过了一次大的修改,或许工作仍有疏漏,还请各位指正。lt在回帖中提到希望有一个1000字左右的简介,在修订完后我觉得还是必要的。因此我先谈一下我对这篇文章的看法,然后对我认为更有价值的内容再进行提炼浓缩。

我的看法

就像AOSA书名提到的,这本书讨论的是软件的架构(其实我更喜欢他们提到的整个软件开发的原因和历程)。所以接触这本书的大部分应该是稍有经验的开发者或者架构师。但是Tim这里介绍的过于详细,有点User Manual或者Developer Guide的味道。许多例子(比如建立统一入口,以及国际化),实际上已经众所皆知了,完全可以提一句然后让不太明白的读者自己去搜寻。他一个贯穿全文的例子,对于每一行都进行了详尽的解释,然后再拉出与之相关的各种历史。整篇文章翻译完17 000多字,字里行间透露着作者像看待Moodle如孩子一样的细致的感情。我认为这篇文章更重要的或许是间杂其中,作者点滴回忆的Moodle设计历史。从这些设计逐渐的进化中,我们才能够更加看清一个好的设计需要为每一个阶段变更的需求进行调整。但是每一次设计,就像作者最后提到的,要做到恰到好处,既不会过于简单而至于不能满足需求,更不要过于复杂使得难以维护。所以我相信,如果你愿意把原版文章(= =或者我翻译的这个)读完,得到的收获绝对要比下面的总结要大的多。

文章的总结

这篇文章作者通过设计一个简单的"Hello World“插件,来简要说明了在Moodle中四个他认为比较精彩的设计,分别是

  1. 插件化方法
  2. 权限系统
  3. 渲染过程
  4. 独立的数据库抽象层

插件化

一开始,作者先提到Moodle的三部分:放在Web目录下的代码,数据库,和一个存储用户上传文件的目录。而后,他指出Moodle支持两种route方式:

  1. 直接访问脚本,只不过在每个请求的脚本最开始要调require一下config.php (这个文件加载所有Moodle标准库、处理会话、连接数据库并且初始化全局变量)
  2. 创建统一入口(通常这需要在这个目录下进行rewrite)

Moodle本身是插件化的,但是由于历史的原因,它的内核十分庞大,提供了许多额外的功能(其实他们现在一直在努力将内核的功能转移到插件中去,但是特性集的逐渐膨胀使得重构和满足新的功能之间需要调和,所以进展不大)。插件放在一个类型/插件名的目录里面,里面要包含version.php(插件元数据)和index.php(插件的主要行为)等等。

这里面作者提到了两种插件化的方法:一种是说整个项目由一个插件启动器启动(通常负责处理依赖以及初始化等等),其他的部分交由插件自己完成(包括插件之间的交互)。另一种是说提供内核提供一个事件引擎,插件通过注册事件和钩子函数来交互以完成功能。感兴趣的读者可以深入的去查一下,这在事件驱动的框架里面都很常见。

权限系统

Moodle作为一个教学系统,用户和角色必须被严格的区分。一个角色通俗的来说就是一个group(组),而权限系统的任何权限是相对与角色而言的。

要说明白它的权限系统,就要先说它的Context(译文中我翻译成上下文,其实我感觉不用翻译,翻译出来自己都觉得别扭)。context可以认为是用户当前所处的环境,所以它存储了关于这个环境的信息。系统有一个根context,进入到一个插件,或者子模块中,与之对应的context也会加入进来产生作用。所以整个context系统是一个树形结构,有点像某某老喜欢提的备忘录模式。两个context的有相同的环境变量,那么在儿子context中时,儿子的环境变量覆盖祖先的。context之所以关键,因为它记录了当前登录的这个用户在当前context里面都扮演了什么角色。

一个能力实际上就是一个用户可能采取的行为,比如发帖子,打酱油(这里面是打招呼)。每个插件里有一个access.php文件来定义能力。

现在定义权限。权限是说一个角色能不能干一个能力,这些权限被记录在context里。能不能干分四种:

  1. UNSET/INHERIT,不知道,所以我爹能干我就能干
  2. ALLOW,能干
  3. PREVENT,不能干
  4. PROHIBIT,就是不能干,还不能让我的儿子们干

最后当前用户到底能不能打酱油,是这么计算出来的:

  1. 要是我现在有任何一个角色是“就是不能干”,我就不能打
  2. 否则,有任何一个角色说“能干”,我就能打
  3. 过了2还没有,那我就不能打

页面渲染

Moodle有两种从请求(GET或者POST)中获取数据的方法,其中一种是一个PEAR的HTML QuickForm的Wrapper,由于QuickForm本身有严重的实际问题,这个方法已经不维护了。另一个通过一个叫做optional_param的函数,函数需要指明需要获取的变量名,默认值和变量类型。默认值在变量未设置的时候提供值,变量类型用来做合法性验证。

Moodle有几个非常重要的全局变量($CFG$DB$SESSION$COURSE),其中$PAGE$OUTPUT负责页面内容的构建和渲染。注意,这里$PAGE只关心和页面内容相关的问题,而和如何布局和显示则是$OUTPUT的工作。

首先要用$PAGE->setcontext来指明要渲染页面的上下文(因为这会影响到诸如名字的显示,以及时区的显示等问题),然后再用$PAGE->set_url来指明页面的地址。Moodle通过get_string函数来屏蔽内部国际化的复杂度。跟大多数国际化的做法一样,Moodle以及它的插件应该为每一种打算支持的语言设置一个字典,在get_string查询时,根据当前上下文的设置的显示语言从字典中查找函数参数作为键对应的值。一个语言的字典可以从另一门语言中衍生出来,比如en_us美式英语衍生自en英式英语,所以en_us只需要说我老爹是en,然后自己把和en字典不同的部分写进自己的字典里就行了。get_string查找的时候,会先去en_us插,查不到就去查它爹en,以此类推。

$OUTPUT全局变量负责渲染过程。$OUTPUT->header负责计算页面的头部,包括找到用什么主题渲染(一个主题是一个渲染器,主要决定了$OUTPUT不同的方法产生什么样的输出),填充块(Blocks,为了使得页面设计可复用,所以一个页面的某些部分可能空出来充填由$PAGE决定的内容,比如插一个广告进去)。OK,作者承认了现在渲染的方式由于老旧代码的问题显得十分丑陋。它先把整个页面布局(的HTML代码)生成出来,然后在中间(它在标签body里面先生成了布局,比如整个页面共用的头部和脚部。但是这个时候真正的内容还没有被写进来,所以它把脚部先存起来,把脚部删掉,让$OUTPUT接着头部输出,最后用$OUTPUT->footer再把刚才存起来的输出出来。)通过各种它定义的模型(比如box)把内容写进来。最后调用$OUTPUT->footer,它会计算所有需要的js,输出在脚部(因为这样可以先看到页面,后加载JS脚本)。

作者这里提到了几点不足,首当其冲的就是这个例子里面把视图和控制逻辑放在了一起。实际上完全可以通过定义渲染器(一个渲染器相当与一个$OUTPUT),由$PAGE根据context决定选择哪个渲染器然后进行输出。

数据抽象层

Moodle自己重做了数据抽象层,因为之前基于ADO库的版本效率实在太差了。$DB负责创建数据库连接和进行数据库查询。一个典型的用法是$DB->get_record(表名,查询条件),它会根据数据库表生成一个对象,所有的字段都可以公开获取。它也支持直接的SQL查询,但是表名要用{}包裹起来,而且它支持命名和匿名(?)两种占位符。

每一个Moodle插件可以定义个一个install.xml的文件,来对自己的数据结构进行说明。由于老旧代码带来的另一个问题是,Moodle不支持外键,但是install.xml却可以声明。(因为Moodle最开始开发的时候,MySQL还在3.X版本)。

经验教训

整个Moodle的历程(尤其是他的例子中)告诉我们,需求是不断进化的,所以设计要配合每一阶段的需求变更仅仅做最必要的改动。更有意义的是,Moodle这个项目是面向教学群体的,它的主要功能是通过互动、创造和分享来让这个虚拟教学环境中的所有人获益。显而易见,Moodle开发的本身过程,也是一个构建虚拟教学环境的过程。开发者和教学系统的用户,在社区里面相互讨论,为Moodle添加合适的特性集。这个过程本身就证明了Moodle这种教学模式的可行性和成功性,越来越多的人们会通过社区的方式来获得更加活跃的学习体验。

评论

推荐 0
这个版本更适合阅读,原版对不做这个平台的真的太详细了,有的书也是一样,某些内容作者写得不亦乐乎,读者却想赶紧跳过。

推荐 0
一起来乐译吧。

我要评论

需要登录后才能发言
登录未成功,请修改提交。