[译]大规模JavaScript应用架构模式(一)

点击查看原文

译者注:关于本文作者,Addy Osmni,UI/JavaScript工程师,jQuery bug检测团队和API文档团队成员。ShorSaleology的高级项目经理,高级web开发工程师。有多年web应用开发经验。总之,是个牛人,详细资料参见此处。关于本文,作者结合外观模式,和中介者模式,给出了一个构建复杂web应用的架构模式,很有参考价值。

今天我们要讨论一下适用于大规模JavaScript应用架构的模式。文章基于我LondonJS上的同名讲座,灵感来源于Nicholas Zakas 之前的工作。

我是谁?为什么我要写这片文章?

我是一个JavaScript和UI开发者,就职于AOL,职责是为我们的下一代面向客户的应用设计和编写前端架构。由于这些应用既复杂又需要一个可扩展、高可用的架构,所以我的职责之一就是确保用于实现这类应用的模式能够长期保持稳定。

同时,我自认为自己是一个设计模式狂热者(尽管在这个主题上有大量比我经验丰富的专家)。我之前写过一本《Essential JavaScript Design Patterns》,现在正在写针对该书的更深入的细节内容。

你能用140个子概括一下这篇文章吗?

将应用架构划分解为“模块”,“外观”和“中介者”模式。模块发布消息,“中介者”扮演 pub/sub,“外观”模式处理安全问题。

究竟什么是大规模JavaScript应用?

文章正式开始之前,让我们给“大规模Javascript应用一个“定义。我发现对于很多在这个领域有多年经验的开发者,仍然回答不好这个问题,给出的答案很主观。

作为实验,我问了一些中级的开发者。一个开发者说代码超过100 000行的应用可称之为大规模Javascript应用,也有人说代码体积超过1MB的应用可称之为大规模Js应用。这些答案都是不正确的,代码量不是永远都能够反映出一个应用程序的复杂度。

我自己的定义可能不被所有人接受,但是我相信它最接近正确答案:

在我看来,大规模Javascript应用是非常重要的应用,需要开发者投入大量的精力去维护,涉及大量数据操作和在浏览器上的数据展现。

定义的后半部分可能是最有意义的

让我们审视一下你的现有应用架构。

如果你正在开发一个非常大的Javascript应用,一定要记住多花些时间在设计地层架构上。它经常要比你最初想象的要复杂

我找不到更合适的方法来强调它的重要性 – 我见过一些大应用的开发者,常常退一步说:“在我过去中等规模的项目上,有大量成功的理念和模式工作的很好,他们应该也试用于规模稍大一点的项目吧”。一定程度上这种观点可能是对的,但别理所当然的相信它的正确性。大的项目通常有更多的关注点需要考虑。我会简短的讨论一下为什么从长期来看,多花一点点时间来设计应用的结构是值得的。

大多数Javascript开发者可能混合使用如下模式设计其架构。
  • Custom widgets
  • Models
  • Views
  • Controller
  • Templates
  • Libraries/toolkits
  • An application core

你也可能会把你的应用按照功能点划分为不同的模块或者使用其他模式来做到这一点。这很好,但是如果你在你所有的应用中都使用这种模式可能会导致一些潜在的问题 。

1. 这个架构是否可以被立即重用?

一个模块可以毫无依赖独立存在吗?他们是自包含的吗?假设,让我查看一下你或你的团队正在构建的应用的底层代码,然后从中随机选择一个模块,它是否可以不加修改的在任何页面下工作?你可能在考虑这样做的合理性,但我鼓励你去考虑一下未来。如果你的公司打算去构建一个更重要的应用,会复用现有应用的一些功能。比如有人说:“我们的用户喜欢使用我们邮件客户端的聊天模块,让我们把这个模块加入到我们的协同编辑套件中”。这项工作可能避免不了大规模的代码修改。

2. 系统中模块间依赖程度有多大?

他们是否是紧耦合的?在我深入讨论这个问题之前,先声明一下,我知道让系统中的不同模块相互独立,毫无依赖是不可能的。在低粒度的级别,你可能会有一些模块继承了其他模块的一些功能,但这个问题可能与一组功能相互独立的模块有关。对于这些功能相互独立的模块,不依赖太多其他模块就可以工作,是可能的。

3. 如果系统中的个别模块挂掉,系统是否可以继续工作?

如果你正在构建一个类似于Gmail的应用,你的webmail模块出错,这是否会影响到其他的UI,或者是否会妨碍用户使用页面上的其他模块,比如聊天。同时,在理想情况下,模块应该可以独立于你的应用架构而存在。我曾提到过基于用户意图的依赖(或模块)动态加载。例如,Gmail的例子中,默认情况下,如果核心模块没有在页面初始化时加载,那么聊天模块可能无法工作。如果用户表达了使用聊天功能的意图,这时就应该动态的加载相关模块的代码。理想情况下,你希望这一切在不对系统其他部分造成负面影响的前提下成为可能。

