记录些 DDD 实践规范(6)

去哪儿结合DDD, 实现架构大调优

1、案例简述

2020 年疫情对旅游行业影响特别大,公司层面进行了组织架构的调整,酒店业务也经历了深刻的变革。在新的业务团队着手推进实际需求时,他们遇到了产研效率的新问题,主要表现为产品研发需要与多个技术团队协作,导致整体技术架构与业务发展失去同步。

鉴于之前领域驱动设计(DDD)的成功实践,酒店技术部门在2021年中旬主动发起了一次以DDD为指导思想的技术架构战略调整,包括组织架构和系统架构的优化,并设定了一系列核心技术方案的规范。本次将重点阐述这次战略调整的确认和实施过程。通过对这次战略调整的剖析,我们可以看到康威定律和领域驱动设计思想在其中所起的关键作用。

2、案例背景及问题分析

背景

2020 年初,全球爆发了新冠疫情,对旅游行业影响比较大。2020年5月,公司进行了重大调整,首先组建了机票目的地事业群,将机票和酒店两大事业部从独立运营调整为合并为一个事业群。此外,公司还对服务、用户体验等团队进行了统一管理,使之对事业群负责。同时,公司制定了机票与酒店业务交叉的战略目标,旨在提高用户购买机票后的酒店预订体验。

在面对这些组织架构和业务架构调整的同时,我们开始思考如何调整系统架构以支撑这些变化。带着这个疑问,我们来探讨实际过程中遇到的一些问题。

问题及表现

产品吐槽

  • 几乎每个需求都需要与多个技术团队沟通,合作过程繁琐

  • 很多需求的技术方案经常需要升级,才能达成共识

引申问题

  • 一些看似简单的需求,实际上涉及到的链路较长,总有团队在无需修改逻辑的情况下也要进行透传

  • 在讨论技术方案时,不同团队对于某个逻辑由谁来实现存在分歧,本质上是职责边界不够清晰

问题分析一

先来看一下主流程核心的交互:

图片

(注:图中 List 代表列表页、Detail 代表详情页、Booking 代表预订页,Order 代表订单页)

通过上图的交互流程,我们可以总结出以下几个问题:

  • 所有核心流程都涉及的团队是报价团队,报价团队是后端团队,比较底层

  • 用户端跨页面交互的统一处理需要在底层的报价团队完成

  • 底层修改,会带动整个链路跟着修改(典型的比如:基础数据新增字段)

  • 报价团队的核心职责是根据用户需求计算酒店房型、价格及优惠细节,而基础数据和相关逻辑不需要过多关注

问题分析二

继续看一下主流程核心团队及核心设计:

图片

通过上图,我们可以总结出以下几点:

  • 很多团队在自己的领域层上面都有应用层

  • 这些应用层导致了一些任务上下游团队都能完成

  • 而在众多团队都能承担任务的情况下,可能会出现无人愿意承担的情况

  • “万金油”式的架构在业务上升期较为适用,不适合稳定期

3、案例分析与规划

基于 Explicit Architecture(清晰架构)分析

图片

