diff --git a/AGENTS.md b/AGENTS.md index c63a2bfa..720b53af 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -244,6 +244,16 @@ bash scripts/validate-csharp-naming.sh - Tracking updates MUST reflect completed work, newly discovered issues, validation results, and the next recommended recovery point. - Completing code changes without updating the active tracking document is considered incomplete work. +- For any multi-step refactor, migration, or cross-module task, contributors MUST create or adopt a dedicated recovery + document under `local-plan/todos/` before making substantive code changes. +- Recovery documents MUST record the current phase, the active recovery point identifier, known risks, and the next + recommended resume step so another contributor or subagent can continue the work safely. +- Contributors MUST maintain a matching execution trace under `local-plan/traces/` for complex work. The trace should + record the current date, key decisions, validation milestones, and the immediate next step. +- When a task spans multiple commits or is likely to exceed a single agent context window, update both the recovery + document and the trace at each meaningful milestone before pausing or handing work off. +- If subagents are used on a complex task, the main agent MUST capture the delegated scope and any accepted findings in + the active recovery document or trace before continuing implementation. ### Repository Documentation diff --git a/GFramework.Core.Abstractions/Architectures/IArchitectureContext.cs b/GFramework.Core.Abstractions/Architectures/IArchitectureContext.cs index 3469d8db..9a08dec6 100644 --- a/GFramework.Core.Abstractions/Architectures/IArchitectureContext.cs +++ b/GFramework.Core.Abstractions/Architectures/IArchitectureContext.cs @@ -1,11 +1,13 @@ using GFramework.Core.Abstractions.Command; +using GFramework.Core.Abstractions.Cqrs; +using GFramework.Core.Abstractions.Cqrs.Command; +using GFramework.Core.Abstractions.Cqrs.Query; using GFramework.Core.Abstractions.Environment; using GFramework.Core.Abstractions.Events; using GFramework.Core.Abstractions.Model; using GFramework.Core.Abstractions.Query; using GFramework.Core.Abstractions.Systems; using GFramework.Core.Abstractions.Utility; -using Mediator; using ICommand = GFramework.Core.Abstractions.Command.ICommand; namespace GFramework.Core.Abstractions.Architectures; @@ -118,12 +120,12 @@ public interface IArchitectureContext TResult SendCommand(Command.ICommand command); /// - /// [Mediator] 发送命令的同步版本(不推荐,仅用于兼容性) + /// 发送一个 CQRS 命令并返回结果。 /// - /// 命令响应类型 - /// 要发送的命令对象 - /// 命令执行结果 - TResponse SendCommand(Mediator.ICommand command); + /// 命令响应类型。 + /// 要发送的 CQRS 命令。 + /// 命令执行结果。 + TResponse SendCommand(GFramework.Core.Abstractions.Cqrs.Command.ICommand command); /// @@ -133,14 +135,13 @@ public interface IArchitectureContext Task SendCommandAsync(IAsyncCommand command); /// - /// [Mediator] 异步发送命令并返回结果 - /// 通过Mediator模式发送命令请求,支持取消操作 + /// 异步发送一个 CQRS 命令并返回结果。 /// - /// 命令响应类型 - /// 要发送的命令对象 - /// 取消令牌,用于取消操作 - /// 包含命令执行结果的ValueTask - ValueTask SendCommandAsync(Mediator.ICommand command, + /// 命令响应类型。 + /// 要发送的 CQRS 命令。 + /// 取消令牌。 + /// 包含命令执行结果的值任务。 + ValueTask SendCommandAsync(GFramework.Core.Abstractions.Cqrs.Command.ICommand command, CancellationToken cancellationToken = default); @@ -161,12 +162,12 @@ public interface IArchitectureContext TResult SendQuery(Query.IQuery query); /// - /// [Mediator] 发送查询的同步版本(不推荐,仅用于兼容性) + /// 发送一个 CQRS 查询并返回结果。 /// - /// 查询响应类型 - /// 要发送的查询对象 - /// 查询结果 - TResponse SendQuery(Mediator.IQuery query); + /// 查询响应类型。 + /// 要发送的 CQRS 查询。 + /// 查询结果。 + TResponse SendQuery(GFramework.Core.Abstractions.Cqrs.Query.IQuery query); /// /// 异步发送一个查询请求 @@ -177,14 +178,13 @@ public interface IArchitectureContext Task SendQueryAsync(IAsyncQuery query); /// - /// [Mediator] 异步发送查询并返回结果 - /// 通过Mediator模式发送查询请求,支持取消操作 + /// 异步发送一个 CQRS 查询并返回结果。 /// - /// 查询响应类型 - /// 要发送的查询对象 - /// 取消令牌,用于取消操作 - /// 包含查询结果的ValueTask - ValueTask SendQueryAsync(Mediator.IQuery query, + /// 查询响应类型。 + /// 要发送的 CQRS 查询。 + /// 取消令牌。 + /// 包含查询结果的值任务。 + ValueTask SendQueryAsync(GFramework.Core.Abstractions.Cqrs.Query.IQuery query, CancellationToken cancellationToken = default); /// @@ -265,4 +265,4 @@ public interface IArchitectureContext /// /// 环境对象实例 IEnvironment GetEnvironment(); -} \ No newline at end of file +} diff --git a/GFramework.Core.Abstractions/Cqrs/Command/ICommand.cs b/GFramework.Core.Abstractions/Cqrs/Command/ICommand.cs new file mode 100644 index 00000000..ba04331e --- /dev/null +++ b/GFramework.Core.Abstractions/Cqrs/Command/ICommand.cs @@ -0,0 +1,25 @@ +namespace GFramework.Core.Abstractions.Cqrs.Command; + +/// +/// 表示一个 CQRS 命令。 +/// 命令通常用于修改系统状态。 +/// +/// 命令响应类型。 +public interface ICommand : IRequest +{ +} + +/// +/// 表示一个无显式返回值的 CQRS 命令。 +/// +public interface ICommand : ICommand +{ +} + +/// +/// 表示一个流式 CQRS 命令。 +/// +/// 流式响应元素类型。 +public interface IStreamCommand : IStreamRequest +{ +} diff --git a/GFramework.Core.Abstractions/Cqrs/INotification.cs b/GFramework.Core.Abstractions/Cqrs/INotification.cs new file mode 100644 index 00000000..9d69e28e --- /dev/null +++ b/GFramework.Core.Abstractions/Cqrs/INotification.cs @@ -0,0 +1,9 @@ +namespace GFramework.Core.Abstractions.Cqrs; + +/// +/// 表示一个一对多发布的通知消息。 +/// 通知不要求返回值,允许被零个或多个处理器消费。 +/// +public interface INotification +{ +} diff --git a/GFramework.Core.Abstractions/Cqrs/INotificationHandler.cs b/GFramework.Core.Abstractions/Cqrs/INotificationHandler.cs new file mode 100644 index 00000000..23861d1d --- /dev/null +++ b/GFramework.Core.Abstractions/Cqrs/INotificationHandler.cs @@ -0,0 +1,17 @@ +namespace GFramework.Core.Abstractions.Cqrs; + +/// +/// 表示处理通知消息的处理器契约。 +/// +/// 通知类型。 +public interface INotificationHandler + where TNotification : INotification +{ + /// + /// 处理通知消息。 + /// + /// 要处理的通知。 + /// 取消令牌。 + /// 异步处理任务。 + ValueTask Handle(TNotification notification, CancellationToken cancellationToken); +} diff --git a/GFramework.Core.Abstractions/Cqrs/IPipelineBehavior.cs b/GFramework.Core.Abstractions/Cqrs/IPipelineBehavior.cs new file mode 100644 index 00000000..cd01aad3 --- /dev/null +++ b/GFramework.Core.Abstractions/Cqrs/IPipelineBehavior.cs @@ -0,0 +1,22 @@ +namespace GFramework.Core.Abstractions.Cqrs; + +/// +/// 定义 CQRS 请求处理前后的管道行为。 +/// +/// 请求类型。 +/// 响应类型。 +public interface IPipelineBehavior + where TRequest : IRequest +{ + /// + /// 处理当前请求,并决定是否继续调用后续行为或最终处理器。 + /// + /// 当前请求消息。 + /// 下一个处理委托。 + /// 取消令牌。 + /// 请求响应。 + ValueTask Handle( + TRequest message, + MessageHandlerDelegate next, + CancellationToken cancellationToken); +} diff --git a/GFramework.Core.Abstractions/Cqrs/IRequest.cs b/GFramework.Core.Abstractions/Cqrs/IRequest.cs new file mode 100644 index 00000000..26259fc4 --- /dev/null +++ b/GFramework.Core.Abstractions/Cqrs/IRequest.cs @@ -0,0 +1,10 @@ +namespace GFramework.Core.Abstractions.Cqrs; + +/// +/// 表示一个有响应的 CQRS 请求。 +/// 该接口是命令、查询以及其他请求语义的统一基接口。 +/// +/// 请求响应类型。 +public interface IRequest +{ +} diff --git a/GFramework.Core.Abstractions/Cqrs/IRequestHandler.cs b/GFramework.Core.Abstractions/Cqrs/IRequestHandler.cs new file mode 100644 index 00000000..2415e282 --- /dev/null +++ b/GFramework.Core.Abstractions/Cqrs/IRequestHandler.cs @@ -0,0 +1,18 @@ +namespace GFramework.Core.Abstractions.Cqrs; + +/// +/// 表示处理单个 CQRS 请求的处理器契约。 +/// +/// 请求类型。 +/// 响应类型。 +public interface IRequestHandler + where TRequest : IRequest +{ + /// + /// 处理指定请求并返回结果。 + /// + /// 要处理的请求。 + /// 取消令牌。 + /// 请求结果。 + ValueTask Handle(TRequest request, CancellationToken cancellationToken); +} diff --git a/GFramework.Core.Abstractions/Cqrs/IStreamRequest.cs b/GFramework.Core.Abstractions/Cqrs/IStreamRequest.cs new file mode 100644 index 00000000..05ffa5df --- /dev/null +++ b/GFramework.Core.Abstractions/Cqrs/IStreamRequest.cs @@ -0,0 +1,10 @@ +namespace GFramework.Core.Abstractions.Cqrs; + +/// +/// 表示一个流式 CQRS 请求。 +/// 请求处理器可以逐步产生响应序列,而不是一次性返回完整结果。 +/// +/// 流式响应元素类型。 +public interface IStreamRequest +{ +} diff --git a/GFramework.Core.Abstractions/Cqrs/IStreamRequestHandler.cs b/GFramework.Core.Abstractions/Cqrs/IStreamRequestHandler.cs new file mode 100644 index 00000000..1c6e02a7 --- /dev/null +++ b/GFramework.Core.Abstractions/Cqrs/IStreamRequestHandler.cs @@ -0,0 +1,18 @@ +namespace GFramework.Core.Abstractions.Cqrs; + +/// +/// 表示处理流式 CQRS 请求的处理器契约。 +/// +/// 流式请求类型。 +/// 流式响应元素类型。 +public interface IStreamRequestHandler + where TRequest : IStreamRequest +{ + /// + /// 处理流式请求并返回异步响应序列。 + /// + /// 要处理的请求。 + /// 取消令牌。 + /// 异步响应序列。 + IAsyncEnumerable Handle(TRequest request, CancellationToken cancellationToken); +} diff --git a/GFramework.Core.Abstractions/Cqrs/MessageHandlerDelegate.cs b/GFramework.Core.Abstractions/Cqrs/MessageHandlerDelegate.cs new file mode 100644 index 00000000..172f7f3b --- /dev/null +++ b/GFramework.Core.Abstractions/Cqrs/MessageHandlerDelegate.cs @@ -0,0 +1,14 @@ +namespace GFramework.Core.Abstractions.Cqrs; + +/// +/// 表示 CQRS 请求在管道中继续向下执行的处理委托。 +/// +/// 请求类型。 +/// 响应类型。 +/// 当前请求消息。 +/// 取消令牌。 +/// 请求响应。 +public delegate ValueTask MessageHandlerDelegate( + TRequest message, + CancellationToken cancellationToken) + where TRequest : IRequest; diff --git a/GFramework.Core.Abstractions/Cqrs/Query/IQuery.cs b/GFramework.Core.Abstractions/Cqrs/Query/IQuery.cs new file mode 100644 index 00000000..cbb1586e --- /dev/null +++ b/GFramework.Core.Abstractions/Cqrs/Query/IQuery.cs @@ -0,0 +1,18 @@ +namespace GFramework.Core.Abstractions.Cqrs.Query; + +/// +/// 表示一个 CQRS 查询。 +/// 查询用于读取数据,不应产生副作用。 +/// +/// 查询响应类型。 +public interface IQuery : IRequest +{ +} + +/// +/// 表示一个流式 CQRS 查询。 +/// +/// 流式响应元素类型。 +public interface IStreamQuery : IStreamRequest +{ +} diff --git a/GFramework.Core.Abstractions/Cqrs/Unit.cs b/GFramework.Core.Abstractions/Cqrs/Unit.cs new file mode 100644 index 00000000..7dc3da14 --- /dev/null +++ b/GFramework.Core.Abstractions/Cqrs/Unit.cs @@ -0,0 +1,13 @@ +namespace GFramework.Core.Abstractions.Cqrs; + +/// +/// 表示没有实际返回值的 CQRS 响应类型。 +/// 该类型用于统一命令与请求的泛型签名,避免引入外部库的 Unit 定义。 +/// +public readonly record struct Unit +{ + /// + /// 获取默认的空响应实例。 + /// + public static Unit Value { get; } = new(); +} diff --git a/GFramework.Core.Tests/Architectures/ArchitectureModulesBehaviorTests.cs b/GFramework.Core.Tests/Architectures/ArchitectureModulesBehaviorTests.cs index 05601230..7485d978 100644 --- a/GFramework.Core.Tests/Architectures/ArchitectureModulesBehaviorTests.cs +++ b/GFramework.Core.Tests/Architectures/ArchitectureModulesBehaviorTests.cs @@ -2,14 +2,14 @@ using GFramework.Core.Abstractions.Architectures; using GFramework.Core.Abstractions.Utility; using GFramework.Core.Architectures; using GFramework.Core.Logging; -using Mediator; -using Microsoft.Extensions.DependencyInjection; +using GFramework.Core.Tests; +using GfCqrs = GFramework.Core.Abstractions.Cqrs; namespace GFramework.Core.Tests.Architectures; /// -/// 验证 Architecture 通过 ArchitectureModules 暴露出的模块安装与 Mediator 行为注册能力。 -/// 这些测试覆盖模块安装回调和中介管道行为接入,确保模块管理器仍然保持可观察行为不变。 +/// 验证 Architecture 通过 ArchitectureModules 暴露出的模块安装与 CQRS 行为注册能力。 +/// 这些测试覆盖模块安装回调和请求管道行为接入,确保模块管理器仍然保持可观察行为不变。 /// [TestFixture] public class ArchitectureModulesBehaviorTests @@ -57,7 +57,7 @@ public class ArchitectureModulesBehaviorTests } /// - /// 验证注册的 Mediator 行为会参与请求管道执行。 + /// 验证注册的 CQRS 行为会参与请求管道执行。 /// [Test] public async Task RegisterMediatorBehavior_Should_Apply_Pipeline_Behavior_To_Request() @@ -67,7 +67,9 @@ public class ArchitectureModulesBehaviorTests await architecture.InitializeAsync(); - var response = await architecture.Context.SendRequestAsync(new ModuleBehaviorRequest()); + var response = await CqrsTestRuntime.ExecutePipelineAsync( + architecture.Context, + new ModuleBehaviorRequest()); Assert.Multiple(() => { @@ -83,12 +85,6 @@ public class ArchitectureModulesBehaviorTests /// private sealed class ModuleTestArchitecture(Action registrationAction) : Architecture { - /// - /// 打开 Mediator 服务注册,以便测试中介行为接入。 - /// - public override Action? Configurator => - services => services.AddMediator(options => { options.ServiceLifetime = ServiceLifetime.Singleton; }); - /// /// 在初始化阶段执行测试注入的模块注册逻辑。 /// @@ -136,14 +132,14 @@ public class ArchitectureModulesBehaviorTests /// /// 用于验证管道行为注册是否生效的测试请求。 /// -public sealed class ModuleBehaviorRequest : IRequest +public sealed class ModuleBehaviorRequest : GfCqrs.IRequest { } /// /// 处理测试请求的处理器。 /// -public sealed class ModuleBehaviorRequestHandler : IRequestHandler +public sealed class ModuleBehaviorRequestHandler : GfCqrs.IRequestHandler { /// /// 返回固定结果,便于聚焦验证管道行为是否执行。 @@ -162,8 +158,8 @@ public sealed class ModuleBehaviorRequestHandler : IRequestHandler /// 请求类型。 /// 响应类型。 -public sealed class TrackingPipelineBehavior : IPipelineBehavior - where TRequest : IRequest +public sealed class TrackingPipelineBehavior : GfCqrs.IPipelineBehavior + where TRequest : GfCqrs.IRequest { /// /// 获取当前测试进程中该请求类型对应的行为触发次数。 @@ -179,10 +175,10 @@ public sealed class TrackingPipelineBehavior : IPipelineBeh /// 下游处理器的响应结果。 public async ValueTask Handle( TRequest message, - MessageHandlerDelegate next, + GfCqrs.MessageHandlerDelegate next, CancellationToken cancellationToken) { InvocationCount++; return await next(message, cancellationToken); } -} \ No newline at end of file +} diff --git a/GFramework.Core.Tests/Architectures/ArchitectureServicesTests.cs b/GFramework.Core.Tests/Architectures/ArchitectureServicesTests.cs index 93244298..512e8a55 100644 --- a/GFramework.Core.Tests/Architectures/ArchitectureServicesTests.cs +++ b/GFramework.Core.Tests/Architectures/ArchitectureServicesTests.cs @@ -347,58 +347,61 @@ public class TestArchitectureContextV3 : IArchitectureContext { } - public ValueTask SendRequestAsync(IRequest request, + public ValueTask SendRequestAsync(global::GFramework.Core.Abstractions.Cqrs.IRequest request, CancellationToken cancellationToken = default) { throw new NotImplementedException(); } - public TResponse SendRequest(IRequest request) + public TResponse SendRequest(global::GFramework.Core.Abstractions.Cqrs.IRequest request) { throw new NotImplementedException(); } - public ValueTask SendCommandAsync(global::Mediator.ICommand command, + public ValueTask SendCommandAsync( + global::GFramework.Core.Abstractions.Cqrs.Command.ICommand command, CancellationToken cancellationToken = default) { throw new NotImplementedException(); } - public TResponse SendCommand(global::Mediator.ICommand command) + public TResponse SendCommand(global::GFramework.Core.Abstractions.Cqrs.Command.ICommand command) { throw new NotImplementedException(); } - public ValueTask SendQueryAsync(global::Mediator.IQuery query, + public ValueTask SendQueryAsync( + global::GFramework.Core.Abstractions.Cqrs.Query.IQuery query, CancellationToken cancellationToken = default) { throw new NotImplementedException(); } - public TResponse SendQuery(global::Mediator.IQuery query) + public TResponse SendQuery(global::GFramework.Core.Abstractions.Cqrs.Query.IQuery query) { throw new NotImplementedException(); } public ValueTask PublishAsync(TNotification notification, - CancellationToken cancellationToken = default) where TNotification : INotification + CancellationToken cancellationToken = default) where TNotification : global::GFramework.Core.Abstractions.Cqrs.INotification { throw new NotImplementedException(); } - public IAsyncEnumerable CreateStream(IStreamRequest request, + public IAsyncEnumerable CreateStream( + global::GFramework.Core.Abstractions.Cqrs.IStreamRequest request, CancellationToken cancellationToken = default) { throw new NotImplementedException(); } public ValueTask SendAsync(TCommand command, CancellationToken cancellationToken = default) - where TCommand : IRequest + where TCommand : global::GFramework.Core.Abstractions.Cqrs.IRequest { throw new NotImplementedException(); } - public ValueTask SendAsync(IRequest command, + public ValueTask SendAsync(global::GFramework.Core.Abstractions.Cqrs.IRequest command, CancellationToken cancellationToken = default) { throw new NotImplementedException(); @@ -439,4 +442,4 @@ public class TestArchitectureContextV3 : IArchitectureContext } } -#endregion \ No newline at end of file +#endregion diff --git a/GFramework.Core.Tests/Architectures/GameContextTests.cs b/GFramework.Core.Tests/Architectures/GameContextTests.cs index 8f1bea57..6c2670bc 100644 --- a/GFramework.Core.Tests/Architectures/GameContextTests.cs +++ b/GFramework.Core.Tests/Architectures/GameContextTests.cs @@ -394,58 +394,61 @@ public class TestArchitectureContext : IArchitectureContext { } - public ValueTask SendRequestAsync(IRequest request, + public ValueTask SendRequestAsync(global::GFramework.Core.Abstractions.Cqrs.IRequest request, CancellationToken cancellationToken = default) { throw new NotImplementedException(); } - public TResponse SendRequest(IRequest request) + public TResponse SendRequest(global::GFramework.Core.Abstractions.Cqrs.IRequest request) { throw new NotImplementedException(); } - public ValueTask SendCommandAsync(global::Mediator.ICommand command, + public ValueTask SendCommandAsync( + global::GFramework.Core.Abstractions.Cqrs.Command.ICommand command, CancellationToken cancellationToken = default) { throw new NotImplementedException(); } - public TResponse SendCommand(global::Mediator.ICommand command) + public TResponse SendCommand(global::GFramework.Core.Abstractions.Cqrs.Command.ICommand command) { throw new NotImplementedException(); } - public ValueTask SendQueryAsync(global::Mediator.IQuery query, + public ValueTask SendQueryAsync( + global::GFramework.Core.Abstractions.Cqrs.Query.IQuery query, CancellationToken cancellationToken = default) { throw new NotImplementedException(); } - public TResponse SendQuery(global::Mediator.IQuery query) + public TResponse SendQuery(global::GFramework.Core.Abstractions.Cqrs.Query.IQuery query) { throw new NotImplementedException(); } public ValueTask PublishAsync(TNotification notification, - CancellationToken cancellationToken = default) where TNotification : INotification + CancellationToken cancellationToken = default) where TNotification : global::GFramework.Core.Abstractions.Cqrs.INotification { throw new NotImplementedException(); } - public IAsyncEnumerable CreateStream(IStreamRequest request, + public IAsyncEnumerable CreateStream( + global::GFramework.Core.Abstractions.Cqrs.IStreamRequest request, CancellationToken cancellationToken = default) { throw new NotImplementedException(); } public ValueTask SendAsync(TCommand command, CancellationToken cancellationToken = default) - where TCommand : IRequest + where TCommand : global::GFramework.Core.Abstractions.Cqrs.IRequest { throw new NotImplementedException(); } - public ValueTask SendAsync(IRequest command, + public ValueTask SendAsync(global::GFramework.Core.Abstractions.Cqrs.IRequest command, CancellationToken cancellationToken = default) { throw new NotImplementedException(); @@ -510,4 +513,4 @@ public class TestArchitectureContext : IArchitectureContext { return Environment; } -} \ No newline at end of file +} diff --git a/GFramework.Core.Tests/CqrsTestRuntime.cs b/GFramework.Core.Tests/CqrsTestRuntime.cs new file mode 100644 index 00000000..ae1d3361 --- /dev/null +++ b/GFramework.Core.Tests/CqrsTestRuntime.cs @@ -0,0 +1,73 @@ +using System.Reflection; +using GFramework.Core.Abstractions.Architectures; +using GFramework.Core.Abstractions.Logging; +using GFramework.Core.Abstractions.Rule; +using GFramework.Core.Architectures; +using GFramework.Core.Ioc; +using GFramework.Core.Logging; +using GfCqrs = GFramework.Core.Abstractions.Cqrs; + +namespace GFramework.Core.Tests; + +internal static class CqrsTestRuntime +{ + private static readonly MethodInfo RegisterHandlersMethod = typeof(ArchitectureContext).Assembly + .GetType("GFramework.Core.Cqrs.Internal.CqrsHandlerRegistrar", throwOnError: true)! + .GetMethod( + "RegisterHandlers", + BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static)! + ?? throw new InvalidOperationException("Failed to locate CqrsHandlerRegistrar.RegisterHandlers."); + + public static void RegisterHandlers(MicrosoftDiContainer container, params Assembly[] assemblies) + { + ArgumentNullException.ThrowIfNull(container); + ArgumentNullException.ThrowIfNull(assemblies); + + var logger = LoggerFactoryResolver.Provider.CreateLogger(nameof(CqrsTestRuntime)); + RegisterHandlersMethod.Invoke( + null, + [container, assemblies.Where(static assembly => assembly is not null).Distinct().ToArray(), logger]); + } + + public static ValueTask ExecutePipelineAsync( + IArchitectureContext context, + TRequest request, + CancellationToken cancellationToken = default) + where TRequest : class, GfCqrs.IRequest + { + ArgumentNullException.ThrowIfNull(context); + ArgumentNullException.ThrowIfNull(request); + + var handlers = context.GetServices>(); + if (handlers.Count == 0) + throw new InvalidOperationException( + $"No CQRS request handler registered for {typeof(TRequest).FullName}."); + + if (handlers.Count > 1) + throw new InvalidOperationException( + $"Expected a single CQRS request handler for {typeof(TRequest).FullName}, but found {handlers.Count}."); + + var handler = handlers[0]; + PrepareContext(handler, context); + + GfCqrs.MessageHandlerDelegate pipeline = handler.Handle; + + var behaviors = context.GetServices>(); + for (var index = behaviors.Count - 1; index >= 0; index--) + { + var behavior = behaviors[index]; + PrepareContext(behavior, context); + + var next = pipeline; + pipeline = (message, token) => behavior.Handle(message, next, token); + } + + return pipeline(request, cancellationToken); + } + + private static void PrepareContext(object instance, IArchitectureContext context) + { + if (instance is IContextAware contextAware) + contextAware.SetContext(context); + } +} diff --git a/GFramework.Core.Tests/Mediator/MediatorAdvancedFeaturesTests.cs b/GFramework.Core.Tests/Mediator/MediatorAdvancedFeaturesTests.cs index bc5cd782..d3bf4041 100644 --- a/GFramework.Core.Tests/Mediator/MediatorAdvancedFeaturesTests.cs +++ b/GFramework.Core.Tests/Mediator/MediatorAdvancedFeaturesTests.cs @@ -1,10 +1,10 @@ using System.Diagnostics; using System.Reflection; +using GFramework.Core.Abstractions.Cqrs; using GFramework.Core.Architectures; using GFramework.Core.Ioc; using GFramework.Core.Logging; -using Mediator; -using Microsoft.Extensions.DependencyInjection; +using GFramework.Core.Tests; namespace GFramework.Core.Tests.Mediator; @@ -26,11 +26,10 @@ public class MediatorAdvancedFeaturesTests loggerField?.SetValue(_container, LoggerFactoryResolver.Provider.CreateLogger(nameof(MediatorAdvancedFeaturesTests))); - // 注册Mediator及相关处理器 - _container.ExecuteServicesHook(configurator => - { - configurator.AddMediator(options => { options.ServiceLifetime = ServiceLifetime.Singleton; }); - }); + CqrsTestRuntime.RegisterHandlers( + _container, + typeof(MediatorAdvancedFeaturesTests).Assembly, + typeof(ArchitectureContext).Assembly); _container.Freeze(); _context = new ArchitectureContext(_container); @@ -487,4 +486,4 @@ public sealed record TestDatabaseRequest : IRequest public List Storage { get; init; } = new(); } -#endregion \ No newline at end of file +#endregion diff --git a/GFramework.Core.Tests/Mediator/MediatorArchitectureIntegrationTests.cs b/GFramework.Core.Tests/Mediator/MediatorArchitectureIntegrationTests.cs index 85bbf3aa..056517f7 100644 --- a/GFramework.Core.Tests/Mediator/MediatorArchitectureIntegrationTests.cs +++ b/GFramework.Core.Tests/Mediator/MediatorArchitectureIntegrationTests.cs @@ -1,12 +1,12 @@ using System.Diagnostics; using System.Reflection; using GFramework.Core.Abstractions.Architectures; +using GFramework.Core.Abstractions.Cqrs; using GFramework.Core.Architectures; using GFramework.Core.Command; using GFramework.Core.Ioc; using GFramework.Core.Logging; -using Mediator; -using Microsoft.Extensions.DependencyInjection; +using GFramework.Core.Tests; using ICommand = GFramework.Core.Abstractions.Command.ICommand; namespace GFramework.Core.Tests.Mediator; @@ -33,11 +33,10 @@ public class MediatorArchitectureIntegrationTests _commandBus = new CommandExecutor(); _container.RegisterPlurality(_commandBus); - // 注册Mediator - _container.ExecuteServicesHook(configurator => - { - configurator.AddMediator(options => { options.ServiceLifetime = ServiceLifetime.Singleton; }); - }); + CqrsTestRuntime.RegisterHandlers( + _container, + typeof(MediatorArchitectureIntegrationTests).Assembly, + typeof(ArchitectureContext).Assembly); _container.Freeze(); _context = new ArchitectureContext(_container); @@ -559,4 +558,4 @@ public class TestTraditionalCommand : ICommand public IArchitectureContext GetContext() => null!; } -#endregion \ No newline at end of file +#endregion diff --git a/GFramework.Core.Tests/Mediator/MediatorComprehensiveTests.cs b/GFramework.Core.Tests/Mediator/MediatorComprehensiveTests.cs index 020bc26b..27522c16 100644 --- a/GFramework.Core.Tests/Mediator/MediatorComprehensiveTests.cs +++ b/GFramework.Core.Tests/Mediator/MediatorComprehensiveTests.cs @@ -2,6 +2,7 @@ using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using GFramework.Core.Abstractions.Architectures; +using GFramework.Core.Abstractions.Cqrs; using GFramework.Core.Abstractions.Events; using GFramework.Core.Architectures; using GFramework.Core.Command; @@ -10,13 +11,9 @@ using GFramework.Core.Events; using GFramework.Core.Ioc; using GFramework.Core.Logging; using GFramework.Core.Query; -using Mediator; -using Microsoft.Extensions.DependencyInjection; +using GFramework.Core.Tests; using ICommand = GFramework.Core.Abstractions.Command.ICommand; - -// ✅ Mediator 库的命名空间 - -// ✅ 使用 global using 或别名来区分 +using Unit = GFramework.Core.Abstractions.Cqrs.Unit; namespace GFramework.Core.Tests.Mediator; @@ -25,7 +22,7 @@ public class MediatorComprehensiveTests { /// /// 测试初始化方法,在每个测试方法执行前运行。 - /// 负责初始化日志工厂、依赖注入容器、Mediator以及各种总线服务。 + /// 负责初始化日志工厂、依赖注入容器、自有 CQRS 处理器以及各种总线服务。 /// [SetUp] public void SetUp() @@ -51,13 +48,11 @@ public class MediatorComprehensiveTests _container.RegisterPlurality(_asyncQueryBus); _container.RegisterPlurality(_environment); - // ✅ 注册 Mediator - _container.ExecuteServicesHook(configurator => - { - configurator.AddMediator(options => { options.ServiceLifetime = ServiceLifetime.Singleton; }); - }); + CqrsTestRuntime.RegisterHandlers( + _container, + typeof(MediatorComprehensiveTests).Assembly, + typeof(ArchitectureContext).Assembly); - // ✅ Freeze 容器 _container.Freeze(); _context = new ArchitectureContext(_container); @@ -194,19 +189,19 @@ public class MediatorComprehensiveTests /// - /// 测试未注册的Mediator抛出InvalidOperationException + /// 测试未注册的 CQRS handler 时抛出 InvalidOperationException /// [Test] - public void Unregistered_Mediator_Should_Throw_InvalidOperationException() + public void Unregistered_Cqrs_Handler_Should_Throw_InvalidOperationException() { - var containerWithoutMediator = new MicrosoftDiContainer(); - containerWithoutMediator.Freeze(); + var containerWithoutHandlers = new MicrosoftDiContainer(); + containerWithoutHandlers.Freeze(); - var contextWithoutMediator = new ArchitectureContext(containerWithoutMediator); + var contextWithoutHandlers = new ArchitectureContext(containerWithoutHandlers); var testRequest = new TestRequest { Value = 42 }; Assert.ThrowsAsync(async () => - await contextWithoutMediator.SendRequestAsync(testRequest)); + await contextWithoutHandlers.SendRequestAsync(testRequest)); } /// @@ -270,10 +265,10 @@ public class MediatorComprehensiveTests } /// - /// 测试并发Mediator请求不会相互干扰 + /// 测试并发 CQRS 请求不会相互干扰 /// [Test] - public async Task Concurrent_Mediator_Requests_Should_Not_Interfere() + public async Task Concurrent_Cqrs_Requests_Should_Not_Interfere() { const int requestCount = 10; var tasks = new List>(); @@ -389,10 +384,10 @@ public class MediatorComprehensiveTests } /// - /// 测试Mediator性能基准 + /// 测试 CQRS 性能基准 /// [Test] - public async Task Performance_Benchmark_For_Mediator() + public async Task Performance_Benchmark_For_Cqrs() { const int iterations = 1000; var stopwatch = Stopwatch.StartNew(); @@ -413,10 +408,10 @@ public class MediatorComprehensiveTests } /// - /// 测试Mediator和传统CQRS可以共存 + /// 测试自有 CQRS 和传统 CQRS 可以共存 /// [Test] - public async Task Mediator_And_Legacy_CQRS_Can_Coexist() + public async Task Cqrs_And_Legacy_CQRS_Can_Coexist() { // 使用传统方式 var legacyCommand = new TestLegacyCommand(); @@ -726,4 +721,4 @@ public sealed class TestStreamRequestHandler : IStreamRequestHandler /// 为服务容器设置上下文并执行扩展配置钩子。 - /// 这一步统一承接 Mediator 等容器扩展的接入点,避免 直接操作容器细节。 + /// 这一步统一承接 CQRS 运行时与容器扩展的接入点,避免 直接操作容器细节。 /// /// 当前架构上下文。 /// 可选的服务集合配置委托。 private void ConfigureServices(IArchitectureContext context, Action? configurator) { services.SetContext(context); + CqrsHandlerRegistrar.RegisterHandlers( + services.Container, + [architectureType.Assembly, typeof(ArchitectureContext).Assembly], + logger); if (configurator is null) - logger.Debug("Mediator-based cqrs will not take effect without the service setter configured!"); + logger.Debug("No external service configurator provided. Using built-in CQRS runtime registration only."); services.Container.ExecuteServicesHook(configurator); } @@ -115,4 +120,4 @@ internal sealed class ArchitectureBootstrapper( { await services.ModuleManager.InitializeAllAsync(asyncMode); } -} \ No newline at end of file +} diff --git a/GFramework.Core/Architectures/ArchitectureContext.cs b/GFramework.Core/Architectures/ArchitectureContext.cs index 1e3d72a7..77c04fcf 100644 --- a/GFramework.Core/Architectures/ArchitectureContext.cs +++ b/GFramework.Core/Architectures/ArchitectureContext.cs @@ -1,14 +1,19 @@ using System.Collections.Concurrent; using GFramework.Core.Abstractions.Architectures; using GFramework.Core.Abstractions.Command; +using GFramework.Core.Abstractions.Cqrs; +using GFramework.Core.Abstractions.Cqrs.Command; +using GFramework.Core.Abstractions.Cqrs.Query; using GFramework.Core.Abstractions.Environment; using GFramework.Core.Abstractions.Events; using GFramework.Core.Abstractions.Ioc; +using GFramework.Core.Abstractions.Logging; using GFramework.Core.Abstractions.Model; using GFramework.Core.Abstractions.Query; using GFramework.Core.Abstractions.Systems; using GFramework.Core.Abstractions.Utility; -using Mediator; +using GFramework.Core.Cqrs.Internal; +using GFramework.Core.Logging; using ICommand = GFramework.Core.Abstractions.Command.ICommand; namespace GFramework.Core.Architectures; @@ -20,23 +25,15 @@ public class ArchitectureContext(IIocContainer container) : IArchitectureContext { private readonly IIocContainer _container = container ?? throw new ArgumentNullException(nameof(container)); private readonly ConcurrentDictionary _serviceCache = new(); + private readonly ILogger _logger = LoggerFactoryResolver.Provider.CreateLogger(nameof(ArchitectureContext)); + private CqrsDispatcher? _cqrsDispatcher; - #region Mediator Integration + #region CQRS Integration /// - /// 获取 Mediator 实例(延迟加载) + /// 获取 CQRS 运行时分发器(延迟初始化)。 /// - private IMediator Mediator => GetOrCache(); - - /// - /// 获取 ISender 实例(更轻量的发送器) - /// - private ISender Sender => GetOrCache(); - - /// - /// 获取 IPublisher 实例(用于发布通知) - /// - private IPublisher Publisher => GetOrCache(); + private CqrsDispatcher CqrsDispatcher => _cqrsDispatcher ??= new CqrsDispatcher(_container, this, _logger); /// /// 获取指定类型的服务实例,如果缓存中存在则直接返回,否则从容器中获取并缓存 @@ -64,30 +61,23 @@ public class ArchitectureContext(IIocContainer container) : IArchitectureContext } /// - /// [Mediator] 发送请求(Command/Query) - /// 这是推荐的新方式,统一处理命令和查询 + /// 发送请求(Command/Query) + /// 使用 GFramework 自有 CQRS runtime 统一处理命令和查询。 /// /// 响应类型 /// 请求对象(Command 或 Query) /// 取消令牌 /// 响应结果 - /// 当 Mediator 未注册时抛出 public async ValueTask SendRequestAsync( IRequest request, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(request); - - var mediator = Mediator; - if (mediator == null) - throw new InvalidOperationException( - "Mediator not registered. Call EnableMediator() in your Architecture.OnInitialize() method."); - - return await mediator.Send(request, cancellationToken); + return await CqrsDispatcher.SendAsync(request, cancellationToken); } /// - /// [Mediator] 发送请求的同步版本(不推荐,仅用于兼容性) + /// 发送请求的同步版本(不推荐,仅用于兼容性) /// /// 响应类型 /// 请求对象 @@ -98,8 +88,8 @@ public class ArchitectureContext(IIocContainer container) : IArchitectureContext } /// - /// [Mediator] 发布通知(一对多) - /// 用于事件驱动场景,多个处理器可以同时处理同一个通知 + /// 发布通知(一对多) + /// 使用 GFramework 自有 CQRS runtime 分发到所有已注册通知处理器。 /// /// 通知类型 /// 通知对象 @@ -110,16 +100,11 @@ public class ArchitectureContext(IIocContainer container) : IArchitectureContext where TNotification : INotification { ArgumentNullException.ThrowIfNull(notification); - - var publisher = Publisher; - if (publisher == null) - throw new InvalidOperationException("Publisher not registered."); - - await publisher.Publish(notification, cancellationToken); + await CqrsDispatcher.PublishAsync(notification, cancellationToken); } /// - /// [Mediator] 发送请求并返回流(用于大数据集) + /// 发送请求并返回流(用于大数据集) /// /// 响应项类型 /// 流式请求 @@ -130,12 +115,7 @@ public class ArchitectureContext(IIocContainer container) : IArchitectureContext CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(request); - - var mediator = Mediator; - if (mediator == null) - throw new InvalidOperationException("Mediator not registered."); - - return mediator.CreateStream(request, cancellationToken); + return CqrsDispatcher.CreateStream(request, cancellationToken); } /// @@ -180,12 +160,12 @@ public class ArchitectureContext(IIocContainer container) : IArchitectureContext } /// - /// [Mediator] 发送查询的同步版本(不推荐,仅用于兼容性) + /// 发送 CQRS 查询的同步版本(不推荐,仅用于兼容性) /// /// 查询响应类型 /// 要发送的查询对象 /// 查询结果 - public TResponse SendQuery(Mediator.IQuery query) + public TResponse SendQuery(GFramework.Core.Abstractions.Cqrs.Query.IQuery query) { return SendQueryAsync(query).AsTask().GetAwaiter().GetResult(); } @@ -205,23 +185,17 @@ public class ArchitectureContext(IIocContainer container) : IArchitectureContext } /// - /// [Mediator] 异步发送查询并返回结果 - /// 通过Mediator模式发送查询请求,支持取消操作 + /// 异步发送 CQRS 查询并返回结果。 /// /// 查询响应类型 /// 要发送的查询对象 /// 取消令牌,用于取消操作 /// 包含查询结果的ValueTask - public async ValueTask SendQueryAsync(Mediator.IQuery query, + public async ValueTask SendQueryAsync(GFramework.Core.Abstractions.Cqrs.Query.IQuery query, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(query); - - var sender = Sender; - if (sender == null) - throw new InvalidOperationException("Sender not registered."); - - return await sender.Send(query, cancellationToken); + return await SendRequestAsync(query, cancellationToken); } #endregion @@ -347,23 +321,17 @@ public class ArchitectureContext(IIocContainer container) : IArchitectureContext #region Command Execution /// - /// [Mediator] 异步发送命令并返回结果 - /// 通过Mediator模式发送命令请求,支持取消操作 + /// 异步发送 CQRS 命令并返回结果。 /// /// 命令响应类型 /// 要发送的命令对象 /// 取消令牌,用于取消操作 /// 包含命令执行结果的ValueTask - public async ValueTask SendCommandAsync(Mediator.ICommand command, + public async ValueTask SendCommandAsync(GFramework.Core.Abstractions.Cqrs.Command.ICommand command, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(command); - - var sender = Sender; - if (sender == null) - throw new InvalidOperationException("Sender not registered."); - - return await sender.Send(command, cancellationToken); + return await SendRequestAsync(command, cancellationToken); } /// @@ -393,12 +361,12 @@ public class ArchitectureContext(IIocContainer container) : IArchitectureContext } /// - /// [Mediator] 发送命令的同步版本(不推荐,仅用于兼容性) + /// 发送 CQRS 命令的同步版本(不推荐,仅用于兼容性) /// /// 命令响应类型 /// 要发送的命令对象 /// 命令执行结果 - public TResponse SendCommand(Mediator.ICommand command) + public TResponse SendCommand(GFramework.Core.Abstractions.Cqrs.Command.ICommand command) { return SendCommandAsync(command).AsTask().GetAwaiter().GetResult(); } @@ -491,4 +459,4 @@ public class ArchitectureContext(IIocContainer container) : IArchitectureContext } #endregion -} \ No newline at end of file +} diff --git a/GFramework.Core/Command/AbstractCommandWithInput.cs b/GFramework.Core/Command/AbstractCommandWithInput.cs index 5326fe6e..7512c760 100644 --- a/GFramework.Core/Command/AbstractCommandWithInput.cs +++ b/GFramework.Core/Command/AbstractCommandWithInput.cs @@ -9,13 +9,13 @@ namespace GFramework.Core.Command; /// /// 命令输入参数类型,必须实现 ICommandInput 接口 /// 命令执行所需的输入参数 -public abstract class AbstractCommand(TInput input) : ContextAwareBase, ICommand +public abstract class AbstractCommand(TInput input) : ContextAwareBase, GFramework.Core.Abstractions.Command.ICommand where TInput : ICommandInput { /// /// 执行命令的入口方法,实现 ICommand 接口的 Execute 方法 /// - void ICommand.Execute() + void GFramework.Core.Abstractions.Command.ICommand.Execute() { OnExecute(input); } @@ -25,4 +25,4 @@ public abstract class AbstractCommand(TInput input) : ContextAwareBase, /// /// 命令执行所需的输入参数 protected abstract void OnExecute(TInput input); -} \ No newline at end of file +} diff --git a/GFramework.Core/Command/AbstractCommandWithResult.cs b/GFramework.Core/Command/AbstractCommandWithResult.cs index 67901821..7ecc9522 100644 --- a/GFramework.Core/Command/AbstractCommandWithResult.cs +++ b/GFramework.Core/Command/AbstractCommandWithResult.cs @@ -10,14 +10,14 @@ namespace GFramework.Core.Command; /// 命令输入参数类型,必须实现 ICommandInput 接口 /// 命令执行后返回的结果类型 /// 命令执行所需的输入参数 -public abstract class AbstractCommand(TInput input) : ContextAwareBase, ICommand +public abstract class AbstractCommand(TInput input) : ContextAwareBase, GFramework.Core.Abstractions.Command.ICommand where TInput : ICommandInput { /// /// 执行命令的入口方法,实现 ICommand{TResult} 接口的 Execute 方法 /// /// 命令执行后的结果 - TResult ICommand.Execute() + TResult GFramework.Core.Abstractions.Command.ICommand.Execute() { return OnExecute(input); } @@ -28,4 +28,4 @@ public abstract class AbstractCommand(TInput input) : ContextAw /// 命令执行所需的输入参数 /// 命令执行后的结果 protected abstract TResult OnExecute(TInput input); -} \ No newline at end of file +} diff --git a/GFramework.Core/Cqrs/Behaviors/LoggingBehavior.cs b/GFramework.Core/Cqrs/Behaviors/LoggingBehavior.cs index 4a56c775..4aaf797a 100644 --- a/GFramework.Core/Cqrs/Behaviors/LoggingBehavior.cs +++ b/GFramework.Core/Cqrs/Behaviors/LoggingBehavior.cs @@ -12,9 +12,9 @@ // limitations under the License. using System.Diagnostics; +using GFramework.Core.Abstractions.Cqrs; using GFramework.Core.Abstractions.Logging; using GFramework.Core.Logging; -using Mediator; namespace GFramework.Core.Cqrs.Behaviors; @@ -69,4 +69,4 @@ public sealed class LoggingBehavior : IPipelineBehavior : IPipelineBehavior } } } -} \ No newline at end of file +} diff --git a/GFramework.Core/Cqrs/Command/AbstractCommandHandler.cs b/GFramework.Core/Cqrs/Command/AbstractCommandHandler.cs index 8f7b6112..0ffc7424 100644 --- a/GFramework.Core/Cqrs/Command/AbstractCommandHandler.cs +++ b/GFramework.Core/Cqrs/Command/AbstractCommandHandler.cs @@ -12,7 +12,8 @@ // limitations under the License. using GFramework.Core.Rule; -using Mediator; +using GFramework.Core.Abstractions.Cqrs; +using GFramework.Core.Abstractions.Cqrs.Command; namespace GFramework.Core.Cqrs.Command; @@ -21,7 +22,7 @@ namespace GFramework.Core.Cqrs.Command; /// 继承自ContextAwareBase并实现ICommandHandler接口,为具体的命令处理器提供基础功能 /// /// 命令类型 -public abstract class AbstractCommandHandler : ContextAwareBase, ICommandHandler +public abstract class AbstractCommandHandler : ContextAwareBase, IRequestHandler where TCommand : ICommand { /// @@ -41,7 +42,7 @@ public abstract class AbstractCommandHandler : ContextAwareBase, IComm /// /// 命令类型,必须实现ICommand接口 /// 命令执行结果类型 -public abstract class AbstractCommandHandler : ContextAwareBase, ICommandHandler +public abstract class AbstractCommandHandler : ContextAwareBase, IRequestHandler where TCommand : ICommand { /// @@ -52,4 +53,4 @@ public abstract class AbstractCommandHandler : ContextAwareBa /// 取消令牌,用于取消操作 /// 表示异步操作完成的ValueTask,包含命令执行结果 public abstract ValueTask Handle(TCommand command, CancellationToken cancellationToken); -} \ No newline at end of file +} diff --git a/GFramework.Core/Cqrs/Command/AbstractStreamCommandHandler.cs b/GFramework.Core/Cqrs/Command/AbstractStreamCommandHandler.cs index 064cb279..0f86f36c 100644 --- a/GFramework.Core/Cqrs/Command/AbstractStreamCommandHandler.cs +++ b/GFramework.Core/Cqrs/Command/AbstractStreamCommandHandler.cs @@ -12,7 +12,8 @@ // limitations under the License. using GFramework.Core.Rule; -using Mediator; +using GFramework.Core.Abstractions.Cqrs; +using GFramework.Core.Abstractions.Cqrs.Command; namespace GFramework.Core.Cqrs.Command; @@ -24,7 +25,7 @@ namespace GFramework.Core.Cqrs.Command; /// 流式命令类型,必须实现IStreamCommand接口 /// 流式命令响应元素类型 public abstract class AbstractStreamCommandHandler : ContextAwareBase, - IStreamCommandHandler + IStreamRequestHandler where TCommand : IStreamCommand { /// @@ -35,4 +36,4 @@ public abstract class AbstractStreamCommandHandler : Contex /// 取消令牌,用于取消流式处理操作 /// 异步可枚举的响应序列,每个元素类型为TResponse public abstract IAsyncEnumerable Handle(TCommand command, CancellationToken cancellationToken); -} \ No newline at end of file +} diff --git a/GFramework.Core/Cqrs/Command/CommandBase.cs b/GFramework.Core/Cqrs/Command/CommandBase.cs index ba0e56ad..78fa134e 100644 --- a/GFramework.Core/Cqrs/Command/CommandBase.cs +++ b/GFramework.Core/Cqrs/Command/CommandBase.cs @@ -12,7 +12,6 @@ // limitations under the License. using GFramework.Core.Abstractions.Cqrs.Command; -using Mediator; namespace GFramework.Core.Cqrs.Command; @@ -29,4 +28,4 @@ public abstract class CommandBase(TInput input) : ICommand public TInput Input => input; -} \ No newline at end of file +} diff --git a/GFramework.Core/Cqrs/Internal/CqrsDispatcher.cs b/GFramework.Core/Cqrs/Internal/CqrsDispatcher.cs new file mode 100644 index 00000000..69f6794d --- /dev/null +++ b/GFramework.Core/Cqrs/Internal/CqrsDispatcher.cs @@ -0,0 +1,263 @@ +using System.Collections.Concurrent; +using System.Reflection; +using GFramework.Core.Abstractions.Architectures; +using GFramework.Core.Abstractions.Cqrs; +using GFramework.Core.Abstractions.Ioc; +using GFramework.Core.Abstractions.Logging; +using GFramework.Core.Abstractions.Rule; + +namespace GFramework.Core.Cqrs.Internal; + +/// +/// GFramework 自有 CQRS 运行时分发器。 +/// 该类型负责解析请求/通知处理器,并在调用前为上下文感知对象注入当前架构上下文。 +/// +internal sealed class CqrsDispatcher( + IIocContainer container, + IArchitectureContext context, + ILogger logger) +{ + private delegate ValueTask RequestInvoker(object handler, object request, CancellationToken cancellationToken); + private delegate ValueTask RequestPipelineInvoker( + object handler, + IReadOnlyList behaviors, + object request, + CancellationToken cancellationToken); + private delegate ValueTask NotificationInvoker(object handler, object notification, CancellationToken cancellationToken); + private delegate object StreamInvoker(object handler, object request, CancellationToken cancellationToken); + + private static readonly ConcurrentDictionary<(Type RequestType, Type ResponseType), RequestInvoker> RequestInvokers = new(); + private static readonly ConcurrentDictionary<(Type RequestType, Type ResponseType), RequestPipelineInvoker> RequestPipelineInvokers = new(); + private static readonly ConcurrentDictionary NotificationInvokers = new(); + private static readonly ConcurrentDictionary<(Type RequestType, Type ResponseType), StreamInvoker> StreamInvokers = new(); + + /// + /// 发送请求并返回结果。 + /// + /// 响应类型。 + /// 请求对象。 + /// 取消令牌。 + /// 请求响应。 + public async ValueTask SendAsync( + IRequest request, + CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(request); + + var requestType = request.GetType(); + var handlerType = typeof(IRequestHandler<,>).MakeGenericType(requestType, typeof(TResponse)); + var handler = container.Get(handlerType) + ?? throw new InvalidOperationException( + $"No CQRS request handler registered for {requestType.FullName}."); + + PrepareHandler(handler); + var behaviorType = typeof(IPipelineBehavior<,>).MakeGenericType(requestType, typeof(TResponse)); + var behaviors = container.GetAll(behaviorType); + + foreach (var behavior in behaviors) + PrepareHandler(behavior); + + if (behaviors.Count == 0) + { + var invoker = RequestInvokers.GetOrAdd( + (requestType, typeof(TResponse)), + static key => CreateRequestInvoker(key.RequestType, key.ResponseType)); + + var result = await invoker(handler, request, cancellationToken); + return result is null ? default! : (TResponse)result; + } + + var pipelineInvoker = RequestPipelineInvokers.GetOrAdd( + (requestType, typeof(TResponse)), + static key => CreateRequestPipelineInvoker(key.RequestType, key.ResponseType)); + + var pipelineResult = await pipelineInvoker(handler, behaviors, request, cancellationToken); + return pipelineResult is null ? default! : (TResponse)pipelineResult; + } + + /// + /// 发布通知到所有已注册处理器。 + /// + /// 通知类型。 + /// 通知对象。 + /// 取消令牌。 + public async ValueTask PublishAsync( + TNotification notification, + CancellationToken cancellationToken = default) + where TNotification : INotification + { + ArgumentNullException.ThrowIfNull(notification); + + var notificationType = notification.GetType(); + var handlerType = typeof(INotificationHandler<>).MakeGenericType(notificationType); + var handlers = container.GetAll(handlerType); + + if (handlers.Count == 0) + { + logger.Debug($"No CQRS notification handler registered for {notificationType.FullName}."); + return; + } + + var invoker = NotificationInvokers.GetOrAdd( + notificationType, + CreateNotificationInvoker); + + foreach (var handler in handlers) + { + PrepareHandler(handler); + await invoker(handler, notification, cancellationToken); + } + } + + /// + /// 创建流式请求并返回异步响应序列。 + /// + /// 响应元素类型。 + /// 流式请求对象。 + /// 取消令牌。 + /// 异步响应序列。 + public IAsyncEnumerable CreateStream( + IStreamRequest request, + CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(request); + + var requestType = request.GetType(); + var handlerType = typeof(IStreamRequestHandler<,>).MakeGenericType(requestType, typeof(TResponse)); + var handler = container.Get(handlerType) + ?? throw new InvalidOperationException( + $"No CQRS stream handler registered for {requestType.FullName}."); + + PrepareHandler(handler); + + var invoker = StreamInvokers.GetOrAdd( + (requestType, typeof(TResponse)), + static key => CreateStreamInvoker(key.RequestType, key.ResponseType)); + + return (IAsyncEnumerable)invoker(handler, request, cancellationToken); + } + + /// + /// 为上下文感知处理器注入当前架构上下文。 + /// + /// 处理器实例。 + private void PrepareHandler(object handler) + { + if (handler is IContextAware contextAware) + contextAware.SetContext(context); + } + + /// + /// 生成请求处理器调用委托,避免每次发送都重复反射。 + /// + private static RequestInvoker CreateRequestInvoker(Type requestType, Type responseType) + { + var method = typeof(CqrsDispatcher) + .GetMethod(nameof(InvokeRequestHandlerAsync), BindingFlags.NonPublic | BindingFlags.Static)! + .MakeGenericMethod(requestType, responseType); + return (RequestInvoker)Delegate.CreateDelegate(typeof(RequestInvoker), method); + } + + /// + /// 生成带管道行为的请求处理委托,避免每次发送都重复反射。 + /// + private static RequestPipelineInvoker CreateRequestPipelineInvoker(Type requestType, Type responseType) + { + var method = typeof(CqrsDispatcher) + .GetMethod(nameof(InvokeRequestPipelineAsync), BindingFlags.NonPublic | BindingFlags.Static)! + .MakeGenericMethod(requestType, responseType); + return (RequestPipelineInvoker)Delegate.CreateDelegate(typeof(RequestPipelineInvoker), method); + } + + /// + /// 生成通知处理器调用委托,避免每次发布都重复反射。 + /// + private static NotificationInvoker CreateNotificationInvoker(Type notificationType) + { + var method = typeof(CqrsDispatcher) + .GetMethod(nameof(InvokeNotificationHandlerAsync), BindingFlags.NonPublic | BindingFlags.Static)! + .MakeGenericMethod(notificationType); + return (NotificationInvoker)Delegate.CreateDelegate(typeof(NotificationInvoker), method); + } + + /// + /// 生成流式处理器调用委托,避免每次创建流都重复反射。 + /// + private static StreamInvoker CreateStreamInvoker(Type requestType, Type responseType) + { + var method = typeof(CqrsDispatcher) + .GetMethod(nameof(InvokeStreamHandler), BindingFlags.NonPublic | BindingFlags.Static)! + .MakeGenericMethod(requestType, responseType); + return (StreamInvoker)Delegate.CreateDelegate(typeof(StreamInvoker), method); + } + + /// + /// 执行已强类型化的请求处理器调用。 + /// + private static async ValueTask InvokeRequestHandlerAsync( + object handler, + object request, + CancellationToken cancellationToken) + where TRequest : IRequest + { + var typedHandler = (IRequestHandler)handler; + var typedRequest = (TRequest)request; + var result = await typedHandler.Handle(typedRequest, cancellationToken); + return result; + } + + /// + /// 执行包含管道行为链的请求处理。 + /// + private static async ValueTask InvokeRequestPipelineAsync( + object handler, + IReadOnlyList behaviors, + object request, + CancellationToken cancellationToken) + where TRequest : IRequest + { + var typedHandler = (IRequestHandler)handler; + var typedRequest = (TRequest)request; + + MessageHandlerDelegate next = + (message, token) => typedHandler.Handle(message, token); + + for (var i = behaviors.Count - 1; i >= 0; i--) + { + var behavior = (IPipelineBehavior)behaviors[i]; + var currentNext = next; + next = (message, token) => behavior.Handle(message, currentNext, token); + } + + var result = await next(typedRequest, cancellationToken); + return result; + } + + /// + /// 执行已强类型化的通知处理器调用。 + /// + private static ValueTask InvokeNotificationHandlerAsync( + object handler, + object notification, + CancellationToken cancellationToken) + where TNotification : INotification + { + var typedHandler = (INotificationHandler)handler; + var typedNotification = (TNotification)notification; + return typedHandler.Handle(typedNotification, cancellationToken); + } + + /// + /// 执行已强类型化的流式处理器调用。 + /// + private static object InvokeStreamHandler( + object handler, + object request, + CancellationToken cancellationToken) + where TRequest : IStreamRequest + { + var typedHandler = (IStreamRequestHandler)handler; + var typedRequest = (TRequest)request; + return typedHandler.Handle(typedRequest, cancellationToken); + } +} diff --git a/GFramework.Core/Cqrs/Internal/CqrsHandlerRegistrar.cs b/GFramework.Core/Cqrs/Internal/CqrsHandlerRegistrar.cs new file mode 100644 index 00000000..9f69bc33 --- /dev/null +++ b/GFramework.Core/Cqrs/Internal/CqrsHandlerRegistrar.cs @@ -0,0 +1,81 @@ +using System.Reflection; +using GFramework.Core.Abstractions.Cqrs; +using GFramework.Core.Abstractions.Ioc; +using GFramework.Core.Abstractions.Logging; +using Microsoft.Extensions.DependencyInjection; + +namespace GFramework.Core.Cqrs.Internal; + +/// +/// 在架构初始化期间扫描并注册 CQRS 处理器。 +/// 首批实现采用运行时反射扫描,优先满足“无需 AddMediator 即可工作”的迁移目标。 +/// +internal static class CqrsHandlerRegistrar +{ + /// + /// 扫描指定程序集并注册所有 CQRS 请求/通知/流式处理器。 + /// + /// 目标容器。 + /// 要扫描的程序集集合。 + /// 日志记录器。 + public static void RegisterHandlers( + IIocContainer container, + IEnumerable assemblies, + ILogger logger) + { + ArgumentNullException.ThrowIfNull(container); + ArgumentNullException.ThrowIfNull(assemblies); + ArgumentNullException.ThrowIfNull(logger); + + foreach (var assembly in assemblies.Distinct()) + { + RegisterAssemblyHandlers(container.GetServicesUnsafe, assembly, logger); + } + } + + /// + /// 注册单个程序集里的所有 CQRS 处理器映射。 + /// + private static void RegisterAssemblyHandlers(IServiceCollection services, Assembly assembly, ILogger logger) + { + foreach (var implementationType in assembly.GetTypes().Where(IsConcreteHandlerType)) + { + var handlerInterfaces = implementationType + .GetInterfaces() + .Where(IsSupportedHandlerInterface) + .ToList(); + + if (handlerInterfaces.Count == 0) + continue; + + foreach (var handlerInterface in handlerInterfaces) + { + services.AddSingleton(handlerInterface, implementationType); + logger.Debug( + $"Registered CQRS handler {implementationType.FullName} as {handlerInterface.FullName}."); + } + } + } + + /// + /// 判断指定类型是否可作为可实例化处理器。 + /// + private static bool IsConcreteHandlerType(Type type) + { + return type is { IsAbstract: false, IsInterface: false } && !type.ContainsGenericParameters; + } + + /// + /// 判断接口是否为当前运行时支持的 CQRS 处理器接口。 + /// + private static bool IsSupportedHandlerInterface(Type type) + { + if (!type.IsGenericType) + return false; + + var definition = type.GetGenericTypeDefinition(); + return definition == typeof(IRequestHandler<,>) || + definition == typeof(INotificationHandler<>) || + definition == typeof(IStreamRequestHandler<,>); + } +} diff --git a/GFramework.Core/Cqrs/Notification/AbstractNotificationHandler.cs b/GFramework.Core/Cqrs/Notification/AbstractNotificationHandler.cs index de4772fb..1b1157ab 100644 --- a/GFramework.Core/Cqrs/Notification/AbstractNotificationHandler.cs +++ b/GFramework.Core/Cqrs/Notification/AbstractNotificationHandler.cs @@ -12,7 +12,7 @@ // limitations under the License. using GFramework.Core.Rule; -using Mediator; +using GFramework.Core.Abstractions.Cqrs; namespace GFramework.Core.Cqrs.Notification; @@ -33,4 +33,4 @@ public abstract class AbstractNotificationHandler : ContextAwareB /// 取消令牌,用于取消操作 /// 表示异步操作完成的ValueTask public abstract ValueTask Handle(TNotification notification, CancellationToken cancellationToken); -} \ No newline at end of file +} diff --git a/GFramework.Core/Cqrs/Notification/NotificationBase.cs b/GFramework.Core/Cqrs/Notification/NotificationBase.cs index 96e26ea1..f04488b9 100644 --- a/GFramework.Core/Cqrs/Notification/NotificationBase.cs +++ b/GFramework.Core/Cqrs/Notification/NotificationBase.cs @@ -12,7 +12,7 @@ // limitations under the License. using GFramework.Core.Abstractions.Cqrs.Notification; -using Mediator; +using GFramework.Core.Abstractions.Cqrs; namespace GFramework.Core.Cqrs.Notification; @@ -28,4 +28,4 @@ public abstract class NotificationBase(TInput input) : INotification whe /// 获取通知的输入数据。 /// public TInput Input => input; -} \ No newline at end of file +} diff --git a/GFramework.Core/Cqrs/Query/AbstractQueryHandler.cs b/GFramework.Core/Cqrs/Query/AbstractQueryHandler.cs index 4ce887cf..f861312c 100644 --- a/GFramework.Core/Cqrs/Query/AbstractQueryHandler.cs +++ b/GFramework.Core/Cqrs/Query/AbstractQueryHandler.cs @@ -12,7 +12,8 @@ // limitations under the License. using GFramework.Core.Rule; -using Mediator; +using GFramework.Core.Abstractions.Cqrs; +using GFramework.Core.Abstractions.Cqrs.Query; namespace GFramework.Core.Cqrs.Query; @@ -23,7 +24,7 @@ namespace GFramework.Core.Cqrs.Query; /// /// 查询类型,必须实现IQuery接口 /// 查询结果类型 -public abstract class AbstractQueryHandler : ContextAwareBase, IQueryHandler +public abstract class AbstractQueryHandler : ContextAwareBase, IRequestHandler where TQuery : IQuery { /// @@ -34,4 +35,4 @@ public abstract class AbstractQueryHandler : ContextAwareBase, /// 取消令牌,用于取消操作 /// 表示异步操作完成的ValueTask,包含查询结果 public abstract ValueTask Handle(TQuery query, CancellationToken cancellationToken); -} \ No newline at end of file +} diff --git a/GFramework.Core/Cqrs/Query/AbstractStreamQueryHandler.cs b/GFramework.Core/Cqrs/Query/AbstractStreamQueryHandler.cs index 50cf1817..015da1da 100644 --- a/GFramework.Core/Cqrs/Query/AbstractStreamQueryHandler.cs +++ b/GFramework.Core/Cqrs/Query/AbstractStreamQueryHandler.cs @@ -12,7 +12,8 @@ // limitations under the License. using GFramework.Core.Rule; -using Mediator; +using GFramework.Core.Abstractions.Cqrs; +using GFramework.Core.Abstractions.Cqrs.Query; namespace GFramework.Core.Cqrs.Query; @@ -24,7 +25,7 @@ namespace GFramework.Core.Cqrs.Query; /// 流式查询类型,必须实现IStreamQuery接口 /// 流式查询响应元素类型 public abstract class AbstractStreamQueryHandler : ContextAwareBase, - IStreamQueryHandler + IStreamRequestHandler where TQuery : IStreamQuery { /// @@ -35,4 +36,4 @@ public abstract class AbstractStreamQueryHandler : ContextAwa /// 取消令牌,用于取消流式查询操作 /// 异步可枚举的响应序列,每个元素类型为TResponse public abstract IAsyncEnumerable Handle(TQuery query, CancellationToken cancellationToken); -} \ No newline at end of file +} diff --git a/GFramework.Core/Cqrs/Query/QueryBase.cs b/GFramework.Core/Cqrs/Query/QueryBase.cs index 2ef0f34b..cb0d4782 100644 --- a/GFramework.Core/Cqrs/Query/QueryBase.cs +++ b/GFramework.Core/Cqrs/Query/QueryBase.cs @@ -12,7 +12,6 @@ // limitations under the License. using GFramework.Core.Abstractions.Cqrs.Query; -using Mediator; namespace GFramework.Core.Cqrs.Query; @@ -29,4 +28,4 @@ public abstract class QueryBase(TInput input) : IQuery public TInput Input => input; -} \ No newline at end of file +} diff --git a/GFramework.Core/Cqrs/Request/AbstractRequestHandler.cs b/GFramework.Core/Cqrs/Request/AbstractRequestHandler.cs index 88e9efad..4ef6a270 100644 --- a/GFramework.Core/Cqrs/Request/AbstractRequestHandler.cs +++ b/GFramework.Core/Cqrs/Request/AbstractRequestHandler.cs @@ -12,7 +12,7 @@ // limitations under the License. using GFramework.Core.Rule; -using Mediator; +using GFramework.Core.Abstractions.Cqrs; namespace GFramework.Core.Cqrs.Request; @@ -21,7 +21,7 @@ namespace GFramework.Core.Cqrs.Request; /// 继承自ContextAwareBase并实现IRequestHandler接口 /// /// 请求类型,必须实现IRequest[Unit]接口 -public abstract class AbstractRequestHandler : ContextAwareBase, IRequestHandler +public abstract class AbstractRequestHandler : ContextAwareBase, IRequestHandler where TRequest : IRequest { /// @@ -49,4 +49,4 @@ public abstract class AbstractRequestHandler : ContextAware /// 取消令牌,用于取消操作 /// 表示异步操作的ValueTask,完成时返回处理结果 public abstract ValueTask Handle(TRequest request, CancellationToken cancellationToken); -} \ No newline at end of file +} diff --git a/GFramework.Core/Cqrs/Request/AbstractStreamRequestHandler.cs b/GFramework.Core/Cqrs/Request/AbstractStreamRequestHandler.cs index a6151b49..a15ed5d7 100644 --- a/GFramework.Core/Cqrs/Request/AbstractStreamRequestHandler.cs +++ b/GFramework.Core/Cqrs/Request/AbstractStreamRequestHandler.cs @@ -12,7 +12,7 @@ // limitations under the License. using GFramework.Core.Rule; -using Mediator; +using GFramework.Core.Abstractions.Cqrs; namespace GFramework.Core.Cqrs.Request; @@ -35,4 +35,4 @@ public abstract class AbstractStreamRequestHandler : Contex /// 取消令牌,用于取消流式请求操作 /// 异步可枚举的响应序列,每个元素类型为TResponse public abstract IAsyncEnumerable Handle(TRequest request, CancellationToken cancellationToken); -} \ No newline at end of file +} diff --git a/GFramework.Core/Cqrs/Request/RequestBase.cs b/GFramework.Core/Cqrs/Request/RequestBase.cs index 8b878750..ce85784f 100644 --- a/GFramework.Core/Cqrs/Request/RequestBase.cs +++ b/GFramework.Core/Cqrs/Request/RequestBase.cs @@ -12,7 +12,7 @@ // limitations under the License. using GFramework.Core.Abstractions.Cqrs.Request; -using Mediator; +using GFramework.Core.Abstractions.Cqrs; namespace GFramework.Core.Cqrs.Request; @@ -29,4 +29,4 @@ public abstract class RequestBase(TInput input) : IRequest public TInput Input => input; -} \ No newline at end of file +} diff --git a/GFramework.Core/Extensions/ContextAwareMediatorCommandExtensions.cs b/GFramework.Core/Extensions/ContextAwareMediatorCommandExtensions.cs index 6daccd26..881741e7 100644 --- a/GFramework.Core/Extensions/ContextAwareMediatorCommandExtensions.cs +++ b/GFramework.Core/Extensions/ContextAwareMediatorCommandExtensions.cs @@ -1,16 +1,15 @@ using GFramework.Core.Abstractions.Rule; -using Mediator; +using GFramework.Core.Abstractions.Cqrs.Command; namespace GFramework.Core.Extensions; /// -/// 提供对 IContextAware 接口的 Mediator 命令扩展方法 -/// 使用 Mediator 库的命令模式 +/// 提供对 IContextAware 接口的 CQRS 命令扩展方法。 /// public static class ContextAwareMediatorCommandExtensions { /// - /// [Mediator] 发送命令的同步版本(不推荐,仅用于兼容性) + /// 发送命令的同步版本(不推荐,仅用于兼容性) /// /// 命令响应类型 /// 实现 IContextAware 接口的对象 @@ -28,7 +27,7 @@ public static class ContextAwareMediatorCommandExtensions } /// - /// [Mediator] 异步发送命令并返回结果 + /// 异步发送命令并返回结果 /// /// 命令响应类型 /// 实现 IContextAware 接口的对象 @@ -45,4 +44,4 @@ public static class ContextAwareMediatorCommandExtensions var context = contextAware.GetContext(); return context.SendCommandAsync(command, cancellationToken); } -} \ No newline at end of file +} diff --git a/GFramework.Core/Extensions/ContextAwareMediatorExtensions.cs b/GFramework.Core/Extensions/ContextAwareMediatorExtensions.cs index fa0d699d..1661d87b 100644 --- a/GFramework.Core/Extensions/ContextAwareMediatorExtensions.cs +++ b/GFramework.Core/Extensions/ContextAwareMediatorExtensions.cs @@ -1,10 +1,10 @@ using GFramework.Core.Abstractions.Rule; -using Mediator; +using GFramework.Core.Abstractions.Cqrs; namespace GFramework.Core.Extensions; /// -/// 提供对 IContextAware 接口的 Mediator 统一接口扩展方法 +/// 提供对 IContextAware 接口的 CQRS 统一接口扩展方法。 /// public static class ContextAwareMediatorExtensions { @@ -122,4 +122,4 @@ public static class ContextAwareMediatorExtensions var context = contextAware.GetContext(); return context.SendAsync(command, cancellationToken); } -} \ No newline at end of file +} diff --git a/GFramework.Core/Extensions/ContextAwareMediatorQueryExtensions.cs b/GFramework.Core/Extensions/ContextAwareMediatorQueryExtensions.cs index cbdb01b4..5a4bfeb6 100644 --- a/GFramework.Core/Extensions/ContextAwareMediatorQueryExtensions.cs +++ b/GFramework.Core/Extensions/ContextAwareMediatorQueryExtensions.cs @@ -1,16 +1,15 @@ using GFramework.Core.Abstractions.Rule; -using Mediator; +using GFramework.Core.Abstractions.Cqrs.Query; namespace GFramework.Core.Extensions; /// -/// 提供对 IContextAware 接口的 Mediator 查询扩展方法 -/// 使用 Mediator 库的查询模式 +/// 提供对 IContextAware 接口的 CQRS 查询扩展方法。 /// public static class ContextAwareMediatorQueryExtensions { /// - /// [Mediator] 发送查询的同步版本(不推荐,仅用于兼容性) + /// 发送查询的同步版本(不推荐,仅用于兼容性) /// /// 查询响应类型 /// 实现 IContextAware 接口的对象 @@ -27,7 +26,7 @@ public static class ContextAwareMediatorQueryExtensions } /// - /// [Mediator] 异步发送查询并返回结果 + /// 异步发送查询并返回结果 /// /// 查询响应类型 /// 实现 IContextAware 接口的对象 @@ -44,4 +43,4 @@ public static class ContextAwareMediatorQueryExtensions var context = contextAware.GetContext(); return context.SendQueryAsync(query, cancellationToken); } -} \ No newline at end of file +} diff --git a/GFramework.Core/Ioc/MicrosoftDiContainer.cs b/GFramework.Core/Ioc/MicrosoftDiContainer.cs index 2615ca52..dec4f267 100644 --- a/GFramework.Core/Ioc/MicrosoftDiContainer.cs +++ b/GFramework.Core/Ioc/MicrosoftDiContainer.cs @@ -1,10 +1,10 @@ using GFramework.Core.Abstractions.Bases; +using GFramework.Core.Abstractions.Cqrs; using GFramework.Core.Abstractions.Ioc; using GFramework.Core.Abstractions.Logging; using GFramework.Core.Abstractions.Systems; using GFramework.Core.Logging; using GFramework.Core.Rule; -using Mediator; using Microsoft.Extensions.DependencyInjection; namespace GFramework.Core.Ioc; @@ -804,4 +804,4 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null) } #endregion -} \ No newline at end of file +} diff --git a/GFramework.Core/Query/AbstractQueryWithResult.cs b/GFramework.Core/Query/AbstractQueryWithResult.cs index cb46071a..2c87622a 100644 --- a/GFramework.Core/Query/AbstractQueryWithResult.cs +++ b/GFramework.Core/Query/AbstractQueryWithResult.cs @@ -9,7 +9,7 @@ namespace GFramework.Core.Query; /// /// 查询输入参数的类型,必须实现IQueryInput接口 /// 查询结果的类型 -public abstract class AbstractQuery(TInput input) : ContextAwareBase, IQuery +public abstract class AbstractQuery(TInput input) : ContextAwareBase, GFramework.Core.Abstractions.Query.IQuery where TInput : IQueryInput { /// @@ -27,4 +27,4 @@ public abstract class AbstractQuery(TInput input) : ContextAwar /// 查询输入参数 /// 查询结果,类型为TResult protected abstract TResult OnDo(TInput input); -} \ No newline at end of file +} diff --git a/GFramework.Godot.SourceGenerators/GeWuYou.GFramework.Godot.SourceGenerators.targets b/GFramework.Godot.SourceGenerators/GeWuYou.GFramework.Godot.SourceGenerators.targets index 5116e4da..fdcf958e 100644 --- a/GFramework.Godot.SourceGenerators/GeWuYou.GFramework.Godot.SourceGenerators.targets +++ b/GFramework.Godot.SourceGenerators/GeWuYou.GFramework.Godot.SourceGenerators.targets @@ -18,9 +18,17 @@ - - - + + + + diff --git a/GFramework.Godot/Coroutine/ContextAwareCoroutineExtensions.cs b/GFramework.Godot/Coroutine/ContextAwareCoroutineExtensions.cs index f0ccc21b..d97d08a7 100644 --- a/GFramework.Godot/Coroutine/ContextAwareCoroutineExtensions.cs +++ b/GFramework.Godot/Coroutine/ContextAwareCoroutineExtensions.cs @@ -1,8 +1,10 @@ -using GFramework.Core.Abstractions.Rule; +using GFramework.Core.Abstractions.Cqrs; +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; -using Mediator; namespace GFramework.Godot.Coroutine; @@ -104,4 +106,4 @@ public static class ContextAwareCoroutineExtensions .ToCoroutineEnumerator() .RunCoroutine(segment, tag); } -} \ No newline at end of file +} diff --git a/local-plan/todos/cqrs-rewrite-migration-tracking.md b/local-plan/todos/cqrs-rewrite-migration-tracking.md new file mode 100644 index 00000000..55317f7c --- /dev/null +++ b/local-plan/todos/cqrs-rewrite-migration-tracking.md @@ -0,0 +1,109 @@ +# CQRS 重写迁移跟踪 + +## 目标 + +围绕 `GFramework` 当前的双轨 CQRS 现状,完成一轮以“去 Mediator 外部依赖”为目标的架构迁移: + +- 将 `Mediator` 从 GFramework 公共 API 和运行时主路径中移除 +- 基于 GFramework 自有抽象重建正式 CQRS runtime、行为管道和注册机制 +- 保留 `EventBus` 作为框架级事件系统,不与 CQRS notification 混同 +- 让 `CoreGrid-Migration` 直连本地 `GFramework`,作为真实迁移验证工程 +- 为复杂迁移建立明确恢复点与进度追踪,避免上下文过长或中断后失去状态 + +## 当前恢复点 + +- 恢复点编号:`CQRS-REWRITE-RP-002` +- 当前阶段:`Phase 4` +- 当前焦点: + - 清理剩余 `Mediator` 包依赖与文档残留 + - 评估是否继续把协程扩展和测试项目中的 `Mediator.Abstractions` 完全移除 + - 规划第二阶段优化:代码生成注册、性能收敛、行为 API 命名统一 + +## 本轮计划 + +### Phase 0:工作流基础 + +- [x] 在 `local-plan/todos/` 建立本任务跟踪文档 +- [x] 在 `local-plan/traces/` 建立本任务追踪文档 +- [x] 将恢复点 / trace / subagent 协作规范写入 `AGENTS.md` + +### Phase 1:本地验证链路 + +- [x] 确认 `CoreGrid-Migration` 当前引用形态 +- [x] 将 `CoreGrid-Migration` 从 NuGet 包切到本地 `GFramework` 工程引用 +- [x] 让 `CoreGrid-Migration` 使用本地 Source Generator 而不是外部已发布版本 +- [x] 验证本地引用链路至少能完成 restore / build + +### Phase 2:CQRS 基础重建 + +- [x] 在 `GFramework.Core.Abstractions` 定义自有 CQRS 契约 +- [x] 在 `GFramework.Core` 落地 dispatcher / handler registry / behavior pipeline +- [x] 清理 `IArchitectureContext` 中对 `Mediator.*` 的公共签名依赖 +- [x] 设计 CQRS 模块启用方式,替代 `Configurator => AddMediator(...)` + +### Phase 3:接入迁移 + +- [x] 迁移 `GFramework.Core.Cqrs.*` 基类到新契约 +- [x] 迁移 `ContextAwareMediator*Extensions` 与协程扩展 +- [x] 迁移 `CoreGrid-Migration/scripts/cqrs/**` 到新契约 +- [x] 删除 `GameArchitecture.Configurator` 中的 `AddMediator(...)` + +### Phase 4:收尾 + +- [ ] 移除 `Mediator` 包依赖与相关测试/文档残留 +- [x] 运行目标构建与测试 +- [x] 记录剩余风险与下一恢复点 + +## 当前完成结果 + +- `CoreGrid-Migration` 已直连本地 `GFramework` 源码与本地 source generators。 +- `GameArchitecture` 已不再依赖 `collection.AddMediator(...)` 即可使用 CQRS。 +- `GFramework.Core.Abstractions` 新增自有 CQRS 契约: + - `IRequest` / `INotification` / `IStreamRequest` + - `IRequestHandler<,>` / `INotificationHandler<>` / `IStreamRequestHandler<,>` + - `Unit` + - `IPipelineBehavior<,>` / `MessageHandlerDelegate<,>` +- `ArchitectureBootstrapper` 会在初始化阶段自动扫描并注册当前架构程序集与 `GFramework.Core` 程序集中的 CQRS handlers。 +- `CqrsDispatcher` 已支持: + - request dispatch + - notification publish + - stream dispatch + - context-aware handler 注入 + - request pipeline behavior 链式执行 +- `GFramework.Core.Tests` 中原依赖 `Mediator` 注册路径的测试已切换到框架内建 CQRS 注册路径。 +- 当前验证状态: + - `dotnet build GFramework/GFramework.sln` 通过 + - `dotnet test GFramework/GFramework.Core.Tests/GFramework.Core.Tests.csproj --no-build` 通过,`1621` 个测试全部通过 + - `dotnet build CoreGrid-Migration/CoreGrid.sln` 通过 + +## 当前已知事实 + +- `GFramework` 当前仍同时维护: + - 基于 `CommandExecutor` / `QueryExecutor` / `EventBus` 的轻量旧 CQRS + - 基于 GFramework 自有抽象的新 CQRS runtime +- 仍存在 `Mediator` 残留的区域主要集中在: + - 文档中的历史说明 + - `MediatorCoroutineExtensions` 及对应测试 + - 测试项目对 `Mediator.Abstractions` 的少量残余依赖 +- `CoreGrid-Migration` 已切到本地源码引用,并在当前恢复点完成构建验证 + +## 当前风险 + +- `GFramework` 仓库存在与本任务无关的既有改动,提交时必须避免覆盖 +- `CoreGrid-Migration` 是 worktree,WSL 下原生 `git` 解析该 worktree 路径有兼容问题 +- 当前 `RegisterMediatorBehavior` 命名仍保留历史前缀,但底层已切换为框架自有 CQRS pipeline;若后续要彻底脱媒介命名,需要一次 API 命名迁移 +- 当前 handler 自动注册基于运行时反射扫描;若后续追求冷启动与 AOT 友好性,需要补 source-generator 注册路径 + +## 下次恢复建议 + +若本轮中断,优先从以下顺序恢复: + +1. 查看 `local-plan/traces/cqrs-rewrite-migration-trace.md` +2. 确认当前恢复点 `CQRS-REWRITE-RP-002` 已对应到最新提交 +3. 优先决定是否继续移除 `Mediator.Abstractions` 包与 `MediatorCoroutineExtensions` 历史兼容层 +4. 若继续演进,再处理 CQRS 注册的生成器化与 API 命名统一 + +## 备注 + +- 本文档是当前任务的主恢复点,后续每个关键阶段完成后都要更新 +- 发生方向调整时,不覆盖旧结论,直接追加阶段记录与新的恢复点编号 diff --git a/local-plan/traces/cqrs-rewrite-migration-trace.md b/local-plan/traces/cqrs-rewrite-migration-trace.md new file mode 100644 index 00000000..1df972fe --- /dev/null +++ b/local-plan/traces/cqrs-rewrite-migration-trace.md @@ -0,0 +1,68 @@ +# CQRS 重写迁移追踪 + +## 2026-04-14 + +### 阶段:初始化 + +- 建立 `CQRS-REWRITE-RP-001` 恢复点 +- 已确认本次迁移目标: + - 彻底参考 `Mediator` 思路重写 GFramework 正式 CQRS + - 不保留对 `Mediator` 的兼容层 + - 使用 `abstractions + runtime 可选模块` 边界 + - 保留 `EventBus`,不与 CQRS notification 合并 + +### 已确认的实现前提 + +- `CoreGrid-Migration` 当前仍依赖 NuGet 版 `GeWuYou.GFramework*` +- `CoreGrid/scripts/core/GameArchitecture.cs` 与 `CoreGrid-Migration/scripts/core/GameArchitecture.cs` 通过 `AddMediator(...)` 启用基于生成器的 runtime +- `GFramework` 当前 `IArchitectureContext` 与一批 CQRS 基类直接引用 `Mediator.*` +- `CoreGrid/scripts/cqrs/**` 的 handler 很薄,主要迁移成本在框架 runtime 和注册机制,不在业务逻辑本身 + +### 当前动作 + +- 准备更新 `AGENTS.md`,补充恢复点 / trace / subagent 协作规范 +- 准备将 `CoreGrid-Migration` 切换为本地项目引用,建立真实验证链路 + +### 下一步 + +1. 完成 `AGENTS.md` 规则补充 +2. 改造 `CoreGrid-Migration/CoreGrid.csproj` 为本地项目与本地生成器引用 +3. 进行第一次构建验证,确认本地链路可用 + +### 阶段:CQRS 主路径迁移完成 + +- `CoreGrid-Migration/CoreGrid.csproj` 已切到本地 `ProjectReference` + 本地 source generators +- `CoreGrid-Migration/scripts/core/GameArchitecture.cs` 已删除 `AddMediator(...)` 配置钩子 +- `GFramework.Core.Abstractions` 新增 GFramework 自有 CQRS 契约与 `Unit` +- `IArchitectureContext` / `ArchitectureContext` 已切到自有 CQRS 签名 +- `ArchitectureBootstrapper` 已内建 handler 扫描注册,使用方无需再显式调用 `AddMediator(...)` +- `CqrsDispatcher` 已补齐 request/notification/stream dispatch 与 pipeline behavior 执行 +- `GFramework.Core.Cqrs.*` 基类、`ContextAwareMediator*Extensions`、Godot 协程上下文扩展均已迁到新契约 +- `GFramework.Core.Tests` 中原依赖旧 `Mediator` 注册入口的测试已迁移到 `CqrsTestRuntime` 反射注册路径 + +### 阶段:验证 + +- `dotnet build /mnt/f/gewuyou/System/Documents/WorkSpace/GameDev/GFramework/GFramework.Core/GFramework.Core.csproj` + - 结果:通过 +- `dotnet build /mnt/f/gewuyou/System/Documents/WorkSpace/GameDev/GFramework/GFramework.Core.Tests/GFramework.Core.Tests.csproj` + - 结果:通过 +- `dotnet test /mnt/f/gewuyou/System/Documents/WorkSpace/GameDev/GFramework/GFramework.Core.Tests/GFramework.Core.Tests.csproj --no-build` + - 结果:通过 + - 明细:`1621` 个测试全部通过 +- `dotnet build /mnt/f/gewuyou/System/Documents/WorkSpace/GameDev/GFramework/GFramework.sln` + - 结果:通过 +- `dotnet build /mnt/f/gewuyou/System/Documents/WorkSpace/GameDev/CoreGrid-Migration/CoreGrid.sln` + - 结果:通过 + - 备注:仅存在既有 analyzer warnings,无新增构建错误 + +### 当前残留 + +- 文档与少量历史 API 命名仍保留 `Mediator` 前缀 +- `MediatorCoroutineExtensions` 与少量测试仍依赖 `Mediator.Abstractions` +- handler 自动注册当前使用运行时反射扫描,尚未切回生成器注册 + +### 下一步建议 + +1. 决定是否继续做“完全移除 `Mediator.Abstractions` 包”的第二阶段清理 +2. 若继续,优先迁移协程扩展与相关测试 +3. 评估是否将 `RegisterMediatorBehavior`、`ContextAwareMediator*Extensions` 等历史命名升级为 CQRS 中性命名