有个大佬这样来实现DDD,代码落地的, 大家觉得合理吗-a46c555572fee4c.png

学好英语,再改回来。 21:13:01
挺合理的

@铁皮 好问题
我认为合理
对象有自己的保存行为,没有问题
【依赖的是抽象的资源库,基础设施层依赖资源库的定义然后实现具体保存能力(依赖倒置)】
但是我更倾向于save方法放到领域服务 ,保持实体只有业务规则

铁皮 21:41:49
这里有个点就是,这个person是个实体类,没有注册到spring上下文。需要使用这个person的话,还需要通过PersonFactory来创建

铁皮 21:42:14
然后通过切面,来循环注入person需要的依赖

铁皮 21:42:37
以及自己本身到spring上下文

贺小敖 21:43:01
cola架构中提供的domain组件中有自定义的Entity注解,里面封装了component注解
显然cola作者张建飞认为里面也是可以注入对象的

半径跟圆是一种什么关系?

从现实的角度描述:一个圆具有半径的属性,半径是长度的一种

当我们要把这种关系映射到代码模型时可以这么考虑:
1.简单考虑:
假设半径就是一个长度数字
其表现形式是某个类的基本属性(属性是基本类型)
这时候半径就是圆实体的一个属性

2.考虑的复杂点:
假设半径是一个复杂对象,它拥有长度数字、长度单位、最大长度、最小长度
其表现形式是半径作为一个对象被圆引用(属性是复杂对象)
这个时候半径就属于圆实体的一个值对象
ps:有最大最小长度是假设半径是篮球半径(显然篮球一定有半径范围),我们在映射时会根据现实合理增加不同属性

3.再考虑的复杂点:
一个圆有半径、直径、周长等等属性, 他们都有一个共同的长度,我们在映射时可以统一定义一个长度对象[长度数字 + 长度单位],半径、直径、周长都继承该长度对象,属于长度的直接用父类,属于半径自己特有属性的再单独定义
这样我们按照现实世界的样子更高程度的映射到了代码模型中
ps:还可以更复杂

4.综述
实际业务场景中会根据需求来确定我们要抽象的层级,适度的定义实体(简单需求 简单定义, 复杂需求选映射程度更高的)
同时随着需求的变化,我们之前的映射定义会变得不合适(腐化),要定期审视,及时重构

其他补充
我们对现实进行映射时不止要映射属性, 还要映射对象拥有的复杂关系,如:圆的直径 = 半径 2
在面向过程的写法中这种关系基本都是填充到 业务逻辑类Service中,属性在实体定义中,如果不进行合理划分,特别容易出现一个上帝类, 代码上万行
如果使用面向对象的方式,对象拥有自己的属性和行为,自然而然的就对职责进行了合理划分,就能避免上帝类
如:圆对象有半径和直径的属性,圆对象应该知道直径 = 半径
2 , 这段逻辑就应该放到圆实体中

一个实体只包含了自己的属性和基本的 get set方法 我们称为 贫血对象
一个实体包含了自己的属性和实体自己的行 我们称为 充血对象

附:值对象和实体定义
实体是看得到、摸得着的实实在在的业务对象,实体具有业务属性、业务行为和业务逻辑。而值对象只是若干个属性的集合,只有数据初始化操作和有限的不涉及修改数据的行为,基本不包含业务逻辑。值对象的属性集虽然在物理上独立出来了,但在逻辑上它仍然是实体属性的一部分,用于描述实体的特征。
区别:值对象属于某一个实体
把某个值对象单独拿出来没有任何意义,只有依附于实体才有意义
如: 单独把半径拿出来是没有用的, 我们一般说 xxx的半径 ,半径属于xxx

假如实体没有save方法的话,某个聚合下的其他实体的持久化,应该由聚合负责还是领域服务负责?

持久化一般都是以聚合为单位
一个聚合下的实体、值对象都在一个save方法里
可以在领域服务或者应用层调用save方法
聚合类里只放业务规则,这个聚合该怎么构建
不会调用外部资源
领域层的代码只依赖抽象的持久化方法,只需要定义出我要持久化哪些对象
具体的实现由基础设施层
这样划分职责达到了分离 技术复杂度和业务复杂度 (领域层搞业务,基础设施层搞技术)

分离的好处:
以后如果要迁移底层的服务,我们只需要重新实现持久化实现,领域层的业务逻辑由于依赖抽象的接口定义 完全就不需要动了
举个例子:
一个实体a以前是在数据库里,他的save方法就是插入表
假如这个表被迁移到别的库,我们的具体实现就改为调用rpc或者rest接口的保存方法

关于模型更有表达力的讨论

Q:
领域驱动设计 中,反复提到 ”更强的表达力“上,模型有更好的表达力,这块不好理解。比如109页上,图7-1 三个有点表述中,3,delivery specification是这个模型更强表达力的一种表现。搞不懂了,怎么个逻辑,它就带来了更强的表达力了。在看这本书的同仁,一起讨论下呢。9292667a524fadaca3846c6bbf8ae8ad.jpg
A:
“这个模型具有更强的表达力” 这个每个人有不同的看法吧 , 我觉得你可以不用纠结这一点 ,别光看书, 去实践, 拿你遇到的去讨论 ,这样学的快 ,实践中学

我理解哈, 这里作者的意思:

将货物运送到目的地,我们在实现时,其表述方案有两种

【方案1:】

  1. 要运送 Cargo(货物)- A ,以 Delivery Specification(运送规格)- A 到某一个目的地
  2. 要运送 Cargo(货物)- B ,以 Delivery Specification(运送规格)- B 到某一个目的地
  3. 要运送 Cargo(货物)- C ,以 Delivery Specification(运送规格)- C 到某一个目的地

【方案2】

  1. 要运送 Cargo(货物)到某一个目的地
  2. Cargo(货物)要满足某种 Delivery Specification(运送规格)

【拆分了两个部分】

对比方案 1 和 方案 2 , 我们会发现:

方案1中货物与运送规则紧密耦合, 一种货物就要给他一个运送规格属性 ,运送规格是散落在货物中 【运送规格是货物的一个属性】

方案2则单独把规则抽出来 , 货物 和 运送规格 可以灵活的进行组合 ,只要特定的货物满足某种运送规格, 他都是通用的 【运送规格是独立于货物的】

从这两个方案上看, 方案2 更符合我们现实中的情形: 我们的运送工具能够运输不同规格的东西, 只要货物是这个规格,拿就可以运

相对来说就更有表达力一些

当然,实际建模过程还是要实际分析,不同业务的侧重点不同,两个方案本质上没有高下之分, 你把他用合适了就很好

例如: 一些特定的货物就必须用特定的运输方式, 此时方案1就很合适(它们是绑定的) ,用方案2做也能做,但是写的时候会多出很多代码,要转换

还有,你这个看三遍是怎么个看法?

如果是纯看书三遍,真心不推荐,看一遍就够了

如果是看了一遍后,实践过程中想到问题,感觉不清晰,回过头翻书看三遍,那就非常棒,很好的学习方式

领域驱动的作用

你们有没有实际的使用事件风暴进行领域建模

就我这边的几次实践来看,事件风暴解决的怎么把业务梳理清楚,加强各个角色对业务的统一认知

它不能解决业务该怎么做的问题
即:我们都是基于已经存在的东西 梳理出来

可以说是领域驱动设计不提供业务解决方案,只提供清晰方便交流可落地的业务模型

实现领域驱动设计中的”而不是通过服务或者远程接口” 是什么意思?

问题

Q&A - 图4
“而不是通过服务或者远程接口” 是什么意思?

回答

这是《实现领域驱动设计》一书中第四章架构-REST 章节

“而不是通过服务或者远程接口” 指的是直接把核心领域对象暴露出去的调用方式 ,如: 暴露 Rpc 服务 ,直接把领域对象暴露出去的接口

前置我们需要知道: Restful风格的接口是什么?

全称是 Resource Representational State Transfer:通俗来讲就是:资源在网络中以某种表现形式进行状态转移。分解开来: Resource:资源,即数据(前面说过网络的核心)。比如 newsfeed,friends等; Representational:某种表现形式,比如用JSON,XML,JPEG等; State Transfer:状态变化。通过HTTP动词实现。

GET 用来获取资源, POST 用来新建资源(也可以用于更新资源), PUT 用来更新资源, DELETE 用来删除资源。

1、看Url就知道要什么 2、看http method就知道干什么 3、看http status code就知道结果如何

举例: 现有一个领域对象 User
Restful风格一般会这么设计

  • GET /user/id 查一个 User
  • GET /users 查 User 列表
  • POST /user 创建一个 User
  • DLETE /user/id 删除一个 User
  • PUT /user /id 修改 User 信息

我们会发现 User 相关的接口设计的非常简洁易懂,对 User 的增删改查都很方便 ,这是他的好处

但是我们已发展的眼光来看 ,假设以后 User 核心业务模型有变更:User 变为 typeAUser 、 typeBUser , 核心字段有重大变更,此时调用方必须跟着一起变动 , 必须改写它们的调用接口代码以满足新的业务模型

GET /user/id 查一个 User 举例 ,会被拆为两个

  • GET /typeAUser/id 查一个 User
  • GET /typeBUser/id 查一个 User

为什么会这样?

  • 我们把核心对象 User 对外暴露,调用方不可避免的耦合了我们的核心对象 【在领域对象变动频繁的场景下尤为痛苦】

怎么解决这个问题?
单独为 User 创建一个限界上下文 UserLayer, 然后将其【根据用例】来设计接口暴露出去, 当 User 的领域模型发生变更, 直接在 UserLayer 中做处理

为什么这样能解决?【本质上上分离】

  • 分离了核心领域对象和对外暴露的接口 【分离变化】
  • 相当于加了防腐层

在一致性边界之内建模真正的不变条件是什么意思?

问题

2bb43086bb980d1d.png
在一致性边界之内建模真正的不变条件是什么意思?()翻译的不好

回答

翻译确实不好

我理解哈,大意应该是:在某一个固定的场景下,有几组该场景必须满足的规则,我们在业务建模时要着重考虑这些规则,将规则显示的表达到领域模型中

举例:四轮汽车的场景下,一个汽车不管怎么换轮胎换备件,都必须有四个轮子
规则是四轮汽车有四个轮子

关于领域事件

问题

-1e5a935f55f6a8f4.png

回答

会觉得比较好,解耦好
应对变化的时候也灵活

说明:
事件这个东西,你不学领域驱动设计的领域事件,水平足够好也是能设计出来的,效果是一样的【叫做
消息】

你学了之后的好处在于:有一套明确地设计方法约定,无论你水平高低,一般都能设计出好的消息事件(领域事件)

如 领域驱动设计中明确的描述了领域事件怎么设计有何原则
已过去式命名,代表已经发生的事件
一般是以聚合或实体发布领域事件

基于领域事件能做什么

领域事件的创建和发布

问题

5628cf5270a623d9.png
发布和创建是两个东西吗

回答

这个呀,图片发出来我就懂你意思了,之前一直没理解

创建领域事件由聚合来做(职责)
理论上发布领域事件也应该由聚合做
对外表现为:聚合发布了领域事件

但是如果聚合要把事件发布出去,必然就要依赖一些第三方的组件,而我们一般倾向于让聚合保持一个纯粹的逻辑,不希望依赖其他外部的东西
所以将发布的能力移交出去给事件发布器