跳转至

03 可扩展架构:如何打造一个善变的柔性系统?

你好,我是王庆友,今天我和你聊一聊如何打造可扩展的架构。

在实际工作中,业务需求总在不断变化,因此,你经常会面临以下这些问题:

  • 如何快速地上线新业务?老板很可能明天就想看到效果。
  • 对某个功能进行修改,如何不影响到系统其它的功能?

对于新的需求变化,我们一方面要快快搞定,另一方面要稳稳接住。但问题是软件虽然姓“软”,但也不是想变就能变,如果事先没有经过良好的设计,调整起来,往往牵一发动全身,导致系统到处出问题。

那如何设计一个具有良好扩展性的系统,能够快速支持业务变化落地呢?

接下来,我们围绕系统的可扩展,先来了解下什么是系统,什么样的系统才能具备良好的扩展能力。然后通过一个实际的例子,说明如何通过架构手段打造一个可扩展的系统。

系统的构成:模块+关系

我们天天和系统打交道,但你有没想过系统到底是什么?在我看来,系统内部是有明确结构的,它可以简化表达为:系统 = 模块+关系。

在这里,模块是系统的基本组成部分,它泛指子系统、应用、服务或功能模块。关系指模块之间的依赖关系,简单地讲,就是模块之间有调用,我们知道,调用区分发起方和服务方,因此,依赖关系是有方向性的。

这个模型虽然简单,但它给我们提供了一个深入分析系统的工具。接下来,我们就从业务扩展性出发,讨论什么样的模块是容易修改的,什么样的依赖关系是容易调整的。

模块

我们先看模块,模块定义系统都有哪些基本的“玩家”,分别承担什么职责。从业务的角度看,每个模块都代表了某个业务概念,或者说业务领域。

模块内部由数据和业务逻辑组成,其中数据是核心,业务逻辑围绕着数据,对数据做进一步加工,方便外部使用。

从扩展性的角度出发,首先,我们对模块的要求是:定位明确,概念完整。

每个模块要有明确的定位,模块有了定位,说明我们已经想清楚了它的核心职责是什么,这样,每个人对它的期望和理解就会一致。在实践中,我们经常会争论一个功能应该放到A模块还是B模块,表面上看,各有各的道理,谁也说不服谁,但如果对照模块的定位,回到模块设计的初心,我们往往很快就能有答案。

定位比较抽象,在具体划分模块职责的时候,要保证模块业务概念的完整性。数据上,模块需要覆盖对应业务领域的全部数据,比如一个订单模块,它要覆盖所有渠道的订单,包括三方平台的订单、自有商城的订单、线下门店的订单等,这些不同类型订单的数据模型和实际数据,都由订单模块负责。

功能上,模块要包含业务领域的全部功能,比如订单模块包含所有订单相关的功能,包括订单数据的增删改查、订单业务规则校验、订单的状态和生命周期管理等。

其次,模块还要:自成体系,粒度适中。

模块的业务逻辑尽量围绕自身内部数据进行处理,对外部依赖越小,模块的封装性越好,稳定性也越强,不会随着外部模块的调整而调整。

模块的粒度要保持适中,不能为了追求定位清晰,把粒度划分得很小,导致系统的碎片化。比如系统早期的时候,一般我们把积分功能放到用户模块里面,不单独构建积分模块,如果后续积分的概念越来越突出,承载的业务越来越复杂,到时候可以把积分功能分离出来,单独成模块。

这里,为帮助你更好的理解,我举一个模块划分的反面例子。在实际工作中,很多老系统都有体量很大的模块,我们称之为“肿瘤”,它的特点就是定位模糊,职责泛滥,功能无所不包,这样,模块的可维护性很差,没人敢轻易对它动刀子。

好了,说完了模块,我们再继续看下模块的依赖关系。

依赖关系

依赖关系定义了模块如何协作,一起完成业务流程,依赖关系实质上体现的是模块的组织结构。

