DDD领域驱动设计
本文源于最近学习实践DDD相关知识的自我总结。相关内容源于网络
本文部分引用
1、钟敬老师极客时间的DDD课程——手把手教你落地 DDD
2、欧创新老师极客时间的DDD课程——DDD 实战课
一、DDD基本开发过程
1、捕获行为需求
识别需求流程、功能、参与者、功能结果
(1)常用手段:用例建模
(2)常用方法:事件风暴
2、领域建模
3、架构设计
进程间架构:例如微服务设计、中台设计
进程内设计:DDD分层架构
4、数据库设计
5、代码实现
二、事件风暴
1、参与者:
业务人员:领域专家、一般由业务部门的专家、产品经理、PO(Product Owner)、BA(Business Analyst)等角色来担任
技术人员:架构师、技术经理、开发人员、测试人员
2、准备:
会议室、一面比较长的墙/白板、彩色便利贴
3、事件风暴的过程
(1)识别领域事件
a) 领域事件:业务流程中每个步骤引发的结果,事件风暴的作者认为,从结果入手来梳理需求,比从操作入手,更容易把业务想清楚。
b) 领域事件的命名,如果套用英语的语法来说,一般是完成时 + 被动语态。比如说,订单已提交。在 DDD 中的各种命名,一般都优先使用约定俗成的业务术语。这样的目的是统一业务语言,基于统一的业务语言来沟通可以为团队节省沟通交流的成本。方便业务交流理解。这里的统一业务语言是指在一个项目或者一个模块内是统一的,业务名词在该范围内是唯一的含义,如果是跨模块或项目,那么就不存在统一的业务语言,业务上下文之间的相同业务名词的含义会有改变。
c) 领域事件的识别
可以在墙面/白板上使用橙色(或其他颜色)的便利贴来书写和粘贴,每个参与者按照自己的理解书写自己识别到的领域事件,然后统一粘贴到墙/白板上,由所有参与者一同梳理出来重复的,缺失的,理解不一致的,最后梳理出一套全体参与者认同的领域事件。
ps:
1. 粘贴领域事件的便利贴时,可以按照大致流程顺序来粘贴,但不必强求顺序准确完美,大致的顺序只是为了方便参与者查看。
2.不要把技术事件当成领域事件,领域事件一定要是领域专家关注的,是业务描述
3.查询功能不算领域事件
d) 业务规则
某个领域事件存在相关的限制,这种可以称之为业务规则,可以用灰色便利贴标识于相应的领域事件的橙色便利贴下方
(2)识别命令
命令(command),就是引发领域事件的操作,我们可以通过分析领域事件得到。除了识别出命令本身以外,我们通常还要识别出谁执行的命令,以及为了执行命令我们要查询出什么数据。
事件风暴示例图:来源自钟敬老师极客时间课程
(3)识别领域名词
领域名词,是从命令、领域事件、执行者、查询数据里找到的名词性概念。例如,对于签订合同这个命令而言,受到影响的名词性概念是“合同”;类似地,对于合同已签订这个领域事件,是由于“合同”这个名词性概念的状态变化所导致的。
4、关于事件风暴部分的收获
(1)事件风暴过程中不必强求分析出所有的事件和命令,这是不现实的,也比较浪费时间。我们只需要列出改业务领域主要的,能完整表达和交流领域知识的时间和命令即可,同时粒度不要过细,因为领域建模本身就不是一个力度很细的模型,事件风暴更是如此,领域模型都还要在事件风暴的基础上不断完善而不是以事件风暴分析的信息就此停止。
(2)之前看到的有些网络文章说事件风暴需要有领域专家的参与,就误以为真正的实践必须要有十分精通业务专家。后来在实战的过程中发现即使团队内部对业务没有那么精通(知晓了解业务基本情况)的情况下,事件风暴的开展也取得了不错的结果,在这个过程中解决了业务需求不清晰,理解不统一的问题,统一的业务语言和领域模型对新项目的开展效果很好。老项目如果系统知识团队内缺失的比较严重,也可以开展事件风暴。
(3)其实在实践过程中由于没有足够宽广的墙面和颜色合适的便利贴,我在开展完领域事件之后就把统一后的领域事件搬到了绘图软件上开始后续的分析,对于小团队我自身感觉这样更方便,同时兼顾了保存和后期维护事件风暴结果。
(4)建立领域规则表,作为领域知识的重要组成部分。
三、领域建模
1、模型的表达
使用UML来画,受限识别实体之间的关系,包含一对一,多对多,多对一,所有的多对多都可以拆成两个多对一。此外在绘制过程中要合理的使用抽象,识别实体的自关联,增加关系约束,所有的约束必须在程序中得到实现。
领域模型可以分为宏观层次(包图,模块)、微观层次(模块内部的实体),这样方便理解领域模型,可以在一定程度上减少复杂性。
2、DDD 领域模型与传统方法的不同之处
DDD 强调领域模型要兼顾业务和技术两个视角。围绕领域模型进行开发的方法,在 DDD 中称为模型驱动设计(Model-Driven Design),是 DDD 的核心模式之一。这个模式可以概括为两点:领域模型要和业务需求一致;系统实现要和领域模型一致。
领域模型不仅要对业务进行直观模拟,更要经过提炼,形成浓缩的知识。第一点,是通过抽象思维得到更“深刻”的模型。第二点,是通过深入思考得到更“丰富”的模型。
领域模型和业务相一致,必须通过业务人员和技术人员的协作才能完成。业务人员的参与,能为领域模型带来专业的领域知识,而技术人员可以为领域模型带来更高的抽象性和严谨性。
除了模型驱动设计以外,DDD 中的另一个核心模式是统一语言(Ubiquitous Language)。统一语言包含了两个层面的含义:一是业务和技术人员之间的语言是统一的,二是开发团队内部各角色之间的语言是统一的。最终结果就是每一行代码都能对应到统一语言,从而与业务保持一致。你可能已经发现了,统一语言和领域模型的目的是一致的。领域模型、和词汇表(glossary)和业务规则表等文档性的制品,就是统一语言的“物质基础”。
四、代码实践——目录结构
网上的DDD程序目录由很多种,大部分都是基于DDD的各种架构思想得来的。实践过程中没有完整的采用某种结构,个人感觉DDD主要还是指导思想,实际的代码结构,代码的实现都要看团队实际情况来做出取舍,如果团队对DDD了解的比较少,感觉可以舍弃一些严格的分层写法的限制,节约理解成本。
整体目录结构如下:
适配器层目录结构如下:
应用层结构如下
领域层结构如下:
之前在网上看到的资料有推荐将与数据库的交互放到基础层的,但是我感觉还是按照钟敬老师的方式都放到适配器好一些,这是基于六边形架构的思想做出的代码结构。此外由于项目代码比较简单没有采用单独建PO转DO的工厂或者装配器,而是直接在仓储实现的代码中处理了,其实最好还是单抽出来。项目中DTO转DO还是使用了装配器(Assembler),没有使用工厂的原因是觉得工厂实现起来还是比较复杂代码量较大,此外感觉如果使用工厂由于代码层次是在domain层,没法使用应用层的DTO什么作为入参,这样的话调用工厂相关的代码也不少。项目业务比较简单于是舍弃了工厂写法。
下面放一份网络上其他方案的结构说明:
repository接口名称不应该使用底层实现的语法:我们常见的insert、select、update、delete都属于SQL语法,使用这几个词相当于和DB底层实现做了绑定。相反,我们应该把 Repository 当成一个中性的类 似Collection 的接口,使用语法如 find、save、remove。在这里特别需要指出的是区分 insert/add 和 update 本身也是一种和底层强绑定的逻辑,一些储存如缓存实际上不存在insert和update的差异,在这个 case 里,使用中性的 save 接口,然后在具体实现上根据情况调用 DAO 的 insert 或 update 接口。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步