ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

深入解析 Orleans Journaled Todo List 示例:基于日志一致性提供程序的持久化事件溯源实战

深入解析 Orleans Journaled Todo List 示例:基于日志一致性提供程序的持久化事件溯源实战 后端微服务【免费下载链接】orleansCloud Native application framework for .NET项目地址https://gitcode.com/gh_mirrors/or/orleans点击查看免费下载导读Journaled Todo List 是一个由 .NET Aspire 托管的 Blazor Web 应用示例它完整演示了如何使用 Orleans 的**日志一致性提供程序Log-Consistency Providers**构建持久化、可溯源、支持时间旅行查询的 grain 状态。示例同时展示了日志存储Log Storage与状态存储State Storage两种日志一致性模式的对比用法、基于事件的 grain 设计、以及 grain 观察者Observer模式是理解 Orleans 事件溯源与可确认事件流Confirmed Event Stream的最佳入门教材。阅读本文后你将掌握如何用JournaledGrainTState, TEvent实现事件溯源、如何按时间戳回溯任意历史时刻的 grain 状态以及如何在 Aspire 中为 Orleans 配置 Azure Table/Blob 模拟器存储。示例总览该示例位于 samples/JournaledTodoList/README.md是一个由 Aspire 编排托管的 Blazor Web 应用其核心特性包括运行3 个 Orleans silo 副本通过WithReplicas(3)配置演示多副本集群下的日志一致性收敛使用模拟的 Azure Table Storageemulated作为集群成员clustering存储使用模拟的 Azure Blob Storageemulated作为 grain 状态存储待办清单 grain 采用**日志存储Log Storage**模式每个事件都被持久化支持完整历史查询与任意时间点状态重建待办清单注册表 grain 采用**状态存储State Storage**模式只保存聚合后的状态不保存事件日志两种模式形成鲜明对比。示例中用户可以为待办清单执行添加、完成勾选、删除、改名等操作并在界面上查看完整的事件历史以及任意历史时刻的清单快照——这正是事件溯源架构的核心价值所在。运行示例根据 README 的说明运行前置条件如下安装.NET 10 SDK安装Aspire CLI安装Docker 兼容的容器运行时用于承载 Azure Storage 模拟器。在samples/JournaledTodoList目录下执行aspire run --project JournaledTodoList.AppHost启动后打开 Aspire Dashboard 中显示的webapp端点即可在浏览器中执行添加、完成、删除等操作实际体验 journaled日志化待办清单 grain 的行为每一次操作都会成为日志中的一条事件界面可随时切换到历史时刻查看当时的清单内容。说明示例依赖 Docker 运行 Azure Storage 模拟器Azurite若本机没有 Docker 环境可先启动容器运行时再执行上述命令。整体架构Aspire 编排下的多副本 Orleans 集群示例的宿主项目是 JournaledTodoList.AppHost/Program.cs它定义了整个分布式应用的资源拓扑。核心代码如下var builder DistributedApplication.CreateBuilder(args); // 为 Orleans 集群与 grain 状态存储添加存储资源 var sessionStorage builder.AddAzureStorage(sessionStorage) .RunAsEmulator(); var persistentStorage builder.AddAzureStorage(persistentStorage) .RunAsEmulator(config config.WithLifetime(ContainerLifetime.Persistent)); var clusteringTable sessionStorage.AddTables(clustering); var grainStorage persistentStorage.AddBlobs(grain-state); // 添加 Orleans 资源并配置 clustering 与 grain storage var orleans builder.AddOrleans(default) .WithClustering(clusteringTable) .WithGrainStorage(Default, grainStorage); builder.AddProjectProjects.JournaledTodoList_WebApp(webapp) .WithReference(orleans) .WithReplicas(3) .WithExternalHttpEndpoints() .WaitFor(clusteringTable) .WaitFor(grainStorage); builder.Build().Run();这段代码揭示了几个关键设计点存储资源分离sessionStorage模拟器默认生命周期用于集群成员关系表persistentStorageContainerLifetime.Persistent用于 grain 状态 Blob。持久化容器在 Aspire 停止后依然保留确保日志数据不因编排生命周期结束而丢失Orleans 集成方式通过Aspire.Hosting.Orleans包见 JournaledTodoList.AppHost.csproj提供AddOrleans(default)扩展并分别绑定WithClusteringAzure Table与WithGrainStorage(Default, ...)Azure Blob三副本部署WithReplicas(3)让 Aspire 同时启动 3 个 silo 实例为日志一致性协议Log View Protocol提供了真实的跨副本同步场景便于观察事件如何在不同 silo 间收敛为同一视图就绪等待WaitFor(clusteringTable)与WaitFor(grainStorage)确保存储模拟器就绪后才启动 WebApp。在 WebApp 侧JournaledTodoList.WebApp/Program.cs 通过如下代码将应用自身配置为一个 Orleans silobuilder.AddKeyedAzureTableServiceClient(clustering); builder.AddKeyedAzureBlobServiceClient(grain-state); builder.UseOrleans(siloBuilder { siloBuilder.AddLogStorageBasedLogConsistencyProviderAsDefault(); siloBuilder.AddStateStorageBasedLogConsistencyProvider(name: Constants.StateStorageProviderName); });其中AddLogStorageBasedLogConsistencyProviderAsDefault()把Log Storage模式注册为默认日志一致性提供程序待办清单 grain 的每个事件都会持久化为日志AddStateStorageBasedLogConsistencyProvider(name: StateStorage)注册一个命名提供程序名称来自 Constants.cs 中的StateStorageProviderName StateStorage注册表 grain 通过[LogConsistencyProvider(ProviderName StateStorage)]特性显式绑定它。对应项目引用参见 JournaledTodoList.WebApp.csproj其中包含Microsoft.Orleans.Server、Microsoft.Orleans.EventSourcing、Microsoft.Orleans.Clustering.AzureStorage与Microsoft.Orleans.Persistence.AzureStorage等包。日志一致性提供程序Log Storage 与 State Storage 的区别Orleans 的事件溯源能力由Orleans.EventSourcing命名空间提供其核心抽象是JournaledGrainTGrainState, TEventBase定义见 src/Orleans.EventSourcing/JournaledGrain.cs。它内置了三个关键机制RaiseEventTEvent(TEvent event)将事件追加到当前事务的待确认事件流JournaledGrain.cs 第 46 行ConfirmEvents()等待日志一致性协议将事件持久化并确认JournaledGrain.cs 第 142 行Version当前已确认事件流的版本号JournaledGrain.cs 第 106 行TransitionState(TGrainState state, TEventBase event)将单个事件应用到状态投影上JournaledGrain.cs 第 249 行RetrieveConfirmedEvents(fromVersion, toVersion)从已确认日志中按版本区间取回事件段JournaledGrain.cs 第 184 行。两种提供程序的差异非常明显维度Log Storage默认State StorageStateStorage持久化内容完整事件日志仅聚合后的最终状态历史回溯支持可重放事件重建任意时刻状态不支持存储开销随事件数增长恒定只有一份状态适用场景审计、溯源、时间旅行、事件重放只关心最新状态、追求最小存储注册实现见 src/Orleans.EventSourcing/Hosting/LogStorageSiloBuilderExtensions.csAddLogStorageBasedLogConsistencyProviderAsDefault实际将默认存储绑定到LogConsistencyProvider与 src/Orleans.EventSourcing/Hosting/StateStorageSiloBuilderExtensions.cs基于ProviderConstants.DEFAULT_STORAGE_PROVIDER_NAME或显式命名。示例让清单本体走 Log Storage、让注册表索引走 State Storage恰好完整覆盖了两种典型选择。事件溯源核心TodoListGrain 的实现待办清单 grain 定义在 TodoListGrain.cs类型签名为public sealed class TodoListGrain : JournaledGrainTodoListGrain.TodoListProjection, TodoListEvent, ITodoListGrain接口 ITodoListGrain.cs 继承自IGrainWithStringKey即用字符串主键待办清单的 URL 化名称寻址每个清单。写操作只产生事件不直接改状态所有写操作遵循只记录事件的事件溯源准则例如添加条目public async Task AddTodoItemAsync(string title) { var evt new TodoItemAdded( Version, // 当前日志版本作为事件的序号 DateTimeOffset.UtcNow, // 事件时间戳历史回溯的依据 title); RaiseEvent(evt); await ConfirmEvents(); // 等待事件被持久化并确认 }UpdateTodoItemAsync、ToggleTodoItemAsync、RemoveTodoItemAsync结构完全相同各自构造对应事件对象RaiseEvent后ConfirmEvents。唯一特殊的是SetNameAsync——它在确认改名事件后还会调用注册表 grain 更新索引var registry GrainFactory.GetGrainITodoListRegistryGrain(Constants.TodoListRegistryId); await registry.RegisterTodoListAsync(new TodoListReference(this.GetPrimaryKeyString(), listName));读操作从投影与事件日志取数GetTodoListAsync直接读取当前已确认投影StateGetHistoryAsync调用RetrieveConfirmedEvents(0, Version)取回从版本 0 到当前的全部事件GetTodoListAtTimestampAsync是本示例的亮点——它重新构造一个空白投影将时间戳不晚于目标时刻的事件逐一TransitionState重放从而还原该时刻的清单快照public async TaskTodoList? GetTodoListAtTimestampAsync(DateTimeOffset timestamp) { var allEvents await GetHistoryAsync(); var historicalProjection new TodoListProjection(); foreach (var evt in allEvents.Where(e e.Timestamp timestamp)) { TransitionState(historicalProjection, evt); } return historicalProjection.Timestamp DateTimeOffset.MinValue ? new TodoList( Name: this.GetPrimaryKeyString(), Items: historicalProjection.Items.Values.OrderBy(x x.Id).ToImmutableArray(), Timestamp: historicalProjection.Timestamp) : null; }这里体现了JournaledGrain的投影可重放能力TransitionState既可应用于活状态由框架在事件确认后自动调用也可手动应用于临时构造的历史投影实现任意时间点快照。若目标时刻早于首条事件则返回null。投影类型每个事件一个 Apply 方法TodoListProjection是 grain 的状态容器源码注释特别强调必须是一个可变对象。它内置了与五种事件一一对应的Apply重载框架会自动把事件分派到正确的Apply方法public sealed class TodoListProjection { public Dictionaryint, TodoItem Items { get; set; } []; public DateTimeOffset Timestamp { get; set; } DateTimeOffset.MinValue; public string Name { get; set; } string.Empty; public void Apply(TodoItemAdded added) { Items.Add(added.ItemId, new TodoItem(added.ItemId, added.Title, false)); Timestamp added.Timestamp; } public void Apply(TodoItemUpdated updated) { /* 更新标题 */ } public void Apply(TodoItemToggled toggled) { /* 翻转 IsCompleted */ } public void Apply(TodoItemRemoved removed) { /* 删除条目 */ } public void Apply(TodoListNameChanged nameChanged) { /* 更新名称 */ } }由于每次Apply都会更新Timestamp投影天然记录了最后一次变更时间这也是判断某时间点是否存在数据Timestamp DateTimeOffset.MinValue的依据。事件类型体系所有事件继承自抽象基类 TodoListEvent.cs并用 Orleans 源生成器的[GenerateSerializer, Immutable]特性标记同时定义了用于 UI 展示的文字描述[GenerateSerializer, Immutable] public abstract record class TodoListEvent(DateTimeOffset Timestamp) { public abstract string GetDescription(); }具体事件均位于Grains/Events/目录包括TodoItemAdded.csAdded item {ItemId}: {Title}TodoItemUpdated更新标题TodoItemToggled.csToggled completion status of item {ItemId}TodoItemRemoved删除条目TodoListNameChanged清单改名数据载体TodoItem与TodoList同样是[GenerateSerializer, Immutable]的不可变 record见 TodoItem.cs 与 TodoList.cs保证跨进程反序列化安全与值语义。状态型 Journaled Grain 的另一种写法TodoListRegistryGrain与TodoListGrain形成对照的是注册表 grain TodoListRegistryGrain.cs。它通过特性绑定State Storage提供程序不保存事件日志[LogConsistencyProvider(ProviderName Constants.StateStorageProviderName)] public sealed class TodoListRegistryGrain(ILoggerTodoListRegistryGrain logger) : JournaledGrainTodoListRegistryGrain.TodoListRegistry, TodoListReference , ITodoListRegistryGrain它的两个设计亮点不定义 Apply 方法而是重写TransitionState。源码注释明确说明这是与TodoListGrain的另一种组织方式——直接在状态迁移方法里完成按 Id 替换或追加的逻辑保持注册表引用列表有序protected override void TransitionState(TodoListRegistry state, TodoListReference event) { var existingList state.TodoLists.FirstOrDefault(x x.Id event.Id); if (existingList is not null) { state.TodoLists state.TodoLists.Replace(existingList, event); } else { state.TodoLists state.TodoLists.Add(event); } }注册事件采用去重后写入RegisterTodoListAsync先检查State.TodoLists.Contains(todoListReference)重复注册直接短路返回避免产生冗余事件。其状态类型TodoListRegistry用ImmutableArrayTodoListReference保存引用列表而 TodoListReference.cs 是一个(Id, Name)的不可变 record。观察者模式订阅注册表变更TodoListRegistryGrain还实现了 Orleans 的 grain 观察者模式用于在清单增删改名时实时通知前端。它使用ObserverManagerITodoListRegistryObserverOrleans.Utilities管理订阅者超时 5 分钟自动清理Subscribe将调用方WebApp 进程内的ITodoListRegistryObserver对象引用登记进ObserverManager并立即回推当前状态Unsubscribe退出观察NotifyObservers在每次注册事件确认后把最新State.TodoLists广播给所有观察者OnDeactivateAsyncgrain 停用时清空观察者集合防止悬挂引用。观察者接口定义见 ITodoListRegistryObserver.cspublic interface ITodoListRegistryObserver : IGrainObserver { Task OnTodoListsChanged(ImmutableArrayTodoListReference todoLists); }在服务层 TodoListService.cs 中订阅通过IGrainFactory.CreateObjectReference将进程内对象转为可远程调用的引用并封装为IDisposable的SubscriptionDispose 时自动Unsubscribe——这是 Orleans 观察者模式的标准落地姿势。前端交互事件历史与时间旅行Blazor 页面 TodoListPage.razor.cs 把 grain 的能力完整映射为 UI加载清单时同时获取当前状态与完整事件历史todoService.GetTodoListAsyncGetTodoListHistoryAsyncIsViewingHistory属性判断当前是否处于历史回看模式当视图时间戳小于最新事件时间戳时为 true回看模式下禁用所有写操作避免历史快照与写操作混淆ViewAtTimestamp调用GetTodoListAtTimestampAsync渲染指定时刻的清单ReturnToCurrentVersion一键回到最新版本。这与后端GetHistoryAsync/GetTodoListAtTimestampAsync的能力一一对应事件日志既是数据又是可导航的时间轴。小结Journaled Todo List 示例以极小的体量覆盖了 Orleans 事件溯源的一整套实践要点两种日志一致性提供程序的选择需要完整审计/回溯用 Log Storage只关心最新状态用 State Storage事件溯源编码范式写操作只RaiseEventConfirmEvents读操作读投影或重放日志投影用可变状态对象 每事件一个 Apply 方法或重写 TransitionState组织任意时间点快照基于带时间戳事件的重放GetTodoListAtTimestampAsync提供了可复用的历史查询实现模板Aspire Orleans 编排模拟器存储、三副本 silo、命名提供程序的完整配置路径观察者订阅ObserverManager与CreateObjectReference组合实现跨进程实时通知。无论你是要构建带审计日志的业务系统、实现状态回放/撤销功能还是为既有 grain 引入事件溯源都可以直接参考 samples/JournaledTodoList 中TodoListGrain与TodoListRegistryGrain的对照实现在自己的项目中快速落地。赞分享后端微服务【免费下载链接】orleansCloud Native application framework for .NET项目地址https://gitcode.com/gh_mirrors/or/orleans点击查看免费下载相关推荐Orleans 事件溯源Event Sourcing实战指南JournaledGrain 与日志一致性提供程序深度解析Orleans 事件溯源Event Sourcing实战指南JournaledGrain 与日志一致性提供程序深度解析 事件溯源Event Sourci后端微服务DeepSeek Harness 会话持久化架构解析基于 SessionEvent 事件溯源日志的抽象持久化服务DeepSeek Harness 会话持久化架构解析基于 SessionEvent 事件溯源日志的抽象持久化服务 会话持久化是 DeepSeek Harnes人工智能AI AgentAgent 框架DeepSeekDeepSeek Harness 会话持久化设计基于 SessionEvent 事件溯源日志的抽象持久化服务DeepSeek Harness 会话持久化设计基于 SessionEvent 事件溯源日志的抽象持久化服务 本文是 DeepSeek Harness一切皆人工智能AI AgentAgent 框架DeepSeek上一篇如何用好长按菜单与页面内查找WebToApp让Web应用体验更像原生App下一篇Wolverine Saga模式实战实现复杂的业务流程编排创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表