一聚教程网:一个值得你收藏的教程网站

最新下载

热门教程

DDD 聚合根 + 工厂模式:你的领域建模为何一改就崩

时间:2026-06-16 09:06:26 编辑:袖梨 来源:一聚教程网

DDD 聚合根 + 工厂模式:你的领域建模为什么一改就崩

我之前接手过一个订单系统,4 个开发一起写的,跑了 2 年。某天产品说"加个拼团功能",我看着代码结构,改了 23 个文件才把一个简单的"拼团订单"塞进去

DDD 聚合根 + 工厂模式:你的领域建模为什么一改就崩

复盘时老板问我为什么这么慢。我指着代码说:"这不是订单系统,是一坨面条。"

整个订单系统里,没有聚合根、没有工厂、没有领域事件,全是 Service 层堆业务逻辑。Service 调 Service,方法套方法,500 行的 OrderService.createOrder() 是常事。

这种代码的根本问题不是"烂",是设计模式的彻底缺位。本文用一个真实的订单中心重构案例,讲清楚 DDD 聚合根 + 工厂模式怎么落地。

你写的不是订单中心,是数据库的搬运工

先看你大概率写过的代码:

@Service
public class OrderService {
    @Autowired
    private OrderRepository orderRepository;
    @Autowired
    private OrderItemRepository itemRepository;
    @Autowired
    private StockService stockService;
    @Autowired
    private CouponService couponService;
    @Autowired
    private PaymentService paymentService;
    @Autowired
    private UserService userService;
    
    public Long createOrder(CreateOrderRequest req) {
        // 1. 校验用户
        User user = userService.getById(req.getUserId());
        if (user == null) throw new RuntimeException("用户不存在");
        
        // 2. 校验库存
        for (OrderItemDTO item : req.getItems()) {
            Stock stock = stockService.getBySkuId(item.getSkuId());
            if (stock.getCount() < item.getCount()) {
                throw new RuntimeException("库存不足");
            }
        }
        
        // 3. 校验优惠券
        if (req.getCouponId() != null) {
            Coupon coupon = couponService.getById(req.getCouponId());
            if (!coupon.isValid()) throw new RuntimeException("优惠券无效");
        }
        
        // 4. 算价格
        BigDecimal totalPrice = BigDecimal.ZERO;
        for (OrderItemDTO item : req.getItems()) {
            Product product = productService.getById(item.getSkuId());
            totalPrice = totalPrice.add(product.getPrice().multiply(BigDecimal.valueOf(item.getCount())));
        }
        if (req.getCouponId() != null) {
            totalPrice = couponService.applyDiscount(req.getCouponId(), totalPrice);
        }
        
        // 5. 扣库存
        for (OrderItemDTO item : req.getItems()) {
            stockService.decrease(item.getSkuId(), item.getCount());
        }
        
        // 6. 创建订单
        Order order = new Order();
        order.setUserId(req.getUserId());
        order.setStatus(OrderStatus.PENDING);
        order.setTotalPrice(totalPrice);
        orderRepository.save(order);
        
        // 7. 创建订单项
        for (OrderItemDTO item : req.getItems()) {
            OrderItem orderItem = new OrderItem();
            orderItem.setOrderId(order.getId());
            orderItem.setSkuId(item.getSkuId());
            orderItem.setCount(item.getCount());
            orderItem.setPrice(...);
            itemRepository.save(orderItem);
        }
        
        // 8. 创建支付单
        Payment payment = new Payment();
        payment.setOrderId(order.getId());
        payment.setAmount(totalPrice);
        paymentService.create(payment);
        
        return order.getId();
    }
}

150 行业务逻辑,3 个 if,5 个 for,没有任何抽象。这就是典型的"贫血模型 + 事务脚本"反模式。

加个拼团功能?恭喜你,在 createOrder() 里再加 50 行 if 吧,订单类型再多几种就破 500 行了。这就是"一改就崩"的根因——所有逻辑堆在一个方法里,没有边界。

DDD 聚合根是什么

聚合根(Aggregate Root)是 DDD 里的核心概念:一组相关对象的"老板",外部只能通过它来访问内部对象