如果不对模块的依赖关系做针对性设计的话,依赖关系就是一个多对多的网状结构,一个有N个模块的系统,理论上有N×N个依赖关系,如果考虑依赖具有方向性,这个数字还要加倍。

所以,要简化模块的依赖关系,我们就要同时简化依赖的方向和减少依赖的数量。

首先,我们希望模块之间的依赖是单向的,尽量避免相互调用,为什么单向更好呢?我们知道业务流程是有顺序的,如果模块依赖关系越直观地体现业务流程的顺序,越能帮助人理解,否则,我们会被双向的依赖箭头绕的晕头转向,很难通过模块之间的依赖关系还原实际业务的处理过程。

接下来,我们看下模块的组织结构。我们知道,网状结构是一种松散的结构,节点之间的依赖关系比较复杂,一般用于表示非正式的关系,比如人群的社交关系;而层次结构是一种更有序的结构,一般用于表示正式的关系,比如公司内部的人员关系。

在模块的组织结构设计上也是如此,我们要尽量把网状结构转化为层次结构,模块结构层次化是简化模块依赖关系的有力手段。

具体做法就是,我们按照模块定位的不同,把模块划分为不同层次,比如划分为上面的应用层和下面的资源层。这样,一个层通过把多个模块组织在一起,就形成了概念上更大粒度的模块。有了层以后,我们理解业务时,因为模块定位相同,往往关注这个更大粒度的层就可以,依赖关系只要指向这个层,而不是层里面的各个模块。这样,从人理解业务的角度,依赖的数量大幅度地减少了。

另外,我们知道,层与层之间的依赖关系都是层与层之间自上而下的依赖,相对于多对多的网状依赖,层次依赖的方向更清晰,特别符合人的理解习惯。

举个具体例子,作为开发,我们都比较了解MVC架构,系统模块按照定位,分为表示层、应用层、聚合服务层、基础服务层。

  • 表示层,对应前端的模块,如App、小程序、公众号等,属于View层。
  • 应用层,对应和前端表示层直接关联的服务端,属于Control层。
  • 聚合服务层,如果系统业务比较复杂,经常需要单独的聚合服务层负责业务流程的编排组合,这个属于Model层的加强。
  • 基础服务层,代表最基础的业务模块管理,如订单、商品、用户等,属于实际的Model层。

我在这里贴了一张MVC分层结构图,你可以看到,模块总体上是非常清晰的层次结构。

现在,我们清楚了一个可扩展系统对模块和依赖关系的要求,接下来,我们再回到系统扩展性目标,做个深入总结。

扩展性的本质

在文章开头,我们说因为业务总在变化,所以需要架构设计给系统提供良好的扩展性。

这只是表象,深层的原因是,一个新的需求进来,系统不只是为它增加一个新功能这么简单,系统的调整会引起一系列的连锁反应,从而大面积地影响系统的现有功能。架构设计时,如果模块划分的不好,一个N个模块的系统,它的复杂度就是N×N(这个在上一讲介绍的支付宝一代架构中,体现得很明显)。如果再加一个新的模块,复杂度就变成(N+1)×(N+1),系统的复杂度随着功能的数量指数级地上升,这样一来,当系统的规模到一定程度,复杂度就会失控,导致系统彻底无序。

所以,要支持系统的扩展,架构设计上必须能够控制系统的复杂度,面对新需求,要让系统复杂度做加法而不是乘法,从而保证系统的调整是局部化和最小化的,所以,业务架构扩展性的本质是:通过构建合理的模块体系,有效地控制系统复杂度,最小化业务变化引起的系统调整。

那如何打造一个合理的模块体系呢?具体的架构手段就是按照业务对系统进行拆分和整合:通过拆分,实现模块划分;通过整合,优化模块依赖关系。

接下来,我们以一个在线出行公司为例,它有出租车、快车和顺风车3条业务线,来具体看下如何为它打造合理的模块体系。

