程序员的自我修养 - 架构主题简思

简介: 对架构主题的简思汇总,可以作为日常思考主题,是程序的自我修养。

1 总起

之前写过《简洁应用框架VSEF的架构》的文章, 提出了 VSEF 的应用结构,其中 VSEF 是 “Very Simple Execute Frame” 的意思,聚焦一个应用的简单框架模版。其中也提到,随着业务的发展,各个组成部分会逐渐臃肿,系统风格可能也会变化。所以需要引入一些主题的讨论,结合业务情况进行一些设计,并选取相关组件,进行集成,形成有具体组织特征的、新特性的应用结构。

image.png

在那之后,持续一段时间,以每周写一部分的方式,对“扩展主题”进行了一些展开。本文就是这些文章的汇总展示。

  1. 《VSEF 架构主题 - 组件协议》
  2. 《VSEF 架构主题 - 活动编排》
  3. 《VSEF 架构主题 - 设计模式》
  4. 《VSEF架构主题 - 元数据》
  5. 《VSEF架构主题 - 独立与复用》
  6. 《VSEF 架构主题 - 领域划分》
  7. 《VSEF架构主题 - 逻辑模型》
  8. 《VSEF 架构主题 - 扩展机制》
  9. 《VSEF架构主题 - 模型策略》

image.png

对于程序员员而言,这些思考与框架细节无关,与“感觉”、“起因”有关,离“是什么”、“怎么弄”还有距离,但也可以算作“程序员的自我修养”,日常可以持续思考的主题。

image.png


2 组件协议

2.1 大中台,小前台

前些年,随着基础中间件的逐步完善,业务组件的沉淀和复用也如火如荼,诞生了 “业务中台” 这样的概念。与中台配套的,还有很多前台。此外,为了凸显中台的战略地位,往往会提 “大中台,小前台” 的概念。

业务中台的开发又大多以 “领域驱动设计(DDD)” 为指导理念,通过微服务,划分领域,由各个小团队承接建设,并通过业务流程系统,进行聚合输出。

在这个过程中,业务中台输出的数据,和前台展示的数据,是不完全一致的。刚开始,可能以一份中台标准组件展示数据为标准,各个业务裁剪适配。但是,随着业务的涌入,中台的需求变更非常频繁,组件的演化也逐渐加快。以一份标准数的思路,已经难以为继:

  • 多版本:升级取决于用户和服务方,即使99%的流量都在新版本了,老版本的数据还是不能删除,协议也要持续维护。
  • 不同端:业务可能出现极速版、行业版多种端,采用App矩阵的策略,意味着同时要支持不同的差异展示。
  • 不同生态:PC端主要是网页间衔接,APP是本地Native衔接,小程序是小程序内链接,这导致很多展示区块数据天然不同(如:打开链接)。

这样的趋势,需要在中台和前台之间,提供更多的业务差异定制的可能性。那是不是可以由前台各自转换,中台只给非常基础的数据呢?从独立发展的角度看没有问题,但是有很多APP或者页面是类似的,大同小异,从维护成本的角度看,还是希望进行抽象统一,减少重复建。

image.png

2.2 场景案例:商品详情

举个例子来说,下面两张是京东的商品详情页,第一张来自京东的微信小程序,第二张来自京东的手机APP。

image.png

可以看到,这两个页面结构类似,只是局部的信息和组件不太一样。从复用和独立的角度看,我们期望有一份标准的数据,但是同时希望有不同的页面组件对象转换能力。

从不考虑成本的角度看,完全可以由小程序团队、客户端团队分别拉取数据,进行转换。

但是从提效的角度看,其实可以有一个组件协议转换平台,将服务端基础数据通过配置的手段,直接定制成各个端的不同页面。

此外,从扩展和演化的角度看,后面假如有新的APP或者投放页面,那么也可以在服务端快速配置,后续组件迭代也能快速同步。

2.3 组件协议

为了实现组件的抽象标准化与自定义。我们需要在前台 View 和后端模型 Model 中引入视图模型 ViewModel。这个 ViewModel 相对于 View 的个数来说,往往是很少的。

有了这个 ViewModel 之后,我们就有了一份独立于服务端核心对象的视图基础数据定义。可以进一步通过绑定一些配置平台、引入转换插件包等手段,实现不同页面的自助定制。

image.png

相比于原来 View 直接和 Model  关联的逻辑来看,引入 ViewModel, 我们可以很好地进行再次分类。比如:PC的一套、小程序的一套、手机APP的一套。这样可以更好地进行复用。此外,我们还可以通过更换不同的 ViewModel 来实现增量升级协议和逐步迁移流量。ViewModel 其实是一个适配器,只是是面向较大的生态,一般数量不多。

回过头来,我们再自下而上地看看模型与层次的对应。

  • Model 层:包装领域数据,是 “业务活动” 服务的聚合上下文。
  • ViewModel 层:是服务端的视图层,由少数VO作为聚合根。
  • View 层:视图对象通过组件协议转换框架后产出的标准协议。

image.png

如果视图的转换由相关框架承接,支持扩展设计,那么对于服务端的应用维护者来说:应用中看不到转VO逻辑(在插件中,并不关心)和VO之后业务适配逻辑(配置平台和协议SDK中),只要确保上下文的持续补充即可。

服务端管理的“上下文” 往往是领域对象的翻译和包装,是相对稳定的。这样,也就让 “业务中台” 从业务的页面逻辑中解耦出来,更好地关心 “数据服务” 的层面。

2.4 总结

谈了一些组件协议的理解与思考,认为引入组件配置平台、组件转换扩展插件等能力,能够很好地解耦服务端关注点,支持页面的复用和独立。

当然,这个主题的讨论本身就是可选的,因为有些就是纯粹的数据服务平台,并不收口端的协议。但是,从投放一致性、体验一致性、能力同步成本等角度看,如果有页面协议诉求,这的确是一个值得深入思考的点。


3 活动编排

3.1 复杂需要层次

对于复杂系统,我想以阿里巴巴为例,因为大家耳熟能详。下面是阿里巴巴的生态系统大图,图片来自阿里巴巴官网。

