您的位置:首页 >c#如何使用事件溯源_c#事件溯源完整教程与代码实例
发布于2026-05-03 阅读(0)
扫一扫,手机访问

事件溯源(Event Sourcing)远不止是“给类加个 event 关键字”那么简单。它本质上是一套状态管理哲学,其核心在于用不可变的事件序列来替代直接的状态更新。如果你正在考虑在C#项目中引入事件溯源,首先要问自己两个问题:你的业务模型是否真的需要审计追踪、状态重放和最终一致性?以及,你是否准备好应对由此带来的聚合重建、快照管理和并发冲突等额外的复杂度?
这是最常见的误区:将.NET语言层面的event关键字与领域驱动设计中的“事件溯源”概念混为一谈。前者只是一种基于委托的发布-订阅机制,常用于UI响应或模块解耦;后者则是一套要求将业务事实作为持久化、有序且带版本信息的事件日志的完整模式。用event触发一次OrderShippedEvent,并不意味着这个事件被写入了EventStore,更不意味着能依靠它来重建聚合的完整状态。
追加写入存储、携带聚合根ID和版本号、能够被重放以重建任意时间点的状态。OnOrderShipped()方法中仅仅触发了OrderShipped事件,却忘记了调用_eventStore.AppendAsync(aggregateId, new OrderShippedEvent(...), expectedVersion)来完成事件的持久化。关键在于如何设计状态变更的入口,而非仅仅编写事件类。在C#中,标准的做法是让聚合根只暴露命令方法(例如Ship()),在方法内部进行业务校验,生成对应的事件,将事件应用到聚合自身以更新状态,并最终返回事件列表供外部基础设施持久化。
public class Order
{
private readonly List _uncommittedEvents = new();
public Guid Id { get; private set; }
public int Version { get; private set; }
public OrderStatus Status { get; private set; }
// 从快照或事件流重建时使用的私有构造函数
private Order() { }
public static Order Create(Guid id) => new() { Id = id, Status = OrderStatus.Created };
public void Ship()
{
if (Status != OrderStatus.Confirmed)
throw new InvalidOperationException("Only confirmed orders can be shipped");
var @event = new OrderShippedEvent(Id, DateTime.UtcNow);
Apply(@event); // 内部应用事件,更新状态
_uncommittedEvents.Add(@event);
}
private void Apply(OrderShippedEvent e)
{
Status = OrderStatus.Shipped;
Version++;
}
public IReadOnlyList DequeueUncommittedEvents()
=> _uncommittedEvents.ToList().AsReadOnly();
}
Apply()方法必须严格地根据事件的语义同步更新聚合的内存状态(例如,OrderShippedEvent对应将Status设置为Shipped)。Apply()方法中执行I/O操作或进行跨聚合调用,这些属于应用服务层的职责。DequeueUncommittedEvents()方法是连接领域层与基础设施层的关键桥梁,它负责将聚合在本次操作中产生的新事件取出,交由仓储层持久化到事件存储中,之后清空未提交事件列表。事件溯源模式天然依赖于乐观并发控制。设想这样一个场景:两个线程同时加载了版本号为5的同一个订单聚合,并都试图执行发货操作生成OrderShippedEvent。当第二个线程尝试以expectedVersion=5的条件追加事件时,会发现实际版本号已不匹配,从而抛出OptimisticConcurrencyException。这并非系统缺陷,而是设计上的预期行为。
放弃当前操作、尝试合并事件(例如,转换为OrderPartiallyShippedEvent),还是将操作转入人工审核队列。rowversion列是实现乐观并发的常规选择;若使用Cosmos DB,则需要依赖其_etag字段进行条件更新。Parallel.For同时调用对同一聚合的命令,以验证并发异常是否能被正确抛出和处理。事件溯源真正的挑战,往往不在于具体的代码如何编写,而在于前期的设计决策:事件粒度的划分是否精准反映了业务意图?快照策略能否在状态重建性能和存储成本之间取得平衡?读模型(在CQRS架构中)能否独立于写模型进行演进?在引入任何框架之前,不妨先用纸笔勾勒出核心聚合的完整生命周期——从创建到终结,每一步“发生了什么事实”,然后再决定哪些事实值得被记录为事件。这才是驾驭事件溯源的起点。
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
正版软件
正版软件
正版软件
正版软件
正版软件
1
2
3
7
9