打造可扩展的模块体系:模块拆分

我们先对系统进行模块化拆分,拆分有两种方式:水平拆分和垂直拆分。

水平方向拆分

水平拆分是指从上到下把系统分为多层,按照系统处理的先后顺序,把业务拆分为几个步骤。

比如,整个叫车过程,我们可以分为UI展现、地图搜索、运力调度和订单支付等几个环节,这是根据系统的处理过程进行划分的。

这样一来,我们就把一个复杂流程,分解为几个相对独立的环节,分别进行处理,这么做带来了很多好处。

首先,UI展现部分独立成为一个模块,实现了前后端的分离。我们知道,前端的用户体验和界面样式会经常变化,而后端的数据和业务逻辑相对稳定,通过水平拆分,我们实现了稳定部分和不稳定部分的分开,避免相互影响。

这里的后端包含三个模块,其中地图搜索负责路径规划,运力调度负责人车匹配,订单支付负责交易管理。

可以看到,通过水平拆分,可以使每一块职责都比较明确,功能内聚,每个模块管理自己内部的复杂性。同时,模块之间相互松耦合,一个模块的修改不影响另一个模块,比如地图搜索模块中改变了优先路径的推荐,不会影响运力调度模块中的人车匹配算法。

水平分层可以很好地满足现有业务做深度扩展,当业务有变化时,系统在特定层做调整,对其他层影响有限,这样把变化局限在一个小范围。

垂直方向拆分

垂直拆分指的是按照不同的业务线拆分,比如,将整个出行业务分为出租车业务、快车业务和顺风车业务,按照不同的业务场景,自上而下进行竖切,让每个业务都自成体系,形成自己的业务闭环。

通过垂直拆分,一个复杂的出行场景就拆分为几个具体的场景,我们可以根据各个业务线的特点去设计系统,从而降低了整个系统的复杂性。

垂直拆分可以很好地满足业务广度上的扩展,比如说增加一条新的业务线,可以按照这个思路落地系统。

一般做业务架构时,我们先考虑垂直拆分,从大方向上,把不同业务给区分清楚,然后再针对具体业务,按照业务处理流程进行水平拆分。

如果同时进行垂直拆分和水平拆分,一个大系统被拆分为了一个二维的模块矩阵,每个模块既属于某个业务线,也属于业务流程的某个环节。这样一来,每个模块的职责都很清晰,当业务变化了,我们可以清楚地知道,这个变化涉及哪些模块,然后,对这些模块进行相应的调整就可以。

为了帮你更好地理解这两种拆分方式的好处,我这里举个搭积木的例子。经过拆分,每个业务模块都成为一个积木,然后,我们以搭积木的方式来构造系统。当业务发生变化,我们就调整对应的积木,如果系统拆分得合理,拆分后的模块就具有良好的封装性,也就意味着我们主要是调整积木的内部,而它的外观基本不变。这样一来,相邻的积木不会受到影响,系统整体也不需要大的调整。结果是,系统的变化是局部和可控的,保证了灵活的应对变化能力。

打造可扩展的模块体系:模块整合

系统拆完后,接下来就是模块整合的工作,整合也有两种好的手段:通用化和平台化。

通用化整合

通用化指的是通过抽象设计,让一个模块具备通用的能力,能够替代多个类似功能的模块。

回到刚才的出行平台,我们发现3条业务线都有地图搜索、运力调度、订单支付这些模块,不同的业务线之间,这些同名的模块逻辑高度类似,只是细节方面有差别。

那么,我们能不能对这些类似的模块进行抽象化处理,整合成一个通用的模块呢?答案是可以的,我们可以在模块接口中,通过输入参数标识调用来自哪个业务,是出租车、快车还是顺风车,然后在模块内部,针对不同业务线的差异化部分做针对性处理。结果可能是这个通用模块增加5%的逻辑,但避免了95%的重复逻辑,这样,经过通用化整合,新的模块以很低的代价,就为多个业务线提供了复用。而且,当新的业务线进来,很可能这个通用化的模块,就已经提供了现成的支持。