阿里的业务不仅有国内、国际之分;也有远场和本地商业之分;也有数字娱乐等文化产业。大家可能认为阿里就是一个电商交易系统,应该不需要分那么细,万物皆是商品,都可以进行交易。但是不同的业务,市场规则和玩法都不太一样,进一步细分,是为了更好地提升服务,进行行业标准化。也就是说,复杂的商业,并不是一套固定标准,而是很多业态的组合。

image.png

阿里巴巴生态体系,图片来自阿里巴巴官网

我们再进一步看到中国商业部分。即使是国内的交易,也分淘宝、天猫、淘特、淘菜菜、闲鱼、天猫超市等各个市场。这让一些简单的流程也充满了变数。比如,我们进行一次下单,天然就需要考虑不同市场的规则,支持不同的定制,比如:氛围、优惠、会员、时效等。

image.png

中国商业,图片来自阿里巴巴官网

我们再进一步,跳跃到一个服务于这些业务的具体应用中。如何提供一个基础服务,来服务那么复杂的前台场景?

前台的复杂业态作为问题域,天然决定了应用系统内部充满复杂度。如果用 if-else 去承接,这个量级已经超过了一般人可以理解的地步,也就是说“业务脚本”足够复杂,需要进一步拆分。

分层是程序员解决问题的好办法。流程分类框架(Process Classification Framework,PCF)就是一个面向企业业务解构的辅助理论,会将复杂的商业操作进行归类和细分。对应而言,一个应用的一个入口往往对应于 L3 流程级别。再往下,L4、L5 可以作为应用内进一步细分的依据。

可以看到,复杂业务会产生复杂系统,复杂系统需要进一步分层。

image.png

3.2 流程的编排

再回到关注的应用内的层次。之前也常说,复杂的业务系统往往是以领域设计驱动的,通过分布式服务聚合领域服务。那么再从聚合要素的层面,自底向上看,流程的底层天然具备复用的潜力,流程之间的衔接也不会是完全的独立,存在复用的可能性。

那么,自顶向下的分层述求、自底向上的复用可能,交汇在一起,非常好的结合点就是流程编排。对能力有选择的选取,组合成我们需要的功能服务。这个编排既能引导我们考虑分层,也能让我们思考复用的可能性。这种感觉,非常像节目单的编排,节目就是那么几个,但是可以编排出不同的节目单,来服务不同的晚会。

image.png

流程编排的细化

流程的编排的框架也有很多,比如:Activiti、jBPM、Camunda等。这些框架的背后,抽象的理论是“业务流程建模”,比较规范的标准是 BPMN,全称 Business Process Model and Notation。

image.png

BPMN 要素,图片来自知乎《BPMN流程建模指南(1)概述》

3.3 谈论节点

流程编排是对一个业务操作执行过程进行进一步的划分。但在实际开发过程中,常常会有这样的疑惑:为什么是这几个节点?为什么是这样的顺序?这些往往具有不确定性的,有一定的艺术。

节点的切分

个人感觉“代码快的切分”和我们看到一段非常长的文字,把它切分成几句话是类似的。一个主谓宾讲完某个信息就可以结束了,很多信息组成了一个主题。

那回到代码中,代码块的关键切分的依据是什么?常用的一些思考要素有:

  • 数据:是否需要加载相关数据,这可能涉及到一次数据库操作。
  • 服务:是否依赖外部服务,比如,进行安全校验、资源限制等。
  • 规则:是否需要进行规则校验,比如,判断卖家能否拒绝退款等。
  • 扩展:是否需要进行业务扩展,比如,回收给支付系统的扩展参数。

image.png

切分的背后是资源归类

节点的顺序

基于这些切分原则,可以对一个执行过程的核心要点进行切分,那切分完后排序的依据是什么?为什么一定要是这样的顺序?这个问题本质是需要了解各个部分的关系,这些考虑可能是:

  • 数据依赖:比如需要获取买家信息,才能计算优惠信息。
  • 起点与终点:起点常常初始化基本数据,终点往往对结果收口,进行落库和协议转换。
  • 优先级考虑:比如基于节省操作考虑,往往会先进行校验,校验不通过就不需要继续走后续流程逻辑了。

关键的地方,往往是数据本身的依赖,那可以去梳理这些依赖关系,形成一个有向无环图,转换成类似拓扑排序的问题:每个顶点出现且只出现一次,若存在一条从顶点 A 到顶点 B 的路径(前置依赖),那么在序列中顶点 A 出现在顶点 B 的前面,输出一个满足要求的节点序列。

有时候,这个顺序是唯一的,但大多数时候,这个顺序不是唯一的。所以我们更应该去掌握内部的依赖关系,而不是纠结于表面的顺序,表面的答案并不唯一,也没有意义。

image.png

排序的背后是依赖

3.4 总结

自顶向下看,复杂业务系统由于需要处理要素较多,需要进行处理逻辑的分层。此外,自底向上看,领域驱动的业务基础设施,会让系统的业务组件存在被复用的可能。

在这样的背景下,层次的间的交叉复用是比较频繁的。活动编排,是结合分层和复用的比较好的切入点,起到“提纲挈领”的关键作用,是业务系统值得讨论的一个主题。


4 设计模式

4.1 协作关系层次

“架构”这个词大家往往比较关注。那么架构是什么呢?从抽象的定义来说,架构包含:组成要素、关系、设计/演化规则。这个定义即有静态的现状,也能包含演化的过程,的确是恰到好处。

image.png

架构的定义

其中的“关系”,因为超越要素本身,要考虑要素之间,组合的可能性也比较多,充满不确定性,是比较复杂困难的地方。

理解这个“关系”,我想从“修身、齐家、治国、平天下”这个古语的智慧说起。我们想实现伟大的目标,就需要从自身和小事做起。那么讨论这个“关系”,也可以“以小见大”,先缩小关注点,再逐步放大。

从开发的角度看,关系的关注点可以有这么几个层次:

  • 设计模式:关注类之间的关系,探究类之间如何协作完成信息传递与计算。
  • 应用内架构:关注应用内的结构,可以体现在模块间的设计上。
  • 应用间模式:关注应用间协作,将功能划分到不同应用中,设计应用间协作完成复杂流程。
  • 业务架构:关注系统间协作,将商业活动分解到不同系统中,通过系统协作支撑价值流。

image.png

协作关系的层次

这些层次的关系处理思路可以相互借鉴,是共通的。往往,我们不知不觉地在多个层次间游走,也分不清楚身在哪一层,有种“不识庐山真面目,只缘身在此山中”的感觉。

再谈回“架构”,之前看到一个比喻是“坐飞机”:既要高屋建瓴地站在高空俯瞰,也要深入细节的下降观察。此外,也有人认为架构是一个不断 “Zoom in, Zoom out” (放大、缩小)的过程。通过架构的认知,可以感觉到,“关系” 也应该不仅有水平的联系(平面关系),更应有纵向的关联(层次关联),是可大可小地去思考的。

可见,设计模式不能代表架构,但架构也少不了设计模式。通过设计模式,可以进一步体会“关系”的方法论,是我们做好架构的立足点之一(“修身”)。

image.png

架构需要关注多个层次的关系

4.2 再看设计模式

回到类之间的关系,之前写过一篇文章《谈谈我工作中的23个设计模式》,从工作角度进行了类比进行了理解。

23个设计模式中有:5种创建型模式,7种结构型模式,11种行为型模式。下面会再以生活例子理解一下,可以作为日常开发的模式库。

1 抽象工厂(Abstract Factory): 创建不同类型的产品的实例。例如,汽车生产商生产不同车型,小汽车、面包车、皮卡等。

image.png

抽象工厂模式

2 生成器(Builder):将对象的创建分解为不同构件的生成。比如:汽车的生产拆解为车架、门框、发动机、座椅等不同的部分。

image.png

生成器模式

3 工厂方法(Factory Method):将产品的生产过程进行抽象,通过接口的模式去规范出来。比如:手机产商需要芯片供应,面向芯片的接口解耦,让不同的供应商进行生产。

image.png

工厂方法模式

4 原型(Prototype):利用对象的拷贝方法,减少一些复杂的创建过程。比如:书籍的复印,并不关心文字的排版,直接复印拷贝文字即可。

image.png

原型模式

5 单件(Singleton):在多线程的情况下,确保对象只创建一遍,作为独一无二的资源。比如:医生每天看病需要启动仪器,会提前启动,或者在第一个病人来的时候打开。

image.png

单件模式

6 适配器(Adapter):复用原来的能力,适配新的服务接口。比如:不同国家的插座不一样,有各种转接器。

image.png

适配器模式

7 桥接(Bridge):将具体依赖部分,转换为抽象接口依赖,形成间接关系。比如:找对象为避免一开始就建立深度联系,通过双方媒婆沟通,不直接接触。

image.png

桥接模式

8 组合(Composite):组合模式会递归地去描述一个对象层次。比如:经销渠道的建立,每个销售者将任务分解给下游经销商,以此铺开。

image.png

组合模式

9 装饰(Decorator):将原来能力进行包装,提供新的行为。比如:在皮卡上增加一个帐篷,提供露营功能。

image.png

装饰模式

10 外观(Facade):通过外观去操作复杂系统,不用了解内部细节。比如:代办人员会帮你办完复杂操作,不需要关心具体操作细节。

image.png

外观模式

11 享元(Flyweight):当已经存在内容时,可复用,而不是重新创建,减少开销。比如:图书馆会买书提供给大家借阅,不需要每个人都各自买。

image.png

享元模式

12 代理(Proxy):包装一个类,对操作进行转发或进行管控。比如:案件会请代理律师,全权负责案件,避免当事人言论出差错。

image.png

代理模式

13 责任链(Chain of Responsibility):将请求让队列内的处理器一个个执行,直到找到可以执行的。比如:比武招亲,让人一个个上来比,直到找到厉害的。

image.png

责任链模式

14 命令(Command):将请求包装为命令,执行时与具体的执行逻辑解耦。比如,云上任务,定时调度业务Jar包,不关心具体逻辑,由客户自行实现。

image.png

命令模式

15 解释器(Interpreter):针对上下文,形成一套语言,通过解释表达的方式完成任务。比如:使用大模型能力时,需要提供易于理解的提示词,让模型理解执行。

image.png

解释器模式

16 迭代器(Iterator):将集合的访问功能独立出来,通过迭代的模式去访问。比如:货物运输时,将装卸货物的任务交给相关工人,逐一搬动。

image.png

迭代器模式

17 中介者(Mediator):多个类之间要协调时,引入中介者进行协调,减少大家的知识成本。比如:赛事需要组委会,去协调各个参赛队伍。

image.png

中介者模式

18 备忘录(Memento):对操作进行记录,以备可以恢复到之前的版本。比如:做表格时及时备份保存,避免断电后无法恢复。

image.png

备忘录模式

19 观察者(Observer):通过注册、回掉这样的协作设计,完成变化通知。比如:订阅牛奶后,不需要每天去站点拿,会送货上门。

image.png

观察者模式

20 状态(State):在不同的状态下,有不同的处理行为。比如:运动员如果处于伤病状态,则不能参加比赛,避免影响职业生涯。

image.png

状态模式

21 策略(Strategy):完成事情有不同的算法,可以进行切换。比如:轮胎坏了可以补胎,也可以换轮胎,甚至可以换车。

image.png

策略模式

22 模板(Template):对执行过程进行抽象分解,通过骨架和扩展方法完成执行逻辑。比如:婚庆公司的仪式大同小异,不过致辞、才艺表演等部分需要新人提供。

image.png

模版模式

23 访问者(Visitor):把对元素的访问操作交给访问者自己,因为访问者常常有不同的行为。比如:发放奖品,不发统一礼品,可发购物卡,让大家去购买想买的等价物品。

image.png

访问者模式

4.3 泛化到应用模式

设计模式是关注类之间的关系,比较细。当关注点迁移到应用内和应用间的时候,往往可以称之为“应用架构”和“应用模式”。