上图来自国外顶级架构师 herberto graca 的博客(https://herbertograca.com/),融合了  DDD、洋葱架构、整洁架构、CQRS 的”清晰架构“,这个架构有很多优势,比如:

  • 从外向内,越向内越偏核心原则,核心原则相对稳定。核心原则就是常规的领域层,提供核心能力

  • 外层基于核心原则适应不同业务场景,整合内层能力。这一外层主要包括接口层和应用层,主要使用主动适配器模式,重点关注 BFF 及对内层能力的聚合

  • 内层不依赖外层,不受业务变化而变化,关注能力的扩展,完成核心策略实现

  • 边界明显,尤其是领域层与应用层之间

  • CQRS 机制,耦合度低,通过外层整合内层能力,动态适应业务变化,提高扩展性

...

技术架构构想

结合清晰架构的特点来思考技术架构整体的调整策略

  • 核心思路:整体架构按照DDD分层架构进行,严格区分应用层和领域层

  • 核心动作:将包含核心领域的团队各自的“应用层”交出,统一交给下游网关团队,形成统一的应用层,这些团队的工作将转变为一个个核心领域

图片

技术架构调整规划

围绕上面的构想,整体架构图期望如下图所示:

图片

通过上面这个图可以明确几个关键点:

明确战略调整核心
  • 突出核心领域,壮大“大前台”(内部称为主站,此处为方便理解调整为“大前台”)

明确和之前的区别
  • 负责核心领域的团队聚焦可复用能力,专注核心策略和玩法改进

  • 负责应用层的团队关注业务识别与扩展,专注业务模型设计和整合下游能力

  • 业务收敛到一个团队,这个团队了解下游可提供的能力,能从全局角度看待业务开展

优势劣势分析

围绕着上面的调整,分析一下优劣和劣势

优势(偏向中台方向)
  • 减少影响团队:很多需求,产品只需要对接前台一个团队即可,即上文提到的“大前台”团队

  • 减少数据链路:主流程链路变短,设计技术方案、出问题定位及解决都更容易,典型的就是基础数据链路不再经过报价团队给到下游

这两点,在实际中都验证了确实可以起到产研提效作用。

劣势及可能带来的问题
  • 短期内,已有的工作习惯发生变化:数据链路发生变化,需要多个团队配合落地,然后适应新的链路,同时要适应新的架构

  • 可能带来上游团队人员不稳定:这将导致一些杂活完全交给“大前台”,如果一些人长期从事杂活或小需求,可能会出现人员不稳定的问题。此时需要考虑为这些人安排核心任务,改善业务建模等

4、案例落地过程

系统架构调整

  • 应用归属团队调整

  • 已有逻辑归属调整

这里主要完成的是,将做“应用层”的应用及逻辑上交给大前台团队,先保证各自团队负责该负责的事情,让负责核心领域的团队实际主要负责领域的事情,其他交给大前台

组织架构调整

  • 组织架构按需调整:比如多个网关合并成大前台,同时借助一些需求完成一些人员交叉

  • 核心人员归属团队规划:这里主要是 review 一遍核心成员,看看适合领域团队还是大前台,必要时进行相关调整

  • HC 调整,招聘计划跟进:本次会按需为大前台团队增加 hc,同时加紧招聘节奏保证人员尽早就位,这样也可以避免个别成员不稳定对整体的影响程度

  • 成立业务架构组:监督及保证架构调整,同时避免之后的腐化

技术方案标准制定

数据回传标准化

  • SPU 维度:供应链 -> 基础数据(商家、酒店等信息) -> 大前台 -> 端

图片

  • SKU 维度:供应链 -> 报价 (酒店房型及价格优惠,类比电商里的商品) -> 大前台 -> 端

图片

依据 SPU、SKU 数据回传标准进行链路优化:对比之前的链路,我们需要对基础数据进行一次全面的改造。首先,我们基于 DDD 思想对基础数据进行领域划分,然后根据领域提供相应的对外 API 服务,直接对接大前台。大前台在接收报价团队的应用层应用(内部名为 mprice)时,将原有服务合并至现有应用层应用,并对接基础数据服务 API。这样,链路从之前的 7 个应用缩短至 4 个应用,同时将一些串行操作并行化,不但链路缩短了,同时主流程响应时间降低了 25%,效果特别好。

请求处理标准化

方案制定

图片

上图是我们主流程计算用户支付价的几个核心过程。重点解释一下【商促】:基于用户画像打包,满足特定要求(比如特定的用户身份)的营销活动,拿到更低的底价,获得更高的利润。

接下来讨论一个实际问题:临近节日,业务侧希望解除某些商促限制,让所有用户均可享受。此时,技术方案该如何设计?我们给出两种常规方案:

  • 前台修改用户身份,其他团队不感知。该方案存在的问题:用户身份本质上未发生变化,此时查看用户身份会导致其他链路受到影响

  • 报价团队识别场景,做特殊商促匹配。该方案存在的问题:报价团队原本提供正常能力,现在需要提供定制化功能,与原有定位相差较大

这两种常规方案均存在明显问题。那么,问题究竟出在哪里呢?根本原因是标准方案中缺乏对【场景】的应对。

我们结合下图聊一下业务场景中的身份和场景。

图片

内部做了多次讨论后,最终给出结论:

  • 身份:任意时刻下描述用户(userid)不因此刻本身行为及外部条件影响的自身描述性质或特征的有限个数的标识

  • 场景:谁(who)在何时(when)何地(where)发生了什么事件(what)产生了何种结果 or 情景(how)的标识

举个例子,酒店新客户和机酒用户都是在一段时间内具有一定消费行为(如购买酒店服务、机票等)的用户。通过userid,我们可以直接明确地识别出他们,这属于身份范畴。而“异地面纱”则是一个场景例子,因为对于一个确定的userid,如果他的常住地为北京,而这次要去其他地方,那么“异地”这个条件才能得以满足。需要注意的是,身份特征的数量是有限的,比如门店新客户,对于一个用户来说,他是否是某个门店的新客户通常是明确的。但是,如果我们有近百万家门店,就不适合将其作为身份标识,因为在查询userid时,列出每个门店的信息代价过大,这种更适合拿着 userid + 门店去查询。

在我们的业务场景中,通常都是基于正常定义的身份进行标准计算。如果无法使用身份进行匹配,就需要借助场景来解决问题。

基于对【身份】和【场景】的理解,针对前面的问题,我们给出更合理的方案:报价提供“强制使用指定商促能力”前台识别场景,组装使用新能力。这样,报价仍然关注能力提升,前台主要负责业务适应和组装能力,核心是正确使用身份和场景。

图片

补充说明一下,按照这样架构调整及技术方案执行后,从整体来看整个架构:

  • 领域层能实际暴露出能力,应用层最终暴露出去功能

  • 由于应用层可以根据需求组装各种能力,因此具备很高的扩展性;而对于领域层来说,它专注于能力的复用,因此更具稳定性

  • 应用层通过标准的身份和场景来组装领域层的能力。要么基于标准身份使用常规能力,要么根据场景强制使用特殊能力

  • 功能是为了解决实际场景需求而存在的,变化频繁,因此需要提供众多接口来实现定制功能。而能力则需要尽可能忽略场景,这样才能实现最大程度的复用。为了解决这两者之间的矛盾,我们需要合理地使用身份和场景。

参数透传

在日常开发过程中,我们经常会遇到需要将一个参数透传给上游应用的情况。这种情况下,链路上参与透传的团队和应用往往不需要进行任何业务逻辑处理,却增加了开发工作量,并影响了定义的API。为了解决这个问题,公司内部的业务研发和基础研发部门共同讨论并确认开发了一个新的通用组件:QShareData。各个应用可以根据请求将数据写入QShareData组件,相应的数据id会随trace一起传递。链路上的其他应用可以根据需求通过数据id获取其他应用写入组件的数据,从而减少无意义的透传。当然,这里涉及到一些权限、性能和合理使用等问题,需要结合实际进行相应的限制和要求。

图片

5、案例总结

图片

首先,我们来回顾一下著名的康威定律:设计系统的组织,其产生的设计等同于组织之内、 组织之间的沟通结构。康威定律成立说明组织架构与系统之间必须匹配,但未强调合理。

接下来,我们再看一下 DDD 中的康威定律,它的核心观点是系统架构主导组织架构。由于DDD立足于业务领域,回归业务本质,因此其与业务的契合度较高,更易于实现合理性。

有些人可能会疑惑:如何划分团队才能与业务紧密结合?其实,这个问题的本质在于让DDD中的制约因素成为需要改变的目标。为此,我们可以从以下两个方面着手:

  • 以DDD所提炼的业务领域模型为基准,审视并调整组织架构,合理划分团队

  • 依据业务领域结构构建网状组织架构,并在业务发生重大变化时保持组织架构的灵活性,从而使康威定律发挥反向作用

如此一来,我们便能更好地实现团队与业务的匹配,推动组织架构和系统设计的协调发展。在这个过程中,康威定律为我们提供了一种指导思想,帮助我们认识到组织架构与系统设计之间的相互影响关系。同时,通过领域驱动设计,我们能够更加明确业务领域模型,从而调整组织架构,使其与业务发展相适应。

总之,要想实现团队与业务的紧密结合,关键在于以DDD为指导,审视组织架构,合理划分团队,并在业务变化时保持组织架构的灵活性。这样,我们就能让康威定律发挥积极作用,推动组织与系统设计的和谐发展。

网易新闻App架构重构实践

当前,大多数移动开发团队将 MVP 作为业务层的核心架构模式,并在此基础上实现了客户端的组件化、插件化、容器化等。然而,作为业务层核心的 MVP 架构模式仍存在诸多问题。在领域驱动设计(DDD)思想的指导下,网易新闻 App 对其架构进行了全面重构,取得了良好的重构质量和项目收益。那么,移动端架构与网站架构有何不同?网易新闻客户端的架构演变过程是怎样的?为何选择 DDD 思想来指导重构?在 DDD 实践中应注意哪些方面?

移动端架构与网站架构的区别

传统的网站架构,通过超文本传输协议,使得浏览器能够便捷地将我们想要访问的页面展示出来。每个网站由多个页面组成,这些页面都属于 Web Server 的一部分,如图 1 所示。

图片

图 1 Website 与 Server 抽象模型

网站在大多数情况下无需关注离线化,而主要关注负载均衡、高并发和多级缓存等场景,以实时响应大规模流量。

相较之下,移动端 App 需先让用户进行安装,然后才能访问页面。移动端的高并发网络请求等场景通常交由 Server 端处理,而移动端更关注处理用户交互和界面变化,如图 2 所示。

图片

图 2 Application 与 Server 抽象模型

所以移动端和网站端的核心关注点不同,也就造成了二者在架构上的历史演进的不同。举一个简单的例子,最初起源于 .NET Framework 3.0 的模型 / 视图 / 视图模型(MVVM)思想,从 WPF 应用过渡到 http://ASP.NET,用于网页的开发,后来却在移动端成为最流行的架构模式之一。

随着网站端的历史演进,网页的工作量和跨平台的需求增长迅速,使得网站前端开发的重要性日趋明显,网站端已经抽象为了前端和后端,网站前端通过浏览器实现跨平台,与移动端共同组成了大前端。

目前常见的移动端架构设计模式主要包括关注面向接口编程的 MVP(Model-View-Presenter)、关注数据驱动与双向绑定的 MVVM(Model-View-ViewModel)、关注表现层分离的 MVC(Mode-View-Controller)和符合领域驱动设计思想(DDD)的 The Clean Architecture 等。

网易新闻客户端的架构演进历程

网易新闻客户端的架构演进主要经历了四个阶段:Static Method、MVP、MVPs 和 VIPER。

为摆脱沉重晦涩的架构模型束缚,网易新闻客户端团队将一些软件设计中的元素抽象为轻松的食品加工中的元素,用蛋糕模型来做示例。

第一阶段:Static Method

从最初的设计阶段开始,为了简单地达到代码的可复用性,新闻客户端采用了一种 Static Method 的设计方式,将业务逻辑按照业务模块转移到一些工具类中,使得开发人员可以用最小成本复用这些业务逻辑。

在 Static Method 的模式中,技术团队将 View 抽象为一块蛋糕,蛋糕上需添加奶油、柠檬、樱桃(Model)等食材。负责输送材料的加工厂(Static Method)派遣工人(而非厨师)将材料直接运送并摆放在蛋糕上,如图 3 所示。这样蛋糕便具备了所有应有的食材成分。然而,食材摆放随意,使得蛋糕显得混乱,如果再继续这样堆砌食材,它就不再像是一块蛋糕了。

图片

图 3 Static Method 蛋糕加工模型

因此,随着时间推移和业务迭代加速,这种失去封装、多态和继承的面向对象特性的工具类设计难以应对业务变化。因此,转向流行的 MVP 模式成为必然趋势。

第二阶段:MVP

传统的 MVP 模式中,一个 View 由一个 Presenter 管理,在这种模式下产生了代码复用的难题。

由于 Presenter 与 View 通过接口协议绑定,一个 Presenter 中的业务逻辑通常只能为一个 View 服务。因此,代码复用性较差,容易产生大量冗余代码。

Presenter 与 View 为一对一的方式,就像一块蛋糕(View),指派给一个厨师(Presenter)去制作,但是厨师一个人需要做的事情太多,他需要亲自加工食材(Model),再将这些材料一一装饰在蛋糕上面,如图 4 所示。如果这时候再告诉他我们的蛋糕还需要添加一些突然增加的装饰和点缀,他可能会面临崩溃。

图片

图 4 MVP 蛋糕加工模型

第三阶段:MVPs

为解决 MVP 代码复用问题,许多设计方法将 View 与 Presenter 改为多对一模式,即一个 Presenter 可为多个 View 服务,而一个 View 也可被多个 Presenter 控制。这意味着更多 Presenter 参与其中,会产生更多适应不同 View 的接口。

Presenter 与 View 为多对一的方式,就像一块蛋糕(View),指派给多个厨师(Presenters)在共同加工。而每个厨师可能会处理多块蛋糕,他们同时还要做好手上的装饰品(Model),再亲自将其放在每个蛋糕上。在这期间,厨师们直接接触每块蛋糕时,还加入了很多他们擅长的但是蛋糕不需要的手艺。多个厨师围着一块蛋糕转,蛋糕有了很多他原本不想拥有的东西,而这些厨师们也难以管理,他们直接操控蛋糕,没人能够合理控制他们,如图 5 所示。因此,新闻客户端逐步过渡到符合 DDD 的 VIPER 模式。

图片

图 5 MVPs 蛋糕加工模型

第四阶段:符合 DDD 的 VIPER

在符合领域驱动设计的 VIPER 架构设计模式下,一块蛋糕(View)只由一个主厨(Presenter)进行装饰摆放,但是蛋糕上所有的饰品食材,都由这位主厨指派给多个不同的厨师(Interactor)进行加工(Entity)。当这些厨师加工完毕后,再把材料送过来,通知主厨,由主厨亲自进行摆放。

那些负责加工的厨师没有机会再直接接触到蛋糕,蛋糕只有它原本应有的样子,变得更加利于加工制作。更美好的是,以往蛋糕需要每个厨师亲自配送到它需要被送达的地方,现在厨师只需要打个电话,一切配送工作都将由快递员(Router)去完成,如图 6 所示。

图片

图 6 VIPER 蛋糕加工模型

通过这种分工合作的模式,VIPER 架构不仅提高了工作效率,还降低了出错率。每个环节都有专门的负责人,使得整个流程更加顺畅。此外,这种架构还有助于后续的迭代和升级,因为各个模块之间的耦合度较低,便于独立开发和更新。

基于 DDD 的短视频架构优化

以网易新闻客户端的视频详情页为例,新闻客户端的视频详情页结构可以抽象为图 7。初期设计的视频详情页,所承载的业务并不多,界面也较为简单,Holder、子页面和适配器等都在 Fragment 类中定义实现。但是随着短视频风口的到来,业务加速迭代,视频的业务需求急剧增加,视频详情页所需要承载的业务也越来越多,Fragment 类从最初的几百行,急速扩张到两千多行。基于旧有的视频详情页设计,加入新的业务,使得维护成本变得越来越高,类也变得越来越大,臃肿膨胀的类已经变为了“面条代码”。

图片

图 7 视频详情页抽象结构

基于 DDD 的思想,新闻客户端实现了一套包含 UseCase 的基础框架,划分出了领域模型,由于视频详情页由多 Fragment 组成,技术团队还加入了共享变量层,使拥有统一生命周期的组件之间能解耦传递数据,重构后的视频详情页整体架构如图 8 所示。

图片

图 8 视频详情页重构后的架构设计图

DDD 的选型与实践

选型背景

新闻客户端的多数业务模块在设计初期的时候,多数业务模块所承载的业务量并不大。但随着需求的逐渐增加,为了快速迭代,开发人员往往将代码堆积在一起,导致业务模块间的边界变得模糊,耦合度也越来越高。

为了解决这个问题,DDD 的限界上下文为技术团队提供了明确领域模型边界和实现解耦的方法。VIPER 是一种符合 DDD 理念的架构模型,尽管最初在 iOS 端流行,但其核心思想与 The Clean Architecture 相似,因此同样适用于 Android 端,成为一种通用解决方案。

落地难题

在将 DDD 落地的过程中,团队遇到的比较困难的问题大致是两方面,一方面在于优化工作流程,另一方面在于转变编码思想。

在工作流程方面,传统需求评审涉及产品经理、项目经理、软件工程师、UI 交互设计师等,但关注点往往集中在整体业务,事件划分不够明确。要推动多个团队改变评审方式以确定限界上下文的事件风暴,具有一定的困难。因此,开发团队在编码阶段开始前,进行内部技术评审,邀请准领域专家与开发人员共同分析讨论界限上下文。

在编码思维方面,团队成员需要接受 DDD 思想,转变为领域驱动思维。技术团队组织了一系列相关分享,通过编程中的“隐喻”,让大家逐步建立对 DDD 的认识,加深理解。

重构效果

在基于 DDD 的架构重构初期,新闻客户端选择了视频详情、视频列表和图集三个业务模块进行 VIPER 重构,以探索 DDD 的实际应用。

  • 在重构质量上,先确定领域模型,代码整体解耦符合预期,三个模块重构后上线均未出现严重线上问题。

  • 在项目收益上,一方面,DDD 帮助团队提高迭代开发效率;另一方面,代码可维护性大幅提高,模块错误率降低约 50%。

新闻客户端以半年为周期,统计模块重构前半年与重构后半年的系统故障率(主要指开发期间产生的问题),如图 9 所示。数据表明,业务变化频繁的模块(如视频)通过 DDD 获得的模块稳定性收益更为显著。

通过采用 DDD 理念进行架构重构,新闻客户端在快速迭代和需求变化的过程中,实现了更高的灵活性、可扩展性和代码质量。通过明确领域模型边界、优化工作流程和转变编码思维,团队成功应对了落地难题,为客户端在竞争激烈的市场中奠定了基础。

图片

图 9 DDD 重构前后系统故障率统计图

DDD 落地面面观

自从2003年领域驱动设计(DDD)概念提出以来,它在软件学术界赢得了广泛认可。然而,受到国内外开发环境、开发者观念等多种因素影响,DDD在国内的实际应用并未完全达到预期效果。从2013年开始,微服务架构和中台化在国内逐渐兴起,DDD作为指导原则,有助于明确划分领域和子领域,因此在企业应用实践中取得了良好成果,成为国内突然流行的主要原因之一。

对开发团队而言,实施DDD的关键环节是通过事件风暴进行领域分析建模,这对领军人物的领域素养要求较高,需要承担领域专家的职责。

针对移动端领域,The Clean Architecture 是目前最适合、符合 DDD 理念的架构模型。Google 官方推出的安卓蓝图项目为 MVP 提供了一套符合 The Clean Architecture 的 MVP-Clean 项目,开发者可以借此展开逐步探索和尝试。

由于国内外软件工程师的职业环境和所承受的压力有所不同,在面对突发业务需求冲击时,开发者往往只能疯狂堆叠代码,导致被迫放弃 DDD 设计。需求变更时,容易出现风险。

在风险加剧的情况下,各种推诿责任的现象频发,问题本质归咎于组织结构、环境因素以及边界划分不明确。

针对组织和团队层面,初期无需大规模调整,即可满足 DDD 转型需求,并为后续微服务和中台化建设提供便利。但需注意,改变团队成员固有的开发思维至关重要,团队内应定期举办 DDD 相关分享,使大家对 DDD 观念逐渐认同。

总之,在国内软件开发环境中,实践 DDD 具有重要意义。遵循 DDD 原则,开发者能够更好地应对业务需求变化,提高代码质量和系统稳定性。在组织及团队层面,推动 DDD 转型有助于微服务架构和中台化的实施,提升整体开发效率。为实现这些目标,团队需共同努力,培养领域素养,明确边界划分,并改变固有思维,为 DDD 实践奠定坚实基础。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值