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..b344717d 100644 --- a/GFramework.Core.Abstractions/Architectures/IArchitecture.cs +++ b/GFramework.Core.Abstractions/Architectures/IArchitecture.cs @@ -73,12 +73,22 @@ public interface IArchitecture : IAsyncInitializable, IAsyncDestroyable, IInitia void RegisterUtility(Action? onCreated = null) where T : class, IUtility; /// - /// 注册中介行为管道 - /// 用于配置Mediator框架的行为拦截和处理逻辑。 + /// 注册 CQRS 请求管道行为。 /// 既支持实现 IPipelineBehavior<,> 的开放泛型行为类型, /// 也支持绑定到单一请求/响应对的封闭行为类型。 /// /// 行为类型,必须是引用类型 + void RegisterCqrsPipelineBehavior() + where TBehavior : class; + + /// + /// 注册 CQRS 请求管道行为。 + /// 该成员保留旧名称以兼容历史调用点,内部行为与 一致。 + /// 既支持实现 IPipelineBehavior<,> 的开放泛型行为类型, + /// 也支持绑定到单一请求/响应对的封闭行为类型。 + /// + /// 行为类型,必须是引用类型 + [Obsolete("Use RegisterCqrsPipelineBehavior() instead.")] void RegisterMediatorBehavior() where TBehavior : class; @@ -101,4 +111,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..c56b71fa 100644 --- a/GFramework.Core.Abstractions/Ioc/IIocContainer.cs +++ b/GFramework.Core.Abstractions/Ioc/IIocContainer.cs @@ -90,10 +90,18 @@ public interface IIocContainer : IContextAware void RegisterFactory(Func factory) where TService : class; /// - /// 注册中介行为管道 - /// 用于配置Mediator框架的行为拦截和处理逻辑 + /// 注册 CQRS 请求管道行为。 /// /// 行为类型,必须是引用类型 + void RegisterCqrsPipelineBehavior() + where TBehavior : class; + + /// + /// 注册 CQRS 请求管道行为。 + /// 该成员保留旧名称以兼容历史调用点,内部行为与 一致。 + /// + /// 行为类型,必须是引用类型 + [Obsolete("Use RegisterCqrsPipelineBehavior() instead.")] void RegisterMediatorBehavior() where TBehavior : class; @@ -227,4 +235,4 @@ public interface IIocContainer : IContextAware IServiceScope CreateScope(); #endregion -} \ No newline at end of file +} diff --git a/GFramework.Core.Tests/Architectures/ArchitectureModulesBehaviorTests.cs b/GFramework.Core.Tests/Architectures/ArchitectureModulesBehaviorTests.cs index bf6566b4..ade4a1a7 100644 --- a/GFramework.Core.Tests/Architectures/ArchitectureModulesBehaviorTests.cs +++ b/GFramework.Core.Tests/Architectures/ArchitectureModulesBehaviorTests.cs @@ -59,6 +59,28 @@ public class ArchitectureModulesBehaviorTests /// 验证注册的 CQRS 行为会参与请求管道执行。 /// [Test] + public async Task RegisterCqrsPipelineBehavior_Should_Apply_Pipeline_Behavior_To_Request() + { + var architecture = new ModuleTestArchitecture(target => + target.RegisterCqrsPipelineBehavior>()); + + 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(); + } + + /// + /// 验证兼容别名 RegisterMediatorBehavior 仍会把 CQRS 行为接入请求管道。 + /// + [Test] public async Task RegisterMediatorBehavior_Should_Apply_Pipeline_Behavior_To_Request() { var architecture = new ModuleTestArchitecture(target => 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..28e3cc97 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,136 @@ public class TestArchitectureContext : IArchitectureContext { } - public ValueTask SendRequestAsync(global::GFramework.Core.Abstractions.Cqrs.IRequest request, + /// + /// 测试桩:异步发送统一 CQRS 请求。 + /// + /// 响应类型。 + /// 要发送的请求。 + /// 取消令牌。 + /// 请求响应任务。 + /// 该测试桩未实现此成员。 + public ValueTask SendRequestAsync(IRequest request, CancellationToken cancellationToken = default) { throw new NotImplementedException(); } - public TResponse SendRequest(global::GFramework.Core.Abstractions.Cqrs.IRequest request) + /// + /// 测试桩:同步发送统一 CQRS 请求。 + /// + /// 响应类型。 + /// 要发送的请求。 + /// 请求响应。 + /// 该测试桩未实现此成员。 + public TResponse SendRequest(IRequest request) { throw new NotImplementedException(); } - public ValueTask SendCommandAsync( - global::GFramework.Core.Abstractions.Cqrs.Command.ICommand command, + /// + /// 测试桩:异步发送 CQRS 命令并返回响应。 + /// + /// 命令响应类型。 + /// 要发送的命令。 + /// 取消令牌。 + /// 命令响应任务。 + /// 该测试桩未实现此成员。 + 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) + /// + /// 测试桩:同步发送 CQRS 命令并返回响应。 + /// + /// 命令响应类型。 + /// 要发送的命令。 + /// 命令响应。 + /// 该测试桩未实现此成员。 + public TResponse SendCommand(Abstractions.Cqrs.Command.ICommand command) { throw new NotImplementedException(); } - public ValueTask SendQueryAsync( - global::GFramework.Core.Abstractions.Cqrs.Query.IQuery query, + /// + /// 测试桩:异步发送 CQRS 查询并返回结果。 + /// + /// 查询结果类型。 + /// 要发送的查询。 + /// 取消令牌。 + /// 查询结果任务。 + /// 该测试桩未实现此成员。 + 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) + /// + /// 测试桩:同步发送 CQRS 查询并返回结果。 + /// + /// 查询结果类型。 + /// 要发送的查询。 + /// 查询结果。 + /// 该测试桩未实现此成员。 + public TResponse SendQuery(Abstractions.Cqrs.Query.IQuery query) { throw new NotImplementedException(); } + /// + /// 测试桩:异步发布 CQRS 通知。 + /// + /// 通知类型。 + /// 要发布的通知。 + /// 取消令牌。 + /// 通知发布任务。 + /// 该测试桩未实现此成员。 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(); } + /// + /// 测试桩:创建 CQRS 流式请求响应序列。 + /// + /// 流式响应元素类型。 + /// 流式请求。 + /// 取消令牌。 + /// 异步响应流。 + /// 该测试桩未实现此成员。 public IAsyncEnumerable CreateStream( - global::GFramework.Core.Abstractions.Cqrs.IStreamRequest request, + IStreamRequest request, CancellationToken cancellationToken = default) { throw new NotImplementedException(); } + /// + /// 测试桩:异步发送无返回值 CQRS 命令。 + /// + /// 命令类型。 + /// 要发送的命令。 + /// 取消令牌。 + /// 命令发送任务。 + /// 该测试桩未实现此成员。 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, + /// + /// 测试桩:异步发送带返回值的 CQRS 请求。 + /// + /// 响应类型。 + /// 要发送的请求。 + /// 取消令牌。 + /// 请求响应任务。 + /// 该测试桩未实现此成员。 + public ValueTask SendAsync(IRequest command, CancellationToken cancellationToken = default) { throw new NotImplementedException(); @@ -468,7 +543,7 @@ public class TestArchitectureContext : IArchitectureContext /// 返回值类型 /// 命令对象 /// 命令执行结果 - public TResult SendCommand(Abstractions.Command.ICommand command) + public TResult SendCommand(ICommand command) { return default!; } @@ -489,7 +564,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/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/CqrsCoroutineExtensionsTests.cs b/GFramework.Core.Tests/Coroutine/CqrsCoroutineExtensionsTests.cs new file mode 100644 index 00000000..67e9537d --- /dev/null +++ b/GFramework.Core.Tests/Coroutine/CqrsCoroutineExtensionsTests.cs @@ -0,0 +1,174 @@ +// Copyright (c) 2026 GeWuYou +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using GFramework.Core.Abstractions.Architectures; +using GFramework.Core.Abstractions.Coroutine; +using GFramework.Core.Abstractions.Cqrs; +using GFramework.Core.Abstractions.Rule; +using GFramework.Core.Cqrs.Extensions; + +namespace GFramework.Core.Tests.Coroutine; + +/// +/// 的单元测试类。 +/// 验证新的 CQRS 协程扩展直接走框架内建 CQRS runtime, +/// 并确保协程对命令调度异常的传播行为保持稳定。 +/// +[TestFixture] +public class CqrsCoroutineExtensionsTests +{ + /// + /// 验证SendCommandCoroutine应该返回IEnumerator + /// + [Test] + public void SendCommandCoroutine_Should_Return_IEnumerator_Of_YieldInstruction() + { + var command = new TestCommand("Test"); + var contextAware = new TestContextAware(); + + contextAware.MockContext + .Setup(ctx => ctx.SendAsync(command, It.IsAny())) + .Returns(ValueTask.CompletedTask); + + var coroutine = CqrsCoroutineExtensions.SendCommandCoroutine(contextAware, command); + + Assert.That(coroutine, Is.InstanceOf>()); + } + + /// + /// 验证 SendCommandCoroutine 在底层命令调度失败时会重新抛出原始异常。 + /// + [Test] + public void SendCommandCoroutine_Should_Rethrow_Inner_Exception_When_Command_Fails() + { + var command = new TestCommand("Test"); + var contextAware = new TestContextAware(); + var expectedException = new InvalidOperationException("Command failed."); + + contextAware.MockContext + .Setup(ctx => ctx.SendAsync(command, It.IsAny())) + .Returns(new ValueTask(Task.FromException(expectedException))); + + var coroutine = CqrsCoroutineExtensions.SendCommandCoroutine(contextAware, command); + + Assert.That(coroutine.MoveNext(), Is.True); + var exception = Assert.Throws(() => coroutine.MoveNext()); + 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)); + } + + /// + /// 验证 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()); + } + + /// + /// 测试用的简单命令类 + /// + private sealed record TestCommand(string Data) : IRequest; + + /// + /// 上下文感知基类的模拟实现 + /// + 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.Tests/Coroutine/MediatorCoroutineExtensionsTests.cs b/GFramework.Core.Tests/Coroutine/MediatorCoroutineExtensionsTests.cs deleted file mode 100644 index eeb1493c..00000000 --- a/GFramework.Core.Tests/Coroutine/MediatorCoroutineExtensionsTests.cs +++ /dev/null @@ -1,104 +0,0 @@ -// Copyright (c) 2026 GeWuYou -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -using GFramework.Core.Abstractions.Architectures; -using GFramework.Core.Abstractions.Coroutine; -using GFramework.Core.Abstractions.Rule; -using GFramework.Core.Coroutine.Extensions; -using Mediator; -using Moq; - -namespace GFramework.Core.Tests.Coroutine; - -/// -/// MediatorCoroutineExtensions的单元测试类 -/// 测试Mediator模式与协程集成的扩展方法 -/// 注意:由于 Mediator 使用源生成器,本测试类主要验证接口和参数验证 -/// -[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 contextAware = new TestContextAware(); - - // 创建 mediator 模拟 - var mediatorMock = new Mock(); - contextAware._mockContext - .Setup(ctx => ctx.GetService()) - .Returns(mediatorMock.Object); - - var coroutine = MediatorCoroutineExtensions.SendCommandCoroutine(contextAware, command); - - Assert.That(coroutine, Is.InstanceOf>()); - } - - /// - /// 验证SendCommandCoroutine应该在mediator为null时抛出NullReferenceException - /// - [Test] - public void SendCommandCoroutine_Should_Throw_When_Mediator_Null() - { - var command = new TestCommand { Data = "Test" }; - var contextAware = new TestContextAware(); - - // 设置上下文服务以返回null mediator - contextAware._mockContext - .Setup(ctx => ctx.GetService()) - .Returns((IMediator?)null); - - // 创建协程 - var coroutine = MediatorCoroutineExtensions.SendCommandCoroutine(contextAware, command); - - // 调用 MoveNext 时应该抛出 NullReferenceException - Assert.Throws(() => coroutine.MoveNext()); - } -} \ No newline at end of file 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.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/Architectures/Architecture.cs b/GFramework.Core/Architectures/Architecture.cs index 39b4fd13..ec571ee8 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,14 +145,24 @@ public abstract class Architecture : IArchitecture #region Module Management /// - /// 注册中介行为管道 - /// 用于配置Mediator框架的行为拦截和处理逻辑。 + /// 注册 CQRS 请求管道行为。 /// 可以传入开放泛型行为类型,也可以传入绑定到特定请求的封闭行为类型。 /// /// 行为类型,必须是引用类型 + public void RegisterCqrsPipelineBehavior() where TBehavior : class + { + _modules.RegisterCqrsPipelineBehavior(); + } + + /// + /// 注册 CQRS 请求管道行为。 + /// 该成员保留旧名称以兼容历史调用点,内部行为与 一致。 + /// + /// 行为类型,必须是引用类型 + [Obsolete("Use RegisterCqrsPipelineBehavior() instead.")] public void RegisterMediatorBehavior() where TBehavior : class { - _modules.RegisterMediatorBehavior(); + RegisterCqrsPipelineBehavior(); } /// @@ -328,4 +337,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..7563e276 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,15 +13,25 @@ internal sealed class ArchitectureModules( ILogger logger) { /// - /// 注册中介行为管道 - /// 用于配置Mediator框架的行为拦截和处理逻辑。 + /// 注册 CQRS 请求管道行为。 /// 支持开放泛型行为类型和针对单一请求的封闭行为类型。 /// /// 行为类型,必须是引用类型 + public void RegisterCqrsPipelineBehavior() where TBehavior : class + { + logger.Debug($"Registering CQRS pipeline behavior: {typeof(TBehavior).Name}"); + services.Container.RegisterCqrsPipelineBehavior(); + } + + /// + /// 注册 CQRS 请求管道行为。 + /// 该成员保留旧名称以兼容历史调用点,内部行为与 一致。 + /// + /// 行为类型,必须是引用类型 + [Obsolete("Use RegisterCqrsPipelineBehavior() instead.")] public void RegisterMediatorBehavior() where TBehavior : class { - logger.Debug($"Registering mediator behavior: {typeof(TBehavior).Name}"); - services.Container.RegisterMediatorBehavior(); + RegisterCqrsPipelineBehavior(); } /// @@ -37,4 +47,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/CqrsCoroutineExtensions.cs b/GFramework.Core/Coroutine/Extensions/CqrsCoroutineExtensions.cs new file mode 100644 index 00000000..98667656 --- /dev/null +++ b/GFramework.Core/Coroutine/Extensions/CqrsCoroutineExtensions.cs @@ -0,0 +1,73 @@ +using System.Runtime.ExceptionServices; +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.IsCanceled) + { + // 取消态与成功态区分:协程层统一映射为 TaskCanceledException。 + var canceledException = new TaskCanceledException(task); + if (onError != null) + { + onError.Invoke(canceledException); + 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(); + } +} diff --git a/GFramework.Core/Coroutine/Extensions/MediatorCoroutineExtensions.cs b/GFramework.Core/Coroutine/Extensions/MediatorCoroutineExtensions.cs index 5c301c07..91ed0238 100644 --- a/GFramework.Core/Coroutine/Extensions/MediatorCoroutineExtensions.cs +++ b/GFramework.Core/Coroutine/Extensions/MediatorCoroutineExtensions.cs @@ -12,21 +12,23 @@ // limitations under the License. using GFramework.Core.Abstractions.Coroutine; +using GFramework.Core.Abstractions.Cqrs; using GFramework.Core.Abstractions.Rule; -using Mediator; +using GFramework.Core.Cqrs.Extensions; namespace GFramework.Core.Coroutine.Extensions; /// -/// 提供Mediator模式与协程集成的扩展方法。 -/// 包含发送命令和等待事件的协程实现。 +/// 提供 CQRS 命令与协程集成的扩展方法。 +/// 该类型保留旧名称以兼容历史调用点;新代码应改用 。 /// +[Obsolete("Use GFramework.Core.Cqrs.Extensions.CqrsCoroutineExtensions instead.")] public static class MediatorCoroutineExtensions { /// - /// 以协程方式发送命令并处理可能的异常。 + /// 以协程方式发送无返回值 CQRS 命令并处理可能的异常。 /// - /// 命令的类型 + /// 命令的类型。 /// 上下文感知对象,用于获取服务 /// 要发送的命令对象 /// 发生异常时的回调处理函数 @@ -35,20 +37,8 @@ public static class MediatorCoroutineExtensions this IContextAware contextAware, TCommand command, Action? onError = null) - where TCommand : notnull + where TCommand : IRequest { - var mediator = contextAware - .GetContext() - .GetService()!; - - var task = mediator.Send(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); } -} \ 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/Extensions/ContextAwareCqrsCommandExtensions.cs b/GFramework.Core/Extensions/ContextAwareCqrsCommandExtensions.cs new file mode 100644 index 00000000..b71669ee --- /dev/null +++ b/GFramework.Core/Extensions/ContextAwareCqrsCommandExtensions.cs @@ -0,0 +1,59 @@ +using GFramework.Core.Abstractions.Cqrs.Command; +using GFramework.Core.Abstractions.Rule; + +namespace GFramework.Core.Cqrs.Extensions; + +/// +/// 提供对 接口的 CQRS 命令扩展方法。 +/// +/// +/// 该扩展类将命令分发统一路由到架构上下文中的 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); + } + + /// + /// 异步发送命令并返回结果。 + /// + /// 命令响应类型。 + /// 实现 接口的对象。 + /// 要发送的命令对象。 + /// 取消令牌,用于取消操作。 + /// 包含命令执行结果的 + /// + /// 当 时抛出。 + /// + /// + /// 该方法直接返回底层 ,避免额外的 async 状态机分配。 + /// + 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..ab09e689 --- /dev/null +++ b/GFramework.Core/Extensions/ContextAwareCqrsExtensions.cs @@ -0,0 +1,141 @@ +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..9906bc3d --- /dev/null +++ b/GFramework.Core/Extensions/ContextAwareCqrsQueryExtensions.cs @@ -0,0 +1,50 @@ +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/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.Core/Ioc/MicrosoftDiContainer.cs b/GFramework.Core/Ioc/MicrosoftDiContainer.cs index dec4f267..69b46720 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,13 +309,12 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null) /// - /// 注册中介行为管道 - /// 用于配置Mediator框架的行为拦截和处理逻辑。 + /// 注册 CQRS 请求管道行为。 /// 同时支持开放泛型行为类型和已闭合的具体行为类型, /// 以兼容通用行为和针对单一请求的专用行为两种注册方式。 /// /// 行为类型,必须是引用类型 - public void RegisterMediatorBehavior() where TBehavior : class + public void RegisterCqrsPipelineBehavior() where TBehavior : class { _lock.EnterWriteLock(); try @@ -351,7 +349,7 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null) } } - _logger.Debug($"Mediator behavior registered: {behaviorType.Name}"); + _logger.Debug($"CQRS pipeline behavior registered: {behaviorType.Name}"); } finally { @@ -359,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..98d57936 100644 --- a/GFramework.Godot/Coroutine/ContextAwareCoroutineExtensions.cs +++ b/GFramework.Godot/Coroutine/ContextAwareCoroutineExtensions.cs @@ -4,7 +4,7 @@ using GFramework.Core.Abstractions.Cqrs.Query; using GFramework.Core.Abstractions.Rule; using GFramework.Core.Coroutine; using GFramework.Core.Coroutine.Extensions; -using GFramework.Core.Extensions; +using GFramework.Core.Cqrs.Extensions; namespace GFramework.Godot.Coroutine; @@ -29,8 +29,8 @@ public static class ContextAwareCoroutineExtensions string? tag = null, CancellationToken cancellationToken = default) { - return contextAware - .SendCommandAsync(command, cancellationToken) + return ContextAwareCqrsCommandExtensions + .SendCommandAsync(contextAware, command, cancellationToken) .AsTask() .ToCoroutineEnumerator() .RunCoroutine(segment, tag); @@ -53,8 +53,8 @@ public static class ContextAwareCoroutineExtensions string? tag = null, CancellationToken cancellationToken = default) { - return contextAware - .SendCommandAsync(command, cancellationToken) + return ContextAwareCqrsCommandExtensions + .SendCommandAsync(contextAware, command, cancellationToken) .AsTask() .ToCoroutineEnumerator() .RunCoroutine(segment, tag); @@ -77,8 +77,8 @@ public static class ContextAwareCoroutineExtensions string? tag = null, CancellationToken cancellationToken = default) { - return contextAware - .SendQueryAsync(query, cancellationToken) + return ContextAwareCqrsQueryExtensions + .SendQueryAsync(contextAware, query, cancellationToken) .AsTask() .ToCoroutineEnumerator() .RunCoroutine(segment, tag); @@ -100,8 +100,8 @@ public static class ContextAwareCoroutineExtensions string? tag = null, CancellationToken cancellationToken = default) { - return contextAware - .PublishAsync(notification, cancellationToken) + return 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 d0cf0f9e..3effaa86 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,37 +192,38 @@ 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 行为;默认会自动扫描当前架构所在程序集和 `GFramework.Core` 程序集中的处理器: ```csharp public class GameArchitecture : Architecture { - protected override void Init() + protected override void OnInitialize() { // 注册通用开放泛型行为 - RegisterMediatorBehavior>(); - RegisterMediatorBehavior>(); + RegisterCqrsPipelineBehavior>(); + RegisterCqrsPipelineBehavior>(); - // 处理器会自动通过依赖注入注册 + // 默认只自动扫描当前架构程序集和 GFramework.Core 程序集中的处理器 } } ``` -`RegisterMediatorBehavior()` 同时支持两种形式: +如果处理器位于其他模块或扩展程序集中,需要额外接入对应程序集的处理器注册,而不是依赖默认扫描。 + +`RegisterCqrsPipelineBehavior()` 是推荐入口;旧的 `RegisterMediatorBehavior()` +仅作为兼容名称保留。当前接口支持两种形式: - 开放泛型行为,例如 `LoggingBehavior<,>`,用于匹配所有请求 - 封闭行为类型,例如某个只服务于单一请求的 `SpecialBehavior` @@ -326,7 +324,7 @@ var notification = new PlayerLevelUpNotification(new PlayerLevelUpInput NewLevel = 10 }); -await mediator.Publish(notification); +await this.PublishAsync(notification); ``` ### Pipeline Behaviors(管道行为) @@ -334,16 +332,16 @@ 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, - CancellationToken cancellationToken, - MessageHandlerDelegate next) + MessageHandlerDelegate next, + CancellationToken cancellationToken) { var messageName = message.GetType().Name; Console.WriteLine($"[开始] {messageName}"); @@ -358,12 +356,12 @@ public class LoggingBehavior : IPipelineBehavior : IPipelineBehavior - where TMessage : IMessage + where TMessage : IRequest { public async ValueTask Handle( TMessage message, - CancellationToken cancellationToken, - MessageHandlerDelegate next) + MessageHandlerDelegate next, + CancellationToken cancellationToken) { var stopwatch = Stopwatch.StartNew(); @@ -382,20 +380,20 @@ public class PerformanceBehavior : IPipelineBehavior>(); -RegisterMediatorBehavior>(); +RegisterCqrsPipelineBehavior>(); +RegisterCqrsPipelineBehavior>(); ``` ### 验证行为 ```csharp public class ValidationBehavior : IPipelineBehavior - where TMessage : IMessage + where TMessage : IRequest { public async ValueTask Handle( TMessage message, - CancellationToken cancellationToken, - MessageHandlerDelegate next) + MessageHandlerDelegate next, + CancellationToken cancellationToken) { // 验证输入 if (message is IValidatable validatable) @@ -441,7 +439,7 @@ public class GetAllPlayersStreamQueryHandler : AbstractStreamQueryHandler>(); - RegisterMediatorBehavior>(); + RegisterCqrsPipelineBehavior>(); + RegisterCqrsPipelineBehavior>(); ``` 5. **保持处理器简单**:一个处理器只做一件事 @@ -530,12 +528,12 @@ CalculateDamageRequest **解答**: -- **Notification**:通过 Mediator 发送,处理器在同一请求上下文中执行 +- **Notification**:通过内建 CQRS runtime 发送,处理器在同一请求上下文中执行 - **Event**:通过 EventBus 发送,监听器异步执行 ```csharp // Notification: 同步处理 -await mediator.Publish(notification); // 等待所有处理器完成 +await this.PublishAsync(notification); // 等待所有处理器完成 // Event: 异步处理 this.SendEvent(event); // 立即返回,监听器异步执行 @@ -569,15 +567,13 @@ public override async ValueTask 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..5c4837bf 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 为 `RegisterCqrsPipelineBehavior`) **关键方法**: - `InstallModule()` - 安装模块 -- `RegisterMediatorBehavior()` - 注册中介行为 +- `RegisterCqrsPipelineBehavior()` - 注册 CQRS 管道行为 #### 设计优势 @@ -672,4 +672,3 @@ public interface IController : - 添加 `PhaseChanged` 事件,支持阶段监听 **向后兼容**: 所有公共 API 保持不变,现有代码无需修改。 -