关注点的载体虽然扩大了,但抽象后,问题还是类似的,因此往往可以借鉴设计模式的思想。

具体的场景举例,可以参考之前的文章《浅谈交易链路中的一些设计原则&模式》。简单举例来说:

  • 模版模式:可以将应用主体看做模版,将应用扩展插件看做扩展方法。
  • 责任链模式:在处理多个业务插件时,可以进行优先级排序和依次执行,回收定制结果。
  • 策略模式:在对接下游系统时,考虑基于接口协议的可替换性路由,比如不同的支付系统。
  • 观察者模式:对系统发出的领域消息事件,可以消费监听,进行观察。
  • 状态模式:对领域单据的状态进行管理设计,不同阶段支持不同的操作。
  • 中介者模式:对于复杂业务活动,增加协调者进行串联,比如下单系统串联各个领域。
  • ......

4.4 总结

讨论了设计模式主题,认为架构中的协作关系也分层次。设计模式关注的是比较基础的类间模式,但本人认为不同层次的关系处理思想是可共通的。因此设计模式可深入架构的多个方面,值得研究储备。


5 元数据

有句名言说的是,“世界上最遥远的距离,不是生与死的距离,不是天各一方,而是,我就站在你的面前,你却不知道我爱你”。对于软件开发者来说,软件开发过程中最遥远的距离,莫过于,代码就在你面前,你却找不到具体位置。所以,针对“元数据”的理解,我想从软件认知复杂度的角度谈一谈。

image.png

我们期望的系统结构,就像下面的数据线一样,井井有条,按照约束分布。而且颜色分明,很快能找到对应的线,非常有序。

image.png

但是,实践中,由于增长的业务需求,在原有的约束下,难以快速支持,就会出现拉很多零时的线路的情况,演变下来就会成为下面的局面,俗称“熵增”,往往会归结到业务复杂度过高。

image.png

为了解决混乱的问题,我们就需要采用“分而治之”的策略,把大混乱变为局部小混乱,然后治理局部小混乱。

首先横着看,按层次进行切分。将业务的流程分为不同的粒度。从流程编排到活动节点设计,到具体任务执行,将某个执行过程的步骤,从抽象到具体进行了链接。

image.png

横切还不够,我们还会观察具体的任务执行节点,将类似的部分归类到一起,形成模块任务。这样每层当中,又有了局部关联的逻辑,相当于层次内的纵向切分。

image.png

这样横纵切分的过程,类似生活中的快递排放。既有层次标号,也有层次内的顺序和关系。但是看下面的实际快递场景,我们会一个个去比较么?不会,因为那样是O(n),太慢了。我们实际是基于标签去定位。标签是什么?它就是索引。通过抽样比较,我们可以快速排除不必要的搜索。

image.png

回到代码过程中,我们寻找关键模块,其实也是基于系统理解,进行有规律地探查。这也就是,为什么很多架构师都在规定系统目录,定义模块结构的设计原则,因为统一的认知,可以避免重复学习,让初始化的搜索变得更加快捷。

但是,这样的结构是比较粗的,很难具体到里面的内容。所以我们需要进一步的索引。就好比,除了主键索引,你还可以建很多其它索引,支持场景查询。

image.png

说了那么多,我觉得,元数据的设计,核心还是为了减少我们的认知成本,提高我们寻找代码的效率。举例来说,系统上有很多扩展点,有物流的,有商品的,有支付的,有优惠的等等,你想找到一个选择支付系统的扩展点,该如何找呢?

  • 如果没有元数据,我想可能是先找流程,找到支付相关的部分,向下找支付服务,然后里面选择支付的系统设置,然后看看参数从哪里回收,然后再看看参数是否有调用扩展。
  • 如果有元数据,可能就在元数据后台搜索“自定义支付系统”这几个字,找到类似的扩展点声明,然后就可以直接看到代码声明,减少了对系统结构的依赖。

这样的逻辑,也好比我们在书边上贴便签,快速索引一些关键位置。

image.png

以上讲了元数据对于复杂系统的认知帮助。不可否认的是,元数据的确是我们链接认知的关键桥梁,是我们常用的知识便签。此外,因为元数据的轻量,可事后添加等特性,它往往比本身的结构设计有更好地适配性、灵活性,可以作为大家设计的重要考虑要素。

image.png

6 复用独立

软件架构中,独立与复用是永不过时的主题。之所以不过时,是因为的确存在非常大的不确定性和局部自主性。人人都有机会接触与思考这样的主题,纠结之处可以让大家产生共鸣。

说到这个主题,还是从阿里的中台战略提起。因为技术只是载体,业务的思考才能够让我们更加理解它。阿里巴巴比较著名的就是下面的双中台策略,数据中台统一数据处理,业务中台统一电商交易服务,期待可以链接、赋能。当然,现在大家也知道,阿里已经执行了去中台战略,现在已经让各个部门独立发展。

image.png

那为什么要建中台来复用,为什么又要去中台保持独立那?我们从下面的角度看,本质还是业务A 和 业务B 是否要产生交集,如果要产生交集,意味着会产生复用,如果完全不复用,A和B之间就没有交集,完全独立。重叠意味着什么?意味着趋同。


image.png

我们再回到中台角度,可以看到复用和独立的阶段背景是不一样。

当要提中台复用的时候,淘宝天猫正是如火如荼,大杀四方的时候,当时处于电商行业的领先地位,可以说是top1。这时候,以淘宝天猫为基础,建立业务中台,进行复用,可以很快地让其他业务对齐行业领先体验,提升认可度和市场占有率。所以,当时大家都想进入业务中台,虽然知道后续差异迭代可能会慢,但是进去之后,想想可以快速复用一堆产品能力,也是充满诱惑的。

现在提去除中台的时候,是因为不再处于行业领先。如果还是趋同,那么体验也不再是领先地位。而且,即使想要独立发展,也因为融合较深,独立发展的部分空间有限。这就造成了类似泰坦尼克号的沉没场景,虽然知道要撞冰山了,但是因为本身的惯性,难以掉头了。所以,后面也只能快刀斩乱麻,去除中台的旗帜,从组织上不再鼓励大经济体的趋同,而是自负盈亏,独立跟进市场领先标准。