4. 是否可以方便的测试独立的模块。

当系统规模很大,有大量用户使用系统不同的部分,对于那些在系统不同部分进行重用的模块,是很有必要进行完备测试的。测试要求模块无论是在架构内,还是架构外都可以工作。在我看来,这为当模块放入其他系统中仍可正常工作提供了保证。

从长远考虑

当划分一个系统的架构时,考虑长远目标很重要。不仅仅是从现在开始的一个月或者一年,而是更长的时间。什么可能变化?当然,完全猜到将来你的应用会发展成什么样是不可能的,但是却有大量的空间可以考虑它未来可能会是什么样。至少有一个方面可以考虑。

开发者经常把dom操作的代码和其他代码耦合在一起,尽管他们已经陷入无法把它们的核心逻辑的代码中抽象出来并封装到模块中的困境。想一想吧,为什么从长远考虑这不是一个好主意。

我的一个听众说,是因为幻灯片中定义的严格的建构模式在将来可能不合适。然而,有一点没有考虑进去可能会造成更糟的结果。

将来,你可能出于性能、安全或者设计的考虑在Dojo, jQuery, Zepto 或者 YUI间切换你的框架。这可能会成为一个问题,因为基础库并不容易切换,如果它们和你的应用紧耦合切换代价也会很大。

如果你是一个Dojo开发者,你可能会说没什么好切换的,可又有谁能够保证2-3年后不会有更好的框架出现呢。

对于小应用这可能不重要,但对于大应用却很重要。一个灵活到可以不用关心模块中使用的是什么基础库的架构,可以在经济上、时间上带给你很多益处。

总之,如果现在让你重新审视你的架构,是否可以做到不更改应用源码的情况下切换基础库?如果不是,考虑一下继续往下读,因为今天要介绍的架构模式可以解决这个问题。

一些有影响力的Javascript开发者之前也介绍过一些相关内容。我想引用三点,作为分享。

创建大应用的秘诀是永远不要创建大应用。将你的应用打碎为小的模块。然后把这些可测试的、小体积的模块组装为你的大应用” – Justin Meyer, JavaScriptMVC的作者
关键是在开始时就承认你无法预知它会发展成什么样。当你承认你不是所有事都知道,你就会开始防御式的设计你的系统。你会发现将来可能发生变化的关键部分,而这个部分通常只要你肯投入一些时间,都很容易实现。例如,你应该认识到,应用中任何与其他系统通信的模块将来都有可能发生变化,所以你需要把这部分抽象出来。” – Nicholas Zakas, “High-performance Javascript websites”的作者
模块耦合度越高,重用就会越难,想要不更改其他模块去做一些变化就会更难”—Rebecca Murphey, jQuery 基础模块作者。
这些原则对于架构设计十分重要。他们经得起时间的考验,应该永记于心。

头脑风暴

让我们先考虑一下我们在尝试获取什么。
我们需要一个松耦合的架构,这个架构把功能点划分为互相独立的模块。当特定事件发生时,模块会向系统的其它部分发送消息,一个中间层负责处理和响应这些消息。

例如,如果我们有一个“网上面包店”的Javascript应用,一个模块发出的特定消息可能是“42个面包圈已经准备好被分发”。

我们使用一个单独的层来处理来自模块的消息,于是a) 模块不直接与核心模块通信; b) 模块之间不需要直接的交互。这帮助我们防止应用由于个别模块出错而无法工作。也给我们提供了一个重启出错模块的方式。

另一个考虑是安全问题。现实是大多数人不太关心应用内部的安全问题。他们告诉自己:我足够聪明去决定哪些可以公开,哪些应该私有。

然后,如果有一个方式可以帮助你决定一个模块允许在系统中做什么,是否会有帮助?例如,如果我知道我已经限制了公共聊天模块与管理模块和数据库写模块的交互,我就可以降低他们利用漏洞攻击系统的可能。模块不应该什么都可以访问。目前的大多数系统中,这都是允许的,但这真的有必要吗?

利用一个中间层来管理哪些模块可以访问系统的那些部分给了你额外的安全性。这意味着一个模块只可以做我们允许它做的事情。

推荐的架构

构建我们需要的架构需要联合三个家喻户晓的设计模式:模块(module),外观(façade)和中介者(mediator)。

与传统的模块间直接通信的方式不同,在这个松耦合的架构中,他们只可以发布特定的消息(理想情况下,模块不知道系统中还有其他模块的存在)。中介者用于从模块订阅消息和决定对于特定消息该做什么样的响应。外观模式用于限制模块的行为。

接下来,我会深入讨论这些模式的细节。





07 Dec 2011