在40岁老架构师 尼恩的读者交流群(50+)中,最近有小伙伴拿到了一线互联网企业如阿里、滴滴、极兔、有赞、希音、百度、网易、美团的面试资格,遇到很多很重要的面试题:
谈谈你的DDD落地经验?
谈谈你对DDD的理解?
如何保证RPC代码不会腐烂,升级能力强?
微服务如何拆分?
微服务爆炸,如何解决?
你们的项目,DDD是怎么落地实操的?
所以,这里尼恩给大家做一下系统化、体系化的梳理,使得大家可以充分展示一下大家雄厚的 “技术肌肉”,让面试官爱到 “不能自已、口水直流”。
也一并把这个题目以及参考答案,收入咱们的 《尼恩Java面试宝典PDF》V123版本,供后面的小伙伴参考,提升大家的 3高 架构、设计、开发水平。
《尼恩 架构笔记》《尼恩高并发三部曲》《尼恩Java面试宝典》的PDF,请到文末公号【技术自由圈】获取
除了本文,尼恩输出了一个 《从0到1,带大家精通DDD》系列,帮助大家彻底掌握DDD,链接地址是:
《阿里DDD大佬:从0到1,带大家精通DDD》
《阿里大佬:DDD 落地两大步骤,以及Repository核心模式》
《阿里大佬:DDD 领域层,该如何设计?》
《极兔面试:微服务爆炸,如何解决?Uber 是怎么解决2200个微服务爆炸的?》
《阿里大佬:DDD中Interface层、Application层的设计规范》
《字节面试:请说一下DDD的流程,用电商系统为场景》
大家可以先看前面的文章,再来看本篇,效果更佳。
另外,尼恩会结合一个工业级的DDD实操项目,在第34章视频《DDD的顶奢面经》中,给大家彻底介绍一下DDD的实操、COLA 框架、DDD的面试题。
作者介绍
李全党,2021 年加入去哪儿网,担任酒店供应链代理商和基础信息业务负责人、业务架构SIG成员,拥有 10 年以上系统研发和软件架构设计经验主导搭建多个 DDD 项目,有高并发、分布式服务、高可用的建设优化经验。
朱浩曼,2021年9月加入去哪儿网,担任标准代理商业务负责人、业务架构SIG成员。
这种业务架构的演变路径,从侧面反映了互联网企业的演变路径。每种架构的好坏并非绝对,选择与否,仅取决于是否适应当前和可预见的未来。
本次分享主要介绍服务化到平台化的过程,即从服务细粒度到领域能力积累的演进过程。
以业务为中心、适应业务变化是架构设计成功的关键。指导业务架构设计的维度包括:
1)商业模式及成熟度
传统行业的业务相对稳定和成熟,非必要情况下建议构建单一服务。如需拆分,建议将变化频繁和不频繁的业务拆分。
互联网行业分为初创公司和成熟稳定的公司:
初创、商业不稳定的公司:需要多种业务快速试错,可采用微服务。通过微小的单体服务器,快速构建探索场景,以技术的确定性应对未来发展的不确定性。例如,去哪儿网的某些团队在制定简单方案后,可使用微服务获取市场反馈,快速验证效果。
商业稳定或固化的公司:不再需要技术端的灵活性,也不愿承担灵活性带来的架构维护成本,此时可以考虑合并微服务,以降低运营成本。
目前旅游行业已相对稳定,去哪儿网符合上述第二种情况,可以考虑将之前拆分过细的微服务进行合并。这也是去哪儿网架构演进的原因之一,原有业务拆分过细,达到人均10个应用,维护成本极高。
2)面向业务的变化
为快速适应业务变化,需识别业务核心问题,明确业务边界,实现业务组件的最大化复用;区分变化与不变的业务,将变化限制在一定范围内,从而降低影响。
面向业务变化与不变的情况下,组件颗粒度要拆分到什么程度?
组件拆分粒度过细时,可复用性强,但组装复杂;拆分粒度过大时,使用方便,但应用场景有限。
3)技术延迟决策
《架构整洁之道》一书提到:“良好的架构设计应关注用例,并将它们与其他周边因素隔离。”
在前期,应关注用例,后期再决策具体技术。
4)康威和逆康威定律
5)面向测试、运维
测试是确保系统质量的关键环节,采用测试驱动开发(TDD)来验证架构的合理性、可隔离性和易测试性。
6)软件质量属性
在开发和运行阶段,软件质量特性表现为可用性、可维护性、性能、安全性、易用性等。
以功能性为核心进行架构设计,依据质量特性进行增量式的迭代重构和优化。
上图是架构的一些关键技术,这张图的粒度较粗。
从下往上观察,公司底层主要由容器和自动化技术支持,上层则是监控和治理、前后端分离的系统。
根据这张图,领域驱动设计(DDD)成为整个架构的指导原则。
上列四张图简单展示了去哪儿网本次重构的主题,也是酒店基础信息部所负责的业务。
注意:请点击图像以查看清晰的视图!
上图展示了酒店基础信息业务对应的架构。
去哪儿网售卖的酒店,来源于各个代理商和集团分销的信息,按照图示自下到上,经过基础层,然后到达基础信息部门的主要业务层。
业务层最重要的内容是酒店聚合,包括代理商酒店Tree和Q物理酒店。
我们将各个代理商提供的酒店信息,按照一定业务逻辑规则,聚合到去哪儿网的Q侧物理酒店,并将这部分信息对外销售,以优化用户体验。
为什么能够提升用户体验?
举个例子,比如现在投放的是季枫酒店,A代理商将其称为季枫酒店北京店,B代理商将其称为季枫酒店北京中关村店,C代理商称之为季枫酒店北京中关村苏州街店,用户容易混淆。
因此,去哪儿网将各代理商提供的酒店信息,按照一定的业务逻辑、规则整合为外部可见的唯一物理酒店。
酒店Tree的含义是,每个代理商投放的酒店,对应到去哪儿网的酒店,以去哪儿网的酒店为根,下方挂接不同代理商投放的酒店信息,形成对应关系。
目前,我们团队的核心业务是将基于业务层的酒店信息,提供给应用层,如 APP 搜索或筛选时展示的信息。
旅游业可能是受疫情影响最大的行业之一。在此背景下,技术中心在 2022 年提出了“巩固效率之本,分担产品之忧”的战略,从而开启了领域驱动设计(DDD)的重构之旅。
如上图,重构前,业务和业务架构存在以下问题:
为了解决这些问题,技术中心决定采用领域驱动设计(DDD)进行系统重构。通过这一方法,我们希望能够提高产品需求交付效率,缩短数据写入链路,以及降低核心业务受到的侵入程度。此外,DDD 还能够帮助我们实现核心业务的模块化,使得各个系统之间的耦合度降低,从而提高整个系统的灵活性和可扩展性。
没有最好的架构,只有最合适的架构。
以下是备选的系统重构模式:
修缮者:在现有系统基础上,新增一层抽象层,确保对外服务能力不变,然后对内部进行优化改造。
绞杀者:修缮没有办法适应现状的情况下,需要另起炉灶,在外部构建新功能,逐步剥离原有逻辑。为新功能提供服务,逐步淘汰或重构各个需下线的服务。在重构过程中,要权衡颠覆者的优缺点。
优点:不影响原有业务,一旦条件成熟,新系统可完全替代旧系统。
缺点:在一段时间内需要维护两套系统,付出额外的开发维护成本。
演进式:对于老项目逻辑模糊的情况,采用演进式迭代。识别老系统中的核心业务逻辑,从最小可行性产品(MVP)版本开始,快速迭代核心业务,率先上线观察效果,然后逐步将剩余的边缘业务切换过来,并及时调整。
优点:控制迭代风险,避免全面替换系统,降低不可预测的影响。
前文讲解了架构的演变路径、理念及改造模式的选择,最终衍生出来的系统重构框架是什么样子?
以业务为导向,适应业务变化是现代架构设计成功的核心要素,而领域驱动设计(DDD)的理念恰恰符合这一成功架构设计的原则。
站在EA(企业架构)角度(包括业务架构 BA、应用架构 AA、数据架构 DA、技术架构 TA)的角度出发,DDD 能够将业务架构与应用架构相结合,将问题领域与应用架构分离。通过分解业务架构中的“价值流 + 业务能力”,实现能力的下移。同时,根据 DDD 划分的限界上下文和聚合,构建应用架构,实现自下而上的“高内聚、低耦合”。
DDD 的核心思想包括:
总之,自上而下地拆解业务,并以此为指导,自下而上地构建模型,最终达到高内聚低耦合的状态。
在系统重构过程中,我们选择了绞杀模式和演进模式。鉴于系统复杂度高,了解业务细节的人员较少,为了降低重构对现有业务的影响,我们将核心资源投入到核心业务中,快速上线以观察效果。以下将详细介绍演进实践。
上图是以业务驱动的微服务架构演进的实战过程,介绍DDD的完整流程和关键路径。
进行领域驱动设计时,需要对组内成员进行定位。
最重要的是识别领域专家,即那些对特定领域有深入认知的人,他们能帮助团队成员更好地理解业务,有利于后续的头脑风暴和建模过程;其次是技术专家和开发团队。
领域驱动设计的关键路径如下:
第一步,领域专家与开发团队针对具体问题,明确业务愿景,探讨需求,从而确立统一的语言,积累领域知识。统一语言意味着对问题领域内的概念达成共识,例如,团队成员对某个词语的定义有明确的认识,没有歧义,从而降低沟通成本。
第二步,分析问题域并划分子域(比如核心子域、支撑子域、通用子域),进而划分限界上下文,构建上下文地图。
第三步,领域建模并实现模型。将以上两步分析,映射到代码层面,进行模型实现。这一步骤可以概括为“两关联一循环”。“两关联”指的是统一语言与模型之间的关联,以及模型与软件实践之间的关联。“一循环”意味着在实践过程中,可能会遇到各种困难和不确定性,需要在不断迭代的过程中提炼知识,最终趋近于完美的模型。
总之,领域驱动设计是一个动态迭代的过程,通过明确团队成员的角色,发掘领域专家,建立统一语言,分析问题领域,进行建模和实践,不断优化和完善模型。在这个过程中,团队成员需要密切合作,充分发挥各自专长,共同推动领域驱动设计取得成功。
上图展示了基于DDD落地实践的过程。
首先是定位愿景,其重要性在于决定了后续的发展道路;
其次,分析问题域中的现有业务场景;
然后,根据划分的子域,识别限界上下文;
最后,在限界内进行领域建模和实现模型。
① 定位愿景
麦肯锡提出“电梯演讲”概念是指,在乘坐电梯的30秒之内,向顾客清晰准确地解释解决方案,即使用简短的语言精准说明业务价值。
比如,去哪儿网的核心价值是“总有你要的低价”。因此,所有核心工作都应围绕低价展开。落实到基础信息团队,我们的愿景就是提供丰富的信息聚合。
② 明确领域专家
由于产品迭代频繁,系统演进缺乏领域专家。因此,按照产品、QA、技术人员的顺序来确立领域专家。
为了分析原有项目中,哪些用户用例与我们的愿景密切相关,我们整理了用例图,并安排同学逐一分析。
分析结果显示,经过多年的迭代,许多业务已经不再使用,旧业务无法适应现有的商业模式。
因此,我们将这些业务下线或重新分配资源,最终将 188 个用例精简为 79 个,大大简化了工作内容。
③ 事件风暴
事件风暴主要关注三个方面:识别领域事件、识别决策命令、识别领域名词。
事件风暴的输出将作为后续领域建模的输入,需要遵循以下原则:
④ 统一语言
没有 DDD 经验的同学可能会问,什么可以作为统一语言?
答案是,什么都可以作为团队的统一语言。
例如,如果一个老系统的内部逻辑复杂且难以理解,重构成本高,不易修改,可以将这部分代码作为团队内部的统一语言。这样一来,产品和技术的同学都了解老系统的目标、能力和应对策略。
由于技术同学在描述事物时偏向于使用代码逻辑,这可能导致与产品同学的沟通出现偏差。
在这种情况下,统一语言可以拉近双方的认知差距。技术同学只需抛出几个领域名词,产品同学就能理解。
需要注意的一点是,统一语言的术语表应包含中英文对照,以便在后期编码和解码过程中,在代码层面实现认知统一。
识别限界上下文的总体原则是先业务后技术,上图展示了领域层面的划分流程。
降低技术复杂度:根据限界上下文承受的流量不同,我们通过创建弹性边界、部署和可用性测试,采用不同方法对待不同的限界上下文。
降低管理复杂度:基于领域层划分的业务边界会影响工作边界的划分。上述提到的康威定律和两个披萨原则(一个高效的技术研发团队,最佳团队规模应控制在能吃两个披萨的人数)都对工作边界具有指导意义。
降低业务复杂度:在业务层面,要消除语言的歧义。例如,在去哪儿网内部,商务和技术团队可能对“酒店”这个词汇的理解有所不同,因此在确定限界上下文时要避免这种情况。
限界上下文的特征:
在绘制上下文依赖地图时,要遵循三不原则:避免双向依赖、循环依赖和过长依赖。
如上右图所示,我们在制作上下文依赖地图时,我们发现酒店解析依赖酒店抓取,酒店抓取依赖酒店聚合信息,酒店聚合信息依赖静态信息,静态信息依赖酒店解析出的数据,形成循环依赖。因此,划分方式不合适。针对这种情况,我们创建了“酒店上下文”环节,打破循环依赖。
在限界上下文后,需要识别核心域、支撑域和通用域,划分参考是与业务愿景的相关性。
识别子域的好处是,对外可以明确告知自身核心竞争力;对内可以明确人员、设备资源分配,评估产品需求优先级以及是否处于核心领域。
根据事件风暴和限界上下文的输出,我们可以构建领域模型。
① 建模意义
② 建模过程
在建模过程中,最棘手的问题在于如何把握尺度,也就是判断哪些方法应被纳入模型,哪些属性应置于哪个模型。我个人认为,只要在团队内达成共识,无论实际结果如何,都可以视为正确。因为建模是一个不断优化的过程,随着对业务理解的深入,要推翻之前的结论并一次性构建完美模型较为困难。
在分层架构中,领域层和业务层都应存在。若盲目将功能和用例纳入领域层,可能导致领域层过于庞大,进而影响其可重用性和业务表达力。因此,我们需要正视模型和领域能力的不确定性,逐步采取迭代方式,将能力下沉至领域模型中。
③ 建模原则
上图展示了两个原则:共性业务能力优先下沉到领域,共性技术问题抽象成业务。
没有完美的模型,也没有正确的模型,领域模型的共识即为正确。因此,团队的整体能力决定了模型完美程度的上限。提供一个参考的检验技巧:在建立完模型后,可以使用业务场景检验模型的完整度。如此循环往复,模型将逐渐完善。
④ 落地实践时划分微服务
如上所示,业务边界、康威定律、业务变更频率、弹性边界、技术选型等,都可作为划分依据。
需要指出的是,一个微服务可以包含多个限界上下文,但只能包含一种子域类型(核心、通用、支撑),不能将核心域和支持域放在同一微服务中。如果支持域的可用性不佳,可能会影响核心逻辑,因此可能为这个问题付出沉重代价。
① 业务流程和领域模型映射
如上所示,业务流程或业务用例包含多个阶段,每个阶段又包含一系列活动。我们将这些业务活动与整个分层架构相结合,构建相应的映射关系。
建立映射关系的好处在于,在分层架构和领域模型高度凝聚、完善的情况下,便于后续需求的接入和扩展。从上到下分解业务流程,进行分层映射,技术负责度得以隔离。
② 模型映射代码清单
如上图所示,应用层、领域层、基础设施层是领域对象的聚合,模型映射代码清单明确了每个层次的能力、领域层的领域对象、是否存在前置依赖对象,以及包名、类名和方法名。这些内容与前面提到的中英文对照表相对应。
构建这份代码清单有助于团队内部协作,提高开发效率;为新成员提供参考,快速了解项目核心逻辑和能力。
③ COLA应用架构
在开发阶段,我们采用了 COLA 开源框架,其分层架构包括适配层、领域层、应用层和基础设施层。
无论是 COLA 还是 DDD 的分层架构,都围绕业务核心,基于稳定的领域模型,对外提供领域能力。
选择COLA的原因如下:
下图是我们内部基于COLA架构落地微服务的实践。
Adaptor:多端适配
Client:业务提供的接口,比如Dubbo
APP:业务用例Case的编排,含executor、publish、qschedule等
Domain(聚合、实体、值对象的定义):
Insfrastructure:Repisitory实现;ACL的定义和实现
Common:提供公共属性、工具类,由domain调用
通过运用 COLA 框架,我们希望实现业务核心的模块化,提高系统的可扩展性和灵活性。在实际开发过程中,团队成员需遵循分层设计原则,紧密协作,不断积累和共享领域知识,共同推动项目的成功。
上图是对前一张图的具体描述,我们采用了 CQRS 模式,即命令与查询职责分离。
前文的一张图描述了架构重构前的状况:核心业务分散在各个服务中,没有得到收敛和整合,业务耦合度较高。通过限界划分和领域建模,我们实现了业务的分离。
在改造前,系统缺乏实时查询功能,各团队将数据拉回并本地缓存。经过重构,我们采用了异步调用机制,实现了数据持久化,并对外提供查询功能。
在重构前,系统没有提供实时查询能力,各团队将数据拉走并进行本地缓存。重构后,基于异步调用机制,实现了数据持久化,并对外提供查询功能。
④ 领域模型与代码模型映射
上图是领域模型与代码模型的映射。分层对应上一张图展示的架构,在 Domain 层,根据领域划分进行聚合分包。图中标蓝的 Hoteltree 就是前文提到的酒店聚合,在该领域内进行功能划分。
Domain Primitive 是 Value Object 的进阶版,它在原始 VO 的基础上要求每个 DP 拥有概念的整体,而不仅仅是值对象。在 VO 的不可变基础上增加了有效性(Validity)和行为。
DP 特征如下:
例如,联系信息对外显示为电话号码,但背后隐藏了区号、国内外来源等隐式属性。通过 DP,我们可以找出这些隐式属性并将其转化为显式,这是推荐使用 DP 的关键原因之一。
通过以上映射,我们希望实现领域模型的清晰划分,降低业务耦合度,提高系统的可扩展性和灵活性。在实际开发过程中,团队成员应紧密协作,不断积累和共享领域知识,共同推动项目的成功。
在架构重构之前,系统核心业务分散在各个服务中,耦合度较高。通过限界划分和领域建模,我们实现了业务的分离。重构后,系统具备了实时查询功能,数据持久化,并对外提供查询服务。在这个过程中,团队遵循分层设计原则,紧密协作,不断积累和共享领域知识,共同推动项目的成功。
组织资源是否集中在了核心业务领域;
是否能用统一语言沟通描述业务,体现在需求评审、站会等有关会议的效率上;
领域知识是否得到沉淀,是否有人能承担“领域专家”;
团队间职责模糊地带少,相互扯皮的机会少。
技术团队从被动了解业务转变为主动了解业务,解读业务策略变化,为其定义测量,提出数字化方案。产品经理的核心价值是成为技术与业务之间的桥梁,但通过 DDD,技术同学也更加关注业务,实现产研融合。
业务架构是领域,技术架构是容器,脱离灵魂的容器没有技术意义。
A1:首先,我们在进行 DDD 重构时,要依托公司技术中心的战略,公司对此表示鼓励和倡导;其次,重构模式包括修缮者、绞杀者、演进式。面临与产品上线需求的矛盾时,我们可以选择绞杀者,重新构建优化,在原有业务中也不会影响产品新需求的接入。
A2:首先,COLA 是阿里开源的,具有大厂背书,可信度较高;其次,COLA 具备优秀的分层架构和规范,项目 Github 中提供了最佳实践。如果初期不确定如何进行重构,可以直接参考官方 demo,将其映射到自己的业务中,后期再加入自身见解,进行系统优化。
DDD架构如何落地,是是非常常见的面试题。
以上的内容,如果大家能对答如流,如数家珍,基本上 面试官会被你 震惊到、吸引到。
在面试之前,建议大家系统化的刷一波 5000页《尼恩Java面试宝典PDF》,并且在刷题过程中,如果有啥问题,大家可以来 找 40岁老架构师尼恩交流。
最终,让面试官爱到 “不能自已、口水直流”。offer, 也就来了。
当然,关于DDD,尼恩即将给大家发布一波视频 《第34章:DDD的顶奢面经》, 帮助大家彻底穿透DDD。
……完整版尼恩技术圣经PDF集群,请找尼恩领取
《尼恩 架构笔记》《尼恩高并发三部曲》《尼恩Java面试宝典》PDF,请到下面公号【技术自由圈】取↓↓↓