拿订单来说,一个聚合根包含:

  • Order(聚合根):订单本身
  • OrderItem(实体):订单项
  • OrderAddress(值对象):收货地址
  • OrderDiscount(值对象):优惠信息
  • Payment(实体?):支付单(视情况决定是否在订单聚合内)

外部不能直接修改 OrderItem,必须通过 Order 提供的领域方法

public class Order {
    private Long id;
    private Long userId;
    private OrderStatus status;
    private List<OrderItem> items;
    private OrderAddress address;
    private Money totalPrice;
    private List<DomainEvent> events = new ArrayList<>();
    
    // 工厂方法:创建订单
    public static Order create(CreateOrderCommand cmd, PricingService pricingService) {
        // 聚合根内部封装创建逻辑
        Order order = new Order();
        order.userId = cmd.getUserId();
        order.status = OrderStatus.PENDING;
        order.items = cmd.getItems().stream()
            .map(item -> OrderItem.create(item))
            .collect(Collectors.toList());
        order.address = OrderAddress.create(cmd.getAddress());
        order.totalPrice = pricingService.calculate(order.items);
        order.events.add(new OrderCreatedEvent(order));
        return order;
    }
    
    // 领域方法:添加订单项
    public void addItem(OrderItem item) {
        if (this.status != OrderStatus.PENDING) {
            throw new DomainException("只能修改待支付订单");
        }
        this.items.add(item);
        this.totalPrice = this.totalPrice.add(item.getSubtotal());
    }
    
    // 领域方法:支付
    public void pay(Payment payment) {
        if (this.status != OrderStatus.PENDING) {
            throw new DomainException("订单状态异常: " + this.status);
        }
        if (!payment.getAmount().equals(this.totalPrice)) {
            throw new DomainException("支付金额不符");
        }
        this.status = OrderStatus.PAID;
        this.events.add(new OrderPaidEvent(this));
    }
}

所有业务规则在 Order 内部校验,外部 Service 只负责协调(事务、事件发布、跨聚合调用)。

工厂模式怎么落地

普通订单、拼团订单、预售订单——创建逻辑各不相同,但调用方不应该知道这些差异。这就是工厂模式的用武之地。

public interface OrderFactory {
    Order create(CreateOrderCommand cmd);
    boolean supports(OrderType type);
}@Component
public class NormalOrderFactory implements OrderFactory {
    @Override
    public boolean supports(OrderType type) {
        return type == OrderType.NORMAL;
    }
    
    @Override
    public Order create(CreateOrderCommand cmd) {
        // 普通订单的创建逻辑
        Order order = new Order();
        // ...
        return order;
    }
}@Component
public class GroupBuyOrderFactory implements OrderFactory {
    @Override
    public boolean supports(OrderType type) {
        return type == OrderType.GROUP_BUY;
    }
    
    @Override
    public Order create(CreateOrderCommand cmd) {
        // 拼团订单的创建逻辑
        Order order = new GroupBuyOrder();  // 继承自 Order
        order.setGroupId(cmd.getGroupId());
        order.setExpireAt(LocalDateTime.now().plusHours(2));
        // ...
        return order;
    }
}

调用方只看到一个统一的入口

@Component
public class OrderFactoryRegistry {
    private final List<OrderFactory> factories;
    
    public OrderFactoryRegistry(List<OrderFactory> factories) {
        this.factories = factories;
    }
    
    public Order createOrder(CreateOrderCommand cmd) {
        return factories.stream()
            .filter(f -> f.supports(cmd.getOrderType()))
            .findFirst()
            .orElseThrow(() -> new DomainException("不支持的订单类型: " + cmd.getOrderType()))
            .create(cmd);
    }
}

这是策略模式 + 工厂模式 + 依赖注入的组合应用。Spring 帮你把 List<OrderFactory> 注入进来,你不用手动注册。

领域事件 + 观察者模式:解耦副作用

加新功能时,最痛苦的是改一个地方要动 23 个文件。领域事件 + 观察者模式能彻底解决这个问题

public abstract class AggregateRoot {
    private List<DomainEvent> events = new ArrayList<>();
    