image.png

如果从中台的发展历史看,从长远的角度说,是不是独立永远是“政治正确”。为了避免复用成为约束,是不是要凡事都说,后面存在独立发展的可能,因此不建议复用?

如果我们要深入去看复用和独立的问题的话,其实还是要去看“粒度”问题。复用的粒度越小,业务组合的可能就越多,越能给业务发展产生助力;复用的粒度越大,业务初始化的成本越小,启动成本越底,但是后续改造成本会较高。

我觉得从复用的链路看的话,就好比下面的珍珠项链,如果你能给业务每个模块选择组合的权利,提供原始的珍珠,那么客户选择组合的可能性就更多,更能得到想要的装饰。如果是一整条项链,那么很容易产生和业务的不匹配,换一颗都很困难。


image.png

再回到日常开发工作,我们往往不用考虑复杂的业务发展和策略。往往看得是,这个模块需要需要服务多少业务,业务的同质化都有多少,抽象后时候有长期支持的保障等基本因素。当抽象成本不高,业务复用可能较大的时候,复用的决策往往很容易做出。

再看独立,如果只是正向的去思考复用,那么制约复用的核心要素是什么那?我觉得还是说,这块生产资料的所有权是否归你所有,如果说在你的应用里面,你当然期望复用,也可以拍板。但是如果在业务的扩展实现里面,比如说页面样子,生效场景等,收了你也决定不了里面的逻辑,那么就不存在复用的可操作性。

image.png

再回到最后,天下大势分久必合,合久必分,分分合合之后,也不再是原来的朝代,但是一些法制、货币、道德文化等基础的资料还是会传承下来。我们去考虑独立和复用的时候,还是应该往统一、标准化的方向去走,我们需要高水平的抽象,不是低水平的重复建设。这个认知,我想大家都是认同的。

复用设计后,让客户可以做选择题,保持独立性,因为你抽象得好才使用你,不想用的时候,也可以退出。我想是你作为开发,最大的善意。其实实现也很简单,可以想到是,通过策略模式,保持替代可能性。


7 领域划分

7.1 从业务驱动到领域驱动

说到领域划分,不得不提的是领域驱动设计(DDD)。这个概念大家一直提,但是为什么要领域驱动,还是很主观的感觉:细分问题域,能够更加高效。下面想进一步刻画一下,加深一下理解。

首先看下“业务驱动”。如果没有领域,那么业务系统在提供业务服务时,往往只会关注自己系统的视角,将任务拆分为系统内合理的模块。这些模块,在单系统内并不会感觉到不妥,但如果横看多个系统时,会发现有很多类似逻辑。在横向角度,是缺少抽象和复用的,新增业务服务时,并不会因为系统已有的工作而提效,因为并没有进行通用设计,需要从新设计使用。能成为领域的模块也比较厚重,因此重复建设的成本往往也比较高。

image.png

业务驱动

转换为“领域驱动”后,会将多个系统共性的部分下沉到领域中,通过领域服务进行协作。这样系统内部,相关任务就会简化为收集上下文和领域交互,并没有厚重的逻辑。比如:原来支付还要关心下游用的是什么支付系统,考虑适配不同的支付协议,现在只要把订单数据给到支付域就可以了,通过支付服务完成支付操作。这样的支付服务,也会给很多其它系统使用,具备很高的复用性。

image.png

领域驱动

此外,再往领域内部看,领域在收口横向逻辑的时候,也需要提供一定的扩展能力,确保兼容不同业务服务的场景特性。这样的扩展能力往往会通过插件实现。这样,业务以前可能在单系统内能够闭环,现在则需要进行跨系统开发了。团队协作的增加也是系统职责划分的一个客观副作用。但是,整体看,全局的效能应该是能按期望提升的。不然,就需要反向行之了。

7.2 领域的层次

什么算领域?只能中台的服务算领域么?我觉得误区就在于:大的业务才能成为领域,大家常提的才能算作领域。这的确是领域的一个显著特征,但并不是核心因素,也不是问题的关键。

从“业务驱动”到“领域驱动”的分析来看,能够给上游系统提供抽象,给多个上游系统调用复用的,是可以成为领域服务的,并不是特定组织内的才能成为领域。

进一步看,这样的思考背后还是要看一下领域的互相依赖层次:

  • 业务方:上游来调用的可以叫做业务方,可能它本身也是一个功能的领域,但是站在我们的角度看,并不理解上游,或者说知之甚少,难以判断。
  • 业务域:大家把你的系统称为领域,但是你可能认为也只是做业务的。这没有错,因为是否是基础功能,这个是相对的。站在服务内部,基本都是业务活动的流程编排,进行下游服务的集成,最终落个数据库。看起来和做业务的确没啥大的区别。
  • 基础域:对于一些复杂的操作,我们并不需要理解,只需要转发给下游服务。下游的服务在我们的业务之下,相对于我们的业务看,是相对稳定的,所以站在我们的视角看,下游是基础域,觉得别人就是领域专家。

image.png

领域存在递归层次,角色是相对的

站在上游业务方的角度看,依赖的业务域,还是业务域依赖的基础域,对业务方来说都是下游领域,并不会再进一步区分。所以,业务逻辑的下游往往会统称为领域,这一层也会拉平,但进入特定领域内部,又可以以业务方的视角再去展开下游领域。这是一个递归的过程,而这个视角的关键在于,你身处哪个系统。

7.3 特定的前提

如果有前面的讨论,我们可以再看“领域的划分”。做这个讨论的前提是,我们会锁定我们的系统,将这些系统的共性部分进行下沉,讨论的是这些下沉领域的划分。

这个视角也的确存在不确定性:业务越高层,下游的领域就越多,划分的可能性就越多;业务越基础,下游的领域就越少,划分的确定性就越高。

有了这样的认知,我们去讨论领域划分的时候,就应该限定在一个特定环境中(大团队),这样的讨论存在团队上下文,更具意义。也可以看到领域的划分,是特定团队的领域划分。

7.4 划分的动态性

