网易新闻App架构重构实践:DDD正走向流行

https://mp.weixin.qq.com/s/FdwrT_xn3CQqpWoRVBttvQ

小智 InfoQ 2020-05-14

作者 | 小智嘉宾 | 李云鹏当前,大多数移动开发团队选择以 MVP 作为业务层的核心架构模型,在此基础上实现了客户端的组件化、插件化、容器化等,但作为业务层核心的 MVP 架构模式至今仍有诸多弊端。网易新闻 App 在领域驱动设计(DDD)思想指导下,对其架构做了整体重构,得到了不错的重构质量与项目收益。移动端架构与网站架构的区别是什么?网易新闻客户端的架构演进历程是怎样的?为什么要选择 DDD 思想来指导重构?DDD 落地中应当关注哪些方面?带着这些问题,InfoQ 记者采访了网易高级客户端工程师李云鹏,他也将在 QCon 北京 2020 上带来《在领域驱动设计(DDD)下重构网易新闻 APP 架构》的演讲。1移动端架构与网站架构的区别

传统意义上的网站架构,通过超文本传输协议,浏览器可以快捷方便地将我们想要访问的页面呈现在眼前。每个网站由多个页面组成,这些页面也都属于 Web Server 的一部分,如图 1 所示。

图 1  Website 与 Server 抽象模型

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

而移动端 App 需要用户先进行安装操作,然后再进行页面访问,其中的高并发网络请求等场景通常交由 Server 端处理,移动端则更关注处理用户交互与界面变化,如图 2 所示。

图 2  Application 与 Server 抽象模型

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

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

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

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

网易新闻客户端的架构演进主要经历了四个阶段: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 介入也意味着这些 Presenter 为了适应不同的 View 将产生更多的接口。

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

图 5  MVPs 蛋糕加工模型

第四阶段:符合 DDD 的 VIPER

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

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

图 6  VIPER 蛋糕加工模型

3基于 DDD 的短视频架构优化

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

图 7  视频详情页抽象结构

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

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

4DDD 的选型与实践选型背景

新闻客户端的多数业务模块在设计初期的时候,承载的业务都不是很多。当某些业务模块的需求逐渐增加,开发人员为了快速完成迭代工作,代码经常会冲刺堆积在一起,久而久之,业务模块之间的边界变得越来越不清晰,耦合也变得越来越严重。

DDD 的限界上下文可以帮助技术团队定义出清晰的领域模型边界,以达到解耦的目的。VIPER 是符合 DDD 理念的架构模型,VIPER 曾一直流行于 iOS 端,在 Android 端的应用案例非常少,但其实 VIPER 的核心思想是 The Clean Architecture,所以它同样适用于 Android 端,是一个通用的解决方案。

落地难题

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

在工作流程方面,大多数互联网公司都会有需求评审会,其中会有产品经理、项目经理、软件工程师、UI 交互设计师等参与。但传统形式的需求评审主要关注点还是在整体的业务本身,在事件划分上是非常模糊的,这与确定限界上下文的事件风暴相差一段距离,推动多个团队变更评审方式的难度比较大。所以,开发团队内部在编码阶段开始前,还会进行一次技术评审,由准领域专家们参与到其中,与开发人员共同分析讨论界限上下文。

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

重构效果

在基于 DDD 的架构重构初期,新闻客户端选取了视频详情、视频列表和图集三个业务模块使用 VIPER 重构,对 DDD 进行尝试性探索。

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

  • 在项目收益上,一方面,DDD 帮助团队加速了迭代的开发效率,另一方面,代码的可维护性也有了比较大的提高,同时,模块错误率也约降低了约 50%。

新闻客户端以半年为一个维度,统计出模块重构前半年和重构后半年的系统故障率(主要指程序开发期间产生的问题),得出了如图 9 所示的信息。数据发现,越是业务变化很频繁的模块(如视频),通过 DDD 获得的模块稳定性收益就越大。

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

5DDD 落地面面观

DDD 从 2003 年被提出来以后,得到了软件学术界的高度认可,但由于国内外开发环境和开发人员思想理念不同等多种影响,导致 DDD 在国内的实践并不是很理想。从 2013 年开始,微服务架构和中台化在国内逐渐盛行,而 DDD 可以作为其指导思想,帮助微服务架构进行清晰的领域和子域划分,DDD 逐渐在企业应用实践上获得理想的成果,这正是 DDD 在国内突然变得流行的最主要原因之一。

对于开发团队来说,实践 DDD 最重要的一步就是通过事件风暴来进行领域分析建模,但这对于带头发起人的领域素养要求较高,需要团队内有一位布道师开路,担任领域专家的职责。

针对移动端来说,目前最合适的、符合 DDD 的架构模型就是 The Clean Architecture,Google 官方推出的安卓蓝图项目也针对 MVP 提供了一套符合 The Clean Architecture 的 MVP-Clean 项目,开发者可以由此为起点进行逐步探索和尝试。

由于国内外软件工程师职业环境和所承受的压力有所不同,在很多突发性的业务需求的冲击下,使得开发者只能对代码做疯狂堆叠,导致开发者不得已放弃 DDD 的设计,而期间发生需求变更就很容易导致风险。

