第1章 因变而变

第1章 因变而变

“企业家总在寻求变化,他们适应变化,并把它当作一次机遇。”

——彼得·德鲁克,“现代管理学之父”

是怎样的变化促使开发者采用客户端-服务器端的架构?用户行为、技术和软件开发流程的变迁是其中最重要的原因,它们促使开发者改变过去的设计模式。这其中的每个因素,都在以自己独特且有效的方式让已有的模式不合时宜。它们一起激励了相关领域的创新,虽然没有强制标准化,但是在实践中,人们的开发方式正在趋同。

用户变了。早期的Web用户满足于静态页面和最基本的用户界面,而现在的用户期待的是高性能、可交互、精心设计的动态体验。大量新技术的涌现和浏览器能力的扩展满足了用户的期待。现在的Web开发者需要使用工具和新的开发方式,以适应现代Web应用的使用场景。

技术变了。浏览器和JavaScript引擎变得更快。台式机和笔记本性能更强,更不用说无数的移动设备现在也用来网上冲浪了。Web服务API再也不是一个可有可无的功能,而是人们对现代Web应用一个再也正常不过的期望。云计算彻底颠覆了Web应用的部署和运作。

软件开发方式变了。流行的“敏捷宣言”提倡:

  • 个体和互动高于流程和工具;
  • 工作的软件高于详尽的文档;
  • 客户合作高于合同谈判;
  • 响应变化高于遵循计划。

现在,让Web应用先快跑起来变成可能——至少在小范围内,可以验证技术的可行性。搭建原型价值非凡。正如《人月神话》(The Mythical Man Month,Addison-Wesley Professional)的作者Fred Brooks那句名言所说:“准备着扔掉一个吧,无论如何,你都会这样干的。”原型让早期客户或用户参与交互,帮助在前期确定需求。花几分钟时间编写一个能工作的Web应用已经不是什么不可能的事了。

1.1 Web用户

对如何与现代Web应用交互,用户的需求是明确的:

  • Web应用需能跨平台访问
  • Web应用需在不同平台上提供一致的用户体验
  • Web应用需响应及时,没有延迟