领域怎么划分比较合适?工作中常提:领域是按“一个或多个实体对象”来切分。这看上去很合理,因为领域的核心职责就是对领域实体进行管理。

但这是果,还是因?在切分时,是因为有了对领域的判断,所以某些实体被分在一起比较合适;还是因为这些实体有明显边界,才形成一个领域?就比如下面的图,有很多分类角度:

  • 切分成3个(红、黄、绿)。
  • 整体作为一个部分。
  • 按横轴的正、负切分2个部分:左边1个黄,右边2个红、绿。
  • 按竖着的正、负切分2个部分:上面1个红,下面2个黄、绿。

image.png

聚类的例子

这的确引人深思。切分容易时,往往是因为已经有了行业的标准(如:电商有订单、物流、支付等领域是合理的)。那行业的标准又来自哪里?是来自于演化:

  • 大泥团:开始时,只是一个大交易,比如:支付早期是买卖双方线下协商,并不需要建模。
  • 建模细分:后面发展到线上结算,也就独立出来一个支付域。

所以,领域的演化和划分,类似“启发式算法”:

  • 初始化:按照经验初步的划分,这个经验也可以参考行业标准。
  • 花费评价:高效的生产、交付过程,关注理解成本、研发效率、系统稳定性、运维成本等度量指标。
  • 更优解:在支撑业务过程中,计算花费评价,分析影响评价的“坏因素”、“好因素”,进行进一步优化。

往往到最后,我们会发现:

  • 调整的原则:追求职责的内聚,精细化分工。
  • 调整的内容:其实是匹配生产关系。
  • 不断调整的原因:业务在发展,内聚的标准需也要与时俱进。

此外,“康威定律”指出,往往组织沟通结构就能体现架构关系,也就能看到领域的边界。核心原因还是组织架构也是在适应生产关系,follow更优解的结构,是相辅相成的,也就能互相窥探。

7.5 总结

探讨了领域驱动的价值,并提到了领域是存在层次的,并不是只有特定部门的特定服务才可以作为领域入口。正因为如此,我们谈论领域划分的时候,也需要考虑所处的系统、团队视角,这个视角是特定的前提。站在这个视角去进行领域划分的时候,往往也是一个不断调优的动态过程,目标是与生产关系进行匹配,提升生产效率。


8 逻辑模型

生活中,我们如果购买二手房,一般会进行再次装修,形成自己想要的房子类型。这里的场景,其实可以发现,房子的整体结构已经成型,难以改变,但是里面的装饰可以进行调整,满足不同房主的喜好。因此,可以说是“铁打的结构,流水的装饰”。

image.png

本文想说的是逻辑模型,是指数据库模型在软件中的建模。和房子装修的现象类似,随着时间的发展,软件系统内的模型对象在不停地演进,但是对象背后的存储表结构,可能还是开始的样子。之所以这样,也是因为数据库的结构更改成本很高,而模型对象的调整相对简单。

为什么数据库的对象(DO)和内部逻辑对象(PO)的直观差异会不停发展变大呢?一个常见的情况是,新增场景需要记录状态,然而之前没有考虑预留,只能往表里面的扩展字段里加。这个扩展字段也往往是一个属性Map的映射。这样,不停演变之后,属性Map成为了一个主要的业务沉淀区域,但这里的字段是缺少明确的字段属性解释的(逻辑上只是KV)。所以,在内存对象中,我们往往会将属性字段提取出来,进行建模和管理,减少理解成本。

image.png

逻辑模型的易变性,也是软件“软”的所在。工作中,会遇到领域的分分合合等情况。比如:订单拆分价格域、物流域、资金域, 营销域、资金域合并成营销域。这些场景下,如果是表结构的归类可能比较简单,比较复杂的可能是表结构内不同字段的归属调整。较好的去管理这些属性,并和领域边界对齐的话,可以去重新设计逻辑对象的字段聚合关系。这样使得领域的分分合合可以出现在逻辑层,不用动具体的数据库。

image.png

在具体实践方面,可以探讨一下逻辑模型和表之间的关系。最容易理解、维护以及发展的情况,当然是领域模型与数据模型能够形成 1:1 的维护关系。但是由于底层数据很难变动,而理解和业务发展是与时俱进的,当我们持续设计的时候,逻辑模型势必会不太一样。常见的一些场景有:

  • 一个逻辑模型维护一个表:这个是比较简单的场景,一个逻辑模型直接负责一个表,比较干净易理解。
  • 多个逻辑模型共享一个表:这个是因为表中记录的数据比较多,但是实际上有不同的领域数据存的冗余储,比如交易订单上的营销优惠信息、资金信息、物流信息等。
  • 一个逻辑模型维护多个表:当我们对原始的领域重新划分,又不能重新建立表结构的时候,对于较大的一些域,往往数据来自多个表,这时候就会遇到这样的情况,一个领域模型要从多个数据库表里面聚合数据。
  • 聚合逻辑模型的复杂情况:这个在DDD里面主要是聚合根这样聚合了多个实体的情况。举个例子来说,资金模型涉及有订单表、支付单表、还有支付明细表,这样的聚合关系就更加复杂了。

image.png

逻辑模型的存在,其实是把双刃剑。因为它的低成本,让大家更愿意去改变它。但是随着字段的增多,设计人员的迭代,大家已经很难找到字段和数据之间的映射关系了。往往会出现:“找不到数据库字段对应的逻辑模型字段是哪个”、“数据字段被预期外的领域行为更新了”。这其实还是因为灵活性带来的代价,使得规则更加隐性,让管理和认知变得更加困难了。

所以有些系统使用和数据库模型一样的逻辑模型,减少当中的层次映射,这样的“不建模”模式,反而成为了有意为之的“设计善意”。当然这样的距离拉近,其实可以通过额外的元数据来缩短距离,可选的方案不仅仅是“减少层次”。


9 扩展机制

9.1 业务与平台隔离

扩展的思想大家都耳熟能详,设计原则之一“开闭原则”就提出:软件中的对象(类、模块、函数等)应该对于扩展是开放的,对于修改是封闭的。

