在 CQRS 架构篇提到,由于 Command 和 Query 内部驱动力完全不同,需要在架构层就进行分离,但其中有个一个原则极为重要:
站在用户的角度思考问题,与客户深入沟通,找到通城网站设计与通城网站推广的解决方案,凭借多年的经验,让设计与互联网技术结合,创造个性化、用户体验好的作品,建站类型包括:网站建设、成都网站制作、企业官网、英文网站、手机端网站、网站推广、国际域名空间、雅安服务器托管、企业邮箱。业务覆盖通城地区。
可见 Command 远比 Query 棘手的多,其中最关键的便是使用哪种模式来承载业务?
最常见的业务承载模式有:
事务脚本 和 领域模型 都是承载业务的不同模型,都有其适合的场景,没有绝对的对和错。核心的决策依据只有一个:选择最合适的业务场景即可。
简单且直观的对两者进行区分:
大家最常听说也是最反感的便是:被别人称为 CRUD boy,更多时候说的便是 事务脚本。
事务脚本(Transaction Script)是一种应用程序架构模式,主要用于处理简单的业务场景。它将业务逻辑和数据库访问紧密耦合在一起,以便实现对数据的操作。
事务脚本,将整个业务逻辑封装在一个事务中,借助数据库事务来满足业务操作的 ACID 特性。通过将逻辑和事务封装在一起,从而简化应用程序的处理和开发。
下图是基于事务脚本的生单流程:
图片
简单描述就是:将“脚本”(SQL)进行打包,然后放在一个“事务”中运行。这也就是“事务脚本”命名的由来。
接下来,看一个订单改价流程:
图片
和生单流程基本一致,在此不做过多介绍。
领域驱动设计(Domain-Driven Design,DDD)是应对复杂业务场景的利器,它是对业务领域中的关键概念和业务规则的抽象。领域模型是一个对象模型,它主要描述各领域对象之间的关系和行为。
和事务脚本不同,领域模型使用对象来承载业务逻辑,领域模型的设计基于业务领域知识,强调领域专家的参与,以提高软件系统的质量和开发效率。
下图是基于领域模型的生单流程:
图片
简单描述就是:核心业务逻辑全部由对象实现(addItems方法),数据库仅做数据存储。
接下来,看下基于DDD的订单改价流程:
图片
和生单流程基本一致,核心逻辑由 Order 的 modify price 实现。
相比之下,领域模型就复杂太多,它由多个实体 (Entity)、值对象 (Value Object)、聚合 (Aggregate)、领域服务 (Domain Service)、工厂 (Factory) 等组成,它们共同构成了领域对象模型。在模型中,实体和值对象表示业务中的实际对象,聚合是由多个高内聚实体和值对象形成的组合提,领域服务表示不属于任何一个实体或值对象的操作,工厂则用于创建复杂的对象,比如实体和值对象等。
两者都是承载业务逻辑的架构,但区别巨大:
除此之外,DDD 还有很多的特点,比如:
对于程序员来说,文字显得不够直观,在此我们通过代码来体验下两者的不同。
为了更好的体现两者的区别,将会从两个场景进行对比:
在日常开发中,物理删除场景用的非常少,甚至很多公司都明令禁止使用“delete”语句。通常使用 “逻辑删除” 替代,它可归属为标准的更新场景,在此暂不对比 物理删除场景。
在电商中,一个标准的下单需求主要包括:
核心代码如下:
@Transactional
public void createOrder(CreateOrderCommand createOrderCommand) {
// 1. 库存校验
for (OrderItemDTO itemDTO : createOrderCommand.getItems()) {
Integer stock = inventoryMapper.getStock(itemDTO.getProductId());
if (stock < itemDTO.getQuantity()) {
throw new IllegalStateException("库存不足");
}
}
// 2. 锁定库存
for (OrderItemDTO itemDTO : createOrderCommand.getItems()) {
inventoryMapper.lockStock(itemDTO.getProductId(), itemDTO.getQuantity());
}
// 3. 生成订单项
List items = createOrderCommand.getItems().stream()
.map(OrderItem::create)
.collect(Collectors.toList());
orderItemMapper.createOrderItems(items);
// 4. 生成订单
Long totalPrice = items.stream()
.mapToLong(OrderItem::getPrice)
.sum();
Order order = new Order(createOrderCommand.getUserId(), totalPrice, OrderStatus.CREATED);
orderMapper.createOrder(order);
}
事务脚本与需求所需操作流程完全一致,简单来说就是使用“编程语言”对需求进行了翻译。
核心代码如下:
public void createOrder(CreateOrderCommand createOrderCommand) {
// 1. 检查库存,如果足够则进行锁定;如果不够,则抛出异常
this.inventoryService.checkIsEnoughAndLock(createOrderCommand.getItems());
// 2. 创建 Order 聚合,此处使用静态工厂创建复杂的 Order 对象
Order order = Order.create(createOrderCommand);
// 3. 保存 Order 聚合, @Transactional 在OrderRepository上
this.orderRepository.save(order);
}
public class Order {
private Long id;
private Long userId;
private Long totalSellingPrice = 0L;
private Long totalPrice = 0L;
private OrderStatus status;
private List orderItems = new ArrayList<>();
// 避免外部调用
private Order(Long userId) {
this.userId = userId;
}
// 静态工厂,封装复杂的 Order 创建逻辑,并保障创建的 Order 对象是有效的
public static Order create(CreateOrderCommand createOrderCommand) {
Order order = new Order(createOrderCommand.getUserId());
order.addItems(createOrderCommand.getItems());
order.init();
return order;
}
// 添加 OrderItem,并计算总金额
private void addItems(List items) {
if (!CollectionUtils.isEmpty(items)){
items.forEach(item ->{
OrderItem orderItem = OrderItem.create(item);
this.orderItems.add(orderItem);
this.totalPrice += item.getPrice();
});
}
this.totalPrice = totalSellingPrice;
}
// 设置状态完成对象的初始化
private void init() {
this.status = OrderStatus.CREATED;
}
}
和事务脚本相比,由以下几点不同:
在电商中,订单改价主要包括:
核心代码如下:
@Transactional
public void changeOrderPrice(Long orderId, Long newPrice) {
// 1. 校验金额
if (newPrice <= 0) {
throw new IllegalArgumentException("金额必须大于0");
}
// 校验订单有效性
Order order = orderMapper.getOrderById(orderId);
if (order == null) {
throw new IllegalArgumentException("订单不存在");
}
// 2. 对订单项价格进行均摊
allocateDiscount(order, order.getTotalPrice() - newPrice);
// 3. 修改订单价格
order.setTotalPrice(newPrice);
orderMapper.updateOrder(order);
}
public void allocateDiscount(Order order, Long discount) {
if (discount == 0){
return;
}
List items = this.orderItemMapper.getByOrderId(order.getId());
Long totalAmount = order.getTotalPrice();
Long allocatedDiscount = 0L;
for (int i = 0; i < items.size(); i++) {
OrderItem item = items.get(i);
Long itemAmount = item.getSellingPrice();
if (i != items.size() - 1) {
// 按比例进行均摊
Long itemDiscount = itemAmount / totalAmount * discount;
// 重新设置金额
item.setPrice(item.getPrice() - itemDiscount);
// 记录累加金额
allocatedDiscount += itemDiscount;
}else {
// 分摊余下的优惠金额到最后一个订单
Long lastItemDiscount = discount - allocatedDiscount;
item.setPrice(item.getPrice() - lastItemDiscount);
}
// 更新数据库
this.orderItemMapper.update(item);
}
}
和所描述的操作流程完全一致,成功使用“编程语言”完成了对需求的翻译。
核心代码如下:
@Transactional
public void changeOrderPrice(Long orderId, Long newPrice) {
// 1. 校验金额
if (newPrice <= 0) {
throw new IllegalArgumentException("金额必须大于0");
}
// 2. 获取订单聚合根
Optional orderOpt = this.orderRepository.getById(orderId);
Order order = orderOpt.orElseThrow(() -> new IllegalArgumentException("订单不存在"));
// 3. 修改价格
order.changePrice(newPrice);
// 4. 保存 Order 聚合
this.orderRepository.save(order);
}
// Order 聚合根内方法
public void changePrice(Long newPrice) {
if (newPrice <= 0) {
throw new IllegalArgumentException("金额必须大于0");
}
long discount = getTotalPrice() - newPrice;
if (discount == 0){
return;
}
// Item 均摊折扣
discountForItem(discount);
// Order 折扣
discountForOrder(discount);
}
// Item 均摊
private void discountForItem(long discount) {
Long totalAmount = getTotalPrice();
Long allocatedDiscount = 0L;
for (int i = 0; i < getOrderItems().size(); i++) {
OrderItem item = getOrderItems().get(i);
Long itemAmount = item.getSellingPrice();
if (i != getOrderItems().size() - 1) {
// 按比例进行均摊
Long itemDiscount = itemAmount / totalAmount * discount;
// 重新设置金额
item.setPrice(item.getPrice() - itemDiscount);
// 记录累加金额
allocatedDiscount += itemDiscount;
}else {
// 分摊余下的优惠金额到最后一个订单
Long lastItemDiscount = discount - allocatedDiscount;
item.setPrice(item.getPrice() - lastItemDiscount);
}
}
}
// Order 折扣
private void discountForOrder(long discount) {
Long newTotalPrice = getTotalPrice() - discount;
setTotalPrice(newTotalPrice);
}
和生单流程一样:
看过这两种风格代码有什么感觉?你可能会说代码也没少些什么,只是组织方式发生了变化。
确实是,只是组织方式发生变化,代码一行都没少。这是这点变化,带来了革命的创新。
来看个新的场景:业务改价过于随意,产品想增加一个环节:填入改价金额后,先把每个订单项的均摊价格展示出来,确认无误后在提交改价请求。
在不同的模式下,又该怎么解呢?
事务脚本 和 领域模型 是承载业务的不同模式,都有各自适用的场景,需要根据自己的需求进行选择。
事务脚本:流程 + 数据,在操作流程中对数据进行操作;
领域模型:编排 + 模型 + 数据,基于模型能力进行编排,以完成业务操作;操作结果暂存于对象中,最后将其同步到数据库;
DDD 灵活性还体现在:
想了解 DDD 的精髓,让我们进入下一篇:初识极简DDD。
网站标题:DDD 对决:事务脚本 vs. 领域模型,哪个才是业务优化的终极方案?
标题URL:http://www.shufengxianlan.com/qtweb/news8/502958.html
网站建设、网络推广公司-创新互联,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等
声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联