去哪儿网架构演进之路:微服务的尽头原来是DDD……

2023年 10月 11日 62.3k 0

一、架构设计理念与技术

1.架构演变路径

图片图片

  • 单体(又称巨石系统):所有业务融合于一体。在项目早期,公司一般会选择单体以降低运营等各方面成本。
  • 服务化:随着业务飞速发展和流量增长,进入了服务化阶段。在此阶段,经历服务拆分、治理和模型抽象。
  • 平台化:业务膨胀期过后,服务化维护成本高,服务粒度拆分过细、重复造轮子、系统交互混乱等问题暴露,因此迈向平台化(服务能力沉淀、服务合并、领域自治等)。
  • 中台化:打造企业级能力复用平台,是平台化的下一站,其具备数据互通能力和业务变化高响应力。

业务架构的演变路径,侧面展现所在互联网企业的演变路径。每一种架构无关好坏,选择与否,只取决于是否适合当下及可预见的未来。

本次分享主要介绍从服务化到平台化的过程,即从服务细粒度到领域能力沉淀的演进过程。

2.架构设计理念

图片图片

从业务出发、面向业务变化是架构设计成功的关键,指导业务架构设计的维度包括:

1)商业模式及成熟度

传统行业的业务相对稳定和成熟,非必要情况下建议做成单一服务。如需拆分,建议将变化频繁与不频繁的业务拆分。

互联网行业则分为初创公司与成熟稳定的公司:

  • 初创、商业不稳定的公司:需要多种业务进行快速试错,可使用微服务。以微小的单体制服务器,快速构造探索场景,以技术的确定性来应对未来发展的不确定性。去哪儿网的某些团队产生简单方案后,可使用微服务获取市场反响,快速验证效果。
  • 商业稳定或固化的公司:不再需要技术端的灵活性,也不愿承担灵活性带来的架构维护成本,此时可以考虑合并微服务,以减少运营成本。

目前旅游行业已相对稳定,去哪儿网比较符合以上第二种情况,可以考虑将先前拆分粒度太细的微服务进行合并。这也是去哪儿网的架构演进原因之一,原有业务拆分太细,达到人均10个应用,维护成本极高。

2)面向业务的变化

  • 组件设计围绕业务变化
  • 组件调用非强依赖
  • 组件业务复用
  • 组件颗粒度与成熟度

为快速适应业务变化,需识别业务核心问题,划清业务边界,达到业务组件的复用最大化;区别出变与不变的业务,将变化隔离在一定范围内,进而减少变化。

面向业务变化与不变的情况下,组件颗粒度要拆分到什么程度?

组件拆分粒度过细时,可复用性强,但组装麻烦;拆分粒度过大时,好用,但使用场景比较少。

3)技术延迟决策

《架构整洁之道》一书讲到:“良好的架构设计应该只关注用例,并能将他们与其他的周边因素隔离。”前期应该只关注用例,在后期决策使用的具体技术。

图片图片

4)康威和逆康威定律

  • 康威定律:产品必然是其组织沟通结构的缩影。系统设计本质上反映了企业的组织架构,系统各模块之间的接口,反映了企业各部门之间信息流动和合作的方式。
  • 逆康威定律:当前组织效率不够高时,可优先进行系统设计,通过系统设计来演进后续的组织架构。去哪儿网在2021年实行对内DDD对外API的战略,对团队和职责进行合理划分,这是逆康威定律的实例之一。

5)面向测试、运维

测试是保证系统质量的重要途径,使用TDD验证架构是否合理、是否可以隔离、测试性好不好。

6)软件质量属性

在运行期和开发期,软件质量属性体现为可用性、可修改性、性能、安全性、易用性等。以功能性为主进行架构设计,以质量属性为依据进行增量式迭代重构和优化。

图片图片

上图是架构的一些关键技术,这张图的粒度较粗。从下往上看,公司底层基本都由容器与自动化支撑,上层是监控和治理、前后端分离的系统。基于此图,DDD是整个架构的指导思想。

二、业务系统重构背景

1.业务介绍:酒店基础信息