通过模块通用化,模块的数量减少了,模块的定位更清晰,概念更完整,职责更聚焦。在实践中,当不同业务线对某个功能需求比较类似时,我们经常会使用这个手段。

平台化整合

平台化是把定位相同的模块组织在一起,以组团的方式对外提供服务。对于外部系统来说,我们可以把这些模块看成是一个整体,一起对业务场景提供全面的支撑。

如下图所示,我们可以看到,地图搜索、运力调度、订单支付,都是各个业务线都需要的基础和通用的业务能力,当我们增加新的业务线时,还是离不开这些基础能力。

所以,我们可以把这些基础模块放在同一层,构成一个基础业务平台。之前,它们是一个个离散的服务,独立地输出能力,现在变成一个大的业务平台,可以提供整体的能力输出。

通过打造业务平台,一方面,我们对多个业务模块进行包装,形成更大粒度的抽象,相当于减少了模块的数量;另一方面,作为平台,它的定位更明确,系统依赖关系也更清晰;而且,如果新的业务线进来,它可以基于业务平台快速落地。

业务平台化是模块依赖关系层次化的一个特例,只是它偏向于基础能力,在实践中,当业务线很多,业务规则很复杂时,我们经常把底层业务能力抽取出来,进行平台化处理。

总结

好了,下面我来总结一下今天所讲的内容。

首先,我们对系统进行建模,系统=模块+关系,这样会简化你对系统的认识。基于这个模型,我们对模块划分和关系定义提出具体的要求,你可以在实际设计时参考这些要求。

另外,我们深入地分析了扩展性的本质。系统的扩展能力来自于内部模块体系的有序,这样才能低成本地应对业务变化,认识到了这一点,有助于你从根本上理解和重视架构的扩展性设计。

然后,我提供了一个出行平台的例子,来帮助你理解,如何通过模块拆分和整合的手段,具体地设计一个可扩展的架构,希望你能在工作中灵活运用。

最后,给你留一道思考题:你所在公司里有没有类似的肿瘤系统,它包含了太多职责,导致系统内部结构混乱,大家都不敢对它进行调整?

欢迎在留言区和我互动,我会第一时间给你反馈。如果这节课对你有帮助,也欢迎你把它分享给你的朋友。感谢阅读,我们下期再见。

