-
互联网的行业的业务越来越复杂,面临传统行业软件同样的问题;
-
微服务的流行带火了DDD,来解决微服务拆分问题。
价值
详细讲解之前,我们先给出DDD为打赏业务带来的价值。会员业务部门在打赏业务进行了DDD实践后,效率有显著提升:-
新需求接入开发成本节约20%;
-
更换底层中间件开发成本节约20%;
-
项目熟悉成本节约30%(对DDD有基本了解为前提);
-
单测开发成本指数级降低;
-
上线风险、成本降低。
领域驱动设计是什么
讨论领域驱动设计是什么之前,我们先看下面一段代码: 这是一个打赏接口定义,单看这个接口是没有问题的,用户基于活动,选择明星,选择道具进行打赏。业务逻辑上没问题,但我们会发现一些代码的坏味道。-
代码编译后方法的参数名会丢失。
打赏业务逻辑掺杂了很多与业务核心逻辑无关的前置校验逻辑,影响代码可读性。全部逻辑堆叠在一个方法中,增加了测试用例编写复杂度。对于以上代码,问题的根本原因是我们对业务的领域没有明确的划分,只是实现了一个操作流程,是需求的直接实现,缺乏领域抽象,方法的参数定义缺少业务领域含义。对于活动校验本质是活动的一些属性判断,活动是否有效是活动自身的属性决定的。可以抽象出活动校验类ActivityValidate,或在实体中增加validate方法,还可以更近一步,将校验逻辑直接放在活动的构造方法中,这样既达到了校验的目的,也避免了漏校验。对于单测的编写,如果我们能将一个大逻辑拆分为多逻辑单元,无疑会大大减少用例数量。优化后的代码如下: 对于活动的校验,采用构造函数校验,因此打赏方法中无需再校验活动。活动的校验放到前置构造函数,减少了测试用例数量。回到最初的问题,什么是领域驱动设计?1、领域驱动设计基于领域建模而非数据建模上面例子中,代码重构前activity实体只有基本的属性和get/set方法,即“失血模型”,从而导致activity这个领域对象退化为数据对象,只用作orm组件的crud,失血模型在项目代码中随处可见。究其原因,跟对象-关系映射(ORM,比如hibernate)持久化机制的流行是有直接关系的,使用ORM将每个类映射到一张数据表,通过实体对象完成crud,久而久之实体成为了orm框架的专用名词,即丧失了领域能力。进行项目设计时,我们应该从业务领域角度出发思考问题,而不应该从数据库角度,我们将在战略设计部分详细讨论。2、满足六边形架构设计六边形架构在后文进行详细介绍,洋葱架构、干净架构与六边形架构类似。满足以上两点,并对DDD的一些概念进行映射实践,那么你的系统已经符合DDD了。总结,DDD不是一套全新的特殊架构,是任何项目代码经过重构,满足高可维护性、高可扩展性、高可测试性、代码结构清晰之后必将达到的终点。
DDD打赏业务实践
1、打赏业务简介-
观看视频时,选择明星、礼物进行打赏;
-
打赏后屏幕有气泡提示;
-
打赏数据在排行榜进行显示;
-
累计一定的打赏获得某种奖励。
-
运营及产品需求非常简单,只要实现免费打赏并在界面实现打赏气泡,基于此,只有一个领域;
-
经过一阶段的试水,活动效果很好,需要能同时支持多场打赏活动,增加活动支撑子域;
-
需求方又有了新想法,用户完成一定打赏后给用户发放一些奖品,引入奖励子域;
-
为了提升用户参与感,增加排行功能,引入排行子域。
-
打赏核心子域:完成打赏操作。
-
通知子域:实现界面气泡通知能力。
-
奖励子域:奖励策略匹配,奖励发放。
-
排行子域:完成排行功能。
-
活动子域:活动、明星、道具管理。
-
用户子域:完成用户查询、校验等通用能力。
在我们的代码中,有很多直接的外部依赖和实现细节。如mybatis的mapper类、httpclient注入、rocketmq的监听、缓存的直接操作等等。这样的实现有两个比较明显问题,一是当底层更换基础组件时对业务逻辑有直接影响,更换代码改动量及测试范围大大增加。二是不利于功能的复用,如果其他业务有类似逻辑,做不到直接移植复用。2005年Alistair Cockburn提出了六边形架构,又被称为端口和适配器架构。观察上图我们发现,对于核心的应用程序和领域模型来说,其他的底层依赖或实现都可以抽象为输入和输出两类。组织关系变为了一个二维的内外关系,而不是上下结构。每个io与应用程序之前均有适配器完成隔离工作,每个最外围的边都是一个端口。基于六边形架构设计的系统是DDD追求的最终形态。六边形架构的实践在“DDD的优势”部分进行讲解。先给出基于六边形架构实践后,项目模块结构:
模块 | 说明 |
Admin-api | 配置后台相关 |
api | 对外用户接口 |
application | 应用 |
domain | 领域 |
infrastructure | 基础设施 |
query | 查询模块 |
task | 与使用的中间件相关,可忽略 |
worker | 处理事件消息模块 |
common | 基础包 |
DDD的优势
应用DDD的系统符合六边形架构,我们实现了以下目标:-
独立于框架:架构不应该依赖某个外部的库或者框架,不应该被框架的结构所束缚;
-
独立于UI:前台展示的样式可能会随时发生变化,但是底层架构不应该随之而变化;
-
独立于底层数据源:软件架构不应该因为不同的底层数据存储而产生巨大改变;
-
独立于外部依赖:无论外部依赖如何变更、升级,业务的核心逻辑不应随之而大幅变化。
几个关键问题
1、事务在上文中“聚合”章节,我们描述了事务,一个事务内只操作一个聚合实例。如果发现一次事务内逻辑过多,可以考虑剥离出独立的聚合,采用最终一致性。基于这个基础,最适合声明事务的层是应用层。2、查询CQRS 在 DDD 中是一种常常被提及的模式,它的用途在于将领域模型与查询功能进行分离,让一些复杂的查询摆脱领域模型的限制,以更为简单的 DTO 形式展现查询结果。同时分离了不同的数据存储结构,让开发者按照查询的功能与要求更加自由的选择数据存储引擎,CQRS的具体实践可以自行查找资料。3、框架无关基于六边形架构设计,已经做到了与底层实现、框架、中间件无关。但还有一个最大的框架依赖spring,我们的做法是领域内使用的spring bean通过传参方式,实现领域层框架解耦。4、成本成本是我们实践DDD时需要考虑的一个很重要的问题,学习成本、改造成本、兼容成本等等都是需要特别关注的。在动手实践之前,建议优先评估好成本。结束语
DDD不是一套全新的特殊架构,是应对软件复杂性的一套方法论。是面向领域建模,基于六边形架构,项目代码经过重构,满足高可维护性、高可扩展性、高可测试性、代码结构清晰之后必将达到的终点。 DDD缺少权威性的实践指导和代码约束,因此应用过程中会碰到很多问题,爱奇艺会员团队通过几个月的实践积累了一定的经验,欢迎交流。