From 5c112f85456e5dfb99b49adba1b6f4a40f60bdd0 Mon Sep 17 00:00:00 2001 From: GeWuYou <95328647+GeWuYou@users.noreply.github.com> Date: Tue, 14 Apr 2026 22:30:59 +0800 Subject: [PATCH 1/7] =?UTF-8?q?docs(core):=20=E6=B7=BB=E5=8A=A0=20CQRS=20?= =?UTF-8?q?=E5=92=8C=E6=A0=B8=E5=BF=83=E6=A1=86=E6=9E=B6=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 CQRS 详细文档,介绍命令查询职责分离模式 - 添加核心框架概述文档,包含架构图和快速开始指南 - 详细介绍五层架构设计和组件联动机制 - 提供完整的最佳实践和设计理念说明 - 添加架构生命周期管理和模块化设计说明 --- CLAUDE.md | 3 +- .../Architectures/IArchitecture.cs | 7 +- .../GFramework.Core.Abstractions.csproj | 2 +- .../Ioc/IIocContainer.cs | 7 +- .../ArchitectureServicesTests.cs | 31 +++--- .../Architectures/GameContextTests.cs | 28 +++--- .../MediatorCoroutineExtensionsTests.cs | 96 ++++++++----------- .../GFramework.Core.Tests.csproj | 5 - GFramework.Core/Architectures/Architecture.cs | 7 +- .../Architectures/ArchitectureBootstrapper.cs | 3 +- .../Architectures/ArchitectureModules.cs | 10 +- .../Extensions/MediatorCoroutineExtensions.cs | 22 ++--- .../Cqrs/Internal/CqrsHandlerRegistrar.cs | 2 +- GFramework.Core/Ioc/MicrosoftDiContainer.cs | 7 +- docs/zh-CN/core/cqrs.md | 53 +++++----- docs/zh-CN/core/index.md | 7 +- 16 files changed, 128 insertions(+), 162 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 570606c7..1962098f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -73,7 +73,8 @@ Architecture 负责统一生命周期编排,核心阶段包括: ### CQRS -命令与查询分离,支持同步与异步执行。Mediator 模式通过源码生成器集成,以减少模板代码并保持调用路径清晰。 +命令与查询分离,支持同步与异步执行。当前版本内建自有 CQRS runtime、行为管道和 handler 自动注册;公开 API 里仍保留少量历史 +`Mediator` 命名以兼容旧调用点。 ### EventBus diff --git a/GFramework.Core.Abstractions/Architectures/IArchitecture.cs b/GFramework.Core.Abstractions/Architectures/IArchitecture.cs index 0055d3fe..bb855189 100644 --- a/GFramework.Core.Abstractions/Architectures/IArchitecture.cs +++ b/GFramework.Core.Abstractions/Architectures/IArchitecture.cs @@ -2,7 +2,6 @@ using GFramework.Core.Abstractions.Lifecycle; using GFramework.Core.Abstractions.Model; using GFramework.Core.Abstractions.Systems; using GFramework.Core.Abstractions.Utility; -using Microsoft.Extensions.DependencyInjection; namespace GFramework.Core.Abstractions.Architectures; @@ -73,8 +72,8 @@ public interface IArchitecture : IAsyncInitializable, IAsyncDestroyable, IInitia void RegisterUtility(Action? onCreated = null) where T : class, IUtility; /// - /// 注册中介行为管道 - /// 用于配置Mediator框架的行为拦截和处理逻辑。 + /// 注册 CQRS 请求管道行为。 + /// 历史方法名保留了 Mediator 前缀,但当前用于配置框架内建 CQRS runtime 的行为拦截和处理逻辑。 /// 既支持实现 IPipelineBehavior<,> 的开放泛型行为类型, /// 也支持绑定到单一请求/响应对的封闭行为类型。 /// @@ -101,4 +100,4 @@ public interface IArchitecture : IAsyncInitializable, IAsyncDestroyable, IInitia /// /// 表示异步等待操作的任务 Task WaitUntilReadyAsync(); -} \ No newline at end of file +} diff --git a/GFramework.Core.Abstractions/GFramework.Core.Abstractions.csproj b/GFramework.Core.Abstractions/GFramework.Core.Abstractions.csproj index 8ec77b72..84d53f63 100644 --- a/GFramework.Core.Abstractions/GFramework.Core.Abstractions.csproj +++ b/GFramework.Core.Abstractions/GFramework.Core.Abstractions.csproj @@ -26,6 +26,6 @@ all runtime; build; native; contentfiles; analyzers - + diff --git a/GFramework.Core.Abstractions/Ioc/IIocContainer.cs b/GFramework.Core.Abstractions/Ioc/IIocContainer.cs index b61d0f3a..d00211ed 100644 --- a/GFramework.Core.Abstractions/Ioc/IIocContainer.cs +++ b/GFramework.Core.Abstractions/Ioc/IIocContainer.cs @@ -1,6 +1,5 @@ using GFramework.Core.Abstractions.Rule; using GFramework.Core.Abstractions.Systems; -using Microsoft.Extensions.DependencyInjection; namespace GFramework.Core.Abstractions.Ioc; @@ -90,8 +89,8 @@ public interface IIocContainer : IContextAware void RegisterFactory(Func factory) where TService : class; /// - /// 注册中介行为管道 - /// 用于配置Mediator框架的行为拦截和处理逻辑 + /// 注册 CQRS 请求管道行为。 + /// 历史方法名保留了 Mediator 前缀,但当前用于配置框架内建 CQRS runtime 的行为拦截和处理逻辑。 /// /// 行为类型,必须是引用类型 void RegisterMediatorBehavior() @@ -227,4 +226,4 @@ public interface IIocContainer : IContextAware IServiceScope CreateScope(); #endregion -} \ No newline at end of file +} diff --git a/GFramework.Core.Tests/Architectures/ArchitectureServicesTests.cs b/GFramework.Core.Tests/Architectures/ArchitectureServicesTests.cs index 512e8a55..94b749b6 100644 --- a/GFramework.Core.Tests/Architectures/ArchitectureServicesTests.cs +++ b/GFramework.Core.Tests/Architectures/ArchitectureServicesTests.cs @@ -1,5 +1,6 @@ using GFramework.Core.Abstractions.Architectures; using GFramework.Core.Abstractions.Command; +using GFramework.Core.Abstractions.Cqrs; using GFramework.Core.Abstractions.Environment; using GFramework.Core.Abstractions.Events; using GFramework.Core.Abstractions.Ioc; @@ -13,7 +14,6 @@ using GFramework.Core.Environment; using GFramework.Core.Events; using GFramework.Core.Ioc; using GFramework.Core.Query; -using Mediator; using ICommand = GFramework.Core.Abstractions.Command.ICommand; namespace GFramework.Core.Tests.Architectures; @@ -34,6 +34,10 @@ namespace GFramework.Core.Tests.Architectures; [TestFixture] public class ArchitectureServicesTests { + private TestArchitectureContextV3? _context; + + private ArchitectureServices? _services; + [SetUp] public void SetUp() { @@ -41,9 +45,6 @@ public class ArchitectureServicesTests _context = new TestArchitectureContextV3(); } - private ArchitectureServices? _services; - private TestArchitectureContextV3? _context; - private void RegisterBuiltInServices() { _services!.ModuleManager.RegisterBuiltInModules(_services.Container); @@ -347,61 +348,59 @@ public class TestArchitectureContextV3 : IArchitectureContext { } - public ValueTask SendRequestAsync(global::GFramework.Core.Abstractions.Cqrs.IRequest request, + public ValueTask SendRequestAsync(IRequest request, CancellationToken cancellationToken = default) { throw new NotImplementedException(); } - public TResponse SendRequest(global::GFramework.Core.Abstractions.Cqrs.IRequest request) + public TResponse SendRequest(IRequest request) { throw new NotImplementedException(); } - public ValueTask SendCommandAsync( - global::GFramework.Core.Abstractions.Cqrs.Command.ICommand command, + public ValueTask SendCommandAsync(Abstractions.Cqrs.Command.ICommand command, CancellationToken cancellationToken = default) { throw new NotImplementedException(); } - public TResponse SendCommand(global::GFramework.Core.Abstractions.Cqrs.Command.ICommand command) + public TResponse SendCommand(Abstractions.Cqrs.Command.ICommand command) { throw new NotImplementedException(); } - public ValueTask SendQueryAsync( - global::GFramework.Core.Abstractions.Cqrs.Query.IQuery query, + public ValueTask SendQueryAsync(Abstractions.Cqrs.Query.IQuery query, CancellationToken cancellationToken = default) { throw new NotImplementedException(); } - public TResponse SendQuery(global::GFramework.Core.Abstractions.Cqrs.Query.IQuery query) + public TResponse SendQuery(Abstractions.Cqrs.Query.IQuery query) { throw new NotImplementedException(); } public ValueTask PublishAsync(TNotification notification, - CancellationToken cancellationToken = default) where TNotification : global::GFramework.Core.Abstractions.Cqrs.INotification + CancellationToken cancellationToken = default) where TNotification : INotification { throw new NotImplementedException(); } public IAsyncEnumerable CreateStream( - global::GFramework.Core.Abstractions.Cqrs.IStreamRequest request, + IStreamRequest request, CancellationToken cancellationToken = default) { throw new NotImplementedException(); } public ValueTask SendAsync(TCommand command, CancellationToken cancellationToken = default) - where TCommand : global::GFramework.Core.Abstractions.Cqrs.IRequest + where TCommand : IRequest { throw new NotImplementedException(); } - public ValueTask SendAsync(global::GFramework.Core.Abstractions.Cqrs.IRequest command, + public ValueTask SendAsync(IRequest command, CancellationToken cancellationToken = default) { throw new NotImplementedException(); diff --git a/GFramework.Core.Tests/Architectures/GameContextTests.cs b/GFramework.Core.Tests/Architectures/GameContextTests.cs index 6c2670bc..e11f0892 100644 --- a/GFramework.Core.Tests/Architectures/GameContextTests.cs +++ b/GFramework.Core.Tests/Architectures/GameContextTests.cs @@ -1,5 +1,6 @@ using GFramework.Core.Abstractions.Architectures; using GFramework.Core.Abstractions.Command; +using GFramework.Core.Abstractions.Cqrs; using GFramework.Core.Abstractions.Environment; using GFramework.Core.Abstractions.Events; using GFramework.Core.Abstractions.Ioc; @@ -13,7 +14,6 @@ using GFramework.Core.Environment; using GFramework.Core.Events; using GFramework.Core.Ioc; using GFramework.Core.Query; -using Mediator; using ICommand = GFramework.Core.Abstractions.Command.ICommand; namespace GFramework.Core.Tests.Architectures; @@ -394,61 +394,59 @@ public class TestArchitectureContext : IArchitectureContext { } - public ValueTask SendRequestAsync(global::GFramework.Core.Abstractions.Cqrs.IRequest request, + public ValueTask SendRequestAsync(IRequest request, CancellationToken cancellationToken = default) { throw new NotImplementedException(); } - public TResponse SendRequest(global::GFramework.Core.Abstractions.Cqrs.IRequest request) + public TResponse SendRequest(IRequest request) { throw new NotImplementedException(); } - public ValueTask SendCommandAsync( - global::GFramework.Core.Abstractions.Cqrs.Command.ICommand command, + public ValueTask SendCommandAsync(Abstractions.Cqrs.Command.ICommand command, CancellationToken cancellationToken = default) { throw new NotImplementedException(); } - public TResponse SendCommand(global::GFramework.Core.Abstractions.Cqrs.Command.ICommand command) + public TResponse SendCommand(Abstractions.Cqrs.Command.ICommand command) { throw new NotImplementedException(); } - public ValueTask SendQueryAsync( - global::GFramework.Core.Abstractions.Cqrs.Query.IQuery query, + public ValueTask SendQueryAsync(Abstractions.Cqrs.Query.IQuery query, CancellationToken cancellationToken = default) { throw new NotImplementedException(); } - public TResponse SendQuery(global::GFramework.Core.Abstractions.Cqrs.Query.IQuery query) + public TResponse SendQuery(Abstractions.Cqrs.Query.IQuery query) { throw new NotImplementedException(); } public ValueTask PublishAsync(TNotification notification, - CancellationToken cancellationToken = default) where TNotification : global::GFramework.Core.Abstractions.Cqrs.INotification + CancellationToken cancellationToken = default) where TNotification : INotification { throw new NotImplementedException(); } public IAsyncEnumerable CreateStream( - global::GFramework.Core.Abstractions.Cqrs.IStreamRequest request, + IStreamRequest request, CancellationToken cancellationToken = default) { throw new NotImplementedException(); } public ValueTask SendAsync(TCommand command, CancellationToken cancellationToken = default) - where TCommand : global::GFramework.Core.Abstractions.Cqrs.IRequest + where TCommand : IRequest { throw new NotImplementedException(); } - public ValueTask SendAsync(global::GFramework.Core.Abstractions.Cqrs.IRequest command, + public ValueTask SendAsync(IRequest command, CancellationToken cancellationToken = default) { throw new NotImplementedException(); @@ -468,7 +466,7 @@ public class TestArchitectureContext : IArchitectureContext /// 返回值类型 /// 命令对象 /// 命令执行结果 - public TResult SendCommand(Abstractions.Command.ICommand command) + public TResult SendCommand(ICommand command) { return default!; } @@ -489,7 +487,7 @@ public class TestArchitectureContext : IArchitectureContext /// 查询结果类型 /// 查询对象 /// 查询结果 - public TResult SendQuery(Abstractions.Query.IQuery query) + public TResult SendQuery(IQuery query) { return default!; } diff --git a/GFramework.Core.Tests/Coroutine/MediatorCoroutineExtensionsTests.cs b/GFramework.Core.Tests/Coroutine/MediatorCoroutineExtensionsTests.cs index eeb1493c..9dfa7b1e 100644 --- a/GFramework.Core.Tests/Coroutine/MediatorCoroutineExtensionsTests.cs +++ b/GFramework.Core.Tests/Coroutine/MediatorCoroutineExtensionsTests.cs @@ -13,68 +13,32 @@ using GFramework.Core.Abstractions.Architectures; using GFramework.Core.Abstractions.Coroutine; +using GFramework.Core.Abstractions.Cqrs; using GFramework.Core.Abstractions.Rule; using GFramework.Core.Coroutine.Extensions; -using Mediator; -using Moq; namespace GFramework.Core.Tests.Coroutine; /// -/// MediatorCoroutineExtensions的单元测试类 -/// 测试Mediator模式与协程集成的扩展方法 -/// 注意:由于 Mediator 使用源生成器,本测试类主要验证接口和参数验证 +/// 的单元测试类。 +/// 验证历史命名的协程扩展已切到框架内建 CQRS runtime, +/// 并确保协程对命令调度异常的传播行为保持稳定。 /// [TestFixture] public class MediatorCoroutineExtensionsTests { - /// - /// 测试用的简单命令类 - /// - private class TestCommand : IRequest - { - public string Data { get; set; } = string.Empty; - } - - /// - /// 测试用的简单事件类 - /// - private class TestEvent - { - public string Data { get; set; } = string.Empty; - } - - /// - /// 上下文感知基类的模拟实现 - /// - private class TestContextAware : IContextAware - { - public readonly Mock _mockContext = new(); - - public IArchitectureContext GetContext() - { - return _mockContext.Object; - } - - public void SetContext(IArchitectureContext context) - { - } - } - /// /// 验证SendCommandCoroutine应该返回IEnumerator /// [Test] public void SendCommandCoroutine_Should_Return_IEnumerator_Of_YieldInstruction() { - var command = new TestCommand { Data = "Test" }; + var command = new TestCommand("Test"); var contextAware = new TestContextAware(); - // 创建 mediator 模拟 - var mediatorMock = new Mock(); - contextAware._mockContext - .Setup(ctx => ctx.GetService()) - .Returns(mediatorMock.Object); + contextAware.MockContext + .Setup(ctx => ctx.SendAsync(command, It.IsAny())) + .Returns(ValueTask.CompletedTask); var coroutine = MediatorCoroutineExtensions.SendCommandCoroutine(contextAware, command); @@ -82,23 +46,45 @@ public class MediatorCoroutineExtensionsTests } /// - /// 验证SendCommandCoroutine应该在mediator为null时抛出NullReferenceException + /// 验证 SendCommandCoroutine 在底层命令调度失败时会重新抛出原始异常。 /// [Test] - public void SendCommandCoroutine_Should_Throw_When_Mediator_Null() + public void SendCommandCoroutine_Should_Rethrow_Inner_Exception_When_Command_Fails() { - var command = new TestCommand { Data = "Test" }; + var command = new TestCommand("Test"); var contextAware = new TestContextAware(); + var expectedException = new InvalidOperationException("Command failed."); - // 设置上下文服务以返回null mediator - contextAware._mockContext - .Setup(ctx => ctx.GetService()) - .Returns((IMediator?)null); + contextAware.MockContext + .Setup(ctx => ctx.SendAsync(command, It.IsAny())) + .Returns(new ValueTask(Task.FromException(expectedException))); - // 创建协程 var coroutine = MediatorCoroutineExtensions.SendCommandCoroutine(contextAware, command); - // 调用 MoveNext 时应该抛出 NullReferenceException - Assert.Throws(() => coroutine.MoveNext()); + Assert.That(coroutine.MoveNext(), Is.True); + var exception = Assert.Throws(() => coroutine.MoveNext()); + Assert.That(exception, Is.SameAs(expectedException)); } -} \ No newline at end of file + + /// + /// 测试用的简单命令类 + /// + private sealed record TestCommand(string Data) : IRequest; + + /// + /// 上下文感知基类的模拟实现 + /// + private sealed class TestContextAware : IContextAware + { + public Mock MockContext { get; } = new(); + + public IArchitectureContext GetContext() + { + return MockContext.Object; + } + + public void SetContext(IArchitectureContext context) + { + } + } +} diff --git a/GFramework.Core.Tests/GFramework.Core.Tests.csproj b/GFramework.Core.Tests/GFramework.Core.Tests.csproj index 5e59b144..97663fe0 100644 --- a/GFramework.Core.Tests/GFramework.Core.Tests.csproj +++ b/GFramework.Core.Tests/GFramework.Core.Tests.csproj @@ -10,11 +10,6 @@ 0 - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - diff --git a/GFramework.Core/Architectures/Architecture.cs b/GFramework.Core/Architectures/Architecture.cs index 39b4fd13..ac4fa2dc 100644 --- a/GFramework.Core/Architectures/Architecture.cs +++ b/GFramework.Core/Architectures/Architecture.cs @@ -7,7 +7,6 @@ using GFramework.Core.Abstractions.Systems; using GFramework.Core.Abstractions.Utility; using GFramework.Core.Environment; using GFramework.Core.Logging; -using Microsoft.Extensions.DependencyInjection; namespace GFramework.Core.Architectures; @@ -146,8 +145,8 @@ public abstract class Architecture : IArchitecture #region Module Management /// - /// 注册中介行为管道 - /// 用于配置Mediator框架的行为拦截和处理逻辑。 + /// 注册 CQRS 请求管道行为。 + /// 历史方法名保留了 Mediator 前缀,但当前用于配置框架内建 CQRS runtime 的行为拦截和处理逻辑。 /// 可以传入开放泛型行为类型,也可以传入绑定到特定请求的封闭行为类型。 /// /// 行为类型,必须是引用类型 @@ -328,4 +327,4 @@ public abstract class Architecture : IArchitecture } #endregion -} \ No newline at end of file +} diff --git a/GFramework.Core/Architectures/ArchitectureBootstrapper.cs b/GFramework.Core/Architectures/ArchitectureBootstrapper.cs index f658329a..d022c806 100644 --- a/GFramework.Core/Architectures/ArchitectureBootstrapper.cs +++ b/GFramework.Core/Architectures/ArchitectureBootstrapper.cs @@ -2,7 +2,6 @@ using GFramework.Core.Abstractions.Architectures; using GFramework.Core.Abstractions.Environment; using GFramework.Core.Abstractions.Logging; using GFramework.Core.Cqrs.Internal; -using Microsoft.Extensions.DependencyInjection; namespace GFramework.Core.Architectures; @@ -23,7 +22,7 @@ internal sealed class ArchitectureBootstrapper( /// 因为用户初始化逻辑通常会立即访问事件总线、查询执行器或环境对象。 /// /// 调用方已经提供的上下文;如果为空则创建默认上下文。 - /// 可选的容器配置委托,用于接入 Mediator 等扩展服务。 + /// 可选的容器配置委托,用于接入额外服务或覆盖默认依赖绑定。 /// 是否以异步模式初始化服务模块。 /// 已绑定到当前架构类型的架构上下文。 public async Task PrepareForInitializationAsync( diff --git a/GFramework.Core/Architectures/ArchitectureModules.cs b/GFramework.Core/Architectures/ArchitectureModules.cs index 94acae78..d02f39e2 100644 --- a/GFramework.Core/Architectures/ArchitectureModules.cs +++ b/GFramework.Core/Architectures/ArchitectureModules.cs @@ -5,7 +5,7 @@ namespace GFramework.Core.Architectures; /// /// 架构模块管理器 -/// 负责管理架构模块的安装和中介行为注册 +/// 负责管理架构模块的安装和 CQRS 行为注册 /// internal sealed class ArchitectureModules( IArchitecture architecture, @@ -13,14 +13,14 @@ internal sealed class ArchitectureModules( ILogger logger) { /// - /// 注册中介行为管道 - /// 用于配置Mediator框架的行为拦截和处理逻辑。 + /// 注册 CQRS 请求管道行为。 + /// 历史方法名保留了 Mediator 前缀,但当前用于配置框架内建 CQRS runtime 的行为拦截和处理逻辑。 /// 支持开放泛型行为类型和针对单一请求的封闭行为类型。 /// /// 行为类型,必须是引用类型 public void RegisterMediatorBehavior() where TBehavior : class { - logger.Debug($"Registering mediator behavior: {typeof(TBehavior).Name}"); + logger.Debug($"Registering CQRS pipeline behavior: {typeof(TBehavior).Name}"); services.Container.RegisterMediatorBehavior(); } @@ -37,4 +37,4 @@ internal sealed class ArchitectureModules( logger.Info($"Module installed: {name}"); return module; } -} \ No newline at end of file +} diff --git a/GFramework.Core/Coroutine/Extensions/MediatorCoroutineExtensions.cs b/GFramework.Core/Coroutine/Extensions/MediatorCoroutineExtensions.cs index 5c301c07..42ff1ce2 100644 --- a/GFramework.Core/Coroutine/Extensions/MediatorCoroutineExtensions.cs +++ b/GFramework.Core/Coroutine/Extensions/MediatorCoroutineExtensions.cs @@ -12,21 +12,22 @@ // limitations under the License. using GFramework.Core.Abstractions.Coroutine; +using GFramework.Core.Abstractions.Cqrs; using GFramework.Core.Abstractions.Rule; -using Mediator; namespace GFramework.Core.Coroutine.Extensions; /// -/// 提供Mediator模式与协程集成的扩展方法。 -/// 包含发送命令和等待事件的协程实现。 +/// 提供 CQRS 命令与协程集成的扩展方法。 +/// 历史命名保留了 Mediator 前缀,但当前实现直接走 返回的 +/// CQRS 入口,不再依赖外部 Mediator 服务。 /// public static class MediatorCoroutineExtensions { /// - /// 以协程方式发送命令并处理可能的异常。 + /// 以协程方式发送无返回值 CQRS 命令并处理可能的异常。 /// - /// 命令的类型 + /// 命令的类型。 /// 上下文感知对象,用于获取服务 /// 要发送的命令对象 /// 发生异常时的回调处理函数 @@ -35,13 +36,12 @@ public static class MediatorCoroutineExtensions this IContextAware contextAware, TCommand command, Action? onError = null) - where TCommand : notnull + where TCommand : IRequest { - var mediator = contextAware - .GetContext() - .GetService()!; + ArgumentNullException.ThrowIfNull(contextAware); + ArgumentNullException.ThrowIfNull(command); - var task = mediator.Send(command).AsTask(); + var task = contextAware.GetContext().SendAsync(command).AsTask(); yield return task.AsCoroutineInstruction(); @@ -51,4 +51,4 @@ public static class MediatorCoroutineExtensions else throw task.Exception!.InnerException ?? task.Exception; } -} \ No newline at end of file +} diff --git a/GFramework.Core/Cqrs/Internal/CqrsHandlerRegistrar.cs b/GFramework.Core/Cqrs/Internal/CqrsHandlerRegistrar.cs index 136b76a7..957c0981 100644 --- a/GFramework.Core/Cqrs/Internal/CqrsHandlerRegistrar.cs +++ b/GFramework.Core/Cqrs/Internal/CqrsHandlerRegistrar.cs @@ -7,7 +7,7 @@ namespace GFramework.Core.Cqrs.Internal; /// /// 在架构初始化期间扫描并注册 CQRS 处理器。 -/// 首批实现采用运行时反射扫描,优先满足“无需 AddMediator 即可工作”的迁移目标。 +/// 首批实现采用运行时反射扫描,优先满足“无需额外注册步骤即可工作”的迁移目标。 /// internal static class CqrsHandlerRegistrar { diff --git a/GFramework.Core/Ioc/MicrosoftDiContainer.cs b/GFramework.Core/Ioc/MicrosoftDiContainer.cs index dec4f267..6b45a09e 100644 --- a/GFramework.Core/Ioc/MicrosoftDiContainer.cs +++ b/GFramework.Core/Ioc/MicrosoftDiContainer.cs @@ -5,7 +5,6 @@ using GFramework.Core.Abstractions.Logging; using GFramework.Core.Abstractions.Systems; using GFramework.Core.Logging; using GFramework.Core.Rule; -using Microsoft.Extensions.DependencyInjection; namespace GFramework.Core.Ioc; @@ -310,8 +309,8 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null) /// - /// 注册中介行为管道 - /// 用于配置Mediator框架的行为拦截和处理逻辑。 + /// 注册 CQRS 请求管道行为。 + /// 历史方法名保留了 Mediator 前缀,但当前用于配置框架内建 CQRS runtime 的行为拦截和处理逻辑。 /// 同时支持开放泛型行为类型和已闭合的具体行为类型, /// 以兼容通用行为和针对单一请求的专用行为两种注册方式。 /// @@ -351,7 +350,7 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null) } } - _logger.Debug($"Mediator behavior registered: {behaviorType.Name}"); + _logger.Debug($"CQRS pipeline behavior registered: {behaviorType.Name}"); } finally { diff --git a/docs/zh-CN/core/cqrs.md b/docs/zh-CN/core/cqrs.md index d0cf0f9e..b92566d1 100644 --- a/docs/zh-CN/core/cqrs.md +++ b/docs/zh-CN/core/cqrs.md @@ -1,21 +1,21 @@ --- -title: CQRS 与 Mediator -description: CQRS 模式通过 Mediator 实现命令查询职责分离,提供清晰的业务逻辑组织方式。 +title: CQRS +description: GFramework 内建 CQRS runtime,用统一请求分发、通知发布和流式处理组织业务逻辑。 --- -# CQRS 与 Mediator +# CQRS ## 概述 CQRS(Command Query Responsibility Segregation,命令查询职责分离)是一种架构模式,将数据的读取(Query)和修改(Command)操作分离。GFramework -通过集成 Mediator 库实现了 CQRS 模式,提供了类型安全、解耦的业务逻辑处理方式。 +当前内建自有 CQRS runtime,通过统一的请求分发器、通知发布和流式请求管道提供类型安全、解耦的业务逻辑处理方式。 通过 CQRS,你可以将复杂的业务逻辑拆分为独立的命令和查询处理器,每个处理器只负责单一职责,使代码更易于测试和维护。 **主要特性**: - 命令查询职责分离 -- 基于 Mediator 模式的解耦设计 +- 内建请求分发与解耦设计 - 支持管道行为(Behaviors) - 异步处理支持 - 与架构系统深度集成 @@ -72,7 +72,6 @@ public class GetPlayerQuery : QueryBase ```csharp using GFramework.Core.CQRS.Command; -using Mediator; // 命令处理器 public class CreatePlayerCommandHandler : AbstractCommandHandler @@ -92,19 +91,19 @@ public class CreatePlayerCommandHandler : AbstractCommandHandler // 4. 发送命令 public async Task SaveGame() { - var mediator = this.GetService(); - var command = new SaveGameCommand(new SaveGameInput { SlotId = 1, Data = currentGameData }); - await mediator.Send(command); + await this.SendAsync(command); } ``` @@ -195,21 +192,19 @@ public class GetHighScoresQueryHandler : AbstractQueryHandler> GetHighScores() { - var mediator = this.GetService(); - var query = new GetHighScoresQuery(new GetHighScoresInput { Count = 10 }); - var scores = await mediator.Send(query); + var scores = await this.SendQueryAsync(query); return scores; } ``` ### 注册处理器 -在架构中注册 Mediator 和处理器: +在架构中注册 CQRS 行为并让处理器自动扫描注册: ```csharp public class GameArchitecture : Architecture @@ -225,7 +220,7 @@ public class GameArchitecture : Architecture } ``` -`RegisterMediatorBehavior()` 同时支持两种形式: +`RegisterMediatorBehavior()` 是保留的兼容名称,当前用于注册框架内建 CQRS pipeline,支持两种形式: - 开放泛型行为,例如 `LoggingBehavior<,>`,用于匹配所有请求 - 封闭行为类型,例如某个只服务于单一请求的 `SpecialBehavior` @@ -326,7 +321,7 @@ var notification = new PlayerLevelUpNotification(new PlayerLevelUpInput NewLevel = 10 }); -await mediator.Publish(notification); +await this.PublishAsync(notification); ``` ### Pipeline Behaviors(管道行为) @@ -334,11 +329,11 @@ await mediator.Publish(notification); Behaviors 可以在处理器执行前后添加横切关注点: ```csharp -using Mediator; +using GFramework.Core.Abstractions.Cqrs; // 日志行为 public class LoggingBehavior : IPipelineBehavior - where TMessage : IMessage + where TMessage : IRequest { public async ValueTask Handle( TMessage message, @@ -358,7 +353,7 @@ public class LoggingBehavior : IPipelineBehavior : IPipelineBehavior - where TMessage : IMessage + where TMessage : IRequest { public async ValueTask Handle( TMessage message, @@ -390,7 +385,7 @@ RegisterMediatorBehavior>(); ```csharp public class ValidationBehavior : IPipelineBehavior - where TMessage : IMessage + where TMessage : IRequest { public async ValueTask Handle( TMessage message, @@ -441,7 +436,7 @@ public class GetAllPlayersStreamQueryHandler : AbstractStreamQueryHandler Handle(...) ### 问题:处理器可以调用其他处理器吗? **解答**: -可以,通过 Mediator 发送新的命令或查询: +可以,通过架构上下文继续发送新的命令或查询: ```csharp public override async ValueTask Handle(...) { - var mediator = this.GetService(); - // 调用其他命令 - await mediator.Send(new AnotherCommand(...)); + await this.SendAsync(new AnotherCommand(...)); return Unit.Value; } diff --git a/docs/zh-CN/core/index.md b/docs/zh-CN/core/index.md index 8430e58f..98455237 100644 --- a/docs/zh-CN/core/index.md +++ b/docs/zh-CN/core/index.md @@ -391,17 +391,17 @@ public class PlayerController : IController #### 5. ArchitectureModules (模块管理器) -**职责**: 管理架构模块和中介行为 +**职责**: 管理架构模块和 CQRS 管道行为 **核心功能**: - 模块安装 (IArchitectureModule) -- 中介行为注册 (Mediator Behaviors) +- CQRS 管道行为注册(历史 API 名称仍为 `RegisterMediatorBehavior`) **关键方法**: - `InstallModule()` - 安装模块 -- `RegisterMediatorBehavior()` - 注册中介行为 +- `RegisterMediatorBehavior()` - 注册 CQRS 管道行为 #### 设计优势 @@ -672,4 +672,3 @@ public interface IController : - 添加 `PhaseChanged` 事件,支持阶段监听 **向后兼容**: 所有公共 API 保持不变,现有代码无需修改。 - From 115fe65e88599c173d8175b3e4e035c3f5d00ad0 Mon Sep 17 00:00:00 2001 From: GeWuYou <95328647+GeWuYou@users.noreply.github.com> Date: Tue, 14 Apr 2026 22:54:27 +0800 Subject: [PATCH 2/7] =?UTF-8?q?docs(core):=20=E6=B7=BB=E5=8A=A0=20CQRS=20?= =?UTF-8?q?=E5=92=8C=E6=A0=B8=E5=BF=83=E6=A1=86=E6=9E=B6=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 CQRS 模块详细文档,介绍命令查询职责分离模式 - 添加核心框架架构概述和五层架构设计说明 - 补充快速开始指南和最佳实践建议 - 完善包说明和组件联动机制介绍 - 添加架构生命周期管理和模块化设计说明 --- .../Architectures/IArchitecture.cs | 12 +- .../Ioc/IIocContainer.cs | 10 +- .../ArchitectureModulesBehaviorTests.cs | 4 +- .../RegistryInitializationHookBaseTests.cs | 19 ++- ...sts.cs => CqrsCoroutineExtensionsTests.cs} | 12 +- GFramework.Core/Architectures/Architecture.cs | 14 +- .../Architectures/ArchitectureModules.cs | 16 ++- .../Extensions/CqrsCoroutineExtensions.cs | 43 ++++++ .../Extensions/MediatorCoroutineExtensions.cs | 18 +-- .../ContextAwareCqrsCommandExtensions.cs | 44 +++++++ .../Extensions/ContextAwareCqrsExtensions.cs | 123 ++++++++++++++++++ .../ContextAwareCqrsQueryExtensions.cs | 44 +++++++ .../ContextAwareMediatorCommandExtensions.cs | 22 ++-- .../ContextAwareMediatorExtensions.cs | 58 ++++----- .../ContextAwareMediatorQueryExtensions.cs | 22 ++-- GFramework.Core/Ioc/MicrosoftDiContainer.cs | 14 +- .../ContextAwareCoroutineExtensions.cs | 19 ++- docs/zh-CN/core/cqrs.md | 15 ++- docs/zh-CN/core/index.md | 4 +- 19 files changed, 402 insertions(+), 111 deletions(-) rename GFramework.Core.Tests/Coroutine/{MediatorCoroutineExtensionsTests.cs => CqrsCoroutineExtensionsTests.cs} (86%) create mode 100644 GFramework.Core/Coroutine/Extensions/CqrsCoroutineExtensions.cs create mode 100644 GFramework.Core/Extensions/ContextAwareCqrsCommandExtensions.cs create mode 100644 GFramework.Core/Extensions/ContextAwareCqrsExtensions.cs create mode 100644 GFramework.Core/Extensions/ContextAwareCqrsQueryExtensions.cs diff --git a/GFramework.Core.Abstractions/Architectures/IArchitecture.cs b/GFramework.Core.Abstractions/Architectures/IArchitecture.cs index bb855189..78303a2d 100644 --- a/GFramework.Core.Abstractions/Architectures/IArchitecture.cs +++ b/GFramework.Core.Abstractions/Architectures/IArchitecture.cs @@ -73,11 +73,21 @@ public interface IArchitecture : IAsyncInitializable, IAsyncDestroyable, IInitia /// /// 注册 CQRS 请求管道行为。 - /// 历史方法名保留了 Mediator 前缀,但当前用于配置框架内建 CQRS runtime 的行为拦截和处理逻辑。 /// 既支持实现 IPipelineBehavior<,> 的开放泛型行为类型, /// 也支持绑定到单一请求/响应对的封闭行为类型。 /// /// 行为类型,必须是引用类型 + void RegisterCqrsPipelineBehavior() + where TBehavior : class; + + /// + /// 注册 CQRS 请求管道行为。 + /// 该成员保留旧名称以兼容历史调用点,内部行为与 一致。 + /// 既支持实现 IPipelineBehavior<,> 的开放泛型行为类型, + /// 也支持绑定到单一请求/响应对的封闭行为类型。 + /// + /// 行为类型,必须是引用类型 + [Obsolete("Use RegisterCqrsPipelineBehavior() instead.")] void RegisterMediatorBehavior() where TBehavior : class; diff --git a/GFramework.Core.Abstractions/Ioc/IIocContainer.cs b/GFramework.Core.Abstractions/Ioc/IIocContainer.cs index d00211ed..0e55908a 100644 --- a/GFramework.Core.Abstractions/Ioc/IIocContainer.cs +++ b/GFramework.Core.Abstractions/Ioc/IIocContainer.cs @@ -90,9 +90,17 @@ public interface IIocContainer : IContextAware /// /// 注册 CQRS 请求管道行为。 - /// 历史方法名保留了 Mediator 前缀,但当前用于配置框架内建 CQRS runtime 的行为拦截和处理逻辑。 /// /// 行为类型,必须是引用类型 + void RegisterCqrsPipelineBehavior() + where TBehavior : class; + + /// + /// 注册 CQRS 请求管道行为。 + /// 该成员保留旧名称以兼容历史调用点,内部行为与 一致。 + /// + /// 行为类型,必须是引用类型 + [Obsolete("Use RegisterCqrsPipelineBehavior() instead.")] void RegisterMediatorBehavior() where TBehavior : class; diff --git a/GFramework.Core.Tests/Architectures/ArchitectureModulesBehaviorTests.cs b/GFramework.Core.Tests/Architectures/ArchitectureModulesBehaviorTests.cs index bf6566b4..c5c0b685 100644 --- a/GFramework.Core.Tests/Architectures/ArchitectureModulesBehaviorTests.cs +++ b/GFramework.Core.Tests/Architectures/ArchitectureModulesBehaviorTests.cs @@ -59,10 +59,10 @@ public class ArchitectureModulesBehaviorTests /// 验证注册的 CQRS 行为会参与请求管道执行。 /// [Test] - public async Task RegisterMediatorBehavior_Should_Apply_Pipeline_Behavior_To_Request() + public async Task RegisterCqrsPipelineBehavior_Should_Apply_Pipeline_Behavior_To_Request() { var architecture = new ModuleTestArchitecture(target => - target.RegisterMediatorBehavior>()); + target.RegisterCqrsPipelineBehavior>()); await architecture.InitializeAsync(); diff --git a/GFramework.Core.Tests/Architectures/RegistryInitializationHookBaseTests.cs b/GFramework.Core.Tests/Architectures/RegistryInitializationHookBaseTests.cs index 4e517194..38e06805 100644 --- a/GFramework.Core.Tests/Architectures/RegistryInitializationHookBaseTests.cs +++ b/GFramework.Core.Tests/Architectures/RegistryInitializationHookBaseTests.cs @@ -5,7 +5,6 @@ using GFramework.Core.Abstractions.Model; using GFramework.Core.Abstractions.Systems; using GFramework.Core.Abstractions.Utility; using GFramework.Core.Architectures; -using Microsoft.Extensions.DependencyInjection; namespace GFramework.Core.Tests.Architectures; @@ -181,11 +180,17 @@ public class TestArchitectureWithRegistry : IArchitecture throw new NotImplementedException(); } - public void RegisterMediatorBehavior() where TBehavior : class + public void RegisterCqrsPipelineBehavior() where TBehavior : class { throw new NotImplementedException(); } + [Obsolete("Use RegisterCqrsPipelineBehavior() instead.")] + public void RegisterMediatorBehavior() where TBehavior : class + { + RegisterCqrsPipelineBehavior(); + } + public IArchitectureModule InstallModule(IArchitectureModule module) { throw new NotImplementedException(); @@ -306,11 +311,17 @@ public class TestArchitectureWithoutRegistry : IArchitecture throw new NotImplementedException(); } - public void RegisterMediatorBehavior() where TBehavior : class + public void RegisterCqrsPipelineBehavior() where TBehavior : class { throw new NotImplementedException(); } + [Obsolete("Use RegisterCqrsPipelineBehavior() instead.")] + public void RegisterMediatorBehavior() where TBehavior : class + { + RegisterCqrsPipelineBehavior(); + } + public IArchitectureModule InstallModule(IArchitectureModule module) { throw new NotImplementedException(); @@ -363,4 +374,4 @@ public class TestArchitectureWithoutRegistry : IArchitecture public void RegisterLifecycleHook(IArchitectureLifecycleHook hook) { } -} \ No newline at end of file +} diff --git a/GFramework.Core.Tests/Coroutine/MediatorCoroutineExtensionsTests.cs b/GFramework.Core.Tests/Coroutine/CqrsCoroutineExtensionsTests.cs similarity index 86% rename from GFramework.Core.Tests/Coroutine/MediatorCoroutineExtensionsTests.cs rename to GFramework.Core.Tests/Coroutine/CqrsCoroutineExtensionsTests.cs index 9dfa7b1e..2adba37a 100644 --- a/GFramework.Core.Tests/Coroutine/MediatorCoroutineExtensionsTests.cs +++ b/GFramework.Core.Tests/Coroutine/CqrsCoroutineExtensionsTests.cs @@ -15,17 +15,17 @@ using GFramework.Core.Abstractions.Architectures; using GFramework.Core.Abstractions.Coroutine; using GFramework.Core.Abstractions.Cqrs; using GFramework.Core.Abstractions.Rule; -using GFramework.Core.Coroutine.Extensions; +using GFramework.Core.Cqrs.Extensions; namespace GFramework.Core.Tests.Coroutine; /// -/// 的单元测试类。 -/// 验证历史命名的协程扩展已切到框架内建 CQRS runtime, +/// 的单元测试类。 +/// 验证新的 CQRS 协程扩展直接走框架内建 CQRS runtime, /// 并确保协程对命令调度异常的传播行为保持稳定。 /// [TestFixture] -public class MediatorCoroutineExtensionsTests +public class CqrsCoroutineExtensionsTests { /// /// 验证SendCommandCoroutine应该返回IEnumerator @@ -40,7 +40,7 @@ public class MediatorCoroutineExtensionsTests .Setup(ctx => ctx.SendAsync(command, It.IsAny())) .Returns(ValueTask.CompletedTask); - var coroutine = MediatorCoroutineExtensions.SendCommandCoroutine(contextAware, command); + var coroutine = CqrsCoroutineExtensions.SendCommandCoroutine(contextAware, command); Assert.That(coroutine, Is.InstanceOf>()); } @@ -59,7 +59,7 @@ public class MediatorCoroutineExtensionsTests .Setup(ctx => ctx.SendAsync(command, It.IsAny())) .Returns(new ValueTask(Task.FromException(expectedException))); - var coroutine = MediatorCoroutineExtensions.SendCommandCoroutine(contextAware, command); + var coroutine = CqrsCoroutineExtensions.SendCommandCoroutine(contextAware, command); Assert.That(coroutine.MoveNext(), Is.True); var exception = Assert.Throws(() => coroutine.MoveNext()); diff --git a/GFramework.Core/Architectures/Architecture.cs b/GFramework.Core/Architectures/Architecture.cs index ac4fa2dc..ec571ee8 100644 --- a/GFramework.Core/Architectures/Architecture.cs +++ b/GFramework.Core/Architectures/Architecture.cs @@ -146,13 +146,23 @@ public abstract class Architecture : IArchitecture /// /// 注册 CQRS 请求管道行为。 - /// 历史方法名保留了 Mediator 前缀,但当前用于配置框架内建 CQRS runtime 的行为拦截和处理逻辑。 /// 可以传入开放泛型行为类型,也可以传入绑定到特定请求的封闭行为类型。 /// /// 行为类型,必须是引用类型 + public void RegisterCqrsPipelineBehavior() where TBehavior : class + { + _modules.RegisterCqrsPipelineBehavior(); + } + + /// + /// 注册 CQRS 请求管道行为。 + /// 该成员保留旧名称以兼容历史调用点,内部行为与 一致。 + /// + /// 行为类型,必须是引用类型 + [Obsolete("Use RegisterCqrsPipelineBehavior() instead.")] public void RegisterMediatorBehavior() where TBehavior : class { - _modules.RegisterMediatorBehavior(); + RegisterCqrsPipelineBehavior(); } /// diff --git a/GFramework.Core/Architectures/ArchitectureModules.cs b/GFramework.Core/Architectures/ArchitectureModules.cs index d02f39e2..7563e276 100644 --- a/GFramework.Core/Architectures/ArchitectureModules.cs +++ b/GFramework.Core/Architectures/ArchitectureModules.cs @@ -14,14 +14,24 @@ internal sealed class ArchitectureModules( { /// /// 注册 CQRS 请求管道行为。 - /// 历史方法名保留了 Mediator 前缀,但当前用于配置框架内建 CQRS runtime 的行为拦截和处理逻辑。 /// 支持开放泛型行为类型和针对单一请求的封闭行为类型。 /// /// 行为类型,必须是引用类型 - public void RegisterMediatorBehavior() where TBehavior : class + public void RegisterCqrsPipelineBehavior() where TBehavior : class { logger.Debug($"Registering CQRS pipeline behavior: {typeof(TBehavior).Name}"); - services.Container.RegisterMediatorBehavior(); + services.Container.RegisterCqrsPipelineBehavior(); + } + + /// + /// 注册 CQRS 请求管道行为。 + /// 该成员保留旧名称以兼容历史调用点,内部行为与 一致。 + /// + /// 行为类型,必须是引用类型 + [Obsolete("Use RegisterCqrsPipelineBehavior() instead.")] + public void RegisterMediatorBehavior() where TBehavior : class + { + RegisterCqrsPipelineBehavior(); } /// diff --git a/GFramework.Core/Coroutine/Extensions/CqrsCoroutineExtensions.cs b/GFramework.Core/Coroutine/Extensions/CqrsCoroutineExtensions.cs new file mode 100644 index 00000000..55e6a67f --- /dev/null +++ b/GFramework.Core/Coroutine/Extensions/CqrsCoroutineExtensions.cs @@ -0,0 +1,43 @@ +using GFramework.Core.Abstractions.Coroutine; +using GFramework.Core.Abstractions.Cqrs; +using GFramework.Core.Abstractions.Rule; +using GFramework.Core.Coroutine.Extensions; + +namespace GFramework.Core.Cqrs.Extensions; + +/// +/// 提供 CQRS 命令与协程集成的扩展方法。 +/// 这些扩展直接走架构上下文的内建 CQRS runtime,不依赖外部 Mediator 服务。 +/// +public static class CqrsCoroutineExtensions +{ + /// + /// 以协程方式发送无返回值 CQRS 命令并处理可能的异常。 + /// + /// 命令类型。 + /// 上下文感知对象,用于获取架构上下文。 + /// 要发送的命令对象。 + /// 发生异常时的回调处理函数。 + /// 协程枚举器,用于协程执行。 + public static IEnumerator SendCommandCoroutine( + this IContextAware contextAware, + TCommand command, + Action? onError = null) + where TCommand : IRequest + { + ArgumentNullException.ThrowIfNull(contextAware); + ArgumentNullException.ThrowIfNull(command); + + var task = contextAware.GetContext().SendAsync(command).AsTask(); + + yield return task.AsCoroutineInstruction(); + + if (!task.IsFaulted) + yield break; + + if (onError != null) + onError.Invoke(task.Exception!); + else + throw task.Exception!.InnerException ?? task.Exception; + } +} diff --git a/GFramework.Core/Coroutine/Extensions/MediatorCoroutineExtensions.cs b/GFramework.Core/Coroutine/Extensions/MediatorCoroutineExtensions.cs index 42ff1ce2..91ed0238 100644 --- a/GFramework.Core/Coroutine/Extensions/MediatorCoroutineExtensions.cs +++ b/GFramework.Core/Coroutine/Extensions/MediatorCoroutineExtensions.cs @@ -14,14 +14,15 @@ using GFramework.Core.Abstractions.Coroutine; using GFramework.Core.Abstractions.Cqrs; using GFramework.Core.Abstractions.Rule; +using GFramework.Core.Cqrs.Extensions; namespace GFramework.Core.Coroutine.Extensions; /// /// 提供 CQRS 命令与协程集成的扩展方法。 -/// 历史命名保留了 Mediator 前缀,但当前实现直接走 返回的 -/// CQRS 入口,不再依赖外部 Mediator 服务。 +/// 该类型保留旧名称以兼容历史调用点;新代码应改用 。 /// +[Obsolete("Use GFramework.Core.Cqrs.Extensions.CqrsCoroutineExtensions instead.")] public static class MediatorCoroutineExtensions { /// @@ -38,17 +39,6 @@ public static class MediatorCoroutineExtensions Action? onError = null) where TCommand : IRequest { - ArgumentNullException.ThrowIfNull(contextAware); - ArgumentNullException.ThrowIfNull(command); - - var task = contextAware.GetContext().SendAsync(command).AsTask(); - - yield return task.AsCoroutineInstruction(); - - if (!task.IsFaulted) yield break; - if (onError != null) - onError.Invoke(task.Exception!); - else - throw task.Exception!.InnerException ?? task.Exception; + return CqrsCoroutineExtensions.SendCommandCoroutine(contextAware, command, onError); } } diff --git a/GFramework.Core/Extensions/ContextAwareCqrsCommandExtensions.cs b/GFramework.Core/Extensions/ContextAwareCqrsCommandExtensions.cs new file mode 100644 index 00000000..5f86f4e5 --- /dev/null +++ b/GFramework.Core/Extensions/ContextAwareCqrsCommandExtensions.cs @@ -0,0 +1,44 @@ +using GFramework.Core.Abstractions.Cqrs.Command; +using GFramework.Core.Abstractions.Rule; + +namespace GFramework.Core.Cqrs.Extensions; + +/// +/// 提供对 接口的 CQRS 命令扩展方法。 +/// +public static class ContextAwareCqrsCommandExtensions +{ + /// + /// 发送命令的同步版本(不推荐,仅用于兼容同步调用链)。 + /// + /// 命令响应类型。 + /// 实现 接口的对象。 + /// 要发送的命令对象。 + /// 命令执行结果。 + public static TResponse SendCommand(this IContextAware contextAware, ICommand command) + { + ArgumentNullException.ThrowIfNull(contextAware); + ArgumentNullException.ThrowIfNull(command); + + return contextAware.GetContext().SendCommand(command); + } + + /// + /// 异步发送命令并返回结果。 + /// + /// 命令响应类型。 + /// 实现 接口的对象。 + /// 要发送的命令对象。 + /// 取消令牌,用于取消操作。 + /// 包含命令执行结果的 + public static ValueTask SendCommandAsync( + this IContextAware contextAware, + ICommand command, + CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(contextAware); + ArgumentNullException.ThrowIfNull(command); + + return contextAware.GetContext().SendCommandAsync(command, cancellationToken); + } +} diff --git a/GFramework.Core/Extensions/ContextAwareCqrsExtensions.cs b/GFramework.Core/Extensions/ContextAwareCqrsExtensions.cs new file mode 100644 index 00000000..64f9d0e1 --- /dev/null +++ b/GFramework.Core/Extensions/ContextAwareCqrsExtensions.cs @@ -0,0 +1,123 @@ +using GFramework.Core.Abstractions.Cqrs; +using GFramework.Core.Abstractions.Rule; + +namespace GFramework.Core.Cqrs.Extensions; + +/// +/// 提供对 接口的 CQRS 统一扩展方法。 +/// 这些扩展直接委托给架构上下文的内建 CQRS runtime,作为新的中性命名入口。 +/// +public static class ContextAwareCqrsExtensions +{ + /// + /// 发送请求(统一处理 Command/Query)。 + /// + /// 响应类型。 + /// 实现 接口的对象。 + /// 要发送的请求。 + /// 取消令牌。 + /// 请求结果。 + public static ValueTask SendRequestAsync( + this IContextAware contextAware, + IRequest request, + CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(contextAware); + ArgumentNullException.ThrowIfNull(request); + + return contextAware.GetContext().SendRequestAsync(request, cancellationToken); + } + + /// + /// 发送请求(同步版本,不推荐)。 + /// + /// 响应类型。 + /// 实现 接口的对象。 + /// 要发送的请求。 + /// 请求结果。 + public static TResponse SendRequest(this IContextAware contextAware, IRequest request) + { + ArgumentNullException.ThrowIfNull(contextAware); + ArgumentNullException.ThrowIfNull(request); + + return contextAware.GetContext().SendRequest(request); + } + + /// + /// 发布通知(一对多事件)。 + /// + /// 通知类型。 + /// 实现 接口的对象。 + /// 要发布的通知。 + /// 取消令牌。 + /// 异步任务。 + public static ValueTask PublishAsync( + this IContextAware contextAware, + TNotification notification, + CancellationToken cancellationToken = default) + where TNotification : INotification + { + ArgumentNullException.ThrowIfNull(contextAware); + ArgumentNullException.ThrowIfNull(notification); + + return contextAware.GetContext().PublishAsync(notification, cancellationToken); + } + + /// + /// 创建流式请求。 + /// + /// 响应类型。 + /// 实现 接口的对象。 + /// 流式请求。 + /// 取消令牌。 + /// 异步响应流。 + public static IAsyncEnumerable CreateStream( + this IContextAware contextAware, + IStreamRequest request, + CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(contextAware); + ArgumentNullException.ThrowIfNull(request); + + return contextAware.GetContext().CreateStream(request, cancellationToken); + } + + /// + /// 发送无返回值命令。 + /// + /// 命令类型。 + /// 实现 接口的对象。 + /// 要发送的命令。 + /// 取消令牌。 + /// 异步任务。 + public static ValueTask SendAsync( + this IContextAware contextAware, + TCommand command, + CancellationToken cancellationToken = default) + where TCommand : IRequest + { + ArgumentNullException.ThrowIfNull(contextAware); + ArgumentNullException.ThrowIfNull(command); + + return contextAware.GetContext().SendAsync(command, cancellationToken); + } + + /// + /// 发送带返回值命令。 + /// + /// 响应类型。 + /// 实现 接口的对象。 + /// 要发送的命令。 + /// 取消令牌。 + /// 命令执行结果。 + public static ValueTask SendAsync( + this IContextAware contextAware, + IRequest command, + CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(contextAware); + ArgumentNullException.ThrowIfNull(command); + + return contextAware.GetContext().SendAsync(command, cancellationToken); + } +} diff --git a/GFramework.Core/Extensions/ContextAwareCqrsQueryExtensions.cs b/GFramework.Core/Extensions/ContextAwareCqrsQueryExtensions.cs new file mode 100644 index 00000000..42905215 --- /dev/null +++ b/GFramework.Core/Extensions/ContextAwareCqrsQueryExtensions.cs @@ -0,0 +1,44 @@ +using GFramework.Core.Abstractions.Cqrs.Query; +using GFramework.Core.Abstractions.Rule; + +namespace GFramework.Core.Cqrs.Extensions; + +/// +/// 提供对 接口的 CQRS 查询扩展方法。 +/// +public static class ContextAwareCqrsQueryExtensions +{ + /// + /// 发送查询的同步版本(不推荐,仅用于兼容同步调用链)。 + /// + /// 查询响应类型。 + /// 实现 接口的对象。 + /// 要发送的查询对象。 + /// 查询结果。 + public static TResponse SendQuery(this IContextAware contextAware, IQuery query) + { + ArgumentNullException.ThrowIfNull(contextAware); + ArgumentNullException.ThrowIfNull(query); + + return contextAware.GetContext().SendQuery(query); + } + + /// + /// 异步发送查询并返回结果。 + /// + /// 查询响应类型。 + /// 实现 接口的对象。 + /// 要发送的查询对象。 + /// 取消令牌,用于取消操作。 + /// 包含查询结果的 + public static ValueTask SendQueryAsync( + this IContextAware contextAware, + IQuery query, + CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(contextAware); + ArgumentNullException.ThrowIfNull(query); + + return contextAware.GetContext().SendQueryAsync(query, cancellationToken); + } +} diff --git a/GFramework.Core/Extensions/ContextAwareMediatorCommandExtensions.cs b/GFramework.Core/Extensions/ContextAwareMediatorCommandExtensions.cs index 881741e7..9fc9311b 100644 --- a/GFramework.Core/Extensions/ContextAwareMediatorCommandExtensions.cs +++ b/GFramework.Core/Extensions/ContextAwareMediatorCommandExtensions.cs @@ -1,11 +1,14 @@ -using GFramework.Core.Abstractions.Rule; using GFramework.Core.Abstractions.Cqrs.Command; +using GFramework.Core.Abstractions.Rule; +using GFramework.Core.Cqrs.Extensions; namespace GFramework.Core.Extensions; /// -/// 提供对 IContextAware 接口的 CQRS 命令扩展方法。 +/// 提供对 接口的 CQRS 命令扩展方法。 +/// 该类型保留旧名称以兼容历史调用点;新代码应改用 。 /// +[Obsolete("Use GFramework.Core.Cqrs.Extensions.ContextAwareCqrsCommandExtensions instead.")] public static class ContextAwareMediatorCommandExtensions { /// @@ -19,11 +22,7 @@ public static class ContextAwareMediatorCommandExtensions public static TResponse SendCommand(this IContextAware contextAware, ICommand command) { - ArgumentNullException.ThrowIfNull(contextAware); - ArgumentNullException.ThrowIfNull(command); - - var context = contextAware.GetContext(); - return context.SendCommand(command); + return ContextAwareCqrsCommandExtensions.SendCommand(contextAware, command); } /// @@ -38,10 +37,9 @@ public static class ContextAwareMediatorCommandExtensions public static ValueTask SendCommandAsync(this IContextAware contextAware, ICommand command, CancellationToken cancellationToken = default) { - ArgumentNullException.ThrowIfNull(contextAware); - ArgumentNullException.ThrowIfNull(command); - - var context = contextAware.GetContext(); - return context.SendCommandAsync(command, cancellationToken); + return ContextAwareCqrsCommandExtensions.SendCommandAsync( + contextAware, + command, + cancellationToken); } } diff --git a/GFramework.Core/Extensions/ContextAwareMediatorExtensions.cs b/GFramework.Core/Extensions/ContextAwareMediatorExtensions.cs index 1661d87b..4b63e223 100644 --- a/GFramework.Core/Extensions/ContextAwareMediatorExtensions.cs +++ b/GFramework.Core/Extensions/ContextAwareMediatorExtensions.cs @@ -1,11 +1,14 @@ -using GFramework.Core.Abstractions.Rule; using GFramework.Core.Abstractions.Cqrs; +using GFramework.Core.Abstractions.Rule; +using GFramework.Core.Cqrs.Extensions; namespace GFramework.Core.Extensions; /// -/// 提供对 IContextAware 接口的 CQRS 统一接口扩展方法。 +/// 提供对 接口的 CQRS 统一接口扩展方法。 +/// 该类型保留旧名称以兼容历史调用点;新代码应改用 。 /// +[Obsolete("Use GFramework.Core.Cqrs.Extensions.ContextAwareCqrsExtensions instead.")] public static class ContextAwareMediatorExtensions { /// @@ -20,11 +23,10 @@ public static class ContextAwareMediatorExtensions public static ValueTask SendRequestAsync(this IContextAware contextAware, IRequest request, CancellationToken cancellationToken = default) { - ArgumentNullException.ThrowIfNull(contextAware); - ArgumentNullException.ThrowIfNull(request); - - var context = contextAware.GetContext(); - return context.SendRequestAsync(request, cancellationToken); + return ContextAwareCqrsExtensions.SendRequestAsync( + contextAware, + request, + cancellationToken); } /// @@ -38,11 +40,7 @@ public static class ContextAwareMediatorExtensions public static TResponse SendRequest(this IContextAware contextAware, IRequest request) { - ArgumentNullException.ThrowIfNull(contextAware); - ArgumentNullException.ThrowIfNull(request); - - var context = contextAware.GetContext(); - return context.SendRequest(request); + return ContextAwareCqrsExtensions.SendRequest(contextAware, request); } /// @@ -58,11 +56,10 @@ public static class ContextAwareMediatorExtensions TNotification notification, CancellationToken cancellationToken = default) where TNotification : INotification { - ArgumentNullException.ThrowIfNull(contextAware); - ArgumentNullException.ThrowIfNull(notification); - - var context = contextAware.GetContext(); - return context.PublishAsync(notification, cancellationToken); + return ContextAwareCqrsExtensions.PublishAsync( + contextAware, + notification, + cancellationToken); } /// @@ -77,11 +74,10 @@ public static class ContextAwareMediatorExtensions public static IAsyncEnumerable CreateStream(this IContextAware contextAware, IStreamRequest request, CancellationToken cancellationToken = default) { - ArgumentNullException.ThrowIfNull(contextAware); - ArgumentNullException.ThrowIfNull(request); - - var context = contextAware.GetContext(); - return context.CreateStream(request, cancellationToken); + return ContextAwareCqrsExtensions.CreateStream( + contextAware, + request, + cancellationToken); } /// @@ -97,11 +93,10 @@ public static class ContextAwareMediatorExtensions CancellationToken cancellationToken = default) where TCommand : IRequest { - ArgumentNullException.ThrowIfNull(contextAware); - ArgumentNullException.ThrowIfNull(command); - - var context = contextAware.GetContext(); - return context.SendAsync(command, cancellationToken); + return ContextAwareCqrsExtensions.SendAsync( + contextAware, + command, + cancellationToken); } /// @@ -116,10 +111,9 @@ public static class ContextAwareMediatorExtensions public static ValueTask SendAsync(this IContextAware contextAware, IRequest command, CancellationToken cancellationToken = default) { - ArgumentNullException.ThrowIfNull(contextAware); - ArgumentNullException.ThrowIfNull(command); - - var context = contextAware.GetContext(); - return context.SendAsync(command, cancellationToken); + return ContextAwareCqrsExtensions.SendAsync( + contextAware, + command, + cancellationToken); } } diff --git a/GFramework.Core/Extensions/ContextAwareMediatorQueryExtensions.cs b/GFramework.Core/Extensions/ContextAwareMediatorQueryExtensions.cs index 5a4bfeb6..be2d70e1 100644 --- a/GFramework.Core/Extensions/ContextAwareMediatorQueryExtensions.cs +++ b/GFramework.Core/Extensions/ContextAwareMediatorQueryExtensions.cs @@ -1,11 +1,14 @@ -using GFramework.Core.Abstractions.Rule; using GFramework.Core.Abstractions.Cqrs.Query; +using GFramework.Core.Abstractions.Rule; +using GFramework.Core.Cqrs.Extensions; namespace GFramework.Core.Extensions; /// -/// 提供对 IContextAware 接口的 CQRS 查询扩展方法。 +/// 提供对 接口的 CQRS 查询扩展方法。 +/// 该类型保留旧名称以兼容历史调用点;新代码应改用 。 /// +[Obsolete("Use GFramework.Core.Cqrs.Extensions.ContextAwareCqrsQueryExtensions instead.")] public static class ContextAwareMediatorQueryExtensions { /// @@ -18,11 +21,7 @@ public static class ContextAwareMediatorQueryExtensions /// 当 contextAware 或 query 为 null 时抛出 public static TResponse SendQuery(this IContextAware contextAware, IQuery query) { - ArgumentNullException.ThrowIfNull(contextAware); - ArgumentNullException.ThrowIfNull(query); - - var context = contextAware.GetContext(); - return context.SendQuery(query); + return ContextAwareCqrsQueryExtensions.SendQuery(contextAware, query); } /// @@ -37,10 +36,9 @@ public static class ContextAwareMediatorQueryExtensions public static ValueTask SendQueryAsync(this IContextAware contextAware, IQuery query, CancellationToken cancellationToken = default) { - ArgumentNullException.ThrowIfNull(contextAware); - ArgumentNullException.ThrowIfNull(query); - - var context = contextAware.GetContext(); - return context.SendQueryAsync(query, cancellationToken); + return ContextAwareCqrsQueryExtensions.SendQueryAsync( + contextAware, + query, + cancellationToken); } } diff --git a/GFramework.Core/Ioc/MicrosoftDiContainer.cs b/GFramework.Core/Ioc/MicrosoftDiContainer.cs index 6b45a09e..69b46720 100644 --- a/GFramework.Core/Ioc/MicrosoftDiContainer.cs +++ b/GFramework.Core/Ioc/MicrosoftDiContainer.cs @@ -310,12 +310,11 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null) /// /// 注册 CQRS 请求管道行为。 - /// 历史方法名保留了 Mediator 前缀,但当前用于配置框架内建 CQRS runtime 的行为拦截和处理逻辑。 /// 同时支持开放泛型行为类型和已闭合的具体行为类型, /// 以兼容通用行为和针对单一请求的专用行为两种注册方式。 /// /// 行为类型,必须是引用类型 - public void RegisterMediatorBehavior() where TBehavior : class + public void RegisterCqrsPipelineBehavior() where TBehavior : class { _lock.EnterWriteLock(); try @@ -358,6 +357,17 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null) } } + /// + /// 注册 CQRS 请求管道行为。 + /// 该成员保留旧名称以兼容历史调用点,内部行为与 一致。 + /// + /// 行为类型,必须是引用类型 + [Obsolete("Use RegisterCqrsPipelineBehavior() instead.")] + public void RegisterMediatorBehavior() where TBehavior : class + { + RegisterCqrsPipelineBehavior(); + } + /// /// 配置服务 /// diff --git a/GFramework.Godot/Coroutine/ContextAwareCoroutineExtensions.cs b/GFramework.Godot/Coroutine/ContextAwareCoroutineExtensions.cs index d97d08a7..5d1a3485 100644 --- a/GFramework.Godot/Coroutine/ContextAwareCoroutineExtensions.cs +++ b/GFramework.Godot/Coroutine/ContextAwareCoroutineExtensions.cs @@ -2,9 +2,6 @@ using GFramework.Core.Abstractions.Cqrs.Command; using GFramework.Core.Abstractions.Cqrs.Query; using GFramework.Core.Abstractions.Rule; -using GFramework.Core.Coroutine; -using GFramework.Core.Coroutine.Extensions; -using GFramework.Core.Extensions; namespace GFramework.Godot.Coroutine; @@ -29,8 +26,8 @@ public static class ContextAwareCoroutineExtensions string? tag = null, CancellationToken cancellationToken = default) { - return contextAware - .SendCommandAsync(command, cancellationToken) + return Core.Cqrs.Extensions.ContextAwareCqrsCommandExtensions + .SendCommandAsync(contextAware, command, cancellationToken) .AsTask() .ToCoroutineEnumerator() .RunCoroutine(segment, tag); @@ -53,8 +50,8 @@ public static class ContextAwareCoroutineExtensions string? tag = null, CancellationToken cancellationToken = default) { - return contextAware - .SendCommandAsync(command, cancellationToken) + return Core.Cqrs.Extensions.ContextAwareCqrsCommandExtensions + .SendCommandAsync(contextAware, command, cancellationToken) .AsTask() .ToCoroutineEnumerator() .RunCoroutine(segment, tag); @@ -77,8 +74,8 @@ public static class ContextAwareCoroutineExtensions string? tag = null, CancellationToken cancellationToken = default) { - return contextAware - .SendQueryAsync(query, cancellationToken) + return Core.Cqrs.Extensions.ContextAwareCqrsQueryExtensions + .SendQueryAsync(contextAware, query, cancellationToken) .AsTask() .ToCoroutineEnumerator() .RunCoroutine(segment, tag); @@ -100,8 +97,8 @@ public static class ContextAwareCoroutineExtensions string? tag = null, CancellationToken cancellationToken = default) { - return contextAware - .PublishAsync(notification, cancellationToken) + return Core.Cqrs.Extensions.ContextAwareCqrsExtensions + .PublishAsync(contextAware, notification, cancellationToken) .AsTask() .ToCoroutineEnumerator() .RunCoroutine(segment, tag); diff --git a/docs/zh-CN/core/cqrs.md b/docs/zh-CN/core/cqrs.md index b92566d1..742d2ef8 100644 --- a/docs/zh-CN/core/cqrs.md +++ b/docs/zh-CN/core/cqrs.md @@ -212,15 +212,16 @@ public class GameArchitecture : Architecture protected override void Init() { // 注册通用开放泛型行为 - RegisterMediatorBehavior>(); - RegisterMediatorBehavior>(); + RegisterCqrsPipelineBehavior>(); + RegisterCqrsPipelineBehavior>(); // 处理器会自动通过依赖注入注册 } } ``` -`RegisterMediatorBehavior()` 是保留的兼容名称,当前用于注册框架内建 CQRS pipeline,支持两种形式: +`RegisterCqrsPipelineBehavior()` 是推荐入口;旧的 `RegisterMediatorBehavior()` +仅作为兼容名称保留。当前接口支持两种形式: - 开放泛型行为,例如 `LoggingBehavior<,>`,用于匹配所有请求 - 封闭行为类型,例如某个只服务于单一请求的 `SpecialBehavior` @@ -377,8 +378,8 @@ public class PerformanceBehavior : IPipelineBehavior>(); -RegisterMediatorBehavior>(); +RegisterCqrsPipelineBehavior>(); +RegisterCqrsPipelineBehavior>(); ``` ### 验证行为 @@ -471,8 +472,8 @@ await foreach (var player in stream) 4. **使用 Behaviors 处理横切关注点**:日志、性能、验证等 ```csharp - RegisterMediatorBehavior>(); - RegisterMediatorBehavior>(); + RegisterCqrsPipelineBehavior>(); + RegisterCqrsPipelineBehavior>(); ``` 5. **保持处理器简单**:一个处理器只做一件事 diff --git a/docs/zh-CN/core/index.md b/docs/zh-CN/core/index.md index 98455237..5c4837bf 100644 --- a/docs/zh-CN/core/index.md +++ b/docs/zh-CN/core/index.md @@ -396,12 +396,12 @@ public class PlayerController : IController **核心功能**: - 模块安装 (IArchitectureModule) -- CQRS 管道行为注册(历史 API 名称仍为 `RegisterMediatorBehavior`) +- CQRS 管道行为注册(推荐 API 为 `RegisterCqrsPipelineBehavior`) **关键方法**: - `InstallModule()` - 安装模块 -- `RegisterMediatorBehavior()` - 注册 CQRS 管道行为 +- `RegisterCqrsPipelineBehavior()` - 注册 CQRS 管道行为 #### 设计优势 From 088f02d586a9cff9fcbde478f64cb1e16f957d17 Mon Sep 17 00:00:00 2001 From: GeWuYou <95328647+GeWuYou@users.noreply.github.com> Date: Wed, 15 Apr 2026 07:34:01 +0800 Subject: [PATCH 3/7] =?UTF-8?q?docs(core):=20=E6=B7=BB=E5=8A=A0=20CQRS=20?= =?UTF-8?q?=E6=96=87=E6=A1=A3=E5=B9=B6=E5=AE=8C=E5=96=84=E7=9B=B8=E5=85=B3?= =?UTF-8?q?=E6=89=A9=E5=B1=95=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 CQRS 核心概念、命令查询处理器使用指南 - 添加管道行为、流式处理和最佳实践说明 - 实现 CQRS 协程扩展方法支持异步命令执行 - 添加 ContextAware 接口的 CQRS 命令查询扩展 - 集成 Microsoft DI 容器依赖注入支持 - 补充架构模块行为测试验证功能完整性 - 扩展 GameContext 测试用例提高代码覆盖率 --- .../Architectures/IArchitecture.cs | 1 + .../Ioc/IIocContainer.cs | 1 + .../ArchitectureModulesBehaviorTests.cs | 22 ++++++ .../Architectures/GameContextTests.cs | 77 +++++++++++++++++++ .../Coroutine/CqrsCoroutineExtensionsTests.cs | 26 +++++++ GFramework.Core.Tests/GlobalUsings.cs | 4 +- .../Extensions/CqrsCoroutineExtensions.cs | 12 ++- .../ContextAwareCqrsCommandExtensions.cs | 6 ++ .../Extensions/ContextAwareCqrsExtensions.cs | 18 +++++ .../ContextAwareCqrsQueryExtensions.cs | 6 ++ GFramework.Core/GlobalUsings.cs | 3 +- .../ContextAwareCoroutineExtensions.cs | 11 ++- docs/zh-CN/core/cqrs.md | 20 ++--- 13 files changed, 190 insertions(+), 17 deletions(-) diff --git a/GFramework.Core.Abstractions/Architectures/IArchitecture.cs b/GFramework.Core.Abstractions/Architectures/IArchitecture.cs index 78303a2d..b344717d 100644 --- a/GFramework.Core.Abstractions/Architectures/IArchitecture.cs +++ b/GFramework.Core.Abstractions/Architectures/IArchitecture.cs @@ -2,6 +2,7 @@ using GFramework.Core.Abstractions.Lifecycle; using GFramework.Core.Abstractions.Model; using GFramework.Core.Abstractions.Systems; using GFramework.Core.Abstractions.Utility; +using Microsoft.Extensions.DependencyInjection; namespace GFramework.Core.Abstractions.Architectures; diff --git a/GFramework.Core.Abstractions/Ioc/IIocContainer.cs b/GFramework.Core.Abstractions/Ioc/IIocContainer.cs index 0e55908a..c56b71fa 100644 --- a/GFramework.Core.Abstractions/Ioc/IIocContainer.cs +++ b/GFramework.Core.Abstractions/Ioc/IIocContainer.cs @@ -1,5 +1,6 @@ using GFramework.Core.Abstractions.Rule; using GFramework.Core.Abstractions.Systems; +using Microsoft.Extensions.DependencyInjection; namespace GFramework.Core.Abstractions.Ioc; diff --git a/GFramework.Core.Tests/Architectures/ArchitectureModulesBehaviorTests.cs b/GFramework.Core.Tests/Architectures/ArchitectureModulesBehaviorTests.cs index c5c0b685..ade4a1a7 100644 --- a/GFramework.Core.Tests/Architectures/ArchitectureModulesBehaviorTests.cs +++ b/GFramework.Core.Tests/Architectures/ArchitectureModulesBehaviorTests.cs @@ -77,6 +77,28 @@ public class ArchitectureModulesBehaviorTests await architecture.DestroyAsync(); } + /// + /// 验证兼容别名 RegisterMediatorBehavior 仍会把 CQRS 行为接入请求管道。 + /// + [Test] + public async Task RegisterMediatorBehavior_Should_Apply_Pipeline_Behavior_To_Request() + { + var architecture = new ModuleTestArchitecture(target => + target.RegisterMediatorBehavior>()); + + await architecture.InitializeAsync(); + + var response = await architecture.Context.SendRequestAsync(new ModuleBehaviorRequest()); + + Assert.Multiple(() => + { + Assert.That(response, Is.EqualTo("handled")); + Assert.That(TrackingPipelineBehavior.InvocationCount, Is.EqualTo(1)); + }); + + await architecture.DestroyAsync(); + } + /// /// 用于测试模块行为的最小架构实现。 /// diff --git a/GFramework.Core.Tests/Architectures/GameContextTests.cs b/GFramework.Core.Tests/Architectures/GameContextTests.cs index e11f0892..28e3cc97 100644 --- a/GFramework.Core.Tests/Architectures/GameContextTests.cs +++ b/GFramework.Core.Tests/Architectures/GameContextTests.cs @@ -394,45 +394,106 @@ public class TestArchitectureContext : IArchitectureContext { } + /// + /// 测试桩:异步发送统一 CQRS 请求。 + /// + /// 响应类型。 + /// 要发送的请求。 + /// 取消令牌。 + /// 请求响应任务。 + /// 该测试桩未实现此成员。 public ValueTask SendRequestAsync(IRequest request, CancellationToken cancellationToken = default) { throw new NotImplementedException(); } + /// + /// 测试桩:同步发送统一 CQRS 请求。 + /// + /// 响应类型。 + /// 要发送的请求。 + /// 请求响应。 + /// 该测试桩未实现此成员。 public TResponse SendRequest(IRequest request) { throw new NotImplementedException(); } + /// + /// 测试桩:异步发送 CQRS 命令并返回响应。 + /// + /// 命令响应类型。 + /// 要发送的命令。 + /// 取消令牌。 + /// 命令响应任务。 + /// 该测试桩未实现此成员。 public ValueTask SendCommandAsync(Abstractions.Cqrs.Command.ICommand command, CancellationToken cancellationToken = default) { throw new NotImplementedException(); } + /// + /// 测试桩:同步发送 CQRS 命令并返回响应。 + /// + /// 命令响应类型。 + /// 要发送的命令。 + /// 命令响应。 + /// 该测试桩未实现此成员。 public TResponse SendCommand(Abstractions.Cqrs.Command.ICommand command) { throw new NotImplementedException(); } + /// + /// 测试桩:异步发送 CQRS 查询并返回结果。 + /// + /// 查询结果类型。 + /// 要发送的查询。 + /// 取消令牌。 + /// 查询结果任务。 + /// 该测试桩未实现此成员。 public ValueTask SendQueryAsync(Abstractions.Cqrs.Query.IQuery query, CancellationToken cancellationToken = default) { throw new NotImplementedException(); } + /// + /// 测试桩:同步发送 CQRS 查询并返回结果。 + /// + /// 查询结果类型。 + /// 要发送的查询。 + /// 查询结果。 + /// 该测试桩未实现此成员。 public TResponse SendQuery(Abstractions.Cqrs.Query.IQuery query) { throw new NotImplementedException(); } + /// + /// 测试桩:异步发布 CQRS 通知。 + /// + /// 通知类型。 + /// 要发布的通知。 + /// 取消令牌。 + /// 通知发布任务。 + /// 该测试桩未实现此成员。 public ValueTask PublishAsync(TNotification notification, CancellationToken cancellationToken = default) where TNotification : INotification { throw new NotImplementedException(); } + /// + /// 测试桩:创建 CQRS 流式请求响应序列。 + /// + /// 流式响应元素类型。 + /// 流式请求。 + /// 取消令牌。 + /// 异步响应流。 + /// 该测试桩未实现此成员。 public IAsyncEnumerable CreateStream( IStreamRequest request, CancellationToken cancellationToken = default) @@ -440,12 +501,28 @@ public class TestArchitectureContext : IArchitectureContext throw new NotImplementedException(); } + /// + /// 测试桩:异步发送无返回值 CQRS 命令。 + /// + /// 命令类型。 + /// 要发送的命令。 + /// 取消令牌。 + /// 命令发送任务。 + /// 该测试桩未实现此成员。 public ValueTask SendAsync(TCommand command, CancellationToken cancellationToken = default) where TCommand : IRequest { throw new NotImplementedException(); } + /// + /// 测试桩:异步发送带返回值的 CQRS 请求。 + /// + /// 响应类型。 + /// 要发送的请求。 + /// 取消令牌。 + /// 请求响应任务。 + /// 该测试桩未实现此成员。 public ValueTask SendAsync(IRequest command, CancellationToken cancellationToken = default) { diff --git a/GFramework.Core.Tests/Coroutine/CqrsCoroutineExtensionsTests.cs b/GFramework.Core.Tests/Coroutine/CqrsCoroutineExtensionsTests.cs index 2adba37a..d5f85f1d 100644 --- a/GFramework.Core.Tests/Coroutine/CqrsCoroutineExtensionsTests.cs +++ b/GFramework.Core.Tests/Coroutine/CqrsCoroutineExtensionsTests.cs @@ -66,6 +66,32 @@ public class CqrsCoroutineExtensionsTests Assert.That(exception, Is.SameAs(expectedException)); } + /// + /// 验证 SendCommandCoroutine 在提供错误回调时也会传递解包后的原始异常, + /// 避免回调路径暴露 。 + /// + [Test] + public void SendCommandCoroutine_Should_Forward_Inner_Exception_To_Error_Handler() + { + var command = new TestCommand("Test"); + var contextAware = new TestContextAware(); + var expectedException = new InvalidOperationException("Command failed."); + Exception? capturedException = null; + + contextAware.MockContext + .Setup(ctx => ctx.SendAsync(command, It.IsAny())) + .Returns(new ValueTask(Task.FromException(expectedException))); + + var coroutine = CqrsCoroutineExtensions.SendCommandCoroutine( + contextAware, + command, + exception => capturedException = exception); + + Assert.That(coroutine.MoveNext(), Is.True); + Assert.That(coroutine.MoveNext(), Is.False); + Assert.That(capturedException, Is.SameAs(expectedException)); + } + /// /// 测试用的简单命令类 /// diff --git a/GFramework.Core.Tests/GlobalUsings.cs b/GFramework.Core.Tests/GlobalUsings.cs index 18957f6b..fe9b7de1 100644 --- a/GFramework.Core.Tests/GlobalUsings.cs +++ b/GFramework.Core.Tests/GlobalUsings.cs @@ -23,4 +23,6 @@ global using GFramework.Core.Abstractions.StateManagement; global using GFramework.Core.Extensions; global using GFramework.Core.Property; global using GFramework.Core.StateManagement; -global using GFramework.Core.Abstractions.Property; \ No newline at end of file +global using GFramework.Core.Abstractions.Property; +global using Microsoft.Extensions.DependencyInjection; +global using Moq; diff --git a/GFramework.Core/Coroutine/Extensions/CqrsCoroutineExtensions.cs b/GFramework.Core/Coroutine/Extensions/CqrsCoroutineExtensions.cs index 55e6a67f..40367ff8 100644 --- a/GFramework.Core/Coroutine/Extensions/CqrsCoroutineExtensions.cs +++ b/GFramework.Core/Coroutine/Extensions/CqrsCoroutineExtensions.cs @@ -19,6 +19,13 @@ public static class CqrsCoroutineExtensions /// 要发送的命令对象。 /// 发生异常时的回调处理函数。 /// 协程枚举器,用于协程执行。 + /// + /// 当 时抛出。 + /// + /// + /// 当底层命令调度失败时,该扩展会把底层异常解包后传给 , + /// 或在未提供回调时重新抛出同一个异常实例,避免两条失败路径暴露不同的异常类型。 + /// public static IEnumerator SendCommandCoroutine( this IContextAware contextAware, TCommand command, @@ -35,9 +42,10 @@ public static class CqrsCoroutineExtensions if (!task.IsFaulted) yield break; + var exception = task.Exception!.InnerException ?? task.Exception; if (onError != null) - onError.Invoke(task.Exception!); + onError.Invoke(exception); else - throw task.Exception!.InnerException ?? task.Exception; + throw exception; } } diff --git a/GFramework.Core/Extensions/ContextAwareCqrsCommandExtensions.cs b/GFramework.Core/Extensions/ContextAwareCqrsCommandExtensions.cs index 5f86f4e5..233228ed 100644 --- a/GFramework.Core/Extensions/ContextAwareCqrsCommandExtensions.cs +++ b/GFramework.Core/Extensions/ContextAwareCqrsCommandExtensions.cs @@ -15,6 +15,9 @@ public static class ContextAwareCqrsCommandExtensions /// 实现 接口的对象。 /// 要发送的命令对象。 /// 命令执行结果。 + /// + /// 当 时抛出。 + /// public static TResponse SendCommand(this IContextAware contextAware, ICommand command) { ArgumentNullException.ThrowIfNull(contextAware); @@ -31,6 +34,9 @@ public static class ContextAwareCqrsCommandExtensions /// 要发送的命令对象。 /// 取消令牌,用于取消操作。 /// 包含命令执行结果的 + /// + /// 当 时抛出。 + /// public static ValueTask SendCommandAsync( this IContextAware contextAware, ICommand command, diff --git a/GFramework.Core/Extensions/ContextAwareCqrsExtensions.cs b/GFramework.Core/Extensions/ContextAwareCqrsExtensions.cs index 64f9d0e1..ab09e689 100644 --- a/GFramework.Core/Extensions/ContextAwareCqrsExtensions.cs +++ b/GFramework.Core/Extensions/ContextAwareCqrsExtensions.cs @@ -17,6 +17,9 @@ public static class ContextAwareCqrsExtensions /// 要发送的请求。 /// 取消令牌。 /// 请求结果。 + /// + /// 当 时抛出。 + /// public static ValueTask SendRequestAsync( this IContextAware contextAware, IRequest request, @@ -35,6 +38,9 @@ public static class ContextAwareCqrsExtensions /// 实现 接口的对象。 /// 要发送的请求。 /// 请求结果。 + /// + /// 当 时抛出。 + /// public static TResponse SendRequest(this IContextAware contextAware, IRequest request) { ArgumentNullException.ThrowIfNull(contextAware); @@ -51,6 +57,9 @@ public static class ContextAwareCqrsExtensions /// 要发布的通知。 /// 取消令牌。 /// 异步任务。 + /// + /// 当 时抛出。 + /// public static ValueTask PublishAsync( this IContextAware contextAware, TNotification notification, @@ -71,6 +80,9 @@ public static class ContextAwareCqrsExtensions /// 流式请求。 /// 取消令牌。 /// 异步响应流。 + /// + /// 当 时抛出。 + /// public static IAsyncEnumerable CreateStream( this IContextAware contextAware, IStreamRequest request, @@ -90,6 +102,9 @@ public static class ContextAwareCqrsExtensions /// 要发送的命令。 /// 取消令牌。 /// 异步任务。 + /// + /// 当 时抛出。 + /// public static ValueTask SendAsync( this IContextAware contextAware, TCommand command, @@ -110,6 +125,9 @@ public static class ContextAwareCqrsExtensions /// 要发送的命令。 /// 取消令牌。 /// 命令执行结果。 + /// + /// 当 时抛出。 + /// public static ValueTask SendAsync( this IContextAware contextAware, IRequest command, diff --git a/GFramework.Core/Extensions/ContextAwareCqrsQueryExtensions.cs b/GFramework.Core/Extensions/ContextAwareCqrsQueryExtensions.cs index 42905215..9906bc3d 100644 --- a/GFramework.Core/Extensions/ContextAwareCqrsQueryExtensions.cs +++ b/GFramework.Core/Extensions/ContextAwareCqrsQueryExtensions.cs @@ -15,6 +15,9 @@ public static class ContextAwareCqrsQueryExtensions /// 实现 接口的对象。 /// 要发送的查询对象。 /// 查询结果。 + /// + /// 当 时抛出。 + /// public static TResponse SendQuery(this IContextAware contextAware, IQuery query) { ArgumentNullException.ThrowIfNull(contextAware); @@ -31,6 +34,9 @@ public static class ContextAwareCqrsQueryExtensions /// 要发送的查询对象。 /// 取消令牌,用于取消操作。 /// 包含查询结果的 + /// + /// 当 时抛出。 + /// public static ValueTask SendQueryAsync( this IContextAware contextAware, IQuery query, diff --git a/GFramework.Core/GlobalUsings.cs b/GFramework.Core/GlobalUsings.cs index 8add267e..203366e6 100644 --- a/GFramework.Core/GlobalUsings.cs +++ b/GFramework.Core/GlobalUsings.cs @@ -16,4 +16,5 @@ global using System.Collections.Generic; global using System.Linq; global using System.Threading; global using System.Threading.Tasks; -global using System.Threading.Channels; \ No newline at end of file +global using System.Threading.Channels; +global using Microsoft.Extensions.DependencyInjection; diff --git a/GFramework.Godot/Coroutine/ContextAwareCoroutineExtensions.cs b/GFramework.Godot/Coroutine/ContextAwareCoroutineExtensions.cs index 5d1a3485..98d57936 100644 --- a/GFramework.Godot/Coroutine/ContextAwareCoroutineExtensions.cs +++ b/GFramework.Godot/Coroutine/ContextAwareCoroutineExtensions.cs @@ -2,6 +2,9 @@ using GFramework.Core.Abstractions.Cqrs.Command; using GFramework.Core.Abstractions.Cqrs.Query; using GFramework.Core.Abstractions.Rule; +using GFramework.Core.Coroutine; +using GFramework.Core.Coroutine.Extensions; +using GFramework.Core.Cqrs.Extensions; namespace GFramework.Godot.Coroutine; @@ -26,7 +29,7 @@ public static class ContextAwareCoroutineExtensions string? tag = null, CancellationToken cancellationToken = default) { - return Core.Cqrs.Extensions.ContextAwareCqrsCommandExtensions + return ContextAwareCqrsCommandExtensions .SendCommandAsync(contextAware, command, cancellationToken) .AsTask() .ToCoroutineEnumerator() @@ -50,7 +53,7 @@ public static class ContextAwareCoroutineExtensions string? tag = null, CancellationToken cancellationToken = default) { - return Core.Cqrs.Extensions.ContextAwareCqrsCommandExtensions + return ContextAwareCqrsCommandExtensions .SendCommandAsync(contextAware, command, cancellationToken) .AsTask() .ToCoroutineEnumerator() @@ -74,7 +77,7 @@ public static class ContextAwareCoroutineExtensions string? tag = null, CancellationToken cancellationToken = default) { - return Core.Cqrs.Extensions.ContextAwareCqrsQueryExtensions + return ContextAwareCqrsQueryExtensions .SendQueryAsync(contextAware, query, cancellationToken) .AsTask() .ToCoroutineEnumerator() @@ -97,7 +100,7 @@ public static class ContextAwareCoroutineExtensions string? tag = null, CancellationToken cancellationToken = default) { - return Core.Cqrs.Extensions.ContextAwareCqrsExtensions + return ContextAwareCqrsExtensions .PublishAsync(contextAware, notification, cancellationToken) .AsTask() .ToCoroutineEnumerator() diff --git a/docs/zh-CN/core/cqrs.md b/docs/zh-CN/core/cqrs.md index 742d2ef8..3effaa86 100644 --- a/docs/zh-CN/core/cqrs.md +++ b/docs/zh-CN/core/cqrs.md @@ -204,22 +204,24 @@ public async Task> GetHighScores() ### 注册处理器 -在架构中注册 CQRS 行为并让处理器自动扫描注册: +在架构中注册 CQRS 行为;默认会自动扫描当前架构所在程序集和 `GFramework.Core` 程序集中的处理器: ```csharp public class GameArchitecture : Architecture { - protected override void Init() + protected override void OnInitialize() { // 注册通用开放泛型行为 RegisterCqrsPipelineBehavior>(); RegisterCqrsPipelineBehavior>(); - // 处理器会自动通过依赖注入注册 + // 默认只自动扫描当前架构程序集和 GFramework.Core 程序集中的处理器 } } ``` +如果处理器位于其他模块或扩展程序集中,需要额外接入对应程序集的处理器注册,而不是依赖默认扫描。 + `RegisterCqrsPipelineBehavior()` 是推荐入口;旧的 `RegisterMediatorBehavior()` 仅作为兼容名称保留。当前接口支持两种形式: @@ -338,8 +340,8 @@ public class LoggingBehavior : IPipelineBehavior Handle( TMessage message, - CancellationToken cancellationToken, - MessageHandlerDelegate next) + MessageHandlerDelegate next, + CancellationToken cancellationToken) { var messageName = message.GetType().Name; Console.WriteLine($"[开始] {messageName}"); @@ -358,8 +360,8 @@ public class PerformanceBehavior : IPipelineBehavior Handle( TMessage message, - CancellationToken cancellationToken, - MessageHandlerDelegate next) + MessageHandlerDelegate next, + CancellationToken cancellationToken) { var stopwatch = Stopwatch.StartNew(); @@ -390,8 +392,8 @@ public class ValidationBehavior : IPipelineBehavior Handle( TMessage message, - CancellationToken cancellationToken, - MessageHandlerDelegate next) + MessageHandlerDelegate next, + CancellationToken cancellationToken) { // 验证输入 if (message is IValidatable validatable) From 5a2981a5577740e8cf61bad8bec98636913e7196 Mon Sep 17 00:00:00 2001 From: GeWuYou <95328647+GeWuYou@users.noreply.github.com> Date: Wed, 15 Apr 2026 08:18:27 +0800 Subject: [PATCH 4/7] =?UTF-8?q?feat(cqrs):=20=E6=B7=BB=E5=8A=A0=20CQRS=20?= =?UTF-8?q?=E5=91=BD=E4=BB=A4=E5=8D=8F=E7=A8=8B=E6=89=A9=E5=B1=95=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 实现 CqrsCoroutineExtensions 扩展类,提供协程方式发送 CQRS 命令的功能 - 添加 SendCommandCoroutine 方法支持命令异步执行与异常处理 - 实现取消操作的特殊处理逻辑,区分取消、失败和成功状态 - 添加 ContextAwareCqrsCommandExtensions 扩展类,提供同步和异步命令发送方法 - 增加对 TaskCanceledException 的专门处理机制 - 完善相关单元测试,验证取消操作的异常处理行为 --- .../Coroutine/CqrsCoroutineExtensionsTests.cs | 58 +++++++++++++++++++ .../Extensions/CqrsCoroutineExtensions.cs | 20 ++++++- .../ContextAwareCqrsCommandExtensions.cs | 9 +++ 3 files changed, 85 insertions(+), 2 deletions(-) diff --git a/GFramework.Core.Tests/Coroutine/CqrsCoroutineExtensionsTests.cs b/GFramework.Core.Tests/Coroutine/CqrsCoroutineExtensionsTests.cs index d5f85f1d..67e9537d 100644 --- a/GFramework.Core.Tests/Coroutine/CqrsCoroutineExtensionsTests.cs +++ b/GFramework.Core.Tests/Coroutine/CqrsCoroutineExtensionsTests.cs @@ -92,6 +92,53 @@ public class CqrsCoroutineExtensionsTests Assert.That(capturedException, Is.SameAs(expectedException)); } + /// + /// 验证 SendCommandCoroutine 在底层命令被取消且未提供错误回调时会抛出取消异常。 + /// + [Test] + public void SendCommandCoroutine_Should_Throw_TaskCanceledException_When_Command_Is_Canceled() + { + var command = new TestCommand("Test"); + var contextAware = new TestContextAware(); + using var cancellationTokenSource = new CancellationTokenSource(); + + cancellationTokenSource.Cancel(); + contextAware.MockContext + .Setup(ctx => ctx.SendAsync(command, It.IsAny())) + .Returns(new ValueTask(Task.FromCanceled(cancellationTokenSource.Token))); + + var coroutine = CqrsCoroutineExtensions.SendCommandCoroutine(contextAware, command); + + Assert.That(coroutine.MoveNext(), Is.True); + Assert.Throws(() => coroutine.MoveNext()); + } + + /// + /// 验证 SendCommandCoroutine 在底层命令被取消且提供错误回调时会把取消异常转发给回调。 + /// + [Test] + public void SendCommandCoroutine_Should_Forward_TaskCanceledException_To_Error_Handler_When_Command_Is_Canceled() + { + var command = new TestCommand("Test"); + var contextAware = new TestContextAware(); + using var cancellationTokenSource = new CancellationTokenSource(); + Exception? capturedException = null; + + cancellationTokenSource.Cancel(); + contextAware.MockContext + .Setup(ctx => ctx.SendAsync(command, It.IsAny())) + .Returns(new ValueTask(Task.FromCanceled(cancellationTokenSource.Token))); + + var coroutine = CqrsCoroutineExtensions.SendCommandCoroutine( + contextAware, + command, + exception => capturedException = exception); + + Assert.That(coroutine.MoveNext(), Is.True); + Assert.That(coroutine.MoveNext(), Is.False); + Assert.That(capturedException, Is.TypeOf()); + } + /// /// 测试用的简单命令类 /// @@ -102,13 +149,24 @@ public class CqrsCoroutineExtensionsTests /// private sealed class TestContextAware : IContextAware { + /// + /// 提供可配置的架构上下文 Mock。 + /// public Mock MockContext { get; } = new(); + /// + /// 获取当前架构上下文。 + /// + /// 用于 CQRS 调用的架构上下文实例。 public IArchitectureContext GetContext() { return MockContext.Object; } + /// + /// 设置架构上下文。 + /// + /// 要设置的架构上下文。 public void SetContext(IArchitectureContext context) { } diff --git a/GFramework.Core/Coroutine/Extensions/CqrsCoroutineExtensions.cs b/GFramework.Core/Coroutine/Extensions/CqrsCoroutineExtensions.cs index 40367ff8..74782793 100644 --- a/GFramework.Core/Coroutine/Extensions/CqrsCoroutineExtensions.cs +++ b/GFramework.Core/Coroutine/Extensions/CqrsCoroutineExtensions.cs @@ -1,3 +1,4 @@ +using System.Runtime.ExceptionServices; using GFramework.Core.Abstractions.Coroutine; using GFramework.Core.Abstractions.Cqrs; using GFramework.Core.Abstractions.Rule; @@ -22,9 +23,12 @@ public static class CqrsCoroutineExtensions /// /// 当 时抛出。 /// + /// + /// 当底层命令调度被取消且未提供 时抛出。 + /// /// /// 当底层命令调度失败时,该扩展会把底层异常解包后传给 , - /// 或在未提供回调时重新抛出同一个异常实例,避免两条失败路径暴露不同的异常类型。 + /// 在取消时则统一暴露 ,避免成功、失败与取消三种完成状态被混淆。 /// public static IEnumerator SendCommandCoroutine( this IContextAware contextAware, @@ -39,6 +43,18 @@ public static class CqrsCoroutineExtensions yield return task.AsCoroutineInstruction(); + if (task.IsCanceled) + { + var canceledException = new TaskCanceledException(task); + if (onError != null) + { + onError.Invoke(canceledException); + yield break; + } + + ExceptionDispatchInfo.Capture(canceledException).Throw(); + } + if (!task.IsFaulted) yield break; @@ -46,6 +62,6 @@ public static class CqrsCoroutineExtensions if (onError != null) onError.Invoke(exception); else - throw exception; + ExceptionDispatchInfo.Capture(exception).Throw(); } } diff --git a/GFramework.Core/Extensions/ContextAwareCqrsCommandExtensions.cs b/GFramework.Core/Extensions/ContextAwareCqrsCommandExtensions.cs index 233228ed..b71669ee 100644 --- a/GFramework.Core/Extensions/ContextAwareCqrsCommandExtensions.cs +++ b/GFramework.Core/Extensions/ContextAwareCqrsCommandExtensions.cs @@ -6,6 +6,9 @@ namespace GFramework.Core.Cqrs.Extensions; /// /// 提供对 接口的 CQRS 命令扩展方法。 /// +/// +/// 该扩展类将命令分发统一路由到架构上下文中的 CQRS 运行时。 +/// public static class ContextAwareCqrsCommandExtensions { /// @@ -18,6 +21,9 @@ public static class ContextAwareCqrsCommandExtensions /// /// 当 时抛出。 /// + /// + /// 同步方法仅用于兼容同步调用链;新代码建议优先使用异步版本。 + /// public static TResponse SendCommand(this IContextAware contextAware, ICommand command) { ArgumentNullException.ThrowIfNull(contextAware); @@ -37,6 +43,9 @@ public static class ContextAwareCqrsCommandExtensions /// /// 当 时抛出。 /// + /// + /// 该方法直接返回底层 ,避免额外的 async 状态机分配。 + /// public static ValueTask SendCommandAsync( this IContextAware contextAware, ICommand command, From 96ffd49b319a04ee4645a9b22ea522630ca9beea Mon Sep 17 00:00:00 2001 From: GeWuYou <95328647+GeWuYou@users.noreply.github.com> Date: Wed, 15 Apr 2026 08:24:48 +0800 Subject: [PATCH 5/7] =?UTF-8?q?fix(coroutine):=20=E6=9B=B4=E6=96=B0CQRS?= =?UTF-8?q?=E5=8D=8F=E7=A8=8B=E6=89=A9=E5=B1=95=E7=9A=84=E5=BC=82=E5=B8=B8?= =?UTF-8?q?=E5=A4=84=E7=90=86=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将TaskCanceledException更改为Exception以反映实际抛出的异常类型 - 更新异常描述以准确说明在未提供onError时的行为 - 修正文档以反映底层原始异常的传递机制 --- .../Coroutine/Extensions/CqrsCoroutineExtensions.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/GFramework.Core/Coroutine/Extensions/CqrsCoroutineExtensions.cs b/GFramework.Core/Coroutine/Extensions/CqrsCoroutineExtensions.cs index 74782793..b459d7f2 100644 --- a/GFramework.Core/Coroutine/Extensions/CqrsCoroutineExtensions.cs +++ b/GFramework.Core/Coroutine/Extensions/CqrsCoroutineExtensions.cs @@ -23,8 +23,8 @@ public static class CqrsCoroutineExtensions /// /// 当 时抛出。 /// - /// - /// 当底层命令调度被取消且未提供 时抛出。 + /// + /// 当底层命令调度失败且未提供 时,抛出底层原始异常。 /// /// /// 当底层命令调度失败时,该扩展会把底层异常解包后传给 , From 4c0a99d24c5d49aa7f400d6b27a2954381994f19 Mon Sep 17 00:00:00 2001 From: GeWuYou <95328647+GeWuYou@users.noreply.github.com> Date: Wed, 15 Apr 2026 08:25:52 +0800 Subject: [PATCH 6/7] =?UTF-8?q?fix(coroutine):=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E5=8D=8F=E7=A8=8B=E6=89=A9=E5=B1=95=E4=B8=AD=E7=9A=84=E5=BC=82?= =?UTF-8?q?=E5=B8=B8=E5=A4=84=E7=90=86=E6=9C=BA=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加 TaskCanceledException 映射以统一取消状态处理 - 保留原始异常调用栈以避免调试时丢失异常来源 - 优先解包业务异常以避免直接暴露 AggregateException - 使用 ExceptionDispatchInfo.Capture 确保异常栈信息完整 --- .../Coroutine/Extensions/CqrsCoroutineExtensions.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/GFramework.Core/Coroutine/Extensions/CqrsCoroutineExtensions.cs b/GFramework.Core/Coroutine/Extensions/CqrsCoroutineExtensions.cs index b459d7f2..41e54ee2 100644 --- a/GFramework.Core/Coroutine/Extensions/CqrsCoroutineExtensions.cs +++ b/GFramework.Core/Coroutine/Extensions/CqrsCoroutineExtensions.cs @@ -45,6 +45,7 @@ public static class CqrsCoroutineExtensions if (task.IsCanceled) { + // 取消态与成功态区分:协程层统一映射为 TaskCanceledException。 var canceledException = new TaskCanceledException(task); if (onError != null) { @@ -52,16 +53,18 @@ public static class CqrsCoroutineExtensions yield break; } + // 保留原始抛出栈,避免调试时丢失异常来源。 ExceptionDispatchInfo.Capture(canceledException).Throw(); } if (!task.IsFaulted) yield break; - + // 优先解包业务异常,避免直接暴露 AggregateException。 var exception = task.Exception!.InnerException ?? task.Exception; if (onError != null) onError.Invoke(exception); else + // 继续保留原始栈信息。 ExceptionDispatchInfo.Capture(exception).Throw(); } } From c0e2e9a6405bf986a0ccb131ebe506d0c2d1c0a5 Mon Sep 17 00:00:00 2001 From: GeWuYou <95328647+GeWuYou@users.noreply.github.com> Date: Wed, 15 Apr 2026 09:05:22 +0800 Subject: [PATCH 7/7] =?UTF-8?q?docs(coroutine):=20=E6=9B=B4=E6=96=B0CqrsCo?= =?UTF-8?q?routineExtensions=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加了TaskCanceledException异常说明文档 - 详细描述了命令调度取消时的异常情况 - 补充了底层命令调度相关的异常处理说明 --- .../Coroutine/Extensions/CqrsCoroutineExtensions.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/GFramework.Core/Coroutine/Extensions/CqrsCoroutineExtensions.cs b/GFramework.Core/Coroutine/Extensions/CqrsCoroutineExtensions.cs index 41e54ee2..98667656 100644 --- a/GFramework.Core/Coroutine/Extensions/CqrsCoroutineExtensions.cs +++ b/GFramework.Core/Coroutine/Extensions/CqrsCoroutineExtensions.cs @@ -23,6 +23,9 @@ public static class CqrsCoroutineExtensions /// /// 当 时抛出。 /// + /// + /// 当底层命令调度被取消且未提供 时抛出。 + /// /// /// 当底层命令调度失败且未提供 时,抛出底层原始异常。 ///