回到业务系统中,这意味着我们不能把业务逻辑的处理过程大一统地看待,要进一步区分基础逻辑(稳定的部分)和扩展部分(易变的部分)。我们可以把基础的部分称为“平台”,扩展的部分称为“业务”。这样分离的设计,也就实现了“业务与平台”的隔离。

进一步,可以将“基础逻辑”和“扩展部分”划分到不同的模块中,让不同的团队合作开发,实现“共建”。

image.png

隔离平台逻辑和业务逻辑

9.2 业务与业务隔离

业务和平台隔离出来之后,可以进一步关注业务扩展之间的关系。

我们会提供一些抽象接口,一般由“是否支持”(support) 和“具体执行”(execute)两部分组成。业务定制实例会按照一定顺序执行,有时会返回第一个执行结果,有时会回收全部结果。

抽象看,这是“责任链”的执行模式,排在第一个的业务插件拥有“上帝”视角,可以对所有流量进行拦截处理。这样业务之间会存在耦合和影响。试想,如果第一个执行点的“if 条件”抛出了异常,那么整个逻辑都会挂。这样就会导致大家都想往前面排,避免其它业务的影响。

image.png

业务扩展按序执行的模式

前面说到,业务和平台隔离之后,业务的部分可以独立出模块,甚至让业务进来共建。但是,业务插件的边界不清,让业务之间的独立共建存在瓶颈。如何让业务之间也能互相独立呢?

我们发现,业务需要实现的“if 条件”和 “执行逻辑”的复杂度是不一样的,“if 条件” 只是简单地判断一下是不是自己支持的业务, “执行逻辑” 则需要进行复杂的操作。那我们是否可以把“if 条件” 抽象出来,与一个标识挂钩,作为插件的身份,让业务按照插件身份进行扩展。这样在插件身份计算之后,业务实现了“正交”,可以独立开发。

插件身份的计算,会将原来的“if 条件”再精简一下,通过基础要素进行计算,而且会在业务侧达成共识,是比较稳定的。这样将比较薄的“业务计算”解耦出来,进行简单管理后,可以让高频变更的“执行逻辑”部分实现隔离。针对“高低频”、“复杂度”的进一步细分,是“业务之间隔离”的关键解决思路。

image.png

基于插件身份的正交模式

9.3 扩展层次

关于扩展,还想谈谈扩展的层次,这也是常常困恼我们开发的关键点之一。

9.3.1 系统结构

首先,我们回顾一下整体的系统结构。

系统内的流程,可以分为 L3-L5 3个层次,其中:L3 是业务流程,负责业务活动的编排;L4 是活动节点,提供步骤执行能力;L5 是具体任务,负责具体细节执行。

扩展往往会发生在“L5 任务层”,为执行流提供业务扩展,实现业务场景的定制。

image.png

流程编排的细化

此外,我们在领域的讨论中提过,如果考虑多个系统,会将系统内的共性逻辑下沉到领域服务中。那么在L5中的部分扩展,也会被带到下游的领域服务中。这也会造同一个业务活动的多系统视角。

image.png

领域驱动

9.3.2 外调领域

有了上面的大背景,我们看一下外调领域下的扩展。在这个模式下,领域服务都收在了下游系统,业务需要在上层业务活动系统实现一定的定制,同时也需要进入下游领域系统进行定制。

对于每个系统来说,扩展都是针对比较发散的L5任务来说的,一般都认为是业务扩展,并没有过多疑惑。

image.png

外调领域下的扩展

9.3.3 内置领域

正如“0”和“1”之间还有很多数字,很多事情并不是绝对的两面。

在发散的“业务逻辑”到“通用领域”之间,还有很多系统内培养的“系统领域”。这些虽然在多个系统间可能并不通用,但是在单个系统内还是比较通用的。这些“系统领域”出于性能和维护的考虑,往往不会独立出单独的系统,会在系统内进行托管。

这些扩展,我们并不想把它简单地认为是“业务扩展”,因为它不是简单的 L5 任务,而是精心沉淀的“能力”,所以我们更愿意给个单独的名字,叫“领域扩展”。

但是,问题来了,“业务扩展”和“领域扩展”感觉并不是一个层次,“业务扩展”是完全对外的,而“领域扩展”是部分对外,部分对内的。

image.png

内置领域后面临扩展层次不一的问题

解决这个问题的比较好的办法,还是细分领域扩展,将领域扩展对内的部分称为“领域扩展”,对业务的部分称为“业务扩展”,让部分领域扩展去收集业务扩展。

这样的逻辑对于领域逻辑来说,比较好理解。但是对于比较薄的业务来说,业务域很难抽出领域扩展。而这里的处理也会出现分化:

  • 适配领域扩展:注重系统结构统一的,会增加一个领域扩展去适配整体的结构,但是会遇到领域扩展定义(名字合理性)的问题。
  • 直接调用业务扩展:为了直接高效,会直接调用业务扩展,减少领域扩展的这个层次。

image.png

业务扩展与领域扩展共存的场景

9.3.4 小结

站在“系统内”和“系统间”的视角看“扩展层次”:

  • 系统内依赖的复杂通用域,会将扩展移交到下游领域系统。
  • 系统内依赖的简单内部域,会区分领域扩展和业务扩展不同的层次。
  • 系统内依赖的业务逻辑域,可以直接调用业务扩展,也存在适配领域扩展的情况。
  • 业务方看到的都是业务扩展,并不会感知到领域扩展。

image.png

系统内及系统间的扩展关系

9.4 总结

讨论了扩展机制,认为扩展的产生是为了实现“开闭原则”,让易变的部分独立出来。并在扩展独立出来之后,看了业务之间隔离的策略。接着,站在系统内和系统间的角度,讨论了领域扩展和业务扩展的并存情况。

总结来说,扩展本身比较简单,但是如果叠加业务关系、系统关系之后,持续把握扩展的多维度视角,还是需要进一步思考的。


10 模型策略

