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操作甚至内存操作。

相关文章
领域驱动设计(DDD)中的实体,值对象,和聚合
领域驱动设计(DDD)中的实体,值对象,和聚合
|
3月前
|
存储 测试技术 数据库
仓储设计实现问题之聚合实体在DDD中定义如何解决
仓储设计实现问题之聚合实体在DDD中定义如何解决
51 0
|
1月前
|
数据建模 程序员 数据库
领域设计之理解聚合与聚合根!
领域设计之理解聚合与聚合根!
领域设计之理解聚合与聚合根!
|
4月前
|
搜索推荐
领域驱动概念问题之在领域驱动设计中,聚合和实体分别是什么
领域驱动概念问题之在领域驱动设计中,聚合和实体分别是什么
|
3月前
|
容器
OOP 中的组合、聚合和关联
【8月更文挑战第22天】
40 0
|
存储 前端开发 关系型数据库
浅谈DDD中的聚合
在我看来并不是MVC的基础上增加领域层,使用充血模型,解耦基础服务,我的代码就符合DDD了。
浅谈DDD中的聚合
|
测试技术 领域建模 微服务
DDD领域驱动设计实战-聚合(Aggregate)和聚合根(AggregateRoot)(上)
DDD领域驱动设计实战-聚合(Aggregate)和聚合根(AggregateRoot)
932 0
DDD领域驱动设计实战-聚合(Aggregate)和聚合根(AggregateRoot)(上)
|
JSON 前端开发 Java
DDD专题案例二《领域层决策规则树服务设计》
在上一章节介绍了领域驱动设计的基本概念以及按照领域驱动设计的思想进行代码分层,但仅仅只是从一个简单的分层结构上依然没法理解DDD以及如何去开发这样的微服务。另外往往按照这样分层后依然感觉和MVC也没有什么差别,也没有感受到带来什么非常大的好处。那么问题出在哪呢?我个人觉得DDD学起来更像是一套指导思想,不断的将学习者引入到领域触发的思维中去,而这恰恰也是最难学习的地方。时而感觉会了,而实际开发中又不对,本来已经拆解清晰了,但怎么又那么像MVC了。甚至怀疑自己,我在干嘛?
332 0
DDD专题案例二《领域层决策规则树服务设计》
|
存储 弹性计算 安全
DDD领域驱动设计实战-聚合(Aggregate)和聚合根(AggregateRoot)(下)
DDD领域驱动设计实战-聚合(Aggregate)和聚合根(AggregateRoot)
1168 0
|
Java 程序员 测试技术
深入理解领域驱动设计中的聚合
聚合模式是 DDD 的模式结构中较为难于理解的一个,也是 DDD 学习曲线中的一个关键障碍。合理地设计聚合,能清晰地表述业务一致性,也更容易带来清晰的实现,设计不合理的聚合,甚至在设计中没有聚合的概念,则相反。
1053 0
深入理解领域驱动设计中的聚合