当风险变为危险后,发生各种互相推诿的现象,其问题本质归结起来无非是两方面,一方面是组织结构和环境所影响,另一方面是边界划分不明确。

对于组织和团队方面,前期无需变动,也可以满足向 DDD 转型的条件,也为后期再建设微服务和中台化提供了便利。但需要注意的是,改变团队成员的固有开发思维也是十分重要的一个环节,团队内应该定期组织关于 DDD 的分享,有利于使大家对 DDD 的观念潜移默化。

网易新闻App架构重构实践:DDD正走向流行的更多相关文章

  1. App架构师实践指南四之性能优化一

    App架构师实践指南四之性能优化一     1.性能维度常见用来衡量App性能的维度如图9-1所示.其中,性能指标包括电池(电量/温度).流量(上行流量/下行流量等).CPU(平均/最大/最小).内存 ...

  2. 仿网易新闻app下拉标签选择菜单

    仿网易新闻app下拉标签选择菜单 仿网易新闻app下拉标签选择菜单,长按拖动排序,点击增删标签控件 ##示例  ##EasyTagDragView的使用 在layout布局里添加:  

  3. App架构师实践指南六之性能优化三

    App架构师实践指南六之性能优化三 2018年08月02日 13:57:57 nicolelili1 阅读数:190   内存性能优化1.内存机制和原理 1.1 内存管理内存时一个基础又高深的话题,从 ...

  4. App架构师实践指南五之性能优化二

    App架构师实践指南五之性能优化二 2018年07月30日 13:08:44 nicolelili1 阅读数:214   从UI和CPU方面来说App流畅体验优化,核心为流畅度/卡顿性能优化. 1.基 ...

  5. App架构师实践指南二之App开发工具

    App架构师实践指南二之App开发工具     1.Android Studio 2.编译调试---条件断点.右键单击断点,在弹出的窗口中输入Condition条件.---日志断点.右键单击断点,在弹 ...

  6. App架构师实践指南三之基础组件

    App架构师实践指南三之基础组件 1.基础组件库随着时间的增长,代码量的逐渐积累,新旧项目之间有太多可以服用的代码.下面是整理的公共代码库. 2.关于加密密钥的保护以及网络传输安全是移动应用安全最关键 ...

  7. 通过业务系统的重构实践DDD

    最近新接了一个业务系统——社区服务系统,为了快速熟悉和梳理老系统的业务逻辑和代码,同时对老系统代码做一些优化,于是打算花上一个月时间不间断地对老系统服务进行重构.同时,考虑到社区业务的复杂性,想起了之 ...

  8. App架构师实践指南一之App基础语法

    第二章:App基础语法1.编程范式编程范型或编程范式(programming paradigm),是指从事软件工程的一类典型的编程风格.常见的编程范式有过程化(命令行)编程.事件驱动编程.面向对象编程 ...

  9. wap尝试调取app(网易新闻为例)

    <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...

随机推荐

  1. [LeetCode]2. Add Two Numbers链表相加

    注意进位的处理和节点为null的处理 public ListNode addTwoNumbers(ListNode l1, ListNode l2) { int flag = 0; ListNode ...

  2. Redis主从复制getshell技巧

    Redis未授权漏洞常见的漏洞利用方式: Windows下,绝对路径写webshell .写入启动项. Linux下,绝对路径写webshell .公私钥认证获取root权限 .利用contrab计划 ...

  3. SpringBoot全局时间转换器

    SpringBoot全局时间转换器 日常开发中,接收时间类型参数处处可见,但是针对不同的接口.往往需要的时间类型不一致 @DateTimeFormat(pattern = "yyyy-MM- ...

  4. 一个关于JVM类初始化问题

    刚在看虚拟机相关知识点 看到一段代码,大家猜测一下这段代码会触发子类初始化吗 public class SuperClass{ static{ system.out.println("Sup ...

  5. 如何利用Typora编写博客,快速发布到多平台?

    在不同的平台发布同样的文章,最让人头疼的就是图片问题,如果要手动一个个去重新上传,耗时耗力,还容易搞错.下面分享的方法,可以将Typora编写的文章快速发布到CSDN.微信公众号.博客园.简书等平台. ...

  6. 初学VBA

    一个最基本的VBA程序 Sub test() //宏开始 Dim ge As Range //定义变量 For Each ge In Range("a1:a10") //从a1到a ...

  7. UNraid学习随手记:显示主板、CPU传感器温度

    话不多说直接开始 首先安装NerdTools 地址: https://raw.githubusercontent.com/dmacias72/unRAID-NerdPack/master/plugin ...

  8. 【函数分享】每日PHP函数分享(2021-1-8)

    explode() 使用一个字符串分割另一个字符串. array explode( string $delimiter , string $string [, int $limit ]) 参数描述de ...

  9. 如何将未呈现的WPF控件保存到图片

    SaveFileDialog save = new SaveFileDialog(); save.Filter = "BMP|*.bmp|PNG|*.png|JPG|*.jpg"; ...

  10. 实现Vue的多页签组件

    在之前的博客中  关于vue的多页面标签功能,对于嵌套router-view缓存的最终无奈解决方法  有写过vue的多页签功能的解决方案 可以看到我当时那个多页签的组件还是比较简单 的,只有打开跟关闭 ...