点击阅读上篇: 从方法到思维:什么是应用逻辑架构的正确姿势?
架构约束分成了基本约束和业务约束:
而本文讲到的约束基本是逻辑架构上约束,如果考虑业务约束,我们还必须要考虑我们的面向的客户是什么群体之类的约束,如果缺少这样的约束,在设计产品时可能会走偏。
以上这些原则都是判断标准,那么是用什么方法论来实现软件可以帮助我们的软件符合这些原则的呢?答:设计模式。
这里有两个非常重要的关键词:判断标准 + 实现方法,这里判断标准是软件设计原则,实现方法设计模式。
作为一个常年在软件行业摸爬滚打的人,设计模式和设计原则应该是较为熟悉的,或者说常用的设计模式和设计原则都是比较熟悉的。但是大部分书籍讲到的是模块内部如何使用设计模式,并没有重点强调逻辑架构中模块之间如何使用设计模式来让逻辑架构遵循软件设计原则。
而我们设计或者推导逻辑架构时,主要就是用设计模式等方法来让逻辑架构中的各模块之间的关系,以及模块内部的子模块之间的关系符合软件设计原则。
如何用设计模式来让模块间的集成符合软件设计原则,从而降低维护和扩展的成本。架构中的模块之间,模块和子模块,子模块和子模块要遵守软件设计的相关约束。如何遵守呢,领域建模和设计模式是两个具体的方法。
即使不考虑模块之间边界和约束,光考虑模块内部的设计,软件设计原则和设计模式就已然是我们软件工程师的必修课。再加上模块之间的依赖或者边界更加需要软件设计原则和设计模式,那它们的地位就更加神圣不可替代。值得不断的深入学习,实践,思考和总结,这也是为设计逻辑架构打基础,架构师必修课。
虽然我们一开始总是从滥用开始,不过没关系,一开始要做到不偏不倚总是很难的,慢慢的我们就可以窥见的其中的奥妙。
这是具体的技术在某个特定场景下的约束:
总的来说,这里的这些约束更偏向于物理架构上的约束,这里还是提前描述一下。同时每个物理架构要解决的问题不一样,导致它们要遵守的计算机科学与技术上的约束是不一样的,这是架构师们要整理,并倡导执行的。
前面讲到的是软件研发领域的基本约束,这些基本约束在高粒度模块中一般很少被提及,高粒度模块之间的约束关系是根据业务中的思维概念提炼而来,比如电商中提炼出订单,营销活动,商品等等核心概念和核心域,对这些核心概念进行定义,以确定它们之间的关系和边界,从而形成技术上的统一业务约束。
同理,任何一个领域应该都存在这样的约束,只是这样的约束并不是一层不变的,尤其是在业务系统中,业务理解发生了变化,这样的约束也会随之变化,而且业务中约束的目的是驱动业务更好的前进的重要保障。
我们拿国家这个架构来做简单的解读,读了十年历史,大概总结出的一个国家级别主要架构约束是这样的:
历史上不同时期的国家治理有不同的架构(三省是顶层模块,六部是二级模块,然后依次做模块分解,直到一村,一户,这户可以看最是领域模型)和规约。西周和东周的春秋时期靠的是周公旦制作的礼和乐作为国家架构的约束,到了战国时期,礼崩乐坏,百家争鸣,最终以统一国家为目标的法(这个法和保障民生的法是两回事)成为秦国的架构约束,得以让他成功统一六国,但是很快这种法的约束又带来了副作用,于是汉朝建立,确定孔子的儒家伦理道德作为国家架构的主要约束。
然而这种以伦理和道德为主的架构约束对王朝的前 100 年 - 150 年是非常有效的,但是随着时间的发展,这样的约束会越来越弱,约束变弱则利益集团会不断的让架构中的模块边界变的模糊,有些模块的利益变的更大,有些模块的利益更小了,而且依赖关系变的混乱,从而使整体架构的利益受到影响,同时由于利益牵绊太深很少有一个总架构师有能力扭转乾坤。最终于就会被另外一个王朝所洗牌,新的王朝会重新建立架构,重新设定模块间的边界和依赖,同时还是以道德和伦理作为主要的约束。这种局面从汉朝开始周而复始了 2000 年。
不管怎么说,一个符合时宜的架构约束是有利于架构向前发展的,而不符合时宜的约束反而是制约者架构发展的。各种内耗等情况应运而生,最终阻碍了业务向前拓展。
纵上所述,模块间约束无处不在,技术上的约束是最最容易看懂的。越是细粒度模块的约束,我们越容易学习和理解,比如软件设计的原则等等,越是高粒度的模块的约束,越抽象。需要对业务有深刻理解,对组织有深刻的理解,甚至对社会有深刻的理解。
件复用包含很多内容,比如说设计的复用,文档的复用,代码的复用等等。在本章节中的复用特指代码的复用。
提炼的目的是实现复用,复用的目标收益是:
对于复用,我从业务功能和非业务功能的角度来分了一下类,如下:
1)一种是跟业务无关的一些可复用的内容,这些内容存在于基础架构的每一个层次,但是还不能归属于逻辑架构,而且业务技术无关的复用不是本文讨论的重点,所以本文不会重点阐述 MVC 的设计思想是如何在不同的 Web 应用中得以复用的。
2)还有一种是跟业务相关的可复用内容,它的产生取决于抽象能力和技术功底,比如:
具体的复用形式本质上来说是物理架构中要考虑的内容,这里捎带提一下。
1)二方库形式
提炼成二方库,谁使用谁依赖这个二方库,这种情况又分成了两个子类:
2)服务化形式
下沉成服务,通过接口对外暴露,技术手段多种多样,比如说 HSF,SOFA 对外暴露,或者 HTTP 对外暴露等,但是这里的重点不是在使用什么样的技术手段,而是暴露的服务中应该包含哪些内容(有多少客户,他们的需求的共性是什么,我们的业务本质是什么,根据这些内容来设计我们需要暴露的服务,然后在考虑我们接口的规范。至于使用什么样的服务容器之类的内容基础设施架构同学会重点来考量,我们需要需要学习和理解,但是我们的重点还是在前两个,即服务到底是什么,以及服务接口的规范是什么,在这两个上苦下功夫,对业务线的同学拿结果以及个人成长都有莫大的帮助)
3)展示组件
还有我们前端的各种可复用的展示组件的设计,比如说 TMF 的可复用组件等等。
跟业务无关的可以复用内容我们在本文中暂不讨论,本文中我们讨论一下跟业务相关的跨模块复用的两种情况,以及这两种情况之间的异同:
在跟业务相关的跨模块可复用情况中,慢慢的大家都以后者(下沉成服务)作为主要的表现形式,原因有便于发布,变更影响小,等等。虽然后者在调用时有一些远程开销,但是得益于 RPC 简洁的二进制协议(CPU Time 的下降)和日益变小的 RTT(RT 的下降)及日益增加的带宽,其远程开销的代价渐渐变得不那么显眼,甚至可以忽视。
那么是不是后者是不是可以代替前者呢?也并不是这样,有的场景下前者是不能用后者来代替的,比如说通过业务流程的提炼抽象而得来的业务二方库,这个是无法通过服务化来代替的,反而这种情况下,往往是服务化+二方库同时出现,起到一个很好的复用的作用。
所以在业务线的应用逻辑架构中,复用的重点即在提炼出共同的特性(模型上,流程上,计算模型上等),然后以二方库或者服务化应用的方式来进行落地。那么如何在逻辑架构中提炼出共同特性呢?
抽象和提炼基本上会从下面几个点出发:
我相信很多人都有过这样的经验。由此可见提炼就是阴阳调和:
而这些都需要工程师们对领域建模和设计模式的抽象技术,以及对相对的技术特性等计算机技术的深入掌握。这里需要强调光知道领域建模和设计模式等是不够的,不同的技术选型特性不一样,会导致在抽象的实现时产生不同的差别。
在复用这件事情上,抽象技术和计算机技术两手抓,两手都要硬。如果用中国古代传统思想来比喻,那可能可以用阴来比喻抽象技术,阳来比喻计算机技术。 尤其是阴,总是给人捉摸不定的感觉,但是如何深入学习,坚持实践总结,我们就会发现阴原来也是有具体方法论的,但是这个具体方法又不是看看书就能学会的,它对知行合一的要求更高。
从学习的步骤来说,一般的过程都是先从阳(计算机科学与技术)开始,因为先从阴(抽象和架构技术)开始没有阳作为支撑是很难把阴融会贯通的。而且最终要达到的是阴阳调和。如果我们过于偏重阴或者过于偏重阳,都会导致阴阳失调,大概就是这个意思。
来到数据部门之后,我发现已经不能用阴阳来形容我们要学的领域了,现在我们搞的比较多的是统计分析和机器学习(统计分析和机器学习有交集,也有区别),所以目前对我们团队来说,我们的同学有三门学科是必须要掌握的:
我最近一年看的比较多的是统计分析,有同学钉钉我问道:怎么连你也放弃领域建模了。我没放弃,领域建模是抽象和架构的重要方法(但不是唯一的方法,演绎和归纳也是,自顶向下分解也是),工程技术同学是不能放弃的。学习统计分析及统计学习是因为统计学习 + 计算机科学与技术可以更好的解决工程领域遇到的问题,这也是各条线的工程师需要掌握的技能。
复用是软件中一个非常重要的学问,里面结合了抽象技术和计算机技术,而抽象技术还依赖于对业务的理解程度,所以此非一日之功,需要长时间的锻炼才能有所小成。
当然,有时候即使在技术上可以抽象提炼,但是由于组织架构的问题也会让这样的提炼无法落地,或者这里并不是一个稳定的结构从而导致经常调整,带来的结果是提炼的投入产出比比较小,从而导致无法提炼,这些这里就不详细写了。
分层几乎是从每个工程师入门的时候都会接触到的一个普世的概念,在一些书籍里,分层有的被称之为 tier,有的被称之为 layer,比如说 OSI 分层模型是用的 Layer 这个词。而在一些文章里讲到架构时用的是 tier 这个词,当你去查看 wiki 的时候,那就更晕了,因为 wiki 离 tier 和 layer 是混在一起讲的。
谈到分层,各种教科书中分层无不拿出景点的 3 个层次来阐述分层问题,如下:
然后还有扩展出 service layer,这些在工程骨架中非常常见,我们几乎从来没有见过不分层的工程骨架,所以当我们讨论架构分层的时候,很多人脑海里第一映像就是工程骨架中的分层。
工程骨架的分层的一个重要目的是:成为代码组织结构的约束,防止代码混乱不堪。
但是我们讲的逻辑架构分层不是指工程骨架分层,为什么不是?首先来看一下逻辑架构的特点:
根据这个特点,我们可以模糊的看出逻辑架构的分层主要是逻辑架构中各模块的调用关系,甚至更偏向从模块职责的角度来进行归纳从而得出层次。
这种分层的目的是:对同一类职责的模块进行职责上的约束,此时还不一定有代码的存在。
这么看来这两个分层是有着本质的区别:
也许有的同学会说了,再大的架构(就比如说某个 BU 的逻辑架构),我也可以将最靠近用户的模块划分成 presentation layer,中间的所有模块都划分为 business layer,最下面的我都划分成 presentation layer。没错,你可以这样做,但是这样做基本没有任何意义,不能带来指导作用,失去的分层的目的。
在文件系统或者网络协议上,也有各种层次的封装。如下图所示:
这个图中每个模块在不同阶段都有不同阶段要解决的问题,然后每个模块都可以分解,产生更细粒度的模块,这里重点是让大家了解到什么是逻辑架构中模块的分层。
不过这些都不是问题,问题是什么呢?
问题是我们必须时刻知道,目前我们在不同层次的这些模块存在哪些问题,以及不同层次在解决什么问题。比如说上述的操作系统中文件系统和协议分层中,最底层的是跟硬件打交道,能够精准的控制硬件,中间是对操作系统的用户暴露的,更简单易用,上层是针对应用来使用,解决特定领域的问题,不同的层次做了不同的抽象,也是在解决不同的问题。
很多人及一些书中,谈分层必谈工程骨架的分层,这个分层和架构中的分层是两回事,如果我们在谈架构,那么我们要避免把重心放到项目骨架的分层上。
比如说领域建模的相关书籍中,经常会讲到 service, domain, repository 之流,这个些概念处于架构中的什么位置,我们应该什么时候去关心这些概念?
如图所示,在细粒度模块内部,按照纯技术职责来进行划分时,我们将之撸成 service, model, repository,integration 之流的工程骨架,值得注意的是工程骨架的划分层次和具体的业务逻辑架构是没有关系的,他更偏技术,他的职责是对代码做一个高层次的组织和管理。
按照正常流程,系统模型产出之后,应该紧接着考虑模块的设定,依赖,规约,但是很不幸,很多书籍和资料都把 service, model, repository,integration 这部分分层的内容作为了领域的建模的最重要的重点之一。
某些书里的这种观点是不符合实际工作流程的,实际工作时,在领域模型之后我们先考虑的是架构中的各个模块的位置和职责,以及模块内部的子模块,模块之间的关系,以及整体的约束等等(请参考文章开头对架构的定义)。具体表现就是我们在逻辑架构图中不会去画什么 service, model, repository, integration 之类的层。
工程骨架的分层在细粒度模块内部,这是基础设施架构的一部分,也许是你手头目前最重要的部分,但是对于整个应用逻辑架构来说不是最核心的部分也不是最需要先考虑的内容。也就是说,即使你不分 service, model 之类的,对应用逻辑架构中模块的职责划分也是没有影响的。
同时我也见过一些项目,应用逻辑架构比较明确了,但是在落地到物理架构时,把逻辑架构中的所有模块都放在 service 包里,而且没有再分包,这就不合适了,逻辑架构中的模块完全没有落地。
所以我现在在我们部门的项目中,坚决避免将 service, model, repository,integration 之类的放到最高层来考虑。而是将逻辑架构的设计切切实实的落地,这样根据逻辑架构,我们就能看到的我们具体的应用,和应用内包的组织情况。
再次强调:逻辑架构中的分层不是指 service, model,repository, integration 之流的分层,而是指功能模块的分层。如果不了解业务,如果不了解业务概念模块,如果没有业务概念架构,我们是很难做出合理的应用逻辑架构的(当然也包括逻辑架构中模块的分层),撇开业务特征直接谈逻辑架构的分层是不行的。
模块的职责确定之后,模块之间的依赖也必须要确定,然后模块对外暴露接口需要定义规范和技术实现的手段。比如,如果是 restful 接口,那么应该是什么样的规范对外定义,如果是内部的服务的接口,应该是什么样的规范。由于本文篇幅所限,此处不进行详述,前者可参考各大平台的开放接口,后者可参考各 BU 内部服务调用的相关规范,如果没有,那说需要制定一个统一的规范。
这里我引入了一个新概念:逻辑架构颗粒度树。
刚刚讲的都是 2 维上的架构,我们可以看到,架构推导是有方法的,而且如果对方法进行提炼,就是横和竖的问题,但是正如我们开始讲到的,架构也可以是 3 维的,那就是在二维的模块中存在各种粒度子模块或者父模块。
如果非要打个比喻的话,那么下面的宇宙星神合体是一个大的架构:
里面分成了很多小模块,比如物质飞船,探测器等等,而每个小模块又是有很多基础模块构成,宇宙星神合体中只有 3 个层次,及基础部件,模块,及最终的星神合体,它们代表着不同的粒度。而对于业务复杂的架构来说,粒度会更多,层次就会更多。这取决于N个研发资源投入在某个模块上的效率最高,而这个 N 在某个阶段的技术限制下应该是一个比较稳定的值!
抽象一下,模块在不同粒度上,可以整成这么一棵树:
逻辑架构粒度树的 3 条原则:
上述的树形结构,只能描绘出模块和父模块,子模块的关系,但是不能完整的描绘出模块之间的关系,是处于同一层次,还是处在不同层次(就是前面提到的应用逻辑架构中横的问题和竖的问题)。
那么用什么样的图形既可以生动的表达出模块和父模块,及子模块的关系,又能表达出不同模块之间的关系呢,我想了很久,也没有想到一个更容易理解的图形,最后产出了下面这么一幅图:
图中有三层,但是现实生活中可能超过三层,也可能低于三层。我们能归纳的层次越高,那可能我们接触的东西就越宽广,越精深。
这里有一个严肃的话题需要提一下:是不是一线工程师不用考虑逻辑架构问题?当然不是,任何一个同学,你手头的工作都是跟架构相关的,你负责模块可能也存在子模块,而且必定会存在父模块,出于工作,你必须要理解不断迭代你模块中的设计,同时随着能力的成长,你必须要关注你的父模块,父模块的父模块,日积月累,你可以 hold 住的模块粒度会越来越大,你的职责和能力要求会越来越大。
在下述架构模块颗粒度树中,并没有模块和模块之间的依赖关系,这里只是为了概要的说明模块落地到物理架构中的一个演变过程,而具体的案例我们放在后面的文章中来进行阐述。
模块树上的这些不同粒度的模块,在具体落地成物理架构时,可以是不同的形式,如下:
为什么会出现这种情况呢,因为不同的模块在业务的发展的不同时期:
那么我们来看看一个网站从小到大的逻辑架构模块树落地的变化情况。
很显然,这里是一个电商网站起步时候的样子,所有模块都有模有样,只是模块中的逻辑比较简单,这些模块都以包的形式存在于一个应用之中,这个应用是一个大泥球。但是由于模块的职责划分合理,粒度的治理也比较符合发展要求,所以这样的应用在分拆成分布式的时候阻力会比较小。而那些模块职责不合理的大泥球应用,随着业务的发展,要分拆成分布式应用,阻力就大很多。
这是一个度过初期阶段的电商网站,营销,商品,交易模块等已经成型,而且得益之前的模块划分,架构师可以很快的将初期的多个顶级包,分拆出来,变成多个应用。
到了这个时期,已经是一个大型电商网站的样子了,营销平台内部已经分拆出了多个应用,得益于上一阶段中各模块职责的合理分配,所以架构师将在将物理架构进化成这个样子的时候,力气不需要花在逻辑架构的治理上,可以把精力集中投入到物理架构及基础设施架构的建设上,比如同城容灾,异地多活等等。
我不知道,比如中台是不是,要把电商业务中所有的相对稳定的核心抽象出来,可能是领域模型,可能是业务流程转换而成的系统流程,可能是一个计算模型或者算法等等。然后变化的内容(前台)可以依托于这个大的核心概念快速的迭代。如果需要图形化来做概要的理解,我想应该是这样的:
一旦要做一个中台,那么以为着这个中台对前台来说就是一个技术产品,则要考虑如下几个方面的内容:
目的就是要提高客户的生产效率。
多个业务线有无重复的流程抽象,有无重复的领域模型抽象,有无重复的计算模型(数据结构和算法)等等,有无重复的辅助性设施。他们是否在重复建设,等等。
在演变的过程中变化是什么呢?需要的是学习能力,沟通能力,协调资源的能力,领导力,影响力,评估人的能力和用人的能力等等。这些能力需要涉及的范围都从一个小的组织向一个更大的组织前进。
大音希声,大象无形,不管如何发展,基础的规律都还是不变的。
当一个逻辑模块要落地时,我们如何判断一个模块落地成包,还是应用等等,有很多判断的维度,比如:
到底多少人的团队协作效率最高?作为一个应用,在技术不断进步的情况下(比如说新的容器之类的),或者要面对的业务的复杂度不同的情况下,同时可维护的人数也是不一样的,具体目前变化到多少,目前基本是靠经验,然后遇到问题再调整,根据主管的经验不断调整和优化,以达到一个适合当前阶段的最优值,目前我自己这边大概5人左右一个攻坚小组,遇到更大问题域,那就拆解。
这里面,效率,稳定性,性能是最影响逻辑架构落地成物理架构的三大主要因素。
我们在文章的开头对架构两个字给出了一个官方定义,然后按照笔者自己对架构的理解又对架构进行了分类,在架构分类中,出现了产品功能架构,业务架构,应用逻辑架构,应用物理架构等等。
不同的架构都是在解释不同的问题,比如:
正确分析出当前的场合(受众和目的)应该用什么样的架构来阐述我们的意图是非常重要的。
同时我们可以看到小到一个mis系统,大到整个阿里,都可以用架构的角度来解释,架构中出现的各种中台,后台,各种框架等等其实都是架构方法产出的结果。系统大小不一样,抽象的方法是类似的。
架构产生之后,随着业务的迭代,架构不治理,模块职责和依赖,层次不清晰,约束不明确。稳定性,性能,成本都受到影响。积弊越久,回头越难,有时候不得不重头来过。
为了避免推倒重造的问题发生,我们需要不断的自底向上的方式来修正架构,修正其实是在做局部的模块重构,谈到修正,具体的方法是由这里就不得正视归纳和演绎的重要性了,而这里的演绎和归纳是抽象的核心概括。
自底向上的推导的重点在于演绎和归纳,越是底层的越是要使用演绎的方法,越是高层的越是使用归纳。
这两种方法应该什么时候使用?显然当我们的目标(比如说业务目标)或者结论是非常高粒度的时候,需要分解,那么使用自顶向下的推导,在规划未来时一般会用到类似的自顶向下的方法,产出我们宏观结论。
而如果是产品方案已经明确,程序员需要理解这个业务需求,并根据产品方案推导出架构,此时一般使用自底向上的方法,而领域建模就是这种自底向上的分析方法。
对于自底向上的分析方法,如果提炼一下关键词,会得到如下两个关键词:
演绎就是逻辑推导,越是底层的,越需要演绎:
这里的归纳是根据事物的某个维度来进行归类,越是高层的,越需要归纳:
关于归纳,我们前面已经做了大量的讲解,所以这里我们重点阐述一下演绎:
1)我们从对业务的理解,演绎出用例,从用例演绎抽象出业务概念模型,从业务概念演绎抽象出系统模型,从系统模型演绎抽象出物理存储模型。这是一个从 A 推导出 B,从 B 推导出 C,从 C 推导出 D,从 D 推导出E的过程,而在 B,C,D 上又有很多逻辑分支。推导出的层次越深,逻辑分支越广(保障每层的准确度的基础上),一般来说实力越强。
2)我们从对业务的理解,演绎出用例,从用例演绎出业务流程,再从业务流程演绎抽象成系统流程,然后再演绎成数据流。这是也是一个从 A 推导出 B,从 B 推导出 C',从 C' 推导成 D',从 D' 推导出 E' 的过程。这个推导过程比如有方法论辅助,否则逻辑的深度和广度都会受到影响。
总的来说:演绎推导的层次越深,分支逻辑越多,越能穿透迷雾,看问题就越透彻,说明功力越深厚。
打个比喻就是:对应相同品种的树来说,小树的根系和大树的根系在地下深入的长度和广度是完全不一样的,人的逻辑能力大抵也是如此。
其实我们工作中很多时候都在使用演绎和归纳,只是我们不知道我们在使用这类方法,看到这篇文章之后也许可以给大家带去一些思考,看清楚我们自己以前的工作到底是如何使用演绎和归纳的,以及如何改进以前的方法。
除了自底向上的通用思考方法之外,我们还必须要了解计算机领域的相关技能和套路才能产出合适的结果:
这张图是有严密的逻辑路径的,每个步骤的输入,都是上个步骤的输出。更关键的是这个张图是有顺序的,做架构要从上往下做,不可自顾自,不可撇开业务闭门造车。
为什么前面我们问题空间领域模型聊了这么多,原因就是问题空间的领域建模其实是分析阶段,如果分析阶段我们没有做正确,那么设计阶段我们能做正确的可能性是非常小的。
分析阶段,我们得出了正确的分析产出,那么我们在设计阶段,又根据合理正确的方法论,我们就可以得到合理正确的应用逻辑架构。
同时我们可以看出领域建模是抽象和架构的重要方法,但不是唯一的方法,因为归纳和演绎也是抽象及架构的重要方法,自顶向下推演也是架构的重要方法。
这套方法论的关键性总结应该是这样的:
1)架构问题是我们工作中常见的问题,我们要注意识别并定义架构中的问题
2)业务概念模型的产出是通过具体的方法演绎出来的
3)业务概念架构的产出是通过具体的方法归纳出来的
4)系统模型和数据模型的产出是通过具体的方法演绎出来的
5)应用逻辑架构的产出是通过对前面的产出归纳和演绎出来的
6)应用逻辑架构推导所使用的归纳和演绎方法涉及到很多具体的知识
最重要的是这个过程是不断迭代的,这句话比什么都重要,只有运动着的架构,没有静止的架构。有的架构运动时进行不断的重构和调整,所以经久不衰,有的架构缺乏这样的自我否定机制,最终
走向衰败。
【本文为51CTO专栏作者“阿里巴巴官方技术”原创稿件,转载请联系原作者】
戳这里,看该作者更多好文