图片图片

上列四张图简单展示了去哪儿网本次重构的主题,也是酒店基础信息部所负责的业务。

  • 列表页:用户打开APP,填写地址与入住时间,点击搜索,就会跳转到列表页,可以看到搜索地点的所有酒店列表信息。
  • 详情页:点击希望入住的酒店,即可进入详情页。
  • 酒店Info:点击酒店名字,则可进入酒店Info页。
  • 进订页:用户决定预定酒店房间后,就跳转到进订页。

2.基础信息业务架构

图片图片

上图是酒店基础信息业务对应的架构。去哪儿网售卖的酒店,来源于各个代理商和集团分销的信息,按照图示自下到上,经过基础层,然后到达基础信息部门的主要业务层。

业务层最重要的内容是酒店聚合,包括代理商酒店Tree和Q物理酒店。我们将各个代理商提供的酒店信息,按照一定业务逻辑规则,聚合到去哪儿网的Q侧物理酒店,将此部分信息对外售卖,从而提升用户体验。

为什么能够提升用户体验?

举个例子,比如现在投放的是季枫酒店,A代理商将其称为季枫酒店北京店,B代理商将其称为季枫酒店北京中关村店,C代理商称之为季枫酒店北京中关村苏州街店,用户容易混淆。所以去哪儿网将各个代理商提供的酒店信息,按照一定的业务逻辑、规则聚合为外网能够看到的、唯一的物理酒店。

酒店Tree的含义是,每个代理商投放的酒店,映射到去哪儿网的酒店,以去哪儿网的酒店为树根,下方挂靠不同代理商投放的酒店信息,形成对应关系。

当前我们团队的核心业务是,将基于业务层的酒店信息,提供给应用层,比如提供APP搜索或筛选下展示的信息。

3.落地技术中心战略,偿还技术债务

图片图片

旅游行业应该是受疫情影响最大的行业之一,在此背景下,技术中心在2022年提出“巩固效率之本,分担产品之忧”的战略,自此开启DDD重构之路。

如上图,重构前,业务和业务架构存在以下问题:

  • 核心业务分散:上图的灰色条块是我们的核心业务,被耦合于整条链路,分散在各个系统中,导致核心业务入侵。
  • 核心业务入侵:产品需求交付效率低下,一个产品需求可能需要调整5个微服务。
  • 数据写入链路长:没有对核心业务的收口能力,数据更新不及时。由于没有实时查询能力,别的团队调用数据时,需拉取并自行缓存。

4.系统重构模式选择

没有最好的架构,只有最合适的架构。以下是备选的系统重构模式:

图片图片

  • 修缮者:在现有系统的基础上,新增一个抽象层,保证对外提供能力不变,然后对系统内部进行改造。
  • 绞杀者:修缮没有办法适应现状的情况下,需要另起炉灶,在系统以外重新构建新功能,逐步剥离原有逻辑。对外提供新功能,逐步绞杀各个需要下线或重构的服务。重构时需要权衡绞杀者的优缺点。
  • 优点:不影响原有业务,一旦条件成熟,新系统可以完全替换旧系统。
  • 缺点:一段时间内需要维护两套系统,付出额外的开发维护成本。
  • 演进式:老项目的逻辑已经模糊,需要进行演进式迭代。识别老系统中的核心业务逻辑,从MVP版本开始小步快跑,先迭代核心业务,快速上线查看效果,然后将剩下的边缘业务逐渐切换过来,及时调整。
  • 优点:有效控制迭代风险,避免全部替换系统,造成难以估量的影响。

三、系统重构改造模式与架构选择

前文讲解了架构的演变路径、理念及改造模式的选择,最终衍生出来的系统重构框架是什么样子?

1.系统重构模式选择

图片图片

从业务出发、面向业务变化是我们现代架构设计成功的关键,DDD的设计思想完全符合成功架构设计的理念。

1)服务业务战略

