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

一、架构设计理念与技术

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,将其映射到自己的业务中,后期再加入自身见解,进行系统优化。

以上就是本次分享的所有内容,最后为大家带来一个内推信息,欢迎优秀的你加入驼厂~

【内推链接】:app.mokahr.com/recommendat...

相关推荐
开心就好202518 分钟前
不同阶段的 iOS 应用混淆工具怎么组合使用,源码混淆、IPA混淆
后端·ios
架构师沉默26 分钟前
程序员如何避免猝死?
java·后端·架构
码路高手32 分钟前
Trae-Agent的Patch逻辑
人工智能·架构
椰奶燕麦43 分钟前
Windows PackageManager (winget) 核心故障排错与通用修复指南
后端
zjjsctcdl1 小时前
springBoot发布https服务及调用
spring boot·后端·https
zdl6862 小时前
Spring Boot文件上传
java·spring boot·后端
世界哪有真情2 小时前
哇!绝了!原来这么简单!我的 Java 项目代码终于被 “拯救” 了!
java·后端
RMB Player2 小时前
Spring Boot 集成飞书推送超详细教程:文本消息、签名校验、封装工具类一篇搞定
java·网络·spring boot·后端·spring·飞书
重庆小透明2 小时前
【搞定面试之mysql】第三篇 mysql的锁
java·后端·mysql·面试·职场和发展
喝拿铁写前端2 小时前
一套面向 Web、H5、小程序与 Flutter 的多端一致性技术方案
前端·架构