跳转到内容

聚合根

聚合根是聚合的根实体,是仓储操作和事务边界的入口点。它负责维护聚合内所有对象的一致性。

在 DDD 中,聚合(Aggregate)是一组相关对象的集合,这些对象作为一个整体来维护业务规则的一致性。聚合根是聚合的根实体,外部对聚合的所有访问都必须通过聚合根进行。

核心特点:

  • 聚合的唯一对外接口
  • 事务边界
  • 负责维护聚合内的不变量
  • 仓储只能操作聚合根

在 MiCake 中,聚合根需要继承 AggregateRoot<TKey> 基类:

using MiCake.DDD.Domain;
using System.Collections.Generic;
using System.Linq;
public class Order : AggregateRoot<int>
{
private readonly List<OrderItem> _items = new();
public string CustomerName { get; private set; }
public OrderStatus Status { get; private set; }
public decimal TotalAmount { get; private set; }
// 只暴露只读集合
public IReadOnlyCollection<OrderItem> Items => _items.AsReadOnly();
// 私有构造函数
private Order() { }
// 工厂方法
public static Order Create(string customerName)
{
var order = new Order
{
CustomerName = customerName,
Status = OrderStatus.Draft,
TotalAmount = 0
};
order.RaiseDomainEvent(new OrderCreatedEvent(order.Id, customerName));
return order;
}
// 聚合根负责管理聚合内的对象
public void AddItem(int productId, string productName, decimal price, int quantity)
{
if (Status != OrderStatus.Draft)
throw new DomainException("只能向草稿状态的订单添加商品");
var item = new OrderItem(productId, productName, price, quantity);
_items.Add(item);
RecalculateTotalAmount();
RaiseDomainEvent(new OrderItemAddedEvent(Id, productId, quantity));
}
public void RemoveItem(int productId)
{
if (Status != OrderStatus.Draft)
throw new DomainException("只能从草稿状态的订单中移除商品");
var item = _items.FirstOrDefault(x => x.ProductId == productId);
if (item != null)
{
_items.Remove(item);
RecalculateTotalAmount();
RaiseDomainEvent(new OrderItemRemovedEvent(Id, productId));
}
}
public void Confirm()
{
if (Status != OrderStatus.Draft)
throw new DomainException("只能确认草稿状态的订单");
if (!_items.Any())
throw new DomainException("订单至少需要一个商品项");
Status = OrderStatus.Confirmed;
RaiseDomainEvent(new OrderConfirmedEvent(Id, TotalAmount));
}
private void RecalculateTotalAmount()
{
TotalAmount = _items.Sum(item => item.Price * item.Quantity);
}
}
// 聚合内的实体
public class OrderItem : Entity<int>
{
public int ProductId { get; private set; }
public string ProductName { get; private set; }
public decimal Price { get; private set; }
public int Quantity { get; private set; }
private OrderItem() { }
internal OrderItem(int productId, string productName, decimal price, int quantity)
{
ProductId = productId;
ProductName = productName;
Price = price;
Quantity = quantity;
}
}
public enum OrderStatus
{
Draft,
Confirmed,
Paid,
Shipped,
Completed,
Cancelled
}
public class Product : AggregateRoot // 等同于 AggregateRoot<int>
{
public string Name { get; private set; }
public decimal Price { get; private set; }
public int Stock { get; private set; }
}
// 使用 GUID
public class Customer : AggregateRoot<Guid>
{
public string Name { get; private set; }
public string Email { get; private set; }
}
// 使用字符串
public class Tenant : AggregateRoot<string>
{
public string Name { get; private set; }
public bool IsActive { get; private set; }
}

聚合根应该封装聚合内的所有对象,外部不能直接修改它们:

public class Order : AggregateRoot<int>
{
private readonly List<OrderItem> _items = new();
// ✅ 正确:返回只读集合
public IReadOnlyCollection<OrderItem> Items => _items.AsReadOnly();
// ✅ 正确:通过聚合根的方法修改聚合内对象
public void AddItem(int productId, string productName, decimal price, int quantity)
{
var item = new OrderItem(productId, productName, price, quantity);
_items.Add(item);
RecalculateTotalAmount();
}
public void UpdateItemQuantity(int productId, int newQuantity)
{
var item = _items.FirstOrDefault(x => x.ProductId == productId);
if (item == null)
throw new DomainException("订单项不存在");
// 通过聚合根更新
item.UpdateQuantity(newQuantity);
RecalculateTotalAmount();
}
}
// ❌ 错误:暴露可修改的集合
public class Order : AggregateRoot<int>
{
public List<OrderItem> Items { get; set; } // 外部可以直接修改
}

聚合根负责维护聚合内的业务规则(不变量):

public class ShoppingCart : AggregateRoot<int>
{
private readonly List<CartItem> _items = new();
private const int MaxItemCount = 100;
private const decimal MaxTotalAmount = 50000m;
public IReadOnlyCollection<CartItem> Items => _items.AsReadOnly();
public decimal TotalAmount { get; private set; }
public void AddItem(int productId, string productName, decimal price, int quantity)
{
// 不变量 1:购物车商品数量限制
if (_items.Count >= MaxItemCount)
throw new DomainException($"购物车最多只能添加 {MaxItemCount} 个商品");
// 不变量 2:购物车总金额限制
var newTotalAmount = TotalAmount + (price * quantity);
if (newTotalAmount > MaxTotalAmount)
throw new DomainException($"购物车总金额不能超过 {MaxTotalAmount}");
// 不变量 3:每个商品只能添加一次
if (_items.Any(x => x.ProductId == productId))
throw new DomainException("该商品已在购物车中");
var item = new CartItem(productId, productName, price, quantity);
_items.Add(item);
TotalAmount = newTotalAmount;
}
}

聚合根可以触发领域事件来通知领域中发生的重要变化:

public class Order : AggregateRoot<int>
{
public OrderStatus Status { get; private set; }
public void Confirm()
{
Status = OrderStatus.Confirmed;
RaiseDomainEvent(new OrderConfirmedEvent(Id));
}
public void Pay(decimal amount, string paymentMethod)
{
if (Status != OrderStatus.Confirmed)
throw new DomainException("只能支付已确认的订单");
if (amount != TotalAmount)
throw new DomainException("支付金额不正确");
Status = OrderStatus.Paid;
RaiseDomainEvent(new OrderPaidEvent(Id, amount, paymentMethod));
}
public void Ship(string trackingNumber)
{
if (Status != OrderStatus.Paid)
throw new DomainException("只能发货已支付的订单");
Status = OrderStatus.Shipped;
RaiseDomainEvent(new OrderShippedEvent(Id, trackingNumber));
}
}

聚合应该尽可能小,只包含必须保持一致性的对象:

// ✅ 正确:小聚合
public class Order : AggregateRoot<int>
{
private readonly List<OrderItem> _items = new();
public IReadOnlyCollection<OrderItem> Items => _items.AsReadOnly();
// Order 和 OrderItem 必须保持一致性
}
// ❌ 错误:大聚合
public class Order : AggregateRoot<int>
{
public Customer Customer { get; set; } // Customer 应该是独立的聚合根
public List<OrderItem> Items { get; set; }
public Payment Payment { get; set; } // Payment 可能是独立的聚合根
public Shipment Shipment { get; set; } // Shipment 可能是独立的聚合根
}

聚合之间不应该直接持有对象引用,而是通过 ID 引用:

// ✅ 正确:通过 ID 引用
public class Order : AggregateRoot<int>
{
public int CustomerId { get; private set; } // 引用 Customer 聚合的 ID
public int ShippingAddressId { get; private set; }
}
// ❌ 错误:直接持有对象引用
public class Order : AggregateRoot<int>
{
public Customer Customer { get; set; } // 直接引用另一个聚合根
public Address ShippingAddress { get; set; }
}
public class Order : AggregateRoot<int>
{
private Order() { } // 私有构造函数
public static Order Create(int customerId, Address shippingAddress)
{
// 验证
if (customerId <= 0)
throw new DomainException("客户 ID 无效");
if (shippingAddress == null)
throw new DomainException("收货地址不能为空");
var order = new Order
{
CustomerId = customerId,
ShippingAddress = shippingAddress,
Status = OrderStatus.Draft,
CreatedAt = DateTime.UtcNow
};
order.RaiseDomainEvent(new OrderCreatedEvent(order.Id, customerId));
return order;
}
}

2. 使用私有集合和内部构造函数

Section titled “2. 使用私有集合和内部构造函数”
public class Order : AggregateRoot<int>
{
// 私有集合
private readonly List<OrderItem> _items = new();
// 只读集合对外暴露
public IReadOnlyCollection<OrderItem> Items => _items.AsReadOnly();
public void AddItem(int productId, string productName, decimal price, int quantity)
{
// 内部构造函数,只能通过聚合根创建
var item = new OrderItem(productId, productName, price, quantity);
_items.Add(item);
}
}
public class OrderItem : Entity<int>
{
// 内部构造函数
internal OrderItem(int productId, string productName, decimal price, int quantity)
{
ProductId = productId;
ProductName = productName;
Price = price;
Quantity = quantity;
}
}
public class Order : AggregateRoot<int>
{
public OrderStatus Status { get; private set; }
public void Confirm()
{
if (Status != OrderStatus.Draft)
throw new DomainException($"不能从 {Status} 状态确认订单");
Status = OrderStatus.Confirmed;
RaiseDomainEvent(new OrderConfirmedEvent(Id));
}
public void Pay(PaymentInfo paymentInfo)
{
if (Status != OrderStatus.Confirmed)
throw new DomainException($"不能从 {Status} 状态支付订单");
Status = OrderStatus.Paid;
RaiseDomainEvent(new OrderPaidEvent(Id, paymentInfo));
}
public void Cancel(string reason)
{
if (Status == OrderStatus.Shipped || Status == OrderStatus.Completed)
throw new DomainException($"不能取消 {Status} 状态的订单");
Status = OrderStatus.Cancelled;
RaiseDomainEvent(new OrderCancelledEvent(Id, reason));
}
}

聚合根是仓储操作的对象:

public class OrderService
{
private readonly IOrderRepository _orderRepository;
public async Task<int> CreateOrder(CreateOrderDto dto)
{
// 创建聚合根
var order = Order.Create(dto.CustomerId, dto.ShippingAddress);
// 添加订单项
foreach (var item in dto.Items)
{
order.AddItem(item.ProductId, item.ProductName, item.Price, item.Quantity);
}
// 持久化聚合根
await _orderRepository.AddAsync(order);
await _orderRepository.SaveChangesAsync(); // 领域事件在此时自动分发
return order.Id;
}
public async Task ConfirmOrder(int orderId)
{
// 加载聚合根
var order = await _orderRepository.FindAsync(orderId);
if (order == null)
throw new NotFoundException("订单不存在");
// 执行领域操作
order.Confirm();
// 保存更改
await _orderRepository.SaveChangesAsync();
}
}
特性聚合根实体
标识✅ 有✅ 有
可独立存在✅ 是❌ 否
仓储访问✅ 是❌ 否
事务边界✅ 是❌ 否
对外接口✅ 是❌ 否
  1. 聚合应该尽可能小:只包含必须一起修改的对象
  2. 一个事务只修改一个聚合根:跨聚合操作使用领域事件
  3. 通过 ID 引用其他聚合:不要直接持有聚合根引用
  4. 封装聚合内对象:外部只能通过聚合根访问
  5. 维护聚合不变量:确保聚合始终处于有效状态