站在EA(企业架构)角度(包括业务架构BA、应用架构AA、数据架构DA、技术架构TA),DDD可以绑定业务架构和应用架构,将问题域与应用架构相剥离;通过DDD将业务架构的“价值流+业务能力”进行解构化分解,能力下沉。同时依据DDD划分的限界上下文、聚合,进行应用架构的搭建,实现自下而上的“高内聚、低耦合”的应用。

2)演进式架构

DDD的核心思想有哪些:

  • 战略层面:业务问题分析→分解子问题域,识别核心域→分而治之,降低业务复杂度;
  • 战术层面:识别问题域的不同业务上下文→领域建模,定义聚合,组件化业务需求→指导微服务的拆分;
  • 实现层面:利用成熟的分层模式、依赖倒置屏蔽掉技术细节复杂度,通过DDD方法设计的微服务,不仅可以通过限界上下文和聚合实现微服务内外的解耦,同时也可以很容易地实现业务功能积木式模块的重组和更新,从而实现架构演进。

总之,自上而下地拆解业务,并以此为指导,自下而上地构建模型,最终达到高内聚低耦合的状态。

我们选择绞杀模式和演进模式,进行系统重构。由于系统复杂程度高、了解业务细节的同学少,为了减少重构对现有业务的影响,我们将核心资源投入到核心业务中,快速上线以便查看效果,下文将具体介绍演进实践。

四、以业务驱动的微服务架构演进实践

1. 领域驱动设计过程

图片图片

上图是以业务驱动的微服务架构演进的实战过程,介绍DDD的完整流程和关键路径。进行领域驱动设计时,需要对组内成员进行定位。最重要的是识别领域专家,即哪些人对该领域认知深刻,能够帮助成员深入理解业务,有利于后续脑暴和建模过程顺利进行;其次是识别技术专家和开发团队。

领域驱动设计的关键路径如下:

第一步,领域专家和开发团队就具体问题,明确业务愿景,讨论需求,从而建立统一语言,形成领域知识。统一语言,即对问题域内的概念统一认知,比如大家明确某个词语的定义且没有歧义,大大降低交流成本。

第二步,分析问题域并划分子域(比如核心子域、支撑子域、通用子域),进而划分限界上下文,构建上下文地图。

第三步,领域建模并实现模型。将以上两步分析,投射到代码层面进行模型实现,这一步骤可总结为“两关联一循环”。“两关联”是指,统一语言和模型相关联、模型与软件实践相关联。“一循环”是指,实践过程可能经历种种困难与不确定性,需要在不断循环的过程中提炼知识,最终得到趋向完美的模型。

2.基于DDD落地实践

图片图片

上图展示了基于DDD落地实践的过程。首先是定位愿景,其重要性在于决定了后续的发展道路;其次,分析问题域中现有业务场景;然后基于划分的子域,识别限界上下文;最后在限界内进行领域建模、实现模型。

1)问题域分析

① 定位愿景

图片图片

麦肯锡提出“电梯演讲”概念是指,在乘坐电梯的30秒之内,向顾客清晰准确地解释解决方案,即使用简短的语言精准说明业务价值。比如,去哪儿网的核心价值是“总有你要的低价”。因此,所有的核心工作都要围绕低价展开,落实到基础信息团队,我们的愿景就是提供多样的信息聚合。

② 明确领域专家

由于产品迭代频繁,系统演进缺少领域专家,所以按照产品、QA、技术人员的顺位确定领域专家。

图片图片

为剖析原有项目中,哪些用户用例与愿景强相关,我们整理用例图并安排同学逐一分析。由此发现,经过多年迭代,很多业务已经不再使用,旧业务无法适应现有商业模式。所以我们将此类业务下线,或转投资源,最终将188个用例减少为79个用例,极大简化工作内容。

图片图片

③ 事件风暴

事件风暴主要关注三件事:识别领域事件、识别决策命令、识别领域名词。

事件风暴的输出,将作为后续领域建模的输入,需要遵循以下原则:

  • 业务视角事件:从业务视角分析领域事件,比如,某个业务动作对内产生某种数据,触发业务流程状态变化,对外发送消息。技术人员进行头脑风暴时,很容易陷入代码细节,忘记最初目的。
  • 先发散,再收敛:先将所有想法罗列出来,在此基础上再进行收敛。
  • 决策命令:哪些人做了哪些动作导致了事件产生。
