请设计一个支持事件溯源的聚合,并针对该聚合编写单元测试,确保其在各种复杂场景下的正确性。

设计支持事件溯源的聚合及单元测试

1. 聚合设计

假设我们正在设计一个电子商务系统,其中一个核心的聚合根是Order(订单)。这个Order聚合支持事件溯源,每个操作(如创建订单、添加商品、支付订单等)都会产生一个事件,这些事件会记录下来,从而可以重放事件以重建聚合的状态。

1.1 基础结构

首先,定义一个基类或接口来表示聚合根:

public abstract class AggregateRoot
{
    private List<IDomainEvent> _uncommittedChanges;

    public Guid Id { get; protected set; }

    protected AggregateRoot()
    {
        _uncommittedChanges = new List<IDomainEvent>();
    }

    protected void Apply(IDomainEvent @event)
    {
        @event.AggregateId = Id;
        ApplyChange(@event, true);
    }

    private void ApplyChange(IDomainEvent @event, bool isNew)
    {
        if (isNew)
        {
            _uncommittedChanges.Add(@event);
        }

        When((dynamic)@event);
    }

    protected abstract void When(IDomainEvent @event);
}

1.2 具体聚合根实现

接下来,我们实现具体的聚合根Order:

public class Order : AggregateRoot
{
    public Order()
    {
        ApplyChange(new OrderCreated(Id));
    }

    public List<OrderItem> Items { get; private set; } = new List<OrderItem>();
    public OrderStatus Status { get; private set; } = OrderStatus.Created;

    public void AddItem(OrderItem item)
    {
        Apply(new ItemAdded(item.ProductId, item.Quantity));
    }

    public void PayOrder()
    {
        if (Status != OrderStatus.Created)
            throw new InvalidOperationException("Order must be created to be paid.");

        Apply(new OrderPaid());
    }

    protected override void When(IDomainEvent @event)
    {
        switch (@event)
        {
            case OrderCreated e:
                Status = OrderStatus.Created;
                break;
            case ItemAdded e:
                Items.Add(new OrderItem(e.ProductId, e.Quantity));
                break;
            case OrderPaid e:
                Status = OrderStatus.Paid;
                break;
        }
    }
}

2. 单元测试

为了确保Order聚合在各种复杂场景下的正确性,我们需要编写一系列的单元测试。使用xUnit作为测试框架。

2.1 测试订单创建

[Fact]
public void Should_Create_Order()
{
    // Arrange
    var orderId = Guid.NewGuid();
    var order = new Order(orderId);

    // Act
    var events = order.GetUncommittedChanges();

    // Assert
    events.Should().HaveCount(1);
    events[0].Should().BeOfType<OrderCreated>();
}

2.2 测试添加商品

[Fact]
public void Should_Add_Item_To_Order()
{
    // Arrange
    var orderId = Guid.NewGuid();
    var order = new Order(orderId);
    var itemId = Guid.NewGuid();
    var quantity = 2;

    // Act
    order.AddItem(new OrderItem(itemId, quantity));
    var events = order.GetUncommittedChanges();

    // Assert
    events.Should().HaveCount(2);
    events[1].Should().BeOfType<ItemAdded>();
    ((ItemAdded)events[1]).ProductId.Should().Be(itemId);
    ((ItemAdded)events[1]).Quantity.Should().Be(quantity);
}

2.3 测试支付订单

[Fact]
public void Should_Pay_Order()
{
    // Arrange
    var orderId = Guid.NewGuid();
    var order = new Order(orderId);
    var itemId = Guid.NewGuid();
    var quantity = 2;

    order.AddItem(new OrderItem(itemId, quantity));

    // Act
    order.PayOrder();
    var events = order.GetUncommittedChanges();

    // Assert
    events.Should().HaveCount(3);
    events[2].Should().BeOfType<OrderPaid>();
}

2.4 测试无效操作

[Fact]
public void Should_Not_Pay_Order_Twice()
{
    // Arrange
    var orderId = Guid.NewGuid();
    var order = new Order(orderId);
    var itemId = Guid.NewGuid();
    var quantity = 2;

    order.AddItem(new OrderItem(itemId, quantity));
    order.PayOrder();

    // Act & Assert
    order.Invoking(x => x.PayOrder()).Should().Throw<InvalidOperationException>();
}

以上测试涵盖了订单创建、添加商品、支付订单以及无效操作的场景,确保了Order聚合在各种复杂场景下的正确性。