精选留言(15)
  • 航哥很帅 👍(20) 💬(3)

    看到目前,说说目前我对业务系统的理解: 架构的本质其实是如何通过一定的合理编排,让一个软件系统变得的有序,从而满足业务和技术的不断变化。说的高大上一些就是如何让系统变得更有序,而不是系统的熵变得越来越大。那如何让系统变得更有序呢?一般有两种方式:一种是分,一种是合。分就是能够对系统进行拆分,拆分成各个不同的业务模块,同时梳理清楚各个模块之间的关系;合就是能够将模块根据业务的种类,能够把相同的功能模块给合并起来,来统一对外提供服务。 对于业务架构的划分来说,有两个角色好像是在做相同的事情,一个是产品经理,一个是业务架构师。产品经理更多是面向用户侧的,目的是让用户能够对业务认识更清楚,而且产品经理一般不用从业务设计和实现的角度来考虑问题,所以产品经理和开发人员的对接就会比较困难。而业务架构师,则是在产品经理设计完产品功能以后,从面相对象设计的角度来划分模块,让软件的功能设计更加合理,更加适合开发。 一个非常简单的例子就是:产品经理设计的功能,一般都是流程化的,即整个业务流程图。而如果开发人员直接按照这个业务流程来进行开发,很有可能每个业务或功能都要照着流程来做,这样势必会造成大量功能的重复开发。而业务架构师往往就是做这个工作的,既然产品经理已经把哥哥业务流程梳理清楚了,那业务架构师就要把所有的业务流程进行融合和整理,从面相对象的角度来进行设计,画出各个对象之间的关系和依赖图,让开发人员能够更好的进行功能开发。 对于业务架构的可扩展性来说,系统如何能够做到柔性可扩展,是衡量一个系统架构设计好坏的金标准。什么是业务系统,说的简单一些业务系统就等于模块+关系。模块拆分好了,关系梳理好了,往往一个业务系统架构也就定义清楚了。但是一个业务系统好不好还有一个非常关键的指标,那就是:系统的可扩展性和功能的复用性是不是很好,因为这关系到了整个系统的生命周期。 系统的可扩展性好,本质上是系统中各个模块的依赖关系清楚,系统中的各个模块不是复杂和混乱的,而是模块和模块之间关系清楚,很少有相互调用,很少有双向调用。 系统的复用性好,本质上系统的逻辑划分清楚,功能模块能够做到粒度适中,通用功能能够合理整合,给各个业务功能提供调用。

    2020-11-16

  • 偏偏 👍(10) 💬(1)

    老师讲的很好,有几个问题请老师指点一下,1. 如果服务拆分的很细了,而且还有大中台提供服务,一般的做架构的话服务是不是可以不用分层和解耦了,如果按业务来看,影响分层和解藕都有哪些因素需要考量。 2. 关于系统重构,业务梳理并划分清楚后,在技术角度需要考量的因素还有那些。

    2020-02-26

  • 深山小书童 👍(7) 💬(1)

    老师您好,首先得感谢这个课程,案例丰富,干货满满,很有诚意,这十篇文章已经反复研读很多遍。关于可扩展架构的几篇文章老是串不起来,只记得可复用的几篇文章。能否请老师讲一下可扩展的几篇文章的安排思路,方便理解。

    2020-03-14

  • 乖,摸摸头 👍(4) 💬(2)

    老师,我们现在的一种业务场景,我们做的是一个信息对接平台,简化后的主要内容就是这样。 1.信息分为几大类,用户可以发布几种不同的信息,信息发布流程大不分逻辑是一样的,有一小部分差别,和你举例中的打车有点类似。 2.用户查看别人发布的信息需要消耗积分,查看几种分类的信息消耗的积分是不一样的。 3.用户可以置顶自己发布的信息,需要消耗积分,置顶不同分类信息的积分也是不同的。 4.积分是通过用户 拉新、充值等方式获得的。 5.用户的积分是储存在用户表的一个字段中。 6.用户查看信息、或者发布信息 之前会先判断用户的状态,如果用户被我们做了一些标记,就会先让用户做一些其他操作才能查看信息,比如先填手机号填个验证码. 7. 一条信息只扣一次积分,也就是说,在扣分之前会先查询是否已经扣分,扣过分就不再扣分了。 大致的场景就这样,我把系统划分为下面几个模块、和功能。 用户模块: 登录、注册、鉴权... 信息模块: 发布信息、修改信息... 积分模块: 设置各种信息的扣分单价(比如 A类信息需要1分查看,B类信息需要2分查看)。设置 充值单价(10元20积分,20元50积分等)。 记录 积分来源记、积分消耗等积分记录 问题1: 我这样划分模块合不合适? 问题2: 用户查看信息消耗积分这个动作应该放在哪个模块,是放到用户模块、还是积分模块,还是再新增一个模块。 如果放到用户模块: 先去信息模块获取信息,获取用户信息(需要判断用户状态是否健康) 再扣掉用户积分,然后调用积分模块增加一条积分消耗的记录 如果放到积分模块: 先去信息模块获取信息,调用用户模块(需要判断用户状态是否健康) 再次调用用户模块扣掉积分,再增加一条积分消耗记录。

    2020-06-11

  • Carlos 👍(2) 💬(1)

    老师,我在开发中遇到这种模块关系,怎么设计比较好? 我们经常基于客户业务流程开发系统,按照流程节点划分模块,每个模块仅和上一个节点模块关联。模块间关系抽象标识为: 节点1 <- 节点2 <- 节点3 <- 节点4 正常情况下,每个模块仅需要了解相邻上个模块的信息即可。但有些情况下,客户需要在某个业务模块知道跨节点模块的信息,例如"节点4"需要能看到对应"节点1"的信息,这样“节点4”添加一个字段,就需要分别在“节点2”,“节点3”上添加一遍,然后“节点4”才能查到。 这种依赖关系有办法破吗?

    2023-02-10

  • 小洛 👍(2) 💬(1)

    有几个问题请教下老师 1、模块业务逻辑要尽量围绕自身内部数据进行处理,但是有时候需要调用其他模块的数据才能正确处理自己内部的逻辑,就比如订单模块在算价的时候是需要调用优惠模块计算,然后拿到结果做保存,这时候失败了要怎么处理? 2、如何做好模块结构层次化,有什么准则吗?怎么定义相似的模块放到同一层次?开始如果放错,后期调整,如果对业务造成很大影响,是否需要去调整呢? 3、通用化整合,如果修改的是通用逻辑,依赖的多个业务线应该需要全部回归,在业务架构上如何去做权衡

    2020-03-14

  • 黄马 👍(2) 💬(1)

    为了业务流程之间的复用,系统分模块,为了关系模块之间的关系,模块分层; 这些所有的都是给予对业务的理解, 业务的理解公司的业务有关系,如果公司的业务不定,别说构建基础模块,连分模块都不可能。 如果有比较清楚领域知识的,能够识别业务领域的基础能力,直接构建基础能力平台,在此基础上构建各种的业务线 老师理解对吗?

    2020-03-06

  • 川杰 👍(2) 💬(3)

    有。领导觉得没有必要调整,或者,要你利用业余时间提出方案,甚至最好是能改好,还不能有什么BUG,最关键的是领导还是程序员出身;只能等他的短视自食其果了。

    2020-02-26

  • aszt 👍(1) 💬(1)

    王老师,早!重新温习此教程,又有新的收获,就模块划分和模块职责,我这有个小场景,希望你给些建议:标签平台管理标签及标签与商品之间的关系,现在A模块需要通过标签选商品,但标签平台中只有商品的id等少量信息,A模块需要更多的商品信息,应该由哪个模块来完善商品信息?

    2021-04-20

  • Presley 👍(1) 💬(1)

    老师,分布在同一层的基础模块,一个模块可以调用另一模块吗?如果有调用关系,被调用模块,放到更下一层次会不会合理些?

    2020-10-29

  • 梁中华 👍(1) 💬(2)

    看京东的架构除了聚合服务,还有流程服务,感觉这两类服务差不多的定位。

    2020-03-22

  • Geek_kevin 👍(1) 💬(1)

    我们公司是传统生产商,公司在处理物流发货这块有4个系统,分别对接4家不同物流公司的系统,各个系统有80%的重复,以后再来1家物流公司,感觉还要再开发一套,很是头疼

    2020-03-08

  • 睡不着的史先生 👍(1) 💬(1)

    所以,我们可以把这些基础模块放在同一层,构成一个基础业务平台。之前,它们是一个个离散的服务,独立地输出能力,现在变成一个大的业务平台,可以提供整体的能力输出。 这里平台化说白了就是封装了一层对外的api吗?他跟独立的服务好像也没啥区别呢呀?

    2020-03-05

  • 松果 👍(0) 💬(1)

    为什么业务架构会在产品设计完成之后呢? 业务架构应该是更加贴近业务的,像业务未来发展而需要调整业务模型,这类的需求可能业务人员和产品经理都没有办法感知的,如果是从用户需求-产品经理-业务架构-开发,这样是不是没有办法进行未来规划?

    2022-07-29

  • 罗浩铭 👍(0) 💬(1)

    老师,想请教一下您画这些流程和架构图用的什么工具

    2022-05-27