④ 统一语言

图片图片

没有DDD经验的同学可能会问,什么可以作为统一语言?

答案是,什么都可以作为团队的统一语言。比如,一个老系统的内部逻辑特别复杂难懂,这部分代码的重构代价大,难以改动,就可以作为团队内部的统一语言。由此,产品和技术同学都知道老系统的目标、能力及应对态度。

技术同学表达事情的思维,偏向代码逻辑,导致和产品同学沟通存在偏差。此时,统一语言可以拉齐双方认知,技术同学只要抛出几个领域名词,产品同学即可理解。

需要注意的是,统一语言的术语表应具备中英文对照,便于后期编解码时,在代码层面达成认知统一。

2)识别限界及子域划分

图片图片

识别限界上下文的总体原则是先业务后技术,上图展示了领域层面的划分流程。

  • 降低业务复杂度:业务层面,需要杜绝语言的二义性。比如,在去哪儿网内部,商务同学、技术同学可能对“酒店”一词的认知不同,限界上下文时就要避免这种情况。
  • 降低管理复杂度:基于领域层划分的业务边界,影响工作边界的划分。上文提到的康威定律以及著名的两个披萨原则(一个高效的技术研发团队,最佳的团队规模应该控制在2个披萨就可以吃饱的人数),都对工作边界具有指导意义。
  • 降低技术复杂度:限界上下文承接的流量不同,我们通过制作弹性边界、部署及可用性测试,使用不同方式对待不同的限界上下文。

图片图片

限界上下文的特征:

  • 最小完备原则:自治单位履行的职责是完整的,无需求助其他自治单位获取自己的信息。
  • 自我履约原则:自治单元自身决定职责。比如,进行代理商酒店的抓取落地时,无需进行后续解析。
  • 稳定空间原则:外部变化不会影响自治单元。
  • 独立进化原则:对外提供稳定接口,内部变化不影响外部。

绘制上下文依赖地图时,谨记三不原则:不要双向依赖、不要循环依赖、不要过长依赖。

如上右图所示,我们在制作上下文依赖地图时发现,酒店解析依赖酒店抓取,酒店抓取依赖酒店聚合信息,酒店聚合信息依赖静态信息,静态信息依赖酒店解析出来的数据,形成循环,所以划分方式不合适。基于这种情况,创造出“酒店上下文”环节,打破循环依赖。

图片图片

在限界上下文后,需要识别核心域、支撑域和通用域,划分参考是与业务愿景的相关性。识别子域的好处是,对外可以明确告知自身核心竞争力;对内明晰人员的资源分配、机器资源分配,评估产品需求的优先级、是否位于核心领域内。

3)领域建模

图片图片

依据事件风暴和限界上下文的输出,可以构建领域模型。

① 建模意义
  • 聚合来表达业务“高内聚,低耦合”
  • 降低业务复杂度,更好地适应业务变化
② 建模过程
  • 识别实体、值对象、丰富领域逻辑
  • 定义聚合、识别聚合根

建模过程中最难把握的是尺度,哪些方法应该加入模型,属性应该放在哪个模型。个人建议是共识即正确,无论是否正确,组内达成共识,这个决策在当下就是正确的。因为建模是循环提炼的过程,随着后续深化业务理解,推翻之前结论、一次性创建完美模型的难度较大。

分层架构中具有领域层和业务层,如果将功能和用例盲目加入领域层,领域层膨胀会影响复用性和业务表达力。所以,要承认模型和领域能力的不确定性,循序渐进地使用迭代方式,将能力下沉到领域模型中。

③ 建模原则
  • 重点关注核心域建模:投入核心资源
  • 聚合尽量小,适应业务变化
  • 聚合边界内强一致性
  • 抽象模型,防止过多属性拍平(DP):属性被拍平的弊端是,模型内字段太多,无法识别识别模型。

图片图片