高德纳集团(http://www.gartner.com/newsroom/id/1947315)声称,个人云服务将在2014年取代PC机成为人们数字生活的中心。这对Web应用的开发来说有多层含义。用户更加热衷科技,对网站的响应速度有更高的期待。他们不像过去那些年一样被动接受,转而参与其中并互动。网站需要设计得看不出浏览器的局限,提供像原生应用一样的用户体验。

用户希望应用以多种方式提供服务,在不同环境下均可使用。响应式设计、多浏览器、多平台和多设备支持已经成为新的常态。为了支持多样的客户端,使用JavaScript类库和框架至关重要。

《纽约时报》最近报道了Web用户对网站响应时间的忍耐度(http://www.nytimes.com/2012/03/01/technology/impatient-web-users-flee-slow-loading-sites.html?pagewanted=all&_r=0)。他们发现:如果某公司网站的访问速度比直接竞争对手的慢250毫秒,那么访问量就会相对少很多。在开发Web应用时,我们需要将性能视作一个关键的考虑因素。

1.2 技术

使用Java开发Web应用的开发者,一般都熟悉服务器端的动态内容生成。J2EE和JSP经过完善,变成了JEE和JSF。诸如Spring这样的框架为服务器端开发提供了额外的功能。在Web应用的早期,页面相对来说更静态化,服务器的速度相对更快,JavaScript引擎很慢,处理浏览器兼容性的类库和技术也很少,这种开发模型就是合情合理的。

与之相比,现在的客户端-服务器端架构里,服务器更大程度上负责响应客户端请求,提供资源的访问方式(通常使用XML或者JSON交换信息)。在过去的服务器驱动模型里,页面(和与之相关的数据)都在服务器端生成完毕,一起返回客户端在浏览器里渲染。而在客户端-服务器端架构里,服务器先返回一个包含少量数据的初始页面。用户和页面交互时,页面向服务器端发起异步请求,服务器端则返回消息,以使页面刷新来响应这些事件。

刚开始的Web开发主要是创建静态HTML网站,之后加入了服务器端处理(CGI、Java Servlets),为网站注入了动态内容。慢慢地,人们开始使用更加结构化的语言,集成了服务器端模板(ASP、PHP、JSP)和MVC框架。新近出现的技术继续沿着传统的路子,增加了这样那样的抽象。

为了向开发者屏蔽设计和Web的底层架构,组件化的框架出现了。先是出现了标签库,然后是组件化,几种流行的框架都采用了组件化的方式:

  • Java Server Faces(JSF)是一种基于XML的模板系统和组件化框架,导航实行集中化配置;
  • Google Web Toolkit是另一种组件化框架,它发挥了Java程序员的长处,让他们集中精力编写Java代码,而不需要直接修改HTML、CSS和JavaScript。

每一种框架都有它的作用,被成功应用于生产系统。但是,和很多试图屏蔽底层复杂性的方案一样,当你需要更多的控制权(比如想要集成大量的JavaScript代码),或者不满足使用框架的前提条件(比如可用服务器会话)时,就出问题了。Web的基本架构是使用HTTP请求-响应协议的一种客户端-服务器端计算模型,而这些方案却试图去屏蔽它。

浏览器端的创新也促成了责任从服务器端向客户端的转移。20世纪90年代后期,微软开发了后来成为Ajax(Jesse James Garrett于2005年2月18日命名了这一术语)的底层技术。Ajax是“asynchronous JavaScript and XML”的缩写,但是对各种Web页面和服务器进行通信的技术也都适用。这种技术允许少量信息传递,这样在设计基于JavaScript的Web应用时,可以更好地利用带宽。处理器的升级和JavaScript引擎的优化显著地提升了浏览器性能,因此将更多工作从服务器端搬到浏览器端也变得顺理成章。用户界面的响应速度被带向了一个新高度。

移动设备的浏览器更进一步促使了客户端代码和服务器端的分离。有时候,我们可以创建出设计良好的响应式Web应用。如果不行,对所有的客户端设备都使用一致的API这一点也是很吸引人的。

罗伊·T. 菲尔丁于2000年发表的博士论文,让Java EE 6与过去基于组件的API设计背道而驰。人们设计出了JAX-RS(Java API for RESTful Web Services)和Jersey(一个“产品级的参考实现”),用来创建使用REST式风格通信的客户端-服务器端架构。

1.3 软件开发

过去,建立一个新的Java项目相当费事。大量的配置选项让人不胜其烦,而且容易出错。几乎没有自动化,人们假设每个项目都不一样,开发者各有各需要满足的需求。

后来的一些创新,让建立项目变得简单很多。“约定优于配置”是来自Ruby on Rails社区的箴言。Maven(http://www.bit.ly/MLOLbU)和其他一些Java项目也选择了有意义的默认配置,并为一些常用案例提供了快速创建方式。

JVM之上出现的各种脚本语言绕过了Java严格的类型检查,加快了开发过程。Groovy、Python(Jython)和Ruby都是弱类型语言,完成同样的功能需要的代码量更少。还有称为微框架的Sinatra和Play,它们提供了最小化的领域专用语言(DSL),用来快速编写Web应用和服务。时至今日,在开发环境中建立一个最小化的Web服务已经不是什么难事。

采用瀑布式开发的大型软件项目失败的案例已经不胜枚举,这充分说明为最终产品搭建一个小型原型有很多优势。搭建原型有以下几个目的:

  • 验证项目的技术基础;
  • 为不同技术搭建桥梁,将它们纳入同一结构;
  • 让最终用户和系统交互,明确系统用途和用户界面设计;
  • 让系统设计师明确接口和系统之间传递信息的数据结构;
  • 让程序员能在应用的各个部分并行工作。

原型还有很多优点,如下。

  • 原型是具体的、实实在在的,它们代表了待设计的最终系统。因此,原型融入了本应存在于设计文档、图表和其他介质(常常散见于更随意的场合,比如电子邮件,或人们在饮水机旁的闲谈)的信息。
  • 原型是具体的实现。因此,它们代表了真实的需求。这便于人们更好地理解收集上来的需求,并且确定哪些地方需要进一步明确。
  • 原型能迅速暴露可能失败的地方。在具体实现前,失败并不是显而易见的。
  • 上述优点能更好地帮助人们理解究竟要做什么,这样就会有更好的预测和计划。

由于客户端和服务器端分工明确,在搭建原型开发客户端-服务器端的Web应用时可以发挥很大的作用。开发客户端的可以使用服务器端的原型(反之亦然),这样就可以并行开发。即使不并行开发,这样也能快速模拟对服务器端的调用,方便开发客户端代码。

1.4 哪些没变

Web的本质没有变(还是基于HTTP进行消息传递的客户端-服务器端架构)。

新技术并没有改变一切。高级编程语言没有抹去了解操作系统的需要;对象-关系映射框架也没有抹去了解关系型数据库和SQL的需要。同样地,在开发Web应用时,人们一直试图效仿桌面应用,想忽略Web的底层架构。


介质特殊性
介质特殊性是出现在美学与当代艺术批评中的词汇,但是对技术也适用。它隐含了对于给定的艺术主题,需要使用特定介质来表达的合理性。这个观点由来已久,戈特霍尔德·埃夫莱姆·莱辛在《拉奥孔》(Laocoon: An Essay Upon the Limits of Painting and Poetry)一书中这样写道:

物体连同它们看得见的属性是绘画所特有的题材,动作是诗所特有的题材。1

——论诗与画的界限2

1节选自朱光潜的《拉奥孔》译本。——译者注

2“论诗与画的界限”是《拉奥孔》译本的副标题。——译者注

当代艺术作品通常挑战艺术上的传统限制。技术是一项创新性活动,但我们关注的是可用的系统,而不是抽象的美。介质无关性之所以重要,是因为如果忽略了平台的本质,则最终系统要么不能以最优的方式工作,要么根本不能工作。这在很多技术领域已经是显而易见的事。本书的目的是提倡遵循Web本身的设计方式来设计Web应用。由于它遵循了Web的基本限制,而不是去忽略它,这样的Web应用才能更好地工作。


1.4.1 Web的本质

Web的本质没有变。它依然由服务器和客户端构成,通过HTTP协议,服务器向客户端提供HTML文档,如图1-1所示。

图像说明文字

图1-1 HTTP请求和响应

客户端-服务器端的Web应用架构更贴合Web本身的架构。虽然与协议无关,REST是基于HTTP开发的,也和HTTP结合在一起使用。从本质上说,REST定义了使用HTTP的限制。它试图描述一种设计良好的Web应用:该应用是可靠的,运行良好,可扩展,设计优雅,而且方便修改(如图1-2所示)。

图像说明文字

图1-2 REST请求和响应

事实上,为了更准确地强调现代Web环境中的挑战,我们还要考虑多设备和云部署,如图1-3所示。

图像说明文字

图1-3 多设备和云部署

在Web开发(尤其是组件框架)中,Web的无状态、客户端-服务器端结构的本质,成了被忽略的“介质特殊性”。

1.4.2 为什么说服务器驱动的Web开发有害

具备某种功能,不意味着要使用该功能。在很多情况下,服务器驱动、基于组件的Web开发方式都应被客户端-服务器端的架构替换。服务器驱动的方式模糊了Web的本质,它本身就是一种架构于HTTP协议之上的客户端-服务器端模型。忽略或模糊Web的本质,会让开发、调试和支持软件系统变得更难。服务器驱动的Web原本是为了让Web变得更容易理解,但这一意愿很快在所有正式的系统(需要对其具备的功能和工作方式有清晰理解的系统)里被打破。


被认为是有害的

1968年, Edsger W. Dijkstra发表了一封题为“Go To语句被认为是有害的”(Go To Statement conidered Harmful)的公开信。这封信很有趣,除了对结构式编程中减少使用goto语句造成很大影响,它还把“被认为是有害的”(considered harmful)这一说法带入到极客文化中。Tom Christiansen认为使用csh编程是有害的(http://harmful.cat-v.org/software/csh),Douglas Crawford(道格拉斯·克罗克福德)发表了一篇题为“with语句是有害的”(“with Statement Considered Considered Harmful”,参见http://yuiblog.com/blog/2006/04/11/with-statement-considered-harmful/)的博文。该说法还见于其他地方,包括Eric A. Meyer发表的那篇风趣的自我指涉的文章“‘被认为是有害的’系列文章是有害的”(“'Considered Harmful' Essays Considered Harmful'”,参见http://meyerweb.com/eric/comment/chech.html),而且可以想见,这个说法还会不停地出现。

尽管“被认为是有害的”这类博人眼球的文章质量上参差不齐,但其都有一个合理的共识:具有一种语言特性或一个技术解决方案,不过这并不表示它是好的、长久的解决方案。


1.5 为什么需要客户端-服务器端的Web应用

在Web开发中,客户端-服务器端的架构有很多优点。

1.5.1 代码组织结构/软件架构

将代码按逻辑解耦的好处很明显,它增强了原有结构和后续支持系统的内聚性。将客户端和服务器端分层,这可以让代码变得可管理、模块化。另外,数据和显示标记能更清晰地分离。可以使用JSON而不是内嵌的方式发布数据,这和现代JavaScript的不可见JavaScript概念是一致的,页面的行为、结构和表现分离。

好的代码组织结构自然会带来灵活性和代码重用。在应用的整个生命周期内,灵活性表现在很多阶段,不同的代码可以分开开发(暴露新的API、创建移动客户端、测试和发布应用的新版本,这些都可以是独立的)。有了清晰的组件,我们才能更好地重用代码。至少,同一份RESTful API可供不同的浏览器和移动设备使用。

组件化的方式易于引入脆弱的耦合,适配性也不好,很难插入不同的前端。

1.5.2 “设计的灵活性”与“使用开源API”

组件化的方式包括了高度集成的服务器端代码,这些代码要求了解特定的JavaScript技术。从设计和行为的角度看,生成的HTML和CSS限制了选择性。一个单独运行JavaScript的客户端能使用最新的类库,简化浏览器兼容性、标准化DOM操作,并提供复杂的小组件。

1.5.3 原型

由于分层清晰,为客户端-服务器端的Web应用搭建原型很方便。正如前面提到的,原型可以测试和验证初始想法,帮助澄清模糊的概念,方便清晰地交流需求。用户和这种更具体的东西交互能产生新的想法,这远比冗长的文字描述或图片有用得多。原型还能帮助用户快速地认识,纠正错误的想法和不一致性。如果正确使用,原型能帮助节省时间、金钱和资源,最终产生一个更好的产品。

1.5.4 开发者的效率

除了可以分别(或同时)搭建客户端和服务器端的原型,工作也能清晰地拆分,从而进行并行开发。这种隔离让代码能分开编写,这就避免了模块化方式中页面一更改就得构建整个服务器端代码的问题。开发任务花的时间和精力变少,更改也没有原来复杂,故障诊断也变得简单了。

当需要替换、升级或重新分配服务器端代码时,这点变得尤其明显。这样的改动可以独立完成,不会影响客户端。唯一的限制是原来的接口(特别是URL和数据结构)需要保持可用。

1.5.5 应用性能

页面的感知性能对用户体验影响极大。更快的JavaScript引擎让客户端可执行计算密集型操作,从而将服务器的工作负荷转移到客户端。Ajax技术能够按需请求较少的数据,这避免了对整个页面的频繁刷新,请求中传输的数据也减少了。用户和应用交互时,反应更及时,体验更流畅。

无状态的设计让开发者和技术支持的工作更轻松。用于管理会话的资源可以得到释放,这简化了负载均衡和配置。服务器很容易被加进来以应对增长的负荷,方便了水平扩展。而且,这还取代了传统的、通过升级硬件提高吞吐量和性能的流程,那种流程真让人头疼。

优点还不止于此,它从整体上简化了系统架构。比如,在云环境中维持状态是件非常有挑战性的事。如果使用传统的有状态会话,高效地持久化数据是个问题,如何才能在一个用户会话的多个请求中保持数据的一致?如果数据存储在后台的一个服务器中,后续被分配到其他服务器的请求就无法访问该数据,可能的解决方案有如下。

  • 使用支持集群和故障转移功能的应用服务器,以Weblogic为例,它使用了托管服务器的概念。这种解决方案需要额外的管理工作,每个应用服务器的实现方式也不同。

  • 使用会话关联或粘滞会话(sticky session),此时一个用户会话内的所有请求都被发送到同一个后端服务器,但是不提供自动故障转移功能。

  • 使用集中的数据存储。通常都是将数据持久化到数据库中,这种方式的性能不好。

  • 将数据存储在客户端。这避免了将会话数据存储到数据库的性能问题,也解决了使用粘滞会话带来的故障转移问题,因为任何一个后端服务器都能处理来自客户端的请求。

避免管理服务器端状态的做法越来越流行,甚至像JSF这样原来被设计成传统的服务器端管理用户会话的框架,现在也加入了支持无状态的功能。

创建客户端-服务器端的应用有一些不可避免的挑战。我们有必要将JavaScript看作头等开发语言,这意味着需要深入学习该语言、使用现有类库,以及借鉴成熟的开发技巧。原来被普遍接受的一些架构技术需要重新设计,比如管理会话的标准实践。对客户端-服务器端的Web应用并没有定义良好的标准。JEE的某些部分(比如JAX-RS)澄清了一些概念,但其他框架(如JSF)并未遵循这些定义。

越过了初期学习(和忘记原有知识)曲线,使用客户端-服务器端的模式搭建Web应用就变得异常高效和稳定。对服务器端和客户端职责的清晰划分,让修改和扩展代码变得更容易。对于Web本质的清楚认识减少了模糊设计带来的问题,水平扩展的能力也大大超出了使用其他设计所能带来的效果。

1.6 小结

新的挑战带来新的机遇。客户端-服务器端的架构设计是对Web和Web开发变化做出的理所应当的响应。它清楚什么没有改变,因而能够指导人们开发出稳定、持久的解决方案,为后续的增强打好了基础。

目录