说到模型策略,中英文的翻译是个比较好的例子。国内外的文化交流往往需要进行文字翻译,这样可以更好地传递给更多的大众。但是因为本身语言和文化的差异,翻译的结果也存在不确定性,尤其是在诗词等讲究意境的场景。就比如下面的一首词,个人感觉中英文是2种感觉。所以,有些人喜欢阅读原文,体会原来的本意;有些人喜欢看翻译后的内容,体会一下专业人士的理解。

image.png

上面的翻译场景,其实讲述了日常信息传递的过程,存在学习和理解,本质是不同上下文信息的映射。我们日常的系统交互过程中,也存在对象的转换思考,选择follow,还是拷贝,还是抽象定义,往往有不同的策略。

image.png

共享内核 shared kernel:从语言的例子看,这个是将大家的理解沉淀到一起,好比大家选英语作为统一语言,把一些基础的文化融合到英语里面。好处是,可以很容易复用,节约建设成本,随着用的人越多,价值越大。缺点是,抽象的不好的话,反而会成为约束大家的阻力,而且可能因为已经被其他人复用,很难调整。

image.png

客户/供应商关系 customer/supplier:这个是普通的依赖关系,至于如何使用数据对象,完全按照业务的场景来看。可能完全引用,可能局部翻译,可能完全替代,存在多种组合。这里的依赖策略不明确,好处可能在于比较随意,便于敏捷开发,缺点在于后面进行依赖分析和治理的时候会比较复杂和困难。

image.png

跟随者模式 conformist:如果对象很复杂,那么理解的成本也相对较高,如果提供方已经进行了理解,较好的策略是进行跟随follow。比较典型的场景就是完全使用原始对象在流程内流转。好处是解释简单,可利用提供方文档,坏处就是难以精简和改造,把模型优化的命运交给了提供方。

image.png

防腐层 anticorruption layer:通过增加适配层的方式,我们可以将依赖方的变化进行隔离。保证内部逻辑的稳定,比如下游升级了新的协议对象,我们只要切换走不同的适配层就可以,这样可以保证内核的稳定。这样的情况,对外部变化进行了隔离,类似防腐的操作,也叫防腐层。这样虽然隔离了变化,但是天然地增加了转换的成本。所以还是要看变化的情况,如果提供方不是内部团队,是可替换的供应商的话,是很值得使用的。

image.png

各行其道 separate way:这种方式就是独立建设与发展,完全不依赖对方的信息。好处当然是内部闭环,协作成本低。缺点就在于建设的成本,如果比较复杂,那么长期投入的成本是比较高的。什么时候会独立发展那,往往是B满足不了A的诉求,或者B与A的定位期待不一样。就比如,原来字节用钉钉办公,但是后面盘子大了之后,定制化需求和目标也变大,钉钉满足不了这样的趋势后,字节采用自建飞书的策略。

image.png

说了这些模型交互的策略,其实本质还是会走向提升生产效率的目标,如果你有决心、有能力做得更好,那么就会以自己的理解为主,不然会使用别人的认知,减少重复建设的成本。但是,值得注意的是,这些策略不是只能选一种,在一个系统中,往往会针对不同的部分,采用不同的策略,也是系统设计的发力点取舍。


11 总结

如同运动员需要保持高水平训练才能维持竞技状态,作为程序员,日常也需要进行“思考训练”,这样可以提高认知,是我们的自我修养。

架构的主题其实不仅仅是这些,这些只是抛出的砖,但是持续抛砖的惯性,可以让我们较好地去思考其它的主题。

当然,这里也只是“浅尝辄止”,如果要获取深度的认知,还是要讨论各种场景,进行各种定义和边界思考。这也很花时间,也难有止境,到最后可能你已经在思考人生了,因为越深入,愈发会感到理论体系的缺乏。

相关文章
|
3月前
|
缓存 架构师 Java
李光明从程序员到架构师的逆袭之路(一)
我叫李光明,今年20岁,从事计算机软件开发。今年初春,我踏上了前往上海的列车,心情既激动又忐忑。这是我第一次来到这座繁华的都市,这里的高楼大厦、车水马龙都让我感受到了这座城市的活力和魅力。然而,更让我期待的是,这里将是我职业生涯的新起点。
|
3月前
|
架构师 Java 中间件
程序员,如何从开发转型做架构师?
程序员,如何从开发转型做架构师?
|
架构师 安全 Java
年薪120W的架构师简历你见过吗?java程序员该如何达到?
第一个问题是,“大多数Java开发人员都在哪里,我们中有多少人?”我们回答了这个问题,把我们能得到的最准确的信息结合起来,然后推断出我们认为是一个受过良好教育的猜测。
|
4月前
|
数据管理 程序员 人工智能
后台数据管理系统 - 项目架构设计【黑马程序员】
后台数据管理系统 - 项目架构设计【黑马程序员】
236 0
后台数据管理系统 - 项目架构设计【黑马程序员】
|
4月前
|
程序员 数据安全/隐私保护
架构人生,体魄同行:程序员的健康密码解析
架构人生,体魄同行:程序员的健康密码解析
90 0
|
4月前
|
安全 数据挖掘 程序员
程序员必读 | 《业务架构解构与实践》
程序员必读 | 《业务架构解构与实践》
506 0
|
4月前
|
开发框架 架构师 Java
Java程序员不掌握SpringBoot怎么进大厂,阿里架构师推荐实战文档
Spring Boot作为Java编程语言的一个全新开发框架,在国内外才刚刚兴起时,还未得到普及使用。
|
10月前
|
架构师 Java 程序员
GitHub标星百万的程序员转架构之路,竟被阿里用作内部晋升参考
架构师是很多程序员的奋斗目标,也可以说是职场生涯的一个重要选择方向,今天我就跟大家聊一聊如何从一个程序员成长为一个架构师。
|
存储 缓存 架构师
程序员架构修炼:架构设计概要,业务、应用、技术、数据架构
架构设计 在架构设计过程中,我们会根据需要做出不同的架构设计,而在设计时需要涉及一定的架构设计核心要素。
|
3天前
|
安全 应用服务中间件 API
微服务分布式系统架构之zookeeper与dubbo-2
微服务分布式系统架构之zookeeper与dubbo-2

热门文章

最新文章