2.3 集成设计
集成设计的主要目标是:
- 把任务划分到各个应用程序与服务之中。
- 决定数据表与数据库之间的指派关系。
- 以应用程序之间所传递的消息为视角,来定义集成方面的需求。
对于其他更为详细设计的来说,集成设计确定了这些设计的范围。用户界面设计可以针对每个应用程序或服务分别来做(对于服务来说,其用户指的是另一个程序),数据库设计可以针对每个数据库分别来做。尽管技术设计也可以针对每个应用程序或服务分别来做,但是在大多数的公司里面,我们都可以令多个应用程序与服务共用同一套技术设计。
图2-5演示了如何把任务划分到多个应用程序与服务之中。
然而这样的图,其用处并不大,因为现实工作之中的应用程序会有很多项任务,因此很难用这样的图来表示。于是笔者绘制了图2-6,并把每个应用程序所要实现的任务列了出来。图2-6之中的方框表示应用程序或服务,箭头表示请求/应答,但是有一些箭头例外。从某个数据库指向另一个数据库,并且标有SP字样的箭头,表示“选取并投射”(select and project),也就是说,我们不是把所有的数据对象全都传送过去(而是只把自己所选择的那些对象传送过去),即便传送,也未必要把对象的每一个属性都放在数据流里。(从数据对象的全部属性之中选出某些属性,这叫做投射。选取和投射这两个词,都是数据库领域的术语。)该图的下方有一条标有字母D的消息。D表示可延期的(deferrable),意思是说,发送方把这条消息发送出去之后就可以不管了,它无需确认该消息已收到。图2-6中的这条消息,是为了在Order数据库中记录“订单已打包并等待投递”这一事实。
应用程序可以分为三类:
- 在线应用程序(online application)。这种应用程序有终端用户来对其进行操控。笔者把它们直接叫做应用程序(application)。在图2-6中,Order订单录入应用程序和仓库前端就属于这一类。
- 批次应用程序(batch application)。这种应用程序由某些时间事件来触发。例如,负责计算应付利息的银行应用程序,就属于此类。笔者在图中会给这类方框的左上角画一个简单的钟面。
- 服务应用程序(service application)。这些应用程序是供其他程序来调用的,笔者会把它们称为服务。(请注意:应用程序服务与图书馆服务等业务服务不是一回事。业务服务可以由相关成员通过在线应用程序来提供,而服务应用程序则没有直接的真人用户。)
那么,应用程序到底是什么呢?应用程序这个概念,现在已经不再等同于单纯的程序(program)了。我们会把应用程序实现成若干组件,并对这些组件分层,也有可能会把这些组件在各台服务器上面复制一份。组件是安装单元,但由于它们特别小,因此同一个项目或子项目,通常同时需要照管很多个组件。笔者把这些组件称为应用程序。下面给出定义:
应用程序是由计算机代码文件及配置所构成的集合,这些内容总是同时发布并投入生产。
这里有一个重要的问题要注意,如果应用程序A给应用程序B发送消息,那么你最好是做好一种准备:这两个程序有可能不会在同一时刻投入生产。例如,有一款新的订单录入应用程序,它有可能需要同时具备两种订单发送能力,一种是给旧版的订单履行(order fulfillment)应用程序发订单,另一种是给新版的订单履行应用程序发订单。由此可见,即便对于应用程序的开发工作来说,发布单元(unit of release)也依然是一项相当重要的属性,因为它意味着我们可以在某种程度上较为独立地开发各个应用程序。
同样的问题还会发生在数据库上面:你可能只想把数据库升级到新的版本,但并不想同时把所有的应用程序也升级到新的发行版。然而有的时候,例如,修改了某一列的名称时,你必须同时发布应用程序与数据库,但一般来说,应该尽量避免这种情况。
图2-6仅仅是为了演示而绘制的。它不一定代表该业务问题的最佳解决方案。最佳的解决方案需要根据具体的情况来确定。从前,某些公司会研发那种涵盖多项任务的庞大应用程序,而另外一些公司则会研发很多个小的应用程序。当SOA(面向服务的架构)兴起之后,有的公司开始研发庞大的服务与三层应用程序(three-tier application),这种应用程序由(与用户相沟通的)表现应用程序、业务逻辑服务及数据服务构成。然而某些SOA项目做得并不好,于是很多人就开始和SOA保持距离了。最近又流行微服务(microservice)[3]。微服务与传统SOA服务的主要区别是,它会对功能需求做分割,使其能够用很多个小的服务来实现,而不是用少数几个大的服务来实现。这样做所依据的理由是:很多个小的项目要比一个大的项目更容易成功,这个理由能够说得通。
请注意,SOA与微服务这两个词的定义方式有很多种,而且经常互相矛盾。有些人认为SOA就是由SOAP等Web技术所实现的服务。维基百科的微服务词条[4]认为,微服务与SOA不同,因为微服务仅仅实现在某一个应用程序之中,而SOA则会对多个应用程序进行集成。假如真是这样,那微服务就糟糕了,因为集成是个相当重要的工作,它并不是开发者仅仅为了好玩而想出来的东西,我们需要通过集成来为业务功能提供支持。Martin Fowler[3]认为微服务旨在把某块功能交给开发者负责,由于还要有一些应用程序用来处理用户界面,因此,笔者认为,这意味着要把业务逻辑层与数据处理层相合并。这个想法很好,因为大多数的“业务逻辑”,实际上谈的都是数据处理(笔者在20世纪90年代就提倡过这个想法)。Eric Knorr[5]认为,SOA是由管理层来引领的(这有点令人不舒服),而微服务则是开发者来引领的(这很讨人喜欢)。然后,还有一些人认为SOA和微服务在技术上也有区别。前者使用老式的技术,而后者则使用最近兴起的那些更为轻量级的技术。到底应该使用什么技术,取决于我们是不是希望中间件厂商能够提供诸如监控系统及重新格式化数据等额外的功能,其实这些功能也可以在应用程序的代码里面轻松地实现出来。
微服务确实有很多特性,但笔者对IT业所炒作出来的这些万能技术、架构及开发流程感到头疼。虽然单独挑出微服务来评价,可能有失公允,但笔者还是要在本书之中讨论一下这个利弊参半的技术。IT界的流行风潮一个接着一个,我们都不停地去赶时髦。才华已经不再受到重视,机械式的学习却非常吃香,概念问题很少有人关注,许多想法也叫人给丢进垃圾堆里去了,当然,总是会有人从中捡起来几个,稍微加工一下,想借此把自己打造成IT界的大师。Sir Isaac Newton写过一句话:“如果我看得比别人更远,那是因为我站在巨人的肩上”[6],假如牛顿在IT界工作,那他肯定早把巨人的腿给砍了。
造成这种风潮的一个原因在于:IT界的一些知名人士并没有把业务需求与IT概念或是实现该概念的具体技术清楚地分开。对于SOA来说,它所要应对的业务需求,是准确且高效地处理数据,它的IT概念,是以程序来调用服务,并且通常是通过网络进行调用的,至于Web服务,则是实现这种业务需求所采用的一项具体技术。
新技术虽然不断地受到炒作,但某些基本的问题却依然摆在那里,就服务领域来说,下面这几个简单的问题至少争论了30年:
1.到底应不应该使用服务?
2.应该把服务做到多大?
现在我们就来依次讨论这两个问题。
有很多理由促使我们采用服务来实现软件产品,例如:
- 想要复用逻辑与数据。例如,银行业的许多应用程序,都需要从账户之中取钱(有一些还需要向账户之中存钱),因此,最好是把这些代码一次写好,并反复地用它来管理账户的入账与出账。
- 性能方面的理由。我们可能需要用多台服务器及备份服务器来实现更高的性能与更大的弹性。
- 安全方面的理由。例如,你可能不想像图2-6那样,把面向客户的Web网站与订单数据库直接连起来,而是想令网络流量通过一些防火墙层,以阻止其中某些类型的信息达到服务器。
- 想要把大项目分成多个小项目。
还有很多应用程序并没有上述理由,但却依然使用了服务。唯一的解释就是:这些应用程序想要为以后的复用提前做打算。由于同时编写服务与前端应用程序所需的工作量,要比只写一个应用程序大,因此这个理由并不算太好。
那么,服务和应用程序到底应该做到多大呢?对于这个问题,有几种看法。第一种观点认为,这个大小应该根据来用户的情况来定。这意味着面向用户的应用程序,应该要为某个用户组所需的全部工作提供支持。(前面说过,同一个人可以属于两个或两个以上的用户组。)如果两个用户组之间的关系比较紧密(如销售人员与销售经理),那么应用程序可以同时处理这两个组。至于底层的很多处理工作,则可以交给服务来办。第二种观点认为,这个大小应该根据数据的精确度来定,意思是说,你需要确保每份信息都有权威的数据源。数据是可以复制的,但如果同一个事实有两种相互冲突的观察结果,那么就会对业务造成阻碍。这并不是说你非得打造一个庞大的数据库。从理论上讲,我们可以把情境设计之中的每张数据表,都与某一个数据库相对应,这样做的缺点是需要进行更多的管理工作,而且由于很多事务都要更新多张数据表,因此处理效率不太高。我们最好是把数据表分组,并且指派某个任务来专门更新某一组数据表。然而,某些数据表的使用范围很广(例如,存放顾客、订单及产品等数据的表格),于是我们可能会在多个数据库里面存放这些表格。在图2-6中,产品数据与订单数据就存在复本。在复制数据表的时候,只应该把需要用到的那些数据复制出来,这对于性能和安全都有好处。因此,我们只应该把当前正在销售的那些产品保存在Order Processing(订单处理)数据库之中,而且只应该把已提交的订单之中与送货有关那部分信息,从Order(订单)数据库发送到Delivery(投递)数据库之中。第三种观点认为,应该把应用程序与服务指派给很多个小团队来负责开发,并且尽量缩减它们之间的依赖关系(除非进行情境设计,否则很难知道这种依赖关系)。上述三种观点,会把设计推向不同的方向。要想把设计做好,就需要在这几种做法之间权衡,并且要发挥创造力,以便找出有效的解决方案。
分析集成设计的时候,首先应该检查每项任务是否都得到了实现,以及它们是否都能够访问到各自所需的数据。如果要进行数据传输,那就粗略地计算一下应用程序之间所传递的数据量。如果要从一个数据库复制数据到另外一个数据库,那就需要考虑:万一数据复制出现延迟,会不会引发什么后果。
集成设计之所以必须要做,还有一个重要的原因在于:我们需要与已有的应用程序或第三方所开发的应用程序相集成。现实工作中,很少会从头开始实现项目所需的每一段代码,因为可能已经有一些应用程序摆在那里了。这些已有的或第三方的应用程序尽管不太完美,但是采用它们来做项目,要比从头开始做项目更加划算。还有一种情况是:我们计划把当前版本的应用程序升级到新版,在升级过程中,可能需要将旧版应用程序部分替换掉。
我们可以从现有的应用程序之中重建一套情境设计,笔者把这种设计叫做情境模型(context model)。如果我们打算做的那套情境设计,要对现有应用程序进行修改,那么就应该根据现有的应用程序来构建这样一个情境模型,并且与打算做的那套情境设计相互对比,以发现二者之间的区别。当新的应用程序与现有程序之间有复杂的相互依赖关系时,这么做尤其有用。
根据现有应用程序所做的情境模型,也有助于我们对同领域或相似领域的新设计方案进行检查,因为我们可以用该模型来判断自己有没有把所需的全部功能都涵盖进来。此外,这种模型还能帮我们对新的开发项目进行商业论证。