上图展示了两个原则:共性的业务能力优先下沉到领域,共性的技术问题抽象成业务。

没有完美的模型,也没有正确的模型,领域模型共识即正确,所以团队的综合能力决定了模型完美程度的上限。提供一个可参考的检验技巧,建完模型后,可以用业务场景检验模型的完整度。随之循环往复,模型也会越发完善。

图片图片

④ 落地实践时划分微服务

如上所示,业务边界、康威定律、业务变更频率、弹性边界、技术选型等,都可作为划分依据。

需要注意的是,一个微服务可包含多个限界上下文,但只能包含一种子域类型(核心、通用、支撑),不能将一个核心域和一个支撑域放在同一微服务中。如果支撑域可用性不好,影响核心逻辑,就可能为这个不太重要的问题付出沉重代价。

4)模型实现

① 业务流程和领域模型映射

图片图片

如上所示,业务流程或业务用例被分为不同阶段,每个阶段又被分为不同活动,我们将这些业务活动与整个分层架构联系起来,构建映射关系。

构建映射关系的好处是,在分层架构和领域模型高度内聚、完善的情况下,方便后续需求接入和扩展。自上而下分解业务流程,分层映射,隔离技术负责度。

② 模型映射代码清单

图片图片

如上图,应用层、领域层、基础设施层是聚合领域对象,模型映射代码清单划分了每一层具备能力、领域层的领域对象、是否具有前置依赖对象,包名、类名及方法名,这些内容与上文提到的中英文对照表一一对应。

构建这份代码清单,便于达成组内多人合作,提高开发效率;为新人熟悉项目时提供参考,快速说明项目的核心逻辑与能力。

③ COLA应用架构

图片图片

在代码开发阶段,我们选择了开源框架COLA,其分层架构分为适配层、领域层、应用层、基础设施层。

无论是COLA还是DDD的分层架构,都以业务为核心,基于稳定的领域模型,对外提供领域能力。

选择COLA的原因如下:

  • 定义良好的分层结构、规范;
  • 层内部结构“聚合分包,功能分类”;
  • 提供最佳应用架构的最佳实践:《领域驱动设计》一书只提供思想指导,而COLA给出了可参考的模板。

下图是我们内部基于COLA架构落地微服务的实践。

图片图片

  • Adaptor:多端适配
  • Client:业务提供的接口,比如Dubbo
  • APP:业务用例Case的编排,含executor、publish、qschedule等
  • Domain(聚合、实体、值对象的定义):
  • 业务规则显示化,包括逻辑判断,和对象设值代表的含义,纯内存操作
  • Entity解决单个对象的逻辑变更,领域服务解决多对象的业务逻辑变更
  • 不允许跨聚合调用
  • 充血模型
  • Insfrastructure:Repisitory实现;ACL的定义和实现
  • Common:公共属性、工具类,由domain调用  

图片图片

上图是对前一张图的具体描述,我们使用的是CQRS模式,即命令查询职责分离。

前文的一张图描述了架构重构前的状况:核心业务渗透到各个服务中,没有做收拢聚合,业务耦合。而通过限界划分、领域建模,即可实行分离。

基础设施层实现了依赖倒置,即基础设施层依赖领域层,由此无需关心领域层使用ES还是Redis进行存储,可专注于领域能力。

重构前,没有提供实时查询的能力,各个团队将数据拉走,进行本地缓存。重构后,基于异步调用机制,实现数据持久化,对外提供查询。

④ 领域模型与代码模型映射

图片图片

上图是领域模型与代码模型的映射。分层对应上一张图展现的架构,在Domain层,按照领域划分进行聚合分包。上图标蓝的Hoteltree就是前文提到的酒店聚合,在这一领域内进行功能划分。

图片图片

Domain Primitive 是 Value Object 的进阶版,在原始 VO 的基础上要求每个DP拥有概念的整体,而不仅仅是值对象。在 VO 的 Immutable 基础上增加了 Validity 和行为。

DP特征:

  • 拥有完整的概念整体,精准定义
  • 使用业务域中的原生语言
  • 业务域的最小组成部分,可构建复杂合
  • 隐式转显式