    protected void raiseEvent(DomainEvent event) {
        this.events.add(event);
    }
    
    public List<DomainEvent> getDomainEvents() {
        return Collections.unmodifiableList(events);
    }
    
    public void clearEvents() {
        this.events.clear();
    }
}

聚合根在状态变化时发布事件:

public class Order extends AggregateRoot {
    public void pay(Payment payment) {
        // ... 业务校验
        this.status = OrderStatus.PAID;
        raiseEvent(new OrderPaidEvent(this.id, this.totalPrice));  // 发布事件
    }
}

应用层捕获事件并分发:

@Service
public class OrderService {
    @Autowired
    private OrderRepository orderRepository;
    @Autowired
    private ApplicationEventPublisher springPublisher;
    
    @Transactional
    public Long createOrder(CreateOrderCommand cmd) {
        Order order = factoryRegistry.createOrder(cmd);
        orderRepository.save(order);
        
        // 提交事务后发布领域事件
        TransactionSynchronizationManager.registerSynchronization(
            new TransactionSynchronizationAdapter() {
                @Override
                public void afterCommit() {
                    order.getDomainEvents().forEach(springPublisher::publishEvent);
                    order.clearEvents();
                }
            }
        );
        
        return order.getId();
    }
}

事件订阅方各做各的事

@Component
public class StockEventHandler {
    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
    public void handle(OrderPaidEvent event) {
        // 扣库存
        stockService.decrease(event.getOrderId());
    }
}@Component
public class CouponEventHandler {
    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
    public void handle(OrderPaidEvent event) {
        // 标记优惠券已使用
        couponService.markUsed(event.getOrderId());
    }
}@Component
public class NotificationEventHandler {
    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
    public void handle(OrderPaidEvent event) {
        // 发通知
        notificationService.sendOrderPaid(event.getOrderId());
    }
}

加新功能只需要加一个 @Component 事件订阅者,Order 本身一行代码不用改。这就是观察者模式的威力。

重构前后对比

维度重构前重构后
加拼团功能要改的文件23 个1 个(新工厂)+ 1 个(新事件订阅者)
业务校验分散在5 个 ServiceOrder 聚合根内部
单元测试必须 mock 5 个 Service直接测 Order 领域方法
业务规则变更全局搜索 if 改 if改 Order 一个方法
代码总行数5000+1800(含注释和测试)

代码量减少了 60%,但加新功能的速度快了 3 倍。这就是 DDD + 工厂模式 + 观察者模式的真实价值。

你可能踩的 3 个坑

坑 1:聚合根切太大

把 Order、Payment、Refund、AfterSale 全塞进一个聚合根。每次下单要锁整个聚合,高并发下变成单线程

正确做法:Order 聚合只包含创建订单必需的对象,Payment 是另一个聚合,Refund 是另一个聚合。它们之间通过领域事件解耦。

坑 2:领域贫血

聚合根只有 getter/setter,没有业务方法。order.setStatus(PAID) 满天飞。

正确做法:所有状态变更走领域方法order.pay(payment) 而不是 order.setStatus(PAID)。领域方法里做校验、记录事件、调用其他领域方法。

坑 3:工厂模式滥用

每个对象都套个工厂。UserFactoryProductFactoryAddressFactory...

正确做法:只有创建逻辑复杂、多种变体、需要依赖注入的才用工厂。简单的对象 new 一个就行。工厂不是越多越好。

一句话总结

DDD 聚合根 + 工厂模式 + 领域事件不是"装 X 用的高级货",是解决"加功能要改 23 个文件"这个真实痛点的工程方案

如果你正在维护一个"改不动"的老系统,试试从最大的 Service 抽一个聚合根开始重构。先让一个对象有"自己的行为",再谈模式。一个能落地的工厂,比 10 个写得像教科书的工厂强

下次写订单系统之前先问自己:

  1. 哪些对象属于同一个聚合?谁是聚合根?
  2. 创建逻辑有几种变体?工厂要不要按类型分发?
  3. 状态变更要不要发事件?哪些副作用要解耦?

答不上来就先别写——回去看看订单领域到底在干什么。

热门栏目