代码编织梦想

0 文章概述

领域驱动设计 DDD 是一段时间以来比较流行的概念,刚开始接触时觉得概念很多,并且比较难以落地。

1 六个问题

1.1 为什么使用

DDD 方法论的核心是将问题不断分解,把大问题分解为小问题,大业务分解小领域,简而言之就是分而治之,各个击破。

分而治之是指直接面对大业务我们无从下手,需要按照一定方法进行分解,分解为高内聚的小领域,使得业务有边界清晰,而这些小领域是我们有能力处理的,这就是领域驱动设计的核心。

各个击破是指当问题被拆分为小领域后,因为小领域业务内聚,其子领域高度相关,我们在技术维度可以对其进行详细设计,在管理维度可以按照领域对项目进行分工。需要指出 DDD 不能替代详细设计,DDD 是为了更清晰地详细设计。

在微服务流行的互联网行业,当业务逐渐复杂时,技术人员需要解决如何划分微服务边界的问题,DDD 这种清晰化业务边界的特性正好可以用来解决这个问题。

1.2 方法与目标

我们的目标是将业务划分清晰的边界,而 DDD 是达成目标的有效方法之一,这一点是需要格外注意的。DDD 是方法不是目标,不需要为了使用而使用。例如业务模型比较简单可以很容易分析的业务就不需要使用 DDD,还有一些目标是快速验证类型的项目,追求短平快,前期可能也不需要使用领域驱动设计。

1.3 整体与局部

领域可以划分多个子领域,子域可以再划分多个子子域,限界上下文本质上也是一种子子域,那么在业务分解时一个业务模块到底是领域、子域还是子子域?

我认为不用纠结在这个问题,因为这取决于看待这个模块的角度。你认为整体可能是别人的局部,你认为的局部可能是别人的整体,叫什么名字不重要,最重要的是按照高内聚的原则将业务高度相关的模块收敛在一起。

1.4 粒度粗与细

业务划分粒度的粗细并没有统一的标准,还是要根据业务需要、开发资源、技术实力等因素综合考量。例如微服务拆分过细反而会增加开发、部署和维护的复杂度,但是拆分过粗可能会导致大量业务高度耦合,开发部署起来是挺快的,但是缺失可维护性和可扩展性,这需要根据实际情况做出权衡。

1.5 领域与数据

领域对象与数据对象一个重要的区别是值对象存储方式。在讨论领域对象和数据对象之前,我们首先讨论实体和值对象这一组概念。实体是具有唯一标识的对象,而唯一标识会伴随实体对象整个生命周期并且不可变更。值对象本质上是属性的集合,并没有唯一标识。

领域对象在包含值对象的同时也保留了值对象的业务含义,而数据对象可以使用更加松散的结构保存值对象,简化数据库设计。

现在假设我们需要管理足球运动员信息,对应的领域模型和数据模型应该如何设计?姓名、身高、体重是一名运动员本质属性,加上唯一编号可以对应实体对象。跑动距离,传球成功率,进球数是运动员比赛中的表现,这些属性的集合可以对应值对象。

值对象在数据对象中可以用松散的数据结构进行存储,而值对象在领域对象中需要保留其业务含义如下图所示:

 

我们根据图示编写领域对象与数据对象代码:

//数据对象
publicclassFootballPlayerDO{
privateLongid;
privateStringname;
privateIntegerheight;
privateIntegerweight;
privateStringgamePerformance;
}//领域对象
publicclassFootballPlayerDMO{
privateLongid;
privateStringname;
privateIntegerheight;
privateIntegerweight;
privateGamePerformanceVOgamePerformanceVO;
}
publicclassGamePerformanceVO{
privateDoublerunDistance;
privateDoublepassSuccess;
privateIntegerscoreNum;
}

1.6 抽象与灵活

抽象的核心是找相同,对不同事物提取公因式。实现的核心是找不同,扩展各自的属性和特点,体现了灵活性。例如模板方法设计模式正是用抽象构建框架,用实现扩展细节。

我们再回到数据模型的讨论,可以发现脚本化是一种拓展灵活性的方式,脚本化不仅指使用 groovy、QLExpress 脚本增强系统灵活性,还包括松散可扩展的数据结构。数据模型抽象出了姓名、身高、体重这些基本属性,对于频繁变化的比赛表现属性,这些属性值可能经常变化,甚至属性本身也是经常变化,例如可能会加上射门次数,突破次数等,所以采用松散的 JSON 数据结构进行存储。

2 六个步骤

工程理论总是要落地的,落地也是需要一些步骤和方法的。本文我们一起分析一个足球运动员信息管理系统,目标是管理运动员从转会到上场比赛整条链路信息,这个系统大家应该也都没有接触过,我们一起来分析。需要说明本实例着重演示 DDD 方法论如何落地,业务细节可能并不能面面俱到。

2.1 流程梳理

梳理流程有两个问题需要考虑,第一个问题是从什么视角去梳理?因为不同的人看到的流程是不一样的。答案是取决于系统需要解决的是什么问题,因为我们要管理运动员从转会到上场比赛整条链路信息,所以从运动员视角出发是一个合适的选择。

第二个问题是对业务不熟悉怎么办?因为我们不是体育和运动专家,并不清楚整条链路的业务细节。答案是梳理流程时一定要有业务专家在场,因为没有真实业务细节,无法领域驱动设计。同理在互联网梳理复杂业务流程时,一定要有对相关业务熟悉的产品经理或者运营一起参与。

假设足球业务专家梳理出了业务流程,运动员提出转会,协商一致后到新俱乐部体检,体检通过就进行签约。进入新俱乐部后进行训练,训练指标达标后上场比赛,赛后参加新闻发布会。

 

2.2 四色建模

(1) 时标对象

四色建模第一种颜色是红色,表示时标对象。时标对象是四色建模最重要的对象,可以理解为核心业务单据。在业务进行过程中一定要对关键业务留下单据,通过这些单据可以追溯出整个业务流程。

时标对象具有两个特点:第一是事实不可变性,记录了过去某个时间点或时间段内发生的事实。第二是责任可追溯性,记录了管理者关注的信息。现在我们分析本系统时标对象有哪些,需要留下哪些核心业务单据。

转会对应转会单据,体检对应体检单据,签合同对应合同单据,训练对应训练指标单据,比赛对应比赛指标单据,新闻发布会对应采访单据。根据分析绘制如下时标对象:

(2) 参与方、地、物

这三类对象在四色建模中用绿色表示,我们以电商场景为例进行说明。用户支付购买商家的商品时,用户和商家是参与方。物流系统发货时配送单据需要有配送地址对象,地址对象就是地。订单需要商品对象,物流配送需要有货品,商品和货品就是物。

我们分析本例可以知道参与方包含总经理、队医、教练、球迷、记者,地包含训练地址、比赛地址、采访地址,物包含签名球衣和签名足球:

(3) 角色对象

在四色建模中用黄色表示,这类对象表示参与方、地、物是以什么角色参与到业务流程:

 

(4) 描述对象

我们可以为对象增加相关描述信息,在四色建模中用蓝色表示:

 

2.3 划分领域

在四色建模过程中我们体会到时标对象是最重要的对象,因为其承载了业务系统核心单据。在划分领域时我们同样离不开时标对象,通过收敛相关时标对象划分领域。

 

2.4 领域事件

当业务系统发生一件事情时,如果本领域或其它领域有后续动作跟进,那么我们把这件事情称为领域事件,这个事件需要被感知。

例如球员比赛受伤了,这是比赛子域事件,但是医疗和训练子域是需要感知的,那么比赛子域就发出一个事件,医疗和训练子域会订阅。

例如球员比赛取得进球,这也是比赛子域事件,但是训练和合同子域也会关注这个事件,所以比赛子域也会发出一个比赛进球事件,训练和合同子域会订阅。

通过事件交互有一个问题需要注意,通过事件订阅实现业务只能采用最终一致性,需要放弃强一致性,这一点可能会引入新的复杂度需要权衡。

 

2.5 项目搭建

(1) api

接口层:提供面向外部接口声明和 DTO 对象

(2) controller

访问层:提供 HTTP 访问入口

(3) service

业务层:领域层和业务层都包含业务,但是用途不同。业务层可以组合不同领域业务,并且可以增加流控、监控、日志、权限控制切面,相较于领域层更为丰富,提供 BO 对象

(4) domain

领域层:提供 DMO(DomainObject)、VO、事件、数据访问对象,核心是按照领域进行分包,领域内高内聚,领域间低耦合

(5) dependency

外部访问层:在这个模块中调用外部 RPC 服务,解析返回码和返回数据

(6) infrastructure

基础层:包含基础功能,例如缓存工具,消息队列,分布式锁,消息发送等功能

 

我们展开领域层进行分析。领域层的核心是按照领域进行分包,并且提供 DMO、VO、事件、数据访问对象,领域内高内聚,领域间低耦合,例如 domain1 对应合同子域,domain2 对应训练子域,domain3 对应合同子域。

2.6 详细设计

 目前为止领域已经确定了,现在可以根据领域划分任务了,组内成员分别负责一个或多个领域进行详细设计,这个阶段就是大家非常熟悉的用例图,活动图,时序图,数据库设计,接口设计的用武之地。需要说明的是领域驱动设计不是取代详细设计,而是为了更清晰地详细设计。

3 文章总结

本文探讨了 DDD 落地时需要关注的六个问题,并通过一个足球运动员信息管理系统案例落地了六个步骤。在实际应用中各业务形态千差万别,但是方法论却可以通用,我们需要明确 DDD 核心是分而治之各个击破,并配合一些经过检验的有效方法进行建模,希望本文对大家有所帮助。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/jbossjf/article/details/127156365

TX-LCN分布式事物框架-爱代码爱编程

一、运行txlcn-tm 1、下载地址 https://gitee.com/devojiang/tx-lcn/tree/5.0.2.RELEASE/ 2、导入到IDEA  3、在配置文件application.properties里       修改mysql数据库密码 4、资源文件下tx-manager.sql      mysql数据

halcon 深度学习(一):分类-爱代码爱编程

分类识别系统下载地址:https://download.csdn.net/download/jbossjf/78056467 *准备训练集 dev_update_off () *存放分类图片的上一级路径 RawImageFolder := 'D:/软件项目备份/halcon项目/深度学习分类/分类检测/' *存放预处理数据的总路径 Examp

halcon联合C#的实时采集显示-爱代码爱编程

第一步:做好halcon图像采集算法并将其导出为C#代码。 打开VS2019选择Windows窗体应用程序并设置命名工程名称与保存路径。如下图: 可以看到一个类似于白板的窗体(适当拖动窗体大小),接着添加应用,如下如   单击引用在右击添加引用,引用的路径为halcon安装目录下的bin->dotnet35下的halcondotnet.dl

vue confirm提示框-爱代码爱编程

    var that = this this.$confirm('确认跳转到首页成为付费会员吗?', '提示', { iconClass: 'el-icon-question', // 自定义图标样式 confirmButtonText: '确认', // 确认按钮文字更换

限制winform程序只能启动一个应用_长春小霸王的博客-爱代码爱编程

using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Windows.Forms; namespace com.jbossjf.run { static class Program

element 树(el-tree)默认展开指定节点_长春小霸王的博客-爱代码爱编程

<el-tree :data="organization" node-key="id" node="label" height="100%" style="height:100%" ref="tree"

vue项目 - 给页面添加水印_长春小霸王的博客-爱代码爱编程

1.创建文件 watermark.js文件 /** 水印添加方法 */ const setWatermark = (str1, str2) => { const id = '1.23452384164.123412415' if (document.getElementById(id) !== null) {

vue-pdf应用实例_长春小霸王的博客-爱代码爱编程

  1.安装 npm install --save vue-to-pdf 2. main.js引入 import vueToPdf from 'vue-to-pdf'; Vue.use(vueToPdf); 3. 代码 <template> <div> <div class="pdf-box"&g

mysql添加索引的五种方法_长春小霸王的博客-爱代码爱编程

1.添加primary key(主键索引) alter   table  表名   add  primary   key(列名); 2.添加unique(唯一索引) alter  table  表名  add  unique(列名); 3.添加index(普通索引) alter  table  表名  add  index  索引名(index_nam

mysql插入\更新前+判断条件_长春小霸王的博客-爱代码爱编程

1、mysql插入前判断数据是否存在的操作 INSERT INTO table(field1, field2, fieldn) SELECT 'field1', 'field2', 'fieldn' FROM DUAL WHERE NOT EXISTS(SELECT field FROM table WHERE field = ?) 2、不存在则

spring的三级缓存解决循环依赖_长春小霸王的博客-爱代码爱编程

一、什么是Spring三级缓存 第一级缓存:也叫单例池,存放已经经历了完整生命周期的Bean对象。 第二级缓存:存放早期暴露出来的Bean对象,实例化以后,就把对象放到这个Map中。(Bean可能只经过实例化,属性还未填充)。 第三级缓存:存放早期暴露的Bean的工厂。 注: 只有单例的bean会通过三级缓存提前暴露来解决循环依赖的问题,而非

漏桶和令牌桶的区别_长春小霸王的博客-爱代码爱编程

一、漏桶 把请求比作是水,请求进来了就把请求先放进桶里,但是不处理,并以限定的速度出水,出水就相当于处理请求。当水来得过猛而出水不够快时就会导致水直接溢出,即拒绝服务 漏桶算法可以很好的控制流量的访问速度,一旦超过该速度就拒绝服务。 二、令牌桶 令牌桶算法的原理是系统会以一个恒定的速度往桶里放入令牌,而如果请求需要被处理,则需要先从桶里获取

hashmap如何解决哈希冲突?_长春小霸王的博客-爱代码爱编程

1. Hash算法和Hash表 了解Hash冲突首先了解Hash算法和Hash表   Hash算法就是把任意长度的输入通过散列算法变成固定长度的输出,这个输出结果就是一个散列值 Hash表又叫做“散列表”,它是通过key直接访问到内存存储位置的数据结构,在具体的实现上,我们通过Hash函数,把key映射到表中的某个位置,来获取这个位置的数据,从而加

vue view同行显示排列样式_vue同行-爱代码爱编程

1、view <view class="view-flex"> <view class="item1">1</view> <view class="item1">2</view> <view class="item1">3</view

mvcc详解_读已提交 快照-爱代码爱编程

一、什么是MVCC? mvcc,也就是多版本并发控制,是为了在读取数据时不加锁来提高读取效率和并发性的一种手段。 数据库并发有以下几种场景: 读-读:不存在任何问题。 读-写:有线程安全问题,可能出现脏读、幻读、不可重复读。 写-写:有线程安全问题,可能存在更新丢失等。 mvcc解决的就是读写时的线程安全问题,线程不用去争抢读写锁。 mvcc所提到的

mysql事务隔离级别-爱代码爱编程

1.事务存在的问题 1.1 脏读 如果一个事务A读取到了另一个未提交事务B修改过的数据B1,并使用了这个数据B1,然后事务B又回退了对该数据的修改至B0或又将该数据修改为其他值B2,对于A曾经读到这个数据B1,根本没有意义,那么这种情况就是脏读。 1.2 不可重复读 不可重复读是指在一个事务A内,多次读取同一个数据,前后结果不一致。 在这个事务A

mysql之两阶段提交_mysql两阶段提交-爱代码爱编程

什么是两阶段提交 当有数据修改时,会先将修改redo log cache和binlog cache然后在刷入到磁盘形成redo log file,当redo log file全都刷入到磁盘时(prepare 状态)和提交成功后才能将binlog cache刷入磁盘,当binlog全部刷新到磁盘后会记录一个xid,然后在relo log file上打上com