例如,联系信息对外显示为电话号码,背后隐藏了区号、国内外来源等隐式属性。通过DP可以找出隐式属性,并将其转为显式,这是我们推荐DP的重要原因之一。

五、总结和思考

1.项目落地效果

1)组织效率

  • 组织资源是否集中在了核心业务领域;
  • 是否能用统一语言沟通描述业务,表现在需求评审、站会等有关会议的效率上;
  • 领域知识是否得到沉淀,是否有人能承担“领域专家”;
  • 团队间职责模糊地带少,相互扯皮的机会少。

2)开发效率

  • 模块粒度是否合适、模块间依赖是否健康;
  • 接口数量是否稳定,不膨胀;
  • 因为功能理解不足引起的bug数量是否低;
  • 模块和接口的自测性程度高不高;
  • 代码可读性,人员交接和新人上手是否足够快。

3)巩固效率之本,分担产品之忧

  • 清晰领域,核心子域重点投入;
  • 统一语言,减少产运研测沟通成本,增加2名研发业务专家;
  • 承接产品需求75%,助力0.5PM;
  • 21个应用微服务,通过DDD领域划分后下降到13个,微服务减少33%;
  • 开发工时3pd以下产品响应效率提升52.3%,3pd以上32.5%,QA工时下降62.3%;
  • 架构、聚合分层,功能分类,新人上手快。

2.思维模型改变

图片图片

技术人员从被动了解业务,到主动了解业务,解读业务策略变化,为其定义测量,提议数字化方案。产品经历的核心价值是成为技术与业务连接的桥梁,但通过DDD,技术同学也更关注业务,真正做到产研融合。

1)问题域分析领域建模

  • 分治思维
  • 模型思维
  • 抽象思维
  • 结构化思维

2)模型实现

  • 简单思维
  • 契约思维
  • 解耦思维

3.DDD带来的优劣势及建议

1)优势

  • 隐性知识显性化,统一团队语言
  • 围绕业务变化,隔离“变化”
  • 积木式组合业务演进
  • 关注点分离,隔离技术细节
  • 面向测试、运维
  • 业务思维(主动向前看业务,主动提想法,0.5PM)

2)劣势

  • 团队上手有门槛(概念-理解-困惑-深入理解)

3)使用建议

  • 业务场景复杂
  • 业务变化频繁
  • 重点核心业务领域
  • 可部分取用(分层思想、聚合、限界、架构设计、解耦思维等)
  • 团队共识即正确    

业务架构是领域,技术架构是容器,脱离灵魂的容器是没有技术意义的。

Q&A

Q1:DDD重构时,如何协调产品上线需求的矛盾?

A1:首先,我们进行DDD重构的时候,背靠公司技术中心的战略,公司是鼓励和倡导的;其次,重构模式包括修缮者、绞杀者、演进式。面临与产品上线需求的矛盾时,我们可以选择绞杀者,另起炉灶来优化重构,在原有业务中也不影响产品新需求接入。

Q2:选择COLA架构作为DDD重构业务模型的原因是什么?

A2:首先,COLA是阿里开源的,大厂背书,信任度较高;其次,COLA具备很好的分层架构和规范,项目Github中提供了最佳实践。如果初期不清楚如何进行重构,可以直接参考官方demo,将其映射到自己的业务中,后期再加入自身见解,进行系统优化。

作者介绍

李全党,2021 年加入去哪儿网,担任酒店供应链代理商和基础信息业务负责人、业务架构SIG成员,拥有 10 年以上系统研发和软件架构设计经验主导搭建多个 DDD 项目,有高并发、分布式服务、高可用的建设优化经验。

朱浩曼,2021年9月加入去哪儿网,担任标准代理商业务负责人、业务架构SIG成员。

相关文章

JavaScript2024新功能:Object.groupBy、正则表达式v标志
PHP trim 函数对多字节字符的使用和限制
新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
为React 19做准备:WordPress 6.6用户指南
如何删除WordPress中的所有评论

发布评论