1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
| // 强类型 ID(避免基础类型误用)
public readonly record struct OrderId(Guid Value);
public readonly record struct ProductId(Guid Value);
// 聚合根
public class Order : Entity<OrderId>, IAggregateRoot
{
private readonly List<OrderItem> _items = new();
public IReadOnlyCollection<OrderItem> Items => _items.AsReadOnly();
public OrderStatus Status { get; private set; }
public Address ShippingAddress { get; private set; }
private int _version; // 乐观锁版本号
// 仓储加载/EF Core 用,不对外
private Order(OrderId id, Address shipTo) : base(id)
{
Status = OrderStatus.Draft;
ShippingAddress = shipTo;
}
// 工厂:创建即合法(保证不变量)
public static Order Place(OrderId id, Address shipTo, IEnumerable<(ProductId, Money, int)> lines)
{
var order = new Order(id, shipTo);
foreach (var (pid, price, qty) in lines)
order.AddItem(pid, price, qty); // 复用规则
if (!order._items.Any())
throw new DomainException("订单至少要有一项");
order.Status = OrderStatus.Placed;
return order;
}
// 所有修改入口:内部强制不变量
public void AddItem(ProductId product, Money price, int qty)
{
if (Status != OrderStatus.Draft)
throw new DomainException("非草稿态不能加项");
if (qty <= 0) throw new DomainException("数量必须 > 0");
if (price.Amount <= 0) throw new DomainException("价格必须 > 0");
var existing = _items.FirstOrDefault(i => i.ProductId == product);
if (existing is not null) existing.Increase(qty); // 内部实体也只暴露方法
else _items.Add(new OrderItem(product, price, qty));
}
public void Cancel()
{
if (Status >= OrderStatus.Shipped)
throw new DomainException("已发货不能取消");
Status = OrderStatus.Cancelled;
// 发领域事件(第四篇展开)
AddDomainEvent(new OrderCancelled(Id));
}
// 派生值:不变量的体现,不存储
public Money Total => _items.Aggregate(
new Money(0m, _items[0].Price.Currency),
(sum, i) => sum.Add(i.SubTotal));
}
// 内部实体:不独立、不对外,只通过聚合根改
public class OrderItem : Entity<Guid>
{
public ProductId ProductId { get; }
public Money Price { get; }
public int Qty { get; private set; }
public Money SubTotal => new(Price.Amount * Qty, Price.Currency); // 派生
internal void Increase(int n) // internal,外部调不到
{
if (n <= 0) throw new DomainException("增量必须 > 0");
Qty += n;
}
}
public enum OrderStatus { Draft, Placed, Paid, Shipped, Cancelled }
|