领域驱动设计(DDD)战术上一些实践
个人能力有限,如有问题欢迎指导
程序设计谈不上什么最好,无论是面向过程编程,还是面向对象编程,我们都是在追求完美的道路上
不设计和过度设计都会对我们产生一些影响,最合适自己的设计才是最好的设计
DDD(Domain-Driven Design)领域驱动设计
- 领域驱动设计并不是什么银弹,简单的项目并不需要 DDD,引入后反而增加项目难度
- DDD 更加适合解决复杂的业务问题,并不是说DDD设计模式有什么压倒性的优势也不是说它就是完美无缺的只是说它更适合干这事
要不要DDD
-
在业务刚开始的时候,我们的功能都相对于比较简单,就通过CRUD大法就能满足我们的业务需求, 随着产品的各种需求加入,业务逻辑变的越来越复杂,各个模块相互依赖,修改一个功能时需要花大量的时候去理解当时为什么这些写后,然后再编写代码,甚至开发者自己都不知道这样写会不会影响其他模块,这里我想对测试人员说一句你们辛苦了!
-
产生这些问题的原因就是在于系统架构不清晰,划分出来的模块内聚度低、高耦合项目到达这个地步的时候我们就可以考虑要不要引入 DDD 来解决一些问题
贫血模型 VS 充血模型
-
贫血模型: 比如在用户类中我们定义了User的实体,但是操作User的并不是用户类,而是 UserService,这样的设计就是贫血模型简单来说就是(只包含数据,不包含业务逻辑的类) ,就好比我们大学老师说的你有一个车但是不能开只能其他人去开破坏了面向对象的特性,是一种典型的面向过程的编程风格。
-
充血模型: 我们知道了贫血模型是(只包含数据,不包含业务逻辑的类),那我们把数据和对应的业务逻辑被封装到同一个类中是不是就是充血模型,Bingo 回答正确,你现在不仅拥有了车还可以开这就是面向对象编程风格
面向过程 VS 面向对象
需求把东西全部放到柜子你里面
- 面向过程: 我们把整个东西放到一个大柜子里面堆在一起。如果修改了那个部分,可以需要修改其他部分,如果放的东西太多还不知道是否会产生一些不可以预知的错误,有可能就会出现祖传bug!
- 面向对象: 我们把整个东西分类到一个一个的放在小柜子里面然后再放入一个大柜子,如果要修改也是修改需要修改的那个柜子,不会影响其他柜子
实体
用于个性特征或区分不同对象,判断是不是同一个实体主要依据身份标识(identity),唯一身份标识和可变性(mutability)特征将实体对象和值对象(Value Object)区分开来。
- 总结下来就是,实体是一个唯一的东西,可以在一段时间内持续变化
值对象
值对象用于度量和描述事务,当我们只关心某个对象的属性时,该对象就可以作为一个值对象
- 比如在快递单的上的地址,你的地址可以和其他人的地址相同我们只需要关注地址的属性就可以,并不需要给地址一个唯一标识
- 值对象具有不变性,如果要修改的话直接替换整个值对象就行
聚合
将实体和值对象在一致性边界内组成聚合,不变性和一致性边界即是聚合的设计依据
- 不变性: 不变性表示的是一个业务规则,该规则应该总是保持一致
- 一致性边界:单个事务只修改一个聚合实例
领域服务
领域中的服务表示一个无状态的操作,它用于实现特定于某个领域的任务。当某个操作不适合放在聚合、值对象、实体上时,最好的方式便是使用领域服务
- 在我的理解中上面那一句话就是,需要调用多个模型进行处理的就放在领域服务
- eg:
应用服务:获取输入,发送消息给领域层,监听确认消息
领域服务:协调账户模型和邮件进行交互,执行相应的领域行为。
基础服务:按照应用服务的指示发送邮件。
- 不过在实际开发过程中,最好吧领域对象封装在领域服务中,领域知识限制在领域服务当中
工厂
将创建复杂对象和聚合的职责分配给一个单独的对象,该对象本身并不承担领域模型中的职责,但是依然是领域设计的一部分。 工厂应该提供一个创建对象的接口,该接口封装了所有创建对象的复杂操作过程,同时,它并不需要客户去引用那个实际被创建的对象。对于聚合来说,我们应该一次性地创建整个聚合、并且确保它的不变条件得到满足
资源库
简单理解就是一个持久化机制,在DDD设计中一般将聚合实例放在资源库中
- 比如用户实体,和地址值对象可以组合成一个聚合实例
探讨项目结构 golang
├── api
├── cmd 项目启动文件
│ └── user 项目名称
│ └── main.go
├── conf 配置文件
│ └── user.yaml
├── internal
│ └── user 项目名称
│ ├── adapter 适配器,对外提供不同的协议适配
│ │ ├── grpc
│ │ └── http
│ ├── domain
│ │ ├── aggregate 聚合根
│ │ ├── dto 定义入参,出参
│ │ ├── entity 实体
│ │ ├── event 事件
│ │ ├── interface 抽象接口由外部 repository 实现
│ │ ├── service 领域服务
│ │ └── valobj 值对象
│ ├── facade 接口防腐层 调用其他服务接口
│ │
│ └── infrastructure 适配器,是对技术实现的适配
│ └── repository 依赖倒置实现储存服务
└── pkg 三方库工具类
结语
- 个人能力有限欢迎添加QQ一起讨论