跳转到内容

领域事件

领域事件(Domain Event)是领域驱动设计中捕获业务事实的重要模式。它用于记录领域中发生的重要业务事件,实现聚合之间的松耦合通信。

领域事件表示在领域中已经发生的事情,具有以下特征:

  1. 业务意义:反映真实的业务事实
  2. 过去时态:事件名称使用过去时(如 OrderPlaced,而非 PlaceOrder
  3. 不可变性:事件一旦创建就不能修改
  4. 异步处理:事件处理器异步响应事件
using MiCake.DDD.Domain;
// 订单已提交事件
public class OrderSubmittedEvent : IDomainEvent
{
public int OrderId { get; }
public int CustomerId { get; }
public decimal TotalAmount { get; }
public DateTime SubmittedAt { get; }
public OrderSubmittedEvent(int orderId, int customerId, decimal totalAmount)
{
OrderId = orderId;
CustomerId = customerId;
TotalAmount = totalAmount;
SubmittedAt = DateTime.UtcNow;
}
}
// 用户注册事件
public class UserRegisteredEvent : IDomainEvent
{
public int UserId { get; }
public string Email { get; }
public DateTime RegisteredAt { get; }
public UserRegisteredEvent(int userId, string email)
{
UserId = userId;
Email = email;
RegisteredAt = DateTime.UtcNow;
}
}

使用 C# record 可以更简洁地定义事件:

// 使用 record 定义事件
public record ProductCreatedEvent(int ProductId, string Name, decimal Price) : IDomainEvent;
public record PriceChangedEvent(int ProductId, decimal OldPrice, decimal NewPrice) : IDomainEvent;
public record OrderCancelledEvent(int OrderId, string Reason) : IDomainEvent;
public class Order : AggregateRoot<int>
{
private List<OrderItem> _items = new();
public int CustomerId { get; private set; }
public OrderStatus Status { get; private set; }
public void Submit()
{
if (Status != OrderStatus.Draft)
throw new DomainException("Only draft orders can be submitted");
if (!_items.Any())
throw new DomainException("Cannot submit empty order");
// 修改状态
Status = OrderStatus.Submitted;
// 触发领域事件
RaiseDomainEvent(new OrderSubmittedEvent(Id, CustomerId, TotalAmount));
}
public void Cancel(string reason)
{
if (Status == OrderStatus.Shipped)
throw new DomainException("Cannot cancel shipped order");
Status = OrderStatus.Cancelled;
RaiseDomainEvent(new OrderCancelledEvent(Id, reason));
}
public void AddItem(int productId, int quantity, decimal price)
{
var item = new OrderItem(productId, quantity, price);
_items.Add(item);
// 添加商品也可以触发事件
RaiseDomainEvent(new OrderItemAddedEvent(Id, productId, quantity));
}
}
using MiCake.DDD.Domain;
using System.Threading;
using System.Threading.Tasks;
// 订单提交事件处理器
public class OrderSubmittedEventHandler : IDomainEventHandler<OrderSubmittedEvent>
{
private readonly IEmailService _emailService;
private readonly ILogger<OrderSubmittedEventHandler> _logger;
public OrderSubmittedEventHandler(
IEmailService emailService,
ILogger<OrderSubmittedEventHandler> logger)
{
_emailService = emailService;
_logger = logger;
}
public async Task HandleAysnc(OrderSubmittedEvent domainEvent, CancellationToken cancellationToken = default)
{
_logger.LogInformation($"Order {domainEvent.OrderId} submitted by customer {domainEvent.CustomerId}");
// 发送订单确认邮件
await _emailService.SendOrderConfirmationAsync(
domainEvent.CustomerId,
domainEvent.OrderId,
domainEvent.TotalAmount
);
// 其他业务逻辑...
}
}
// 用户注册事件处理器
public class UserRegisteredEventHandler : IDomainEventHandler<UserRegisteredEvent>
{
private readonly IEmailService _emailService;
private readonly IRepository<UserProfile, int> _profileRepository;
public async Task HandleAysnc(UserRegisteredEvent domainEvent, CancellationToken cancellationToken = default)
{
// 1. 发送欢迎邮件
await _emailService.SendWelcomeEmailAsync(domainEvent.Email);
// 2. 创建用户档案
var profile = UserProfile.Create(domainEvent.UserId);
await _profileRepository.AddAsync(profile, cancellationToken);
await _profileRepository.SaveChangesAsync(cancellationToken);
// 3. 记录日志
Console.WriteLine($"User {domainEvent.UserId} registered at {domainEvent.RegisteredAt}");
}
}

一个事件可以有多个处理器:

// 处理器 1:发送邮件
public class OrderSubmittedEmailHandler : IDomainEventHandler<OrderSubmittedEvent>
{
public async Task HandleAysnc(OrderSubmittedEvent domainEvent, CancellationToken cancellationToken)
{
// 发送邮件
}
}
// 处理器 2:更新库存
public class OrderSubmittedInventoryHandler : IDomainEventHandler<OrderSubmittedEvent>
{
public async Task HandleAysnc(OrderSubmittedEvent domainEvent, CancellationToken cancellationToken)
{
// 扣减库存
}
}
// 处理器 3:记录日志
public class OrderSubmittedLoggingHandler : IDomainEventHandler<OrderSubmittedEvent>
{
public async Task HandleAysnc(OrderSubmittedEvent domainEvent, CancellationToken cancellationToken)
{
// 记录日志
}
}
// 这三个处理器会依次执行

MiCake 在调用 SaveChangesAsync() 时自动派发领域事件:

public class OrderService
{
private readonly IRepository<Order, int> _orderRepository;
public async Task SubmitOrder(int orderId)
{
// 1. 加载聚合根
var order = await _orderRepository.FindAsync(orderId);
// 2. 调用业务方法(触发事件,但不立即派发)
order.Submit(); // 内部:RaiseDomainEvent(new OrderSubmittedEvent(...))
// 3. 更新聚合根
await _orderRepository.UpdateAsync(order);
// 4. 保存更改 - 此时自动派发所有事件
await _orderRepository.SaveChangesAsync();
// SaveChangesAsync 内部流程:
// a. 收集聚合根上的所有待处理事件
// b. 持久化数据到数据库
// c. 按顺序派发事件到对应的处理器
// d. 清除已派发的事件
}
}
1. 业务方法调用
order.Submit()
2. 触发领域事件
RaiseDomainEvent(new OrderSubmittedEvent(...))
3. 事件暂存在聚合根
_domainEvents.Add(event)
4. 保存更改
await repository.SaveChangesAsync()
5. 收集所有事件
events = aggregateRoot.DomainEvents
6. 持久化数据
dbContext.SaveChanges()
7. 派发事件
foreach (event in events)
foreach (handler in GetHandlers(event))
await handler.HandleAsync(event)
8. 清除事件
aggregateRoot.ClearDomainEvents()
// 订单聚合
public class Order : AggregateRoot<int>
{
public void Submit()
{
Status = OrderStatus.Submitted;
// 触发事件,通知其他聚合
RaiseDomainEvent(new OrderSubmittedEvent(Id, Items));
}
}
// 库存聚合在事件处理器中响应
public class OrderSubmittedInventoryHandler : IDomainEventHandler<OrderSubmittedEvent>
{
private readonly IRepository<Product, int> _productRepository;
public async Task HandleAysnc(OrderSubmittedEvent domainEvent, CancellationToken cancellationToken)
{
// 扣减库存
foreach (var item in domainEvent.Items)
{
var product = await _productRepository.FindAsync(item.ProductId);
product.DecreaseStock(item.Quantity);
await _productRepository.UpdateAsync(product);
}
await _productRepository.SaveChangesAsync(cancellationToken);
}
}
// 用户注册流程
public class User : AggregateRoot<int>
{
public void Register(string email, string password)
{
// 注册逻辑
Email = email;
SetPassword(password);
Status = UserStatus.Pending;
// 触发注册事件
RaiseDomainEvent(new UserRegisteredEvent(Id, email));
}
}
// 多个处理器协调完成注册流程
public class SendVerificationEmailHandler : IDomainEventHandler<UserRegisteredEvent>
{
public async Task HandleAysnc(UserRegisteredEvent domainEvent, CancellationToken cancellationToken)
{
// 发送验证邮件
}
}
public class CreateUserProfileHandler : IDomainEventHandler<UserRegisteredEvent>
{
public async Task HandleAysnc(UserRegisteredEvent domainEvent, CancellationToken cancellationToken)
{
// 创建用户档案
}
}
public class InitializeUserSettingsHandler : IDomainEventHandler<UserRegisteredEvent>
{
public async Task HandleAysnc(UserRegisteredEvent domainEvent, CancellationToken cancellationToken)
{
// 初始化用户设置
}
}
public class OrderStatusChangedEvent : IDomainEvent
{
public int OrderId { get; }
public OrderStatus OldStatus { get; }
public OrderStatus NewStatus { get; }
public DateTime ChangedAt { get; }
}
public class OrderAuditEventHandler : IDomainEventHandler<OrderStatusChangedEvent>
{
private readonly IAuditLogRepository _auditRepository;
public async Task HandleAysnc(OrderStatusChangedEvent domainEvent, CancellationToken cancellationToken)
{
var auditLog = new AuditLog
{
EntityType = nameof(Order),
EntityId = domainEvent.OrderId,
Action = "StatusChanged",
OldValue = domainEvent.OldStatus.ToString(),
NewValue = domainEvent.NewStatus.ToString(),
Timestamp = domainEvent.ChangedAt
};
await _auditRepository.AddAsync(auditLog);
await _auditRepository.SaveChangesAsync(cancellationToken);
}
}
public class OrderShippedEvent : IDomainEvent
{
public int OrderId { get; }
public int CustomerId { get; }
public string TrackingNumber { get; }
}
public class OrderShippedNotificationHandler : IDomainEventHandler<OrderShippedEvent>
{
private readonly INotificationService _notificationService;
public async Task HandleAysnc(OrderShippedEvent domainEvent, CancellationToken cancellationToken)
{
// 发送邮件通知
await _notificationService.SendEmailAsync(
domainEvent.CustomerId,
"Order Shipped",
$"Your order has been shipped. Tracking number: {domainEvent.TrackingNumber}"
);
// 发送短信通知
await _notificationService.SendSmsAsync(
domainEvent.CustomerId,
$"Order shipped. Track: {domainEvent.TrackingNumber}"
);
// 推送通知
await _notificationService.SendPushNotificationAsync(
domainEvent.CustomerId,
"Order Shipped",
"Your order is on the way!"
);
}
}
// ✅ 正确 - 使用过去时
public class OrderPlacedEvent : IDomainEvent { }
public class PaymentCompletedEvent : IDomainEvent { }
public class UserRegisteredEvent : IDomainEvent { }
// ❌ 错误 - 使用现在时或命令式
public class PlaceOrderEvent : IDomainEvent { }
public class CompletePaymentEvent : IDomainEvent { }
public class RegisterUserEvent : IDomainEvent { }
// ✅ 正确 - 所有属性只读
public class OrderCreatedEvent : IDomainEvent
{
public int OrderId { get; } // 只读
public DateTime CreatedAt { get; }
public OrderCreatedEvent(int orderId)
{
OrderId = orderId;
CreatedAt = DateTime.UtcNow;
}
}
// ❌ 错误 - 属性可修改
public class OrderCreatedEvent : IDomainEvent
{
public int OrderId { get; set; } // 可修改
public DateTime CreatedAt { get; set; }
}
public class OrderCreatedEmailHandler : IDomainEventHandler<OrderCreatedEvent>
{
private readonly IEmailService _emailService;
private readonly IEmailLogRepository _emailLogRepository;
public async Task HandleAysnc(OrderCreatedEvent domainEvent, CancellationToken cancellationToken)
{
// 检查是否已发送(幂等性)
var alreadySent = await _emailLogRepository.ExistsAsync(
l => l.OrderId == domainEvent.OrderId && l.Type == "OrderCreated"
);
if (alreadySent)
return; // 已发送,跳过
// 发送邮件
await _emailService.SendOrderConfirmationAsync(domainEvent.OrderId);
// 记录日志
await _emailLogRepository.AddAsync(new EmailLog
{
OrderId = domainEvent.OrderId,
Type = "OrderCreated",
SentAt = DateTime.UtcNow
});
await _emailLogRepository.SaveChangesAsync(cancellationToken);
}
}
// ✅ 好的做法 - 包含必要信息
public class OrderSubmittedEvent : IDomainEvent
{
public int OrderId { get; }
public int CustomerId { get; }
public decimal TotalAmount { get; }
public List<OrderItemDto> Items { get; } // 包含详细信息
public DateTime SubmittedAt { get; }
// 事件处理器不需要再查询订单详情
}
// ❌ 不好的做法 - 信息不足
public class OrderSubmittedEvent : IDomainEvent
{
public int OrderId { get; } // 只有 ID
// 事件处理器需要查询数据库获取详情
}

5. 避免在事件处理器中执行长时间操作

Section titled “5. 避免在事件处理器中执行长时间操作”
// ❌ 避免 - 同步执行耗时操作
public class OrderPlacedHandler : IDomainEventHandler<OrderPlacedEvent>
{
public async Task HandleAysnc(OrderPlacedEvent domainEvent, CancellationToken cancellationToken)
{
// 这会阻塞事务
await SendEmailAsync(); // 可能很慢
await CallExternalApiAsync(); // 可能失败
await GeneratePdfAsync(); // 很耗时
}
}
// ✅ 推荐 - 发布到消息队列异步处理
public class OrderPlacedHandler : IDomainEventHandler<OrderPlacedEvent>
{
private readonly IMessageQueue _messageQueue;
public async Task HandleAysnc(OrderPlacedEvent domainEvent, CancellationToken cancellationToken)
{
// 快速发布到队列
await _messageQueue.PublishAsync(new SendOrderEmailCommand(domainEvent.OrderId));
await _messageQueue.PublishAsync(new GenerateInvoiceCommand(domainEvent.OrderId));
}
}

MiCake 的领域事件机制:

  • 实现 IDomainEvent 接口定义事件
  • 在聚合根中通过 RaiseDomainEvent 触发事件
  • 实现 IDomainEventHandler<TEvent> 处理事件
  • SaveChangesAsync 时自动派发事件
  • 用于实现聚合间松耦合通信
  • 支持一个事件多个处理器

下一步: