DDD项目落地之充血模型实践
背景:
充血模型是DDD分层架构中实体设计的一种方案,可以使关注点聚焦于业务实现,可有效提升开发效率、提升可维护性;
1、DDD项目落地整体调用关系
调用关系图中的Entity为实体,从进入领域服务(Domin)时开始使用,直到最后返回。
2、实体设计
充血模型是实体设计的一种方法,简单来说,就是一种带有具体行为方法和聚合关联关系的特殊实体;
关于实体设计,需要明白的关键词为:领域服务->聚合->聚合根->实体->贫血模型->充血模型
聚合与聚合根:
聚合是一种关联关系,而聚合根就是这个关系成立的基础,没有聚合根,这个聚合关系就无法成立;
举个例子,存在3个实体:用户、用户组、用户组关联关系,这3个实体形成的关联关系就是聚合,而用户实体就是这个聚合中的聚合根;
实体:
定义在领域层,是领域层的重要元素,从领域划分到工程实践落地,都应该围绕实体进行,DDD中的实体和数据库表不只是1对1关系,可能是1对多或者仅为内存中的对象;
贫血模型:
实体不带有任何行为方法,也不带有聚合关联关系,作用基本相当于值对象(ValueObject),仅作为值传递的对象,和传统三层项目架构中的实体具有相同作用,不建议使用。补充说明:一般我们使用的DTO就可以被当做是值对象
充血模型:
实体中带有具有行为方法和聚合关联关系,行为方法是说create、save、delete等封装了一类可以指代行为的方法,比如在用户实体对象中具有用户组实体的引用,这样当我们需要操作用户组时,只通过用户实体进行操作就可以。
工程实践中,建议采用充血模型,好处是隐藏胶水代码,提升代码可读性,使关注点聚焦于业务实现。
充血模型在实践中的问题:
行为代码量过多,导致实体内部臃肿膨胀,难以阅读,难以维护,对于这种问题,我们需要根据实体行为的代码量多少来采取不同的解决方案。
解决方案:
场景1:行为不会导致实体臃肿的情况下,在实体中完成行为定义
public CooperateServicePackageConfig save() {
// 直接调用基础设施层进行保存
cooperateServicePackageConfigRepository.save(this);
return this;
}
场景2:行为导致实体臃肿的情况下,采用外部定义行为的方式,核心思想是借助其他类实现行为代码定义,将臃肿代码外移,保留干净的实体行为:
1)创建工具类,将某个实体中的行为定义其中,实体负责调用该工具类
public CooperateServicePackageConfig save() {
// 将处理过程放在工具类中
ServicePackageSaveUtils.save(this);
return this;
}
2)创建新实体,将该实体的使用场景明确至某个细分行为,比如一个聚合根(ExampleEntity)的保存可能涉及到5个实体的保存,那么我们定义一个ExampleSaveEntity实体,专门用来处理该聚合下的保存行为
实践经验:
1、关于spring bean注入:充血模型在实体中使用静态注入方法实现。例:
private LabelInfoRepository labelInfoRepository = ApplicationContextUtils.getBean(LabelInfoRepository.class);
2、充血模型的实体序列化,排除非必要属性,在一些redis对象缓存时可能会用到。例:
// 使用注解排除序列化属性
@Getter(AccessLevel.NONE)
private LabelInfoRepository labelInfoRepository = ApplicationContextUtils.getBean(LabelInfoRepository.class);
// 使用注解排除序列化属性
@JSONField(serialize = false)
private ServicePackageConfig servicePackageConfig;
// 使用注解排除序列化 get 方法
@Transient
@JSONField(serialize = false)
public static CooperateServicePackageRepositoryQuery getAllCodeQuery(Long contractId) {
CooperateServicePackageRepositoryQuery repositoryQuery = new CooperateServicePackageRepositoryQuery();
repositoryQuery.setContractIds(com.google.common.collect.Lists.newArrayList(contractId));
repositoryQuery.setCode(RightsPlatformConstants.CODE_ALL);
return repositoryQuery;
}
3、利用Set方法建立聚合绑定关系。例:
public void setServiceSkuInfos(List<ServiceSkuInfo> serviceSkuInfos) {
if (CollectionUtils.isEmpty(serviceSkuInfos))
{
return;
}
this.serviceSkuInfos = serviceSkuInfos;
List<String> allSkuNoSet = serviceSkuInfos
.stream()
.map(one -> one.getSkuNo())
.collect(Collectors.toList());
String skuJoinStr = Joiner.on(GlobalConstant.SPLIT_CHAR).join(allSkuNoSet);
this.setSkuNoSet(skuJoinStr);}
作者:京东健康 张君毅
来源:京东云开发者社区
DDD项目落地之充血模型实践的更多相关文章
- DDD(三)DDD实战、贫血模型与充血模型
DDD(三)DDD实战.贫血模型与充血模型 如果觉得样式不好:跳转即可 http://www.lifengying.site/(md文件复制过来有些样式会不一样) 贫血模型与充血模型 1.贫血模型:一 ...
- EF架构~充血模型设置不被持久化的属性
回到目录 在Poco实体中,一般只有属性没有方法,这在软件设计中称为贫血模型,而在DDD领域驱动设计中,比较提倡充血模型,即你的Poco实体中,即有属性,也有操作属性的方法,注意这里说的是操作属性的方 ...
- (转)【重磅】无监督学习生成式对抗网络突破,OpenAI 5大项目落地
[重磅]无监督学习生成式对抗网络突破,OpenAI 5大项目落地 [新智元导读]"生成对抗网络是切片面包发明以来最令人激动的事情!"LeCun前不久在Quroa答问时毫不加掩饰对生 ...
- 2019元月新SAP项目落地记
2019元月新SAP项目落地记 ~ 追求绝对安全感,要求无缝衔接 ~ 元旦刚过,我就开始了新SAP项目的寻找之旅. 笔者追求绝对的安全感,以及新旧项目之间的无缝衔接.即不能让自己长时间的闲着无项目做, ...
- 领域模型(domain model)&贫血模型(anaemic domain model)&充血模型(rich domain model)
领域模型是领域内的概念类或现实世界中对象的可视化表示,又称为概念模型或分析对象模型,它专注于分析问题领域本身,发掘重要的业务领域概念,并建立业务领域概念之间的关系. 贫血模型是指使用的领域对象中只有s ...
- 什么是领域模型(domain model)?贫血模型(anaemic domain model) 和充血模型(rich domain model)有什么区别
http://blog.csdn.net/helloboat/article/details/51208128 领域模型是领域内的概念类或现实世界中对象的可视化表示,又称为概念模型或分析对象模型,它专 ...
- Vue项目架构设计与工程化实践
摘自Berwin<Vue项目架构设计与工程化实践>github.com/berwin/Blog/issues/14 1.Vue依赖套件 vuex:项目复杂后,用vuex来管理状态 elem ...
- Go 语言优秀资源整理,为项目落地加速🏃
最后更新于2019.11.22 Go 语言优秀资源整理,为项目落地加速
- Redisson实现分布式锁(3)—项目落地实现
Redisson实现分布式锁(3)-项目落地实现 有关Redisson实现分布式锁前面写了两篇博客作为该项目落地的铺垫. 1.Redisson实现分布式锁(1)---原理 2.Redisson实现分布 ...
- SNF快速开发平台2019-用户安全控制-权限管理模型实践-权限都在这里
1.1 是否保存密码 勾选记住密码后,再次开启程序用户密码不需要再次输入,直接显示在密码输入框内,方便快捷. 图 4.1‑1 记住密码的登录页面框 1.2 是否自动登录 勾选自动登录后,再 ...
随机推荐
- 2022-08-17:以下go语言代码输出什么?A:运行时 panic;B:32;C:编译错误;D:0。 package main func main() { var x *struct {
2022-08-17:以下go语言代码输出什么?A:运行时 panic:B:32:C:编译错误:D:0. package main func main() { var x *struct { s [] ...
- 2021-10-16:单词拆分 II。给定一个非空字符串 s 和一个包含非空单词列表的字典 wordDict,在字符串中增加空格来构建一个句子,使得句子中所有的单词都在词典中。返回所有这些可能的句子。
2021-10-16:单词拆分 II.给定一个非空字符串 s 和一个包含非空单词列表的字典 wordDict,在字符串中增加空格来构建一个句子,使得句子中所有的单词都在词典中.返回所有这些可能的句子. ...
- 批处理bat and 汇编小技巧
来自远古时期的汇编软件MASM是没有像现在一样的exe程序的(汇编萌新的认知),所以为了快捷,写一些bat来方便操作.([表情包]奇怪的知识增加了)哈哈哈 我现在是MASM的路径是D:\Masm64\ ...
- postman接口关联-token值
背景: 在测试工作中,测试鉴权的接口需要用到登录接口的token,需要我们先调用登录接口,获得token,然后把即时获得的token填入请求中发送请求,我们可以用设置全局变量的办法解决这个问题 实 ...
- 自从用了 Kiali 以后才知道,配置 Istio 的 流量管理 是如此容易
在生产环境中,直接登录服务器是非常不方便的,我们可以使用Kiali配置Istio的流量管理. 本文以Istio官方提供的Bookinfo应用示例为例,使用Kiali配置Istio的流量管理.Booki ...
- 【RS】多光谱波段和全色波段的区别
<p><strong>1.全色波段(Panchromatic Band)</strong></p> 全色图像是单通道的(即单波段灰色影像),其中全色是指 ...
- CAPL 脚本基本语句
CAPL(Communication Access Programming Language)是一种用于汽车通信网络分析和仿真的脚本语言.以下是CAPL脚本的基本语句: 1.变量声明 variable ...
- JS工具函数
工具函数 用于工程化开发,记录,备用 返回 [min, max) 间的随机整数 /** 返回 [min, max) 间的随机整数 */ export function getRandom(min, m ...
- 衔尾法解决当无法使用空闲中断以及DMA中断时配置DMA接收串口不定长数据
[Ooonly新人贴]记录工作中遇到的问题,话不多说先上干货 问题:类似K线与蓝牙接收部门模块,要求由原来的接收串口中断改为DMA接收.据说要用到空闲中断与DMA中断,但是经仿真发现DMA每完成传输一 ...
- Python编程和数据科学中的机器学习:如何处理和可视化具有噪声和干扰的数据
目录 随着数据科学和机器学习的快速发展,处理和分析具有噪声和干扰的数据成为了一个日益重要的挑战.在数据科学和机器学习中,噪声和干扰通常来自于各种因素,例如随机性和非随机性,数据缺失,数据集中的错误或错 ...