目 录前端
1 引言程序员
0.1 架构数据库
0.2 企业应用浏览器
0.3 企业应用的种类性能优化
0.4 关于性能的考虑服务器
0.5 模式网络
0.5.1 模式的结构多线程
构建计算机系统并不是易事。随着系统复杂性的增大,构建相应软件的难度将呈指数增大。同其余行业同样,咱们只有在不断的学习中进步,从成功经验中学习,从失败教训中学习,才有望克服这些困难。本书的内容就是这样一些“学习”经验。我但愿它们的撰写和编排方式,可以有助于读者更快地学习这些内容,而且,和我在总结出这些模式以前相比,能更有效地与他人进行交流。app
在引用中,我想设定本书讨论的范围,并提供一些相关的背景知识与材料。
软件业的人乐于作这样的事——找一些词汇,并把它们引伸到大量微妙而又互相矛盾的含义。一个最大的受害者就是“架构”(architecture)这个词。我我的对“架构”的感受是,它是一个让人印象深入的词,主要用来表示一些很是重要的东西。固然,我也会当心,不让这些对“系统结构”的“不恭之词”,影响到读者对本书的兴趣。
不少人都试图给“架构”下定义,而这些定义自己却很难统一。可以统一的内容有两点:一点是“最高层次的系统分解”;另外一点是“系统中不易改变的决定”。愈来愈多的人发现:表述一个系统架构的方法不仅一种;一个系统中也可能有不少种不一样的架构,并且,对于什么在架构上意义重大的见解也会随着系统的生命周期变化。
Ralph Johnson常常在邮件列表上发帖,并提出一些使人关注的看法。就在我完成本书初稿的同时,他又发表了一些关于“架构”的观点。他认为,架构是一种主观上的东西,是专家级项目开发人员对系统设计的一些可共享的理解。通常地,这种可共享的理解表现为系统中主要的组成部分以及这些组成间的交互关系。它还包括一些决定,开发者们但愿这些决定能及早作出,由于在开发者看来它们是难以改变的。架构的主观性也来源于此——若是你发现某些决定并不像你想象的那么难以改变,那么它就再也不与架构相关。到了最后,架构天然就浓缩成一些重要的东西,不论这些东西是什么。
在本书中,我提出一些本身的理解,涉及企业应用主要组成部分和我但愿能尽早作出的决定。在这些架构模式中,我最欣赏的就是“层次”,将在第1章中进行详细介绍。全书实际上就是关于如何将企业应用组织成不一样的层次,以及这些层次之间如何协同工做。大多数重要的企业应用都是按照某种形式的层次分层设计的;固然,在某些状况下,别的设计方式(如管道方式、过滤器方式等)也有它们本身的价值。在本书中咱们将不会讨论这些方式,而把注意力集中在层次方式上,由于它是应用最广的设计方式。
本书中的一些模式毫无疑问是关于架构的,它们表示了企业应用各主要组成部分间的重要决定;另一些模式是关于设计的,有助于架构的实现。我没有刻意区分这两类模式,由于正如咱们前面讨论的,是否与架构相关每每带有主观性。
编写计算机软件的人不少,咱们一般把这些活动都称为软件开发。可是软件的种类是不一样的,每种软件都有自身的挑战性和复杂性。我是在与几个从事电信软件开发的朋友交谈后,意识到这个问题的。企业应用在某些方面要比电信软件简单得多——多线程问题没有那么困难,无需关注硬件设备与软件的集成。可是,在某些方面,企业应用又比电信软件复杂得多——企业应用通常都涉及到大量复杂数据,并且必须处理不少“不合逻辑”的业务规则。虽然有些模式是适合全部软件的,可是大多数模式都还只适合某些特定的领域和分支。
个人工做主要是关于企业应用的,所以,这里所谈及的模式也都是关于企业应用的。(企业应用还有一些其余的说法,如“信息系统”或更早期的“数据处理”。)那么,这里的“企业应用”具体指的是什么呢?我没法给出一个精确的定义,可是我能够罗列一些我的的理解。
先举几个例子。企业应用包括工资单、患者记录、发货跟踪、成本分析、信誉评估、保险、供应链、记帐、客户服务以及外币交易等。企业应用不包括车辆加油、文字处理、电梯控制、化工厂控制器、电话交换机、操做系统、编译器以及电子游戏等。
企业应用通常都涉及到持久化数据。数据必须持久化是由于程序的屡次运行都须要用到它们——实际上,有些数据须要持久化若干年。在此期间,操做这些数据的程序每每会有不少变化。这些数据的生命周期每每比最初生成它们的那些硬件、操做系统和编译器还要长。在此期间,数据自己的结构通常也会被扩展,使得它在不影响已有信息的基础上,还能表示更多新信息。即便是有根本性的变化发生,或公司安装了一套全新的软件,这些数据也必须被“迁移”到这些全新的应用上。
企业应用通常都涉及到大量数据——一个中等规模的系统每每都包含1GB以上的数据,这些数据是以百万条记录的方式存在的。巨大的数据量致使数据的管理成为系统的主要工做。早期的系统使用的是索引文件系统,如IBM的VSAM和ISAM。现代的系统每每采用数据库,绝大多数是关系型数据库。数据库的设计和演化已使其自己成为新的技术领域。
企业应用通常还涉及到不少人同时访问数据。对于不少系统来讲,人数可能在100人如下,可是对于一些基于Web的系统,人数会呈指数级增加。要确保这些人都可以正确地访问数据,就必定会存在这样或那样的问题。即便人数没有那么多,要确保两我的在同时操做同一数据项时不出现错误,也是存在问题的。事务管理工具能够处理这个问题,可是它一般没法作到对应用开发者透明。
企业应用还涉及到大量操做数据的用户界面屏幕。有几百个用户界面是不足为奇的。用户使用频率的差别很大,他们也常常没什么技术背景。所以,为了避免同的使用目的,数据须要不少种表现形式。系统通常都有不少批处理过程,当专一于强调用户交互的用例时,这些批处理过程很容易被忽视。
企业应用不多独立存在,一般须要与散布在企业周围的其余企业应用集成。这些各式各样的系统是在不一样时期,采用不一样技术构建的,甚至连协做机制都不一样:COBOL数据文件、CORBA系统或是消息系统。企业常常但愿能用一种统一的通讯技术来集成全部系统。固然,每次这样的集成工做几乎都很难真正实现,全部留下来的就是一个个风格各异的集成环境。当商业用户须要同其业务伙伴进行应用集成时,状况就更糟糕。
即便是某个企业统一了集成技术,它们也仍是会遇到业务过程当中的差别以及数据中概念的不一致性。一个部分可能认为客户是当前签有协议的人;而另一个部门可能还要将那些之前有合同,但如今已经没有了的人计算在内。再有,一个部门可能只关心产品销售而不关心服务销售。粗看起来,这些问题彷佛容易解决,可是,一旦几百个记录中的每一个字段都有可能存在着细微差异,问题的规模就会造成不小的挑战——就算惟一知道这些字段之间差异的员工还在公司任职(固然,也许他在你察觉到以前就早已辞职不干了)。这样,数据就必须被不停地读取、合并、而后写成各类不一样语法和语义的格式。
再接下来的问题是由“业务逻辑”带来的。我认为“业务逻辑”这个词很滑稽,由于很难找出什么东西比“业务逻辑”更加没有逻辑。当咱们构建一个操做系统时,老是尽量地使得系统中的各类事物符合逻辑。而业务逻辑生来就是那样的,没有至关的行政努力,不要想改变它,固然,它们都有本身的理由。你必须面对不少奇怪的条件。并且这些条件相互做用的方式也很是怪异。好比,某个销售人员为了签下其客户几百万美圆的一张单,可能会在商务谈判中与对方达成协议,将该项目的年度到帐时间推迟两天,由于这样才能与该客户的帐务周期相吻合。成千上万的这类“一次特殊状况”最终致使了复杂的业务“无逻辑”,使得商业软件开发那么困难。在这种状况下,必须尽可能将这些业务逻辑组织成有效的方式,由于咱们能够肯定的是,这些“逻辑”必定会随着时间不断变化。
对于一些人来讲,“企业应用”这个词指的是大型系统。可是 须要注意的是,并非全部的企业应用都是大型的,尽管它们可能都为企业提供巨大的价值。不少人认为,因为小型系统的规模不大,能够不用太注意它们,并且在某种程度上,这种观点可以带来必定的成本节约。若是一个小型系统失败了,相对于大型系统的失败,这种失败就不会显得那么起眼了。可是,我认为这种思想没有对小型项目的累积做用给予足够的重视。试想,若是在小型项目上可以进行某些改善措施,那么一旦这些改善措施被成功运用于大型项目,它带来的效果就会很是大。实际上,最好是经过简化架构和过程,将一个大型项目简化成小型项目。
在咱们讨论如何设计企业应用以及使用哪些模式以前,明确这样一个观点是很是重要的,即企业应用是多种多样的,不一样的问题将致使不一样的处理方法。若是有人说“老是这样作” 的时候,就应该敲响警钟了。我认为,设计中最具挑战性(也是我最感兴趣)的地方就是了解有哪些候选的设计方法以及各类不一样设计方法之间的优劣比较。进行选择的控件很大,但我在这里只选三个方面。
考虑一个B2C(Business to Customer)的网上零售商:人们经过浏览器浏览,经过购物车购买商品。经过购物车购买商品。这样一个系统必须可以应付大量的客户,所以,其解决方案不但要考虑到资源利用的有效性,还要考虑到系统的可伸缩性,以便在用户规模增大时可以经过增长硬件的办法加以解决。该系统的业务逻辑能够很是简单:获取订单,进行简单的价格计算和发货计算,给出发货信息。咱们但愿任何人都可以访问该系统,所以用户界面能够选用通用的Web表现方式,以支持各类不一样的浏览器。数据源包括用来存放订单的数据库,还可能包括某种与库存系统的通讯交流,以便得到商品的可用性信息和发货信息。
再考虑一个租约合同自动处理系统。在某些方面,这样的系统比起前面介绍的B2C系统要简单,由于它的用户数不多(在特定时间内不会超过100个),可是它的业务逻辑却比较复杂。计算每一个租约的月供,处理如提前解约和延迟付款这样的事件,签定合同时验证各类数据,这些都是很是复杂的任务,由于租约领域的许多竞争都是以过去的交易为基础稍加变化而出现的。正是由于规则的随意性很大,才使得像这样一个复杂领域具备挑战性。
这样的系统在用户界面(UI)上也很复杂。这就要求HTML界面要能提供更丰富的功能和更复杂的屏幕,而这些要求每每是HTML界面目前没法达到的,须要更常规的胖客户界面。用户交互的复杂性还会带来事务行为的复杂性:签定租约可能要耗时1~2个小时,这期间用户要处于一个逻辑事务中。一个复杂的数据库设计方案中可能也会涉及到200多个表以及一些有关资产评估和计价的软件包。
第三个例子是一家小型公司使用的简单的“开支跟踪系统”。这个系统的用户不多,功能简单,经过HTML表现方式能够很容易实现,涉及的数据源表项也很少。尽管如此,开发这样的系统也不是没有挑战。一方面你必须快速地开发出它,另外一方面你又必须为它之后可能的发展考虑;也许之后会为它增长赔偿校验的功能,也许它会被集成到工资系统中,也许还要增长关于税务的功能,也许要为公司的CFO生成汇总报表,也许会被集成到一个航空订票Web Service中,等等。若是在这个系统的开发中,也试图使用前面两个例子中的一些架构,可能会影响开发进度。若是一个系统会带来业务效益(如全部的企业应用应该的那样),则系统进度延误一样也是开销。若是如今不作决策又有可能影响系统将来的发展。可是,若是如今就考虑了这些灵活性可是考虑不得当,额外的复杂性又可能会影响到系统的发展,进一步延误系统部署,减小系统的效益。虽然这类系统很小,可是一个企业中每每有不少这样的系统,这些系统的架构不良性累积起来,后果将会很是可怕。
这三个企业应用的例子都有难点,并且难点各不相同。固然,也不可能有一个适合于三者的通用架构。选择架构时,必须很清楚地了解面临的问题,在理解的基础上再来选择合适的设计。本书中也没有一个通用的解决方案。实际上,不少模式仅仅是一些可选方案罢了。即便你选择了某种模式,也须要进一步根据面临的问题来修改模式。在构建企业应用时,你不思考是不行的。全部书本知识只是给你提供信息,做为你作决定的基础。
模式是这样,工具也一样如此。在系统开发时应该选取尽量少的工具,同时也要注意,不一样的工具擅长处理的方面也不一样,切记不要用错了工具,不然只会事倍功半。
不少架构的设计决策和性能有关。对于大多数与性能相关的问题,个人办法是首先创建系统,调试运行,而后经过基于测量的严格的优化过程来提升性能。可是,有一些架构上的决策对性能的影响,多是后期优化难以弥补的。并且即便这种影响能够在后期很容易地弥补,参与这个项目的人们任然会从一开始就担忧这些决策。
在这样的一本书中讨论性能一般很困难。这是由于“眼见为实”:全部那些关于性能的条条框框,不在你的具体系统中配置运行一下,是很难有说服力的。我也常常看到一些设计方案由于性能方面的考虑而被接受或拒绝,可是一旦有人在真实的设置环境中作一些测量,就会证实这些考虑是错误的。
本书将提出一些这方面的建议,包括尽可能减小远程调用(它在很长时间内都被认为是优化性能的好建议)。尽管如此,仍是建议读者在运用这些原则以前,在你的应用中具体试一试。一样,本书中的样例代码也有一些地方为了提升可读性而牺牲了效率。在你的系统中,须要自行决定是否进行优化。在作性能优化后,必定要与优化前进行测量对比,以肯定真的获得了优化,不然,你可能只是破坏了代码的可读性。
还有一个很重要的推论:配置上的重大变化会使得某些性能优化失效。所以,在升级虚拟机、硬件、数据库或其余东西到新的版本时,必须从新确认性能优化工做的有效性。不少状况下,配置变动都会对性能优化有影响,有时候你真的会发现,之前为了提高性能作的优化,在新环境下竟然影响性能。
关于性能的另外一个问题是不少术语的使用不一致。最明显的例子就是“可伸缩性”(scalability),它可能有6-7种含义。下面我使用其中一些术语。
响应时间是系统完成一次外部请求处理所须要的时间。这些外部请求多是用户交互行为,例如按下一个按钮,或是服务器API调用。
响应性不一样于请求处理,它是系统响应请求的速度有多快。这个指标在许多系统里很是重要,由于对于一些系统而言,若是其响应性太慢,用户将难以忍受——尽管其响应时间可能不慢。若是在请求处理期间,系统一直处于等待状态,则系统的响应性和响应时间是相同的。然而,若是可以在处理真正完成以前就给用户一些信息代表系统已经接到请求,则响应性就会好一些。例如,在文件拷贝过程当中,为用户提供一个“进度条”,将会提升用户界面的响应性,但并不会提升响应时间。
等待时间是得到系统任何形式响应的最小时间,即便应该作的工做并不存在。一般它是远程系统中的大问题。假设咱们让程序什么都不作,只是调用返回便可,则若是在本机上运行程序,通常都会当即获得响应。可是,若是在远程计算机上运行程序,状况就不同,每每须要数秒的时间才能获得响应。由于从发出请求到获得响应的数秒时间主要用于排除使信息在线路上传输的困难。做为应用开发者,我常常对等待时间无能为力。这也是为何要尽可能避免远程调用的缘由。
吞吐率是给定时间内可以处理多大的请求量。若是考察的是文件拷贝,则吞吐率能够用每秒字节量来表示。对于企业应用来讲,吞吐率一般用每秒事务数(tps)来度量。这种方法的一个问题是指标依赖于事务的复杂程度。对于特定系统的测试,应该选取普通的事务集合。
在这里,性能或指吞吐率,或者指响应时间,由用户本身决定。当经过某种优化技术后,使得系统的吞吐率提升了,可是响应时间降低了,这时就很差说系统的性能提升了,最好用更准确的术语表示。从用户角度而言,响应性每每比响应时间更重要,所以,为了提升响应性而损失一些响应时间或者吞吐率是值得的。
负载是关于系统当前负荷的表述,也许能够用当前有多少用户与系统相连来表示。负载有时也做为其余指标(如响应时间)的背景。所以,咱们能够说:在10个用户的状况下,请求响应时间是0.5秒,在20个用户的状况下,请求响应时间是2秒。
负载敏感度是指响应时间随负载变化的程度。假设:系统A在10~20个用户的状况下,请求响应时间都是0.5秒;系统B在10个用户的状况下,请求响应时间是0.2秒,在20个用户的状况下,请求响应时间上升到2秒。此时,系统A的负载敏感度比系统B低;咱们还能够使用术语衰减(degradation),称系统B衰减得比系统A快。
效率是性能除以资源。若是一个双CPU系统的性能是30tps,另外一个系统有4个一样的CPU,性能是40tps,则前者效率高于后者。
系统的容量是指最大有效负载或吞吐率的指标。它能够是一个绝对最大值或性能衰减至低于一个可接受的阈值以前的临界点。
可伸缩性度量的是向系统中增长资源(一般是硬件)对系统性能的影响。一个可伸缩性的系统容许在增长了硬件后,可以有性能上的合理提升。例如,为了使吞吐率提升一倍,要增长多少服务器等。垂直可伸缩性或称垂直延展,一般指提升单个服务器的性能,例如增长内存。水平可伸缩性或称水平延展,一般指增长服务器的数目。
问题是,设计决策对全部性能指标的做用并不相同。好比,某个服务器上运行着两个软件系统:Swordfish的容量是20tps,而Camel的容量是40tps。哪个的性能更高?哪个的可伸缩性好?仅凭这些数据,咱们没法回答关于可伸缩性的问题,咱们只能说Camel系统在单片机上的效率更高。假设又增长了一台服务器后,咱们发现:Swordfish的容量是35tps,Camel的容量是50tps。尽管Camel的容量仍然大于Swordfish,可是后者在可伸缩性上却显得比前者更好。假设咱们继续增长服务器数目后发现:Swordfish每增长一台服务器提升15tps,Camel每增长一台服务器提升10tps。在得到了这些数据后,咱们才能够说,Swordfish的水平可伸缩性比Camel好,尽管Camel在5个服务器如下会有更好的效率。
当构建企业应用系统时,关注硬件的可伸缩性每每比关注容量或效率更重要。若是须要,可伸缩性能够给予你得到更好性能的选择,可伸缩性也能够更容易实现。有时,设计人员费了九牛二虎之力才提升了少量容量,其开销还不如多买一些硬件。换句话说,假设Camel的费用比Swordfish高,高出的部分正好能够买几台服务器,那么选择Swordfish可能更合算,尽管你目前只须要40tps。如今人们常常抱怨软件对硬件的依赖性愈来愈大,有时为了运行某些软件就不得不对硬件进行升级,就像我同样,为了用最新版本的Word,就必须不断地升级笔记本电脑。可是总的来讲,购买新硬件仍是比修改旧软件来得便宜。一样,增长更多的服务器也比增长更多的程序员来得便宜——只要你的系统有足够的可伸缩性。
模式的概念早就有了。我在这里不想把这段历史从新演绎一遍。只是想简单谈谈我对模式和它们为何是描述设计的重要手段的一些见解。
模式没有统一的定义。可能最好的起点是Christopher Alexander给出的定义(这也是许多模式狂热者的灵感来源):“每个模式描述了一个在咱们周围不断重复发生的问题以及该问题解决方案的核心。这样,你就能一次又一次地使用该方案而没必要作重复劳动”[Alexander et al.]。尽管Alexander是建筑家,他谈论的是建筑模式,但其定义也能很好地适用于软件业。模式的核心就是特定的解决方案,它有效并且有足够的通用性,能解决重复出现的问题,模式的另外一种视角是把它当作一组建议,而创造模式的艺术则是将不少建议分解开来,造成相互独立的组,在此基础上能够相对独立地讨论它们。
模式的关键点是它们源于实践。必须观察人们的工做过程,发现其中好的设计,并找出“这些解决方案的核心”。这并非一个简单的过程,可是一旦发现了某个模式,他将是很是有价值的。对于我来讲,价值之一是可以撰写这样一本参考书。你没必要通读本书的所有内容,也没必要通读全部有关于模式的书。你只须要了解到这些模式都是干什么的,它们解决什么问题,它们是如何解决问题的,就足够了。这样,一旦碰到相似问题,就能够从书中找出相应的模式。那时,再深刻了解相应的模式也不迟。
一旦须要使用模式,就必须知道如何将它运用于当前的问题。使用模式的关键之一是不能盲目使用,这也是模式工具为何都那么惨的缘由。我认为模式是一种“半生不熟品”,为了用好它,还必须在本身的项目中把剩下的那一半“火候”补上。我本人每次在使用模式时,都会东改一点西改一点。所以你会屡次看到同一解决方案,但没有一次是彻底相同的。
每一个模式相对独立,但又不彼此孤立。有时候它们相互影响,如影随形。例如,若是在设计中使用了领域模型,那么常常还会用到类表继承。模式的边界原本也是模糊的,我在本书中也尽可能让它们各自独立。若是有人说“使用工做单元”,你就能够直接去看工做单元这个模式如何使用,而没必要阅读全书。
若是你是一个有经验的企业应用设计师,也许会对大多数模式都很熟悉。但愿本书不会给你带来太大的失望。(实际上我在前言里面已经提醒过了。)模式不是什么新鲜概念。所以,撰写模式书籍的做者们也不会声称咱们“发明”了某某模式,而是说咱们“发现”了某某模式。咱们的职责是记录通用的解决方案,找出其核心,并把最终的模式记录下来。对于一个高级设计师,模式的价值并不在于它给予你一些新东西,而在于它能帮助你更好地交流。若是你和你的同事都明白什么是远程外观,你就能够这样很是简洁地交流大量信息:“这个类是一个远程外观模式。”也能够对新人说:“用数据传输对象模式来解决这个问题。”他们就能够查找本书来搞清楚如何作。模式为设计提供了一套词汇,这也是为何模式的名字这么重要的缘由。
本书的大多数模式是用来解决企业应用的,基本模式一章(见第18章)则更通用一些。我把它们包含进来的缘由是:在前面的讨论中,我引用了这些通用的模式。
每一个做者都必须选择表达模式的形式。一些人采用的表达基于模式的一些经典教材如[Alexander et al.]、[Gang of Four]或[POSA]。另外一些人用他们本身的方式。我在这个问题上也斟酌了好久。一方面我不想象GOF同样太精炼,另外一方面我还要引用他们的东西。这就造成了本书的模式结构。
第一部分是模式的名字。模式名很是重要,由于模式的目的之一就是为设计者们交流提供一组词汇。所以,若是我告诉你Web服务器是用前端控制器和转换试图构建的,而你又了解这些模式,那么你对个人Web服务器的架构就会很是清楚了。
接下来的两部分是相关的:意图和概要。意图用一两句话总结模式;概要是模式的一种可视化表示,一般是(但不老是)一个UML图。这主要是想给模式一个简单的概况,以帮助记忆。若是你对模式已经“心知肚明”,只是不知道它的名字,那么模式的意图和概要这两部分就能为你提供足够的信息。
接下来的部分描述了模式的动机。这可能不是该模式所能解决的惟一问题,但倒是我认为最具表明性的问题。
“运行机制”部分描述了解决方案。在这一部分,我会讨论一些实现问题以及我遇到的变化状况。我会尽量独立于平台来讨论——也有一个部分是针对平台来讨论的,若是不感兴趣能够跳过这部分。为了便于解释,我用了一些UML图来辅助说明。
“使用动机”部分描述了模式什么时候被使用。这部分讨论是使我选择该模式而不是其余模式的权衡考虑。本书中不少模式均可以相互替代,例如页面控制器和前端控制器能够相互替代。不多有什么模式是非它不可的。所以,每当我选择了一种模式以后,我老是问本身“你何时不用它?”这个问题也常常驱使我选择其余方案。
“进一步阅读”部分给出了与该模式相关的其余读物。它并不完善。我只选择我认为有助于理解模式的参考文献,因此我去掉了对本书内容没有价值的任何讨论,固然其中也可能会遗漏一些我不知道的模式。我也没有提到一些我认为可能读者没法找到的参考文献,再就是一些不太稳定的Web连接。
我喜欢为模式增长一个或几个例子。每一个例子都很是简单,它们是用Java语言或C#语言编写的。我之因此选择两种语言,是由于它们多是目前绝大多数专业程序员都能读懂的语言。必须注意,例子自己不是模式。当你使用模式时,不要想固然地认为它会和例子同样,也不要把例子当作某种形式的宏替换。我把例子编得尽可能简单以突出其中模式相关的部分。固然,省略的部分并非不重要,只是它们通常都特定于具体环境,这也是为何模式在使用时通常都必须作适当调整的缘由。
为了尽可能使例子简单可是又可以突出核心意思,我主要选择那些简单而又明确的例子,而不是那些来自于系统中的复杂例子。固然,在简单和过度之间掌握平衡是不容易的,可是咱们必须记住:过度强调具体应用环境反而会增长模式的复杂性,使得模式的核心内容不易理解。
这就是为何我在选择例子时选取的是一些相互独立的例子而不是相互关联的例子的缘由。独立的例子有助于对模式的理解。可是在如何将这些模式联合在一块儿使用上却支持很少。相互关联的例子则相反,它体现了模式间是如何相互做用的,可是对其中每一个模式的理解却依赖于对其余全部模式的理解。理论上,是能够构造出既相互关联又相互独立的例子,但这是一项很是艰巨的工做——至少对于我来讲是这样。所以,我选择了相互独立的例子。
例子中的代码自己也主要用来加强对思想的理解。所以,在其余一些方面考虑可能不够——特别是错误处理,在这方面,我没有花费不少笔墨,由于到目前为止,我尚未得出错误处理方面的模式。在此,那些代码纯粹用来讲明模式,而并非用来显示如何对任何特定的业务问题进行建模。
正是因为这些缘由,我没有把这些代码放到个人网站上供你们下载。为了让那些基本的思想在应用设置下有所意义,本书的每一个样例代码都充满着太多的“脚手架”来简化它们。
并非每一个模式中都包含上面所述的各个部分。若是我不能想出很好的例子或动机等内容,我就会把相应部分省略。
正如我在前言中所述,对于企业应用开发而言,本书介绍的模式并不全面。我对本书的要求,不在于它是否全面,而在于它是否有用。模式这个领域太大了,单凭一我的的头脑是没法作到面面俱到的,更不用说是一本书了。
本书中所列的模式都是我在具体领域中遇到的,但这并不代表我已经理解了每个模式以及它们之间的关系。本书的内容只是反映了我在写书时的理解,在编写本书的过程当中,我对相关内容的理解也不断发展和加深,固然,在本书发表以后,我仍然但愿本人对模式的理解还可以继续发展。对于软件开发而言,有一点是能够确定的,那是软件开发永远不会中止。
当你使用模式时请记住:它们只是开始,而不是结束。任何做者去囊括项目开发中的全部变化和技术是不可能的。我编写本书的目的也只是做为一个开始,但愿它可以把我本身的和我所了解的经验和教训传递给读者,大家能够在此基础上继续努力。请你们记住:全部模式都是不完备的,大家都有责任在本身的系统中完善它们,大家也会在这个过程当中获得乐趣。
在分解复杂的软件系统时,软件设计者用得最多的技术之一就是分层。在计算机自己的架构中,能够看到:处处都有分层的例子:不一样的层从包含了操做系统调用的程序设计语言,到设备驱动程序和CPU指令集,再到芯片内部的各类逻辑门。网络互联中, FTP层架构在TCP之上, TCP架构在IP之上, IP又架构在以太网之上。
当用分层的观点来考虑系统时,能够将各个子系统想像成按照“多层蛋糕”的形式来组织,每一层都依托在其下层之上。在这种组织方式下,上层使用了下层定义的各类服务,而下层对上层一无所知。另外,每一层对本身的上层隐藏其下层的细节。所以,第4层使用第3层的服务,第3层使用第2层的服务,第4层无需知道第2层的细节。(固然,并不是全部的分层架构都这么隔绝,但绝大多数是不透明的,或至少是几乎不透明的。)
将系统按照层次分解有不少重要的好处:
• 在无需过多了解其余层次的基础上,能够将某一层做为一个有机总体来理解。例如,无需知道以太网的工做细节,你照样能够在TCP上构建FTP服务。
• 能够替换某层的具体实现,只要先后提供的服务相同便可。例如, FTP服务不管是在以太网、 PPP上、仍是网络运营商使用的任何网络上都无需改变,并且与提供传输电缆的网络
运营商无关。
• 能够将层次间的依赖性减到最低。假设网络运营商改变了物理传输系统,但只要IP层不变,FTP服务就能够不改变。
• 分层有利于标准化工做。 TCP和IP就是关于它们各自层次如何工做的标准。
• 一旦构建好了某一层次,就能够用它为不少上层服务提供支持。所以, TCP/IP同时被FTP、telnet、 SSH和HTTP使用。不然,全部这些高层协议都必须编写它们各自的底层协议。分层是一种重要的技术,但也有缺陷:
• 层次并不能封装全部东西。有时它会为咱们带来级联修改。最经典的例子就是在一个分层设计的企业应用中,若是要增长一个在用户界面上显示的数据域,就必须在数据库中增长相应的字段,还必须在用户界面和数据库之间的每一层作相应的修改。
• 过多的层次会影响性能。在每一层,通常都会从一种表现形式转换到另外一种。不过底层功能的封装一般带来比代价更大的效率提高。例如,能够优化事务控制层,提升其余各层的效率。
然而,分层架构中最困难的问题是决定创建哪些层次以及每一层的职责是什么。
1.1 企业应用中层次的演化
我虽然没有从事过早期批处理系统时期的任何工做,但我认为当时的软件工做人员不会太
关注层次的概念,只要编写操做某些文件( ISAM、 VSAM等)格式的程序,这就是当时的应用。
它不须要层次。
20世纪90年代,随着客户/服务器系统的出现,分层的概念更明显了。这样的系统是一种两
个层次的系统:客户端包括用户界面和其余应用代码,服务器端一般是关系型数据库。常见的
客户端工具如VB、 PowerBuilder和Delphi。这些工具使得构建数据密集型应用很是容易。由于
它们的用户界面控件一般都是SQL感知的。所以,能够经过将控件拖拽到“设计区域”来创建
界面,而后再使用属性表单把控件链接到后台数据库。
若是应用仅仅包括关系数据的简单显示和修改,那么这种客户/服务器系统的工做方式很是
合适。问题来自领域逻辑:如业务规则、验证、计算等。一般,人们会把它们写在客户端,但
是这样很笨拙,而且每每把领域逻辑直接嵌入到用户界面。随着领域逻辑的不断复杂化,这些
代码将愈来愈难以使用。并且,这样作很容易产生冗余代码,这意味着简单的变化都会致使要
在不少界面中寻找类似代码。
另一种办法是把这些领域逻辑放到数据库端,做为存储过程。可是,存储过程只提供有
限的结构化机制,这将再次致使笨拙的代码。并且,不少人喜欢关系型数据库的缘由之一是
SQL是一个标准,容许他们更换数据库厂商。尽管真正更换数据库厂商的用户寥寥无几,但还
是有不少人但愿拥有这种选择,而且没有太大的附加代价。因为存储过程都是数据库厂商私有
的,所以普通用户被剥夺了这种选择权。
在客户/服务器方式逐渐大众化的同时,面向对象方式开始崛起。面向对象为领域逻辑的问
题找到了答案:转到三层架构的系统。在这种方式下,在表现层实现用户界面,在领域层实现
领域逻辑,在数据源层存取数据。这种方式使你能够将复杂的领域逻辑从界面代码中抽取出来,
单独放到中间层,用对象加以建模和组织。
尽管有这些优点,但一开始面向对象的进展并不大。当时的实际状况是:大多数系统并不
特别复杂,或者至少在构建之初没有那么复杂。所以,当系统比较简单时,相对于三层架构的
优点,强有力的客户/服务器工具的竞争力很是大。但客户/服务器工具很难甚至没法应用于三层
架构系统的配置。
我认为真正巨大的冲击来自Web的兴起。人们突然想在Web浏览器上部署这些客户/服务器
应用。然而,若是全部的领域逻辑都是写在“胖”客户中,则全部这些都必须在Web界面中重写。
对于设计良好的三层系统来讲,只须要增长一个新的表现层,就能够了。另外, Java的出现使得
面向对象语言无所顾忌地向当时的主流技术发起冲击。用于构建Web页面的工具对SQL的绑定也
没有那么紧密了,这也使得它们比较容易适应三层结构。
当人们讨论分层时,经常不容易区分layer和tier。这两个词汇常常被用做同义词,可是不少
人仍是认为tier意味着物理上的分离。客户/服务器系统经常被称为“two-tier system”,其分离是
物理上的分离:客户端是一台台式机,而服务器端是一台服务器。我使用layer,旨在强调无需
把不一样的层次放在不一样的计算机上运行。独立出来的领域逻辑层,既能够运行在台式计算机上,
也能够运行在数据库服务器上。在这种情形下,有两个节点,可是有三个层次。若是数据库也
在本地,还能够在一台笔记本电脑上运行三层软件,固然,仍旧存在三个大相径庭的层次。
1.2 三个基本层次
本书主要就三个基本层次的架构展开讨论:表现层、领域层和数据源层(这里的命名取自
文献[Brown et al.])。表1-1总结了这些层次。
表1-1 三个基本层次
层 次 职 责
表现层 提提供服务,显示信息(例如在Windows或HTML页面中,处理
用户请求(鼠标点击,键盘敲击等), HTTP请求,命令行调用,
批处理API)
领域层 提逻辑,系统中真正的核心
数据源层 提与数据库、消息系统、事务管理器及其余软件包通讯
表现逻辑处理用户与软件间的交互。可能简单到只是命令行或基于文本的菜单系统,可是
当前的客户界面每每是功能完善的胖客户图形界面,或者是基于HTML的浏览器界面(本书中的
“胖客户”是指Windows/Swing/fat-client用户界面,不包括HTML浏览器)。表现层的主要职责是
向用户显示信息并把从用户那里获取的信息解释成领域层或数据源层上的各类动做。
数据源逻辑主要关注与其余系统的交互,这些系统将表明应用完成相关的任务。它们能够
是事务监控器、其余应用、消息系统等。对于大多数企业应用来讲,最主要的数据源逻辑就是
数据库,它的主要责任是存储持久数据。
最后一部分就是领域逻辑,也称为业务逻辑。它就是应用必须作的全部领域相关工做:包
括根据输入数据或已有数据进行计算,对从表现层输入的数据进行验证,以及根据从表现层接
收的命令来肯定应该调度哪些数据源逻辑。
有时,层次组织成领域层对表现层彻底隐藏了数据源层。但更多的时候,是表现层直接对
数据存储进行操做。虽然这样作并不纯粹,可是在实践中每每运行良好。表现层可能解释来自
用户的命令,经过数据源层将相关数据从数据库中提取出来,而后让领域逻辑层在向用户显示
相关数据以前先处理这些相关数据。
一个单独的企业应用,可能在上述的三个层次上都包含多个软件包。若是某个应用不只要
支持用户经过胖客户机界面访问,还要支持用户经过命令行形式访问,则它须要两个表现层:
一个支持胖客户机界面,另外一个支持命令行。对于不一样的数据库,一般也须要多个数据源组件,
特别是在与已有的软件包通讯时。即使是领域逻辑,也有可能被分割成相互独立的不一样部分,
特定的数据源包只能由特定的领域包使用。
到目前为止,咱们一直都在讨论用户。这很天然地会引出一个问题:若是驱动软件的不是
人,状况又怎么样呢?好比说驱动者多是时髦的Web Service或是一个老土但实用的批处理程
序。对于后者,用户将是一个客户程序。这样,很明显,表现层就有可能与数据源层出现某些
类似之处,由于它们都是系统与外界的接口。这就是Alistair Cockburn的Hexagonal Architecture
模式[wiki]背后的逻辑,它将任何系统都视为由到外部系统的接口所围绕的一个核心。在
Hexagonal Architecture中,全部外部的东西都被视为外部接口。所以,从这种意义上说,它是一
种对称视图,而不是本书中的非对称分层视图。
然而,我认为这种非对称性是有益的。由于,为别人提供服务的接口与使用别人服务的接
口存在较大的差异,须要明确区分。这就是表现层和数据源层相对于核心的本质差异。表现层
是系统对外提供服务的外部接口,无论外面是复杂的人类仍是一个简单的远端程序。数据源层
是系统使用外部服务的接口。这样区分的好处是:客户的不一样将改变你对服务的见解。
对每一个企业应用,尽管咱们可以区分出其中的主要的表现层、领域层和数据源层,可是具
体如何分离要取决于应用的复杂程度。 从数据库中读取数据并把它显示在Web页面上的简单脚本,
可能所有在一个过程当中。我将仍然尽可能保持三层架构的风格,不过在这里可能只是把每一个层的
行为放到三个不一样的子程序中。一旦系统再复杂一点,就能够将三个层次的工做分解成不一样的
类。若是复杂度继续增长,则把类分配到不一样的包中。个人整体建议就是根据不一样的问题,选
择一种适合的分离方式,可是切记必定要进行某种形式的分离—至少在子程序级别。
伴随着分离,还有一条关于依赖性的广泛原则:领域层和数据源层绝对不要依赖于表现层。
也就是说,在领域层和数据源层的代码中,不要出现调用表现层代码的状况。这条规则将简化
在相同的基础上替换表现层的代价,也使得表现层的修改所带来的连锁反应尽量小。领域层
与数据源层的关系更复杂,其取决于数据源层的架构模式。
使用领域逻辑时,其中一个最困难的部分就是区分什么是领域逻辑,什么是其余逻辑。一
种不太正规的测试办法就是:假想向系统中增长一个彻底不一样的新层,例如为Web应用增长一个
命令行界面层。若是在这个过程当中,发现须要重复实现某些功能,则说明可能有一些本应该在
领域层实现的逻辑,如今在表现层实现了。相似地,你也能够假想一下,将后台数据库更换成
XML文件格式,看看状况又会如何?
举一个例子。我所知道的一个系统有一张产品列表,其中,当月销售量比上月销售量大10%
的产品须要用红色显示。为实现这个功能,开发者在表现层逻辑中比较当月和上月的销售量,
而后将差异大于10%的产品显示为红色。
这样作的麻烦就是将领域逻辑放到了表现层中。为了进行适当的分离,须要在领域层中定
义一个方法,用来指示该产品的销售量是否较上月有较大提升。该方法完成销售量的比较,返
回一个布尔值。表现层则只须要简单地调用一下这个布尔方法,若是返回值为真,则用红色突
出显示这个产品。这样,该过程就分解成两部分:肯定需不须要突出显示,选择如何突出显示。
固然,我担忧这样也许有些太教条主义了。 Alan Knight在审阅本书时评论说:他本身“很
头大,将部分领域逻辑混入表现层究竟是滑向地狱的第一步呢,仍是只有少数纯粹主义者才会
抱怨的小问题?”咱们之因此担忧,正是由于这种作法二者兼备。
1.3 为各层选择运行环境
本书绝大多数篇幅讨论的都是逻辑层次—将系统中各部分分离,以下降不一样部分之间的
耦合程度。即便是都运行在同一台计算机上,不一样层次间的分离也是很是重要的。固然,系统
物理结构的不一样会有所影响。
对于大多数信息系统来讲,主要的决定就是在哪里运行处理工做,是在客户机上,仍是在
台式机上,又或是在服务器上?
一般,最简单的状况是将全部东西都运行在服务器上。在这种状况下,一个使用Web浏览器
的HTML前端是一个好方法。这样作最大的好处是全部的东西都在有限的环境内,很容易修改维
护。无需考虑将它们分发到不一样的客户端并维护与服务器的同步等问题。也没必要考虑与客户机
上其余软件的兼容性问题。
在客户机上运行应用程序的好处是系统的响应性好,或者在网络断开的状况下也能正常工
做。任何运行在服务器上的逻辑在响应客户请求时,都须要一个来回的通讯开销。若是用户仅
仅是为了试试系统的即时反馈,这个通讯来回也没法避免。它还须要在网络链接保持的状态下
运行。固然,网络的分布可能会无所不在,可是至少在我写本书的时候, 31000英尺高的地方还
没有。也许在不久的未来,就会处处都有了,但有很多人须要当即工做,没必要等待网络链接。
断接操做带来特别的挑战性,我不想在本书中过多讨论。
有了这些约束,咱们就能够逐层分析了。数据源层通常都是运行在服务器上。例外状况是:
当须要断接操做时,能够将服务器的功能复制到一台功能强大的客户机上。在这种状况下,在
离线的客户机上对数据源的任何修改,都须要同步到服务器上。正如我前面提到的那样,关于
这方面的讨论,我想留到之后某个时候或留给另外一位做者。
在何处运行表现层主要取决于用户界面的种类。若是运行的是一个胖客户,则意味着表现
层运行在客户端。若是运行的是一个Web界面,则意味着表现层运行在服务器端。固然,也有例
外—例如,客户软件(如Unix中的X servers)的远程操做在台式机上运行了一个Web服务器—当
然,这是极少数状况。
若是要创建一个B2C系统,就没什么选择了。不管是谁均可能访问你的服务器,你也不想因
为客户用的是TRS-80系统,就把他拒之门外。在这种状况下,能够在服务器上完成全部工做,
并提供一个HTML界面给浏览器。这样作的缺点就是每一个操做都必需要一个来回的通讯开销,可
能会影响响应时间。能够经过可下载的applet或浏览器脚原本缓解问题,可是它们同时会带来浏
览器兼容性等其余问题。使用的HTML越纯粹,事情就会变得越简单。
即便大家的每一台台式机都是由大家公司的信息系统部门手工精心搭建的,这种简单性仍
然很是诱人。由于即便是很是简单的胖客户系统,也会遇到维护客户端一致性以及避免各类软
件不兼容等问题。
人们但愿有胖客户表现层的主要缘由是:有些任务对于用户而言太复杂了,为了有一个可
用的应用系统,它们的须要超出了Web GUI的能力。固然,人们已经在逐渐习惯于采用使Web前
端更可用的各类方法,这些方法减小了对胖客户方式的需求。所以,我很是赞同只要有可能就
用Web表现方式,只有在必需的状况下才使用胖客户方式。
剩下来的就是领域逻辑了。领域逻辑能够全都运行于服务器端,也能够全都运行于客户端,
也能够一分为二。再有,所有运行在服务器端有助于系统维护,向客户端转移是为了响应时间
及断接使用的须要。
若是你必须在客户端运行某种逻辑,能够考虑将全部逻辑都运行在客户端—这样至少保
证了相关的东西都在一块儿。这样,胖客户端和Web服务器联合部署在客户机上,对响应性的改善
不会太大,可是它能够做为一种处理断接操做的办法。在这种状况下,能够经过不一样的模块将
领域逻辑与表现层分开,能够使用事务脚本或领域模型。将全部的领域逻辑放在客户端的问题
是升级和维护代价高。
将领域逻辑分割在客户端和服务器端,应该是最差的选择,由于这样作没法肯定任意一块
逻辑到底在哪。采用这种方式的主要缘由是:只有一小部分领域逻辑须要在客户端完成。其中
的诀窍就是将这一小部分独立出来成为自包含的模块,使得它不依赖于系统的任意其余部分。
这样,就能够在客户端或服务器端运行它了。这将须要采用一些烦人的小技巧,但它的确是一
个解决问题的好办法。
一旦选择了处理节点,接下来就应该尽量使全部代码保持在单一进程内完成(多是在
同一个节点上,也可能拷贝在集群中的多个节点上)。除非不得已,不然不要把层次放在多个进
程中完成。由于那样作不但损失性能,并且增长复杂性,由于必须增长相似下面的模式,如远
程外观和数据传输对象。
如下因素被Jens Coldewey称为复杂性增压器( complexity booster):分布、显式多线程、范
型差别(例如对象/关系)、多平台开发以及极限性能要求(如每秒100个事务以上)。全部这些因
素都会带来很大的代价。固然,有时咱们没法回避它们,可是要切记:这里罗列的每一项都会
为开发和运行维护阶段带来开销。
——选自:《企业应用架构模式》 [Patterns of Enterprise Application Architecture] [英] 福勒 著;王怀民,周斌 译