DDD - 聚合与聚合根_如何理解 Respository与DAO

简介: DDD - 聚合与聚合根_如何理解 Respository与DAO

27cee23cc9134a878c2eacac11c1ff9c.png


Pre


通常情况,我们都会面临这样的一个问题: 架构图说的是一回事,代码说的却是另一回事 。 当然了这里面的影响因素很多,有一个原因就是某些约束没有在设计中体现出来,也就是说设计的表现力不够 , 而这些约束需要阅读代码才能够知道,这就增加了理解和使用这个组件的难度。


这个问题在基于数据建模的设计方法上比较明显, 举个例子:


DDD - 如何理解Entity与VO提到的购物场景 ,我们以数据驱动的方式来设计订单和产品表,

CREATE TABLE `order` (
 `rec_id` BIGINT(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
 `seller_id` BIGINT(11) NOT NULL COMMENT '卖家',
 `buyer_id` BIGINT(11) NOT NULL COMMENT '买家',
 `price` BIGINT(11) NOT NULL COMMENT '订单总价格,按分计算',
 ...
 PRIMARY KEY (`rec_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ;
CREATE TABLE `order_detail` (
 `rec_id` BIGINT(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
 `order_id` BIGINT(11) NOT NULL COMMENT '订单主键',
 `product_name` VARCHAR(50) COMMENT '产品名称',
 `product_desc` VARCHAR(200) COMMENT '产品描述',
 `product_price` BIGINT(11) NOT NULL COMMENT '产品价格,按分计算',
 ...
 PRIMARY KEY (`rec_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ;


从表关系上,只能知道order与order_detail是一对多的关系。


CREATE TABLE `product` (
 `rec_id` BIGINT(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
 `name` VARCHAR(50) COMMENT '产品名称',
 `desc` VARCHAR(200) COMMENT '产品描述',
 ...
 PRIMARY KEY (`rec_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ;
CREATE TABLE `product_comment` (
 `rec_id` BIGINT(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
 `product_id` BIGINT(11) NOT NULL COMMENT '产品',
 `cont` VARCHAR(2000) COMMENT '评价内容',
 ...
 PRIMARY KEY (`rec_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ;


从表关系上,也只能知道product与product_comment之间是一对多的关系


Question

Q: order与order_detail之间的关系与product与product_comment之间的关系是一样的吗 ?


这mmp, 单单从数据模型上完全区分不出来啊 ,那只能看下业务代码

@Service
@Transactional
public class OrderService {
 public void createOrder(Order order,List<OrderDetail> orderDetailList) throws Exception {
   // 保存订单
   // 保存订单详情
    }
  }
}
@Service
@Transactional
public class ProductService {
 public void createProduct(Product prod) throws Exception {
   // 保存产品
   }
 }
}


订单和订单明细是一起保存的,也就是说两者可以作为一个整体来看待 (这个整体就是我们说的聚合)

产品和产品评论之间并不能被看做一个整体,所以没有在一起进行操作

这层逻辑,光看上面的设计是看不出来的,只有看到代码了,才能理清这一层关系 , 无形中就增加了理解和使用难度。


「聚合」就是缓解这种问题的一种手段!


如何理解 聚合和聚合根

public class Artisan {
 public void say() {
   System.out.println("1");
   System.out.println("2");
 }
}


对于上面的代码,如何保障在多线程情况下1和2能按顺序打印出来?最简单的方法就是使用synchronized关键字进行加锁操作

public class Artisan {
 public synchronized void say() {
  System.out.println("1");
   System.out.println("2");
 }
}


synchronized保证了代码的原子性执行. 就像 事务保证了原子性操作一样。


但是,这和「聚合」有什么关系呢?


如果说,synchronized是多线程层面的锁;事务是数据库层面的锁,那么「聚合」就是业务层面的锁!


在业务逻辑上,有些对象需要保持操作上的原子性,否则就没有任何意义。这些对象就组成了「聚合」!


利用聚合解决业务上的原子性操作


对于上面的订单与订单详情,从业务上来看,订单与订单明细需要保持业务上的原子性操作


  • 订单必须要包含订单明细
  • 订单明细必须要属于某个订单
  • 订单和订单明细被视为一个整体,少了任何一个都没有意义

所以其对象模型可以表示为:

image.png


  • 订单和订单明细组成一个「聚合」
  • 订单是操作的主体,所以订单是这个「聚合」的「聚合根」
  • 所有对这个「聚合」的操作,只能通过「聚合根」进行


相应的,产品和产品评价就不构成「聚合」。虽然在表设计时,订单和订单明细的结构关系与产品与产品评价的结构关系是一样的!因为:

  • 虽然产品评价需要属于某个产品
  • 但是产品不一定就有产品评价
  • 产品评价可以独立操作

所以产品与产品评论的模型则可以表示为:


09c5d722995249c1bbd89d1f9670f70e.png62abcc2ea8e84883bb077b1f971593e2.png


  • 产品和产品评论是两个「聚合」
  • 产品评论通过productId与「产品聚合」进行关联

如何确定聚合和聚合根


对象在业务逻辑上是否需要保证原子性操作是确定聚合和聚合根的其中一个约束。

还有一个约束就是「边界」,即聚合多大才合适?过大的「聚合」会带来各种问题。

还是以锁举例,看下面的代码

public class Artisan{
 public synchronized void say() {
   System.out.println("0");
   System.out.println("1");
   System.out.println("2");
   System.out.println("4");
  }
}


只希望12能按顺序打印出来,而0和4没有这个要求!上面的代码能满足要求,但是影响了性能。优化方式是使用同步块,缩小同步范围:

public class Artisan{
 public void say() {
   System.out.println("0");
   synchronized(Locker.class){
     System.out.println("1");
     System.out.println("2");
   }
   System.out.println("4");
 }
}


「边界」就像上面的同步块一样,只将需要的对象组合成聚合!

假设上面的产品和产品评论构成了一个聚合!那会发生什么事情呢?当A,B两个用户同时对这个商品进行评论,A先开始评论,此时就会锁定该产品对象以及下面的所有评论,在A提交评论之前,B是无法操作这个产品对象的,显然这是不合理的。


Respository VS DAO


在理解了聚合之后,就可以很容易的区分Respository与DAO了

  • DAO是技术手段,Respository是抽象方式
  • DAO只是针对对象的操作,而Respository是针对「聚合」的操作


【DAO的操作方式】

@Service
@Transactional
public class OrderService {
 public void createOrder(Order order,List<OrderDetail> orderDetailList) throws Exception {
   Long orderId = orderDao.save(order);
   for(OrderDetail detail : orderDetailList) {
     detail.setOrderId(orderId);
     orderDetailDao.save(detail);
    }
  }
  }
}


  • 订单和和订单明细都有一个对应的DAO
  • 订单和订单明细的关系并没有在对象之间得到体现


【Respository的操作方式】

// 订单和订单明细构成聚合
public clas  Order{
   List<OrderDetail> itemLine; // 这里就保证了设计与编码的一致性
 ...
}
@Service
@Transactional
public class OrderService {
 public void createOrder(Order order) throws Exception {
 orderRespository.save(order);
 //or
 order.save(); // 内部调用orderRespository.save(this);
 }
}


当然,orderRespository的save方法中,可能还是数据库相关操作,但也可能是NoSql操作甚至内存操作。

相关文章
|
5月前
|
Java
Java 对象间关系(依赖、关联、聚合和组合)
面向对象设计 对象间关系:依赖、关联、聚合和组合,四种关系容易混淆。特别后三种,只是在语义上有所区别,所谓语义就是指上下文环境、特定情景等。 
153 1
|
7月前
|
Java
领域驱动设计(DDD)中的实体,值对象,和聚合
领域驱动设计(DDD)中的实体,值对象,和聚合
|
11月前
|
uml C++ 容器
「软件设计」UML中关联,聚合和组合的区别是什么?
「软件设计」UML中关联,聚合和组合的区别是什么?
|
11月前
|
uml C++ 容器
「软件设计」UML中关联,聚合和组合区别
「软件设计」UML中关联,聚合和组合区别
|
存储 设计模式 前端开发
浅谈DDD中的聚合
在我看来并不是MVC的基础上增加领域层,使用充血模型,解耦基础服务,我的代码就符合DDD了。
浅谈DDD中的聚合
|
JSON 前端开发 Java
DDD专题案例二《领域层决策规则树服务设计》
在上一章节介绍了领域驱动设计的基本概念以及按照领域驱动设计的思想进行代码分层,但仅仅只是从一个简单的分层结构上依然没法理解DDD以及如何去开发这样的微服务。另外往往按照这样分层后依然感觉和MVC也没有什么差别,也没有感受到带来什么非常大的好处。那么问题出在哪呢?我个人觉得DDD学起来更像是一套指导思想,不断的将学习者引入到领域触发的思维中去,而这恰恰也是最难学习的地方。时而感觉会了,而实际开发中又不对,本来已经拆解清晰了,但怎么又那么像MVC了。甚至怀疑自己,我在干嘛?
297 0
DDD专题案例二《领域层决策规则树服务设计》
|
存储 弹性计算 安全
DDD领域驱动设计实战-聚合(Aggregate)和聚合根(AggregateRoot)(下)
DDD领域驱动设计实战-聚合(Aggregate)和聚合根(AggregateRoot)
1010 0
|
测试技术 领域建模 微服务
DDD领域驱动设计实战-聚合(Aggregate)和聚合根(AggregateRoot)(上)
DDD领域驱动设计实战-聚合(Aggregate)和聚合根(AggregateRoot)
792 0
DDD领域驱动设计实战-聚合(Aggregate)和聚合根(AggregateRoot)(上)
|
SEO
聚合思想是什么?聚合页面有哪些类型?
做好了站内优化的基本步骤,接下来SEO的主要工作是挖掘长尾关键词、分布优化长尾关键词、发布内容、外链资源建设、优化URL等。 一段时间后,经过我们编辑的辛苦工作,我们网站上会存在很多内容了,成千上万篇,甚至更多。 我们说,一篇内容一般承载1-2个长尾关键词,如果我们发布了一万篇内容,这些内容就承载了1-2万个长尾关键词。
113 0
聚合(根)、实体、值对象精炼思考总结
原文:聚合(根)、实体、值对象精炼思考总结 1.      聚合根、实体、值对象的区别? 从标识的角度: 聚合根具有全局的唯一标识,而实体只有在聚合内部有唯一的本地标识,值对象没有唯一标识,不存在这个值对象或那个值对象的说法; 从是否只读的角度: 聚合根除了唯一标识外,其他所有状态信息都理论...
1300 0