通常情况,我们都会面临这样的一个问题: 架构图说的是一回事,代码说的却是另一回事 。 当然了这里面的影响因素很多,有一个原因就是某些约束没有在设计中体现出来,也就是说设计的表现力不够 , 而这些约束需要阅读代码才能够知道,这就增加了理解和使用这个组件的难度。
这个问题在基于数据建模的设计方法上比较明显, 举个例子:
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之间是一对多的关系
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是多线程层面的锁;事务是数据库层面的锁,那么「聚合」就是业务层面的锁!
在业务逻辑上,有些对象需要保持操作上的原子性,否则就没有任何意义。这些对象就组成了「聚合」!
对于上面的订单与订单详情,从业务上来看,订单与订单明细需要保持业务上的原子性操作:
所以其对象模型可以表示为:
相应的,产品和产品评价就不构成「聚合」。虽然在表设计时,订单和订单明细的结构关系与产品与产品评价的结构关系是一样的!因为:
所以产品与产品评论的模型则可以表示为:
对象在业务逻辑上是否需要保证原子性操作是确定聚合和聚合根的其中一个约束。
还有一个约束就是「边界」,即聚合多大才合适?过大的「聚合」会带来各种问题。
还是以锁举例,看下面的代码
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与DAO了
【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);
}
}
}
}
【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操作甚至内存操作。
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有