15|聚合的实现(上):怎样对聚合进行封装?

2023-01-07 钟敬
《手把手教你落地 DDD》
课程介绍


讲述:钟敬

时长:大小14.06M


你好,我是钟敬。
上节课我们通过为员工技能、工作经验等实体建立领域模型,学习了聚合的概念。接下来三节课,我们会以员工聚合为例,学习聚合的实现。
上节课我们讲过,聚合的一个主要特征是具有不变规则。而维护不变规则的前提是要做好对聚合的封装,否则,外部的对象就可能无意间破坏聚合内部的规则。
在上个迭代,我们已经通过组织(Org)对象学习了对单个对象的封装。而聚合是一组对象,那么封装的方法又有什么不同之处呢?这就是我们这一节课的重点。
下面,我们通过新增员工聚合的功能来讨论对聚合的封装。

数据库设计

我们先为员工聚合中新增的实体设计数据库表。回忆一下领域模型,如下图。

展开全文
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。

精选留言

  • 邓西
    2023-01-30 来自四川
    1. 对象间的关系。前文已经给到:聚合是一组对象的封装。 2. 个人理解,是对运行效率(考虑io开销)和有限内存的balance,聚合内部各对象互访频繁(也算是一种局部性原理)直接加载至内存中提升运算效率;而聚合间使用id则可以利用DB的索引提升查询效率。

    作者回复: 回答得很好。

    
    9
  • 燃
    2023-01-11 来自浙江
    1对象之间的关系 2都可以封装成对象关系当然好,但是会遇到对象膨胀的问题,用id关联可以控制对象膨胀。我这里提个问题,业务中多个流程都修改同一个大对象,如果大对象数据有问题排查异常困难,这种问题如何解决

    作者回复: 大对象本身就是问题,最好尽量把大对象拆成若干小对象。

    
    5
  • Geek_8ac303
    2023-01-08 来自广东
    针对思考题2有个疑问: 课程里的程序在聚合内部使用对象导航,会存在一个性能问题,就是技能和工作经验,在员工详情页是必须显示的,但是在列表页一般都不显示,即使用了jpa的懒加载还存在无法批量查询,性能下降的问题。在国内的数据量级OneToOne、ManyToMany这种自动化获取关联数据用法并不常用

    作者回复: 确实有这个问题。会在第三迭代的CQRS解决。

    
    3
  • 张强
    2023-01-07 来自广东
    1. 聚合是对象之间的关系。 2.能够想到的是性能。 如果聚合间也是用对象导航,就要把所有关联说一句查出来,会涉及很多表。有可能一次操作根本没有另一个聚合什么事,而需要把它查出来,就好多余的操作。

    作者回复: 差不多。第二点主要是性能和方便性的权衡。

    
    3
  • Jxin
    2023-01-13 来自山东
    由于领域对象之间难以通过导航来协作,所以对象内部能实现的领域逻辑就很有限了,大量的逻辑就要在领域服务中实现。所以这种方式下,多数聚合都至少要搭配一个自己的领域服务。 这块理解差异很大。 虽然我们判断一段逻辑放领域服务还是应用层确实会参考是单聚合根实例的操作还是多聚合根实例。 但是本质上领域服务还是来自业务专家的认知,比如拼车功能,合包逻辑等等。它与聚合根是平级对等的。在领域建模时,我们会省掉那些复杂关系和注释 规则。但领域服务和关键领域事件是记录的。 因为这个图是与业务沟通的桥梁,你必然会有些功能不属于任何聚合根,无法仅用聚合根就承接住它们。那么你就需要这些模型来和业务方达成一致。

    作者回复: 第一段讲到不是判断逻辑放在领域服务还是应用层,而是放在领域服务还是领域对象。

    
    2
  • 6点无痛早起学习的和...
    2023-02-07 来自北京
    这里有 2 个代码问题请教 1. 为什么方法参数用包装类型,不担心传 null 的问题吗?是否用基本类型更好一点,这里有什么权衡吗? 2. 方法参数用包装类型,代码里判断相等用 == 隐患可能较高,比如在 Emp 类里

    作者回复: 1 主要权衡的是另一个层面的空值问题。数据库里有整型空字段的话,正好可以和程序里包装类型的null对应。如果用基本类型,就要用一个特殊值代表空值。这么做的代价是略微影响性能。这只是我的习惯,你也可以不这么做。 2 我的程序里有用==吗?麻烦指出是哪一处,可能我疏忽了。

    共 2 条评论
    1
  • aoe
    2023-01-09 来自浙江
    两个疑问: 1. skill 表中 Level 首字母是大写,是有特殊原因吗(没有见到其他的大写字母)? 2. 聚合根对非聚合根的封装示例代码中操作 skills 相关的 3 个方法(getSkill、getSkills、addSkill)都是操作内存数据, 不会持久化到数据库,服务一重启,所有员工的技能都没了,这里只是展示一下“对象关联”的写法吗? ```java public class Emp extends AuditableEntity { // other fields ... private List<Skill> skills; // 读写 // constructors and other getters and setters ... public Optional<Skill> getSkill(Long skillTypeId) { return skills.stream() .filter(s -> s.getSkillTypeId() == skillTypeId) .findAny(); } public List<Skill> getSkills() { return Collections.unmodifiableList(skills); } void addSkill(Long skillTypeId, SkillLevel level , int duration, Long userId) { Skill newSkill = new Skill(tenantId, skillTypeId , LocalDateTime.now(), userId); newSkill.setLevel(level); newSkill.setDuration(duration); skills.add(newSkill); } // 对 experiences、postCodes 进行类似的处理 ... } ```
    展开

    作者回复: 1. 没有特殊原因,是个笔误,谢谢捉虫,我回头改过来 :) 2. 这一步先在内存操作,后面课程会进一步把内存中的数据保存到数据库。

    共 4 条评论
    1
  • bin
    2023-01-09 来自广东
    对于Emp这个聚合根来说,skills和experiences是实体还是值对象?看着像值对象,但是它们又有各自的业务规则。

    作者回复: 这里没有把skill和experience建模为值对象,因为允许修改。另一方面,值对象也是可以而且应该有业务规则,等下一部分详细讲值对象的时候,再细聊。

    
    1
  • 小鱼儿吐泡泡
    2023-01-07 来自广东
    1. 我理解是类对象之间的关系; =》 之前文中提到UML图, 表示就是类之间关系 2. 性能 对于思考题2,我有个疑问? 1. 假设现在有个界面,需要查询所有的员工列表,那么需要加载出整个页相关的员工聚合吗? 这会涉及很多表的读写,是否真的有必要?一定要符合DDD的模式?或者有什么更好的方式吗? 2. 对于聚合来讲,这个是事务操作的基本单位; 比如说我要添加技能,持久化时 - 全部持久化; 性能较差 - 仅持久化差异【技能】; ==》 性能较好,但是需要增加很多工作量 希望老师帮忙解答下? 实际落地中怎么取舍? 感觉实际DDD落地时,对于非JAVA项目,落地需要很多额外的工具支持,或者特定化开发

    作者回复: 关于您的第一个问题,答案是CQRS,将在第三个迭代讨论。 关于您的第二个问题,在后面的课程里有一个方法对差异进行持久化,确实会增加些工作量。在性能不太敏感的场景,全部持久化也可以考虑。

    共 3 条评论
    1
  • 打码的土豆
    2023-11-21 来自浙江
    这里的skill的构造方法和setter方法都是包级别私有,那如果需要批量输入skill列表,emp里需要怎么处理

    作者回复: 您说的pill输入skill列表具体指什么?

    
    