diff --git a/.coderabbit.yaml b/.coderabbit.yaml index 8cb624ec..5fc95d9d 100644 --- a/.coderabbit.yaml +++ b/.coderabbit.yaml @@ -16,6 +16,8 @@ reviews: auto_review: enabled: true drafts: false # draft PR 不 review + base_branches: + - refactor/cqrs-architecture-decoupling chat: auto_reply: true 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..b9b5dc9a 100644 --- a/GFramework.Core.Abstractions/Architectures/IArchitectureContext.cs +++ b/GFramework.Core.Abstractions/Architectures/IArchitectureContext.cs @@ -1,18 +1,23 @@ using GFramework.Core.Abstractions.Command; +using GFramework.Core.Abstractions.Cqrs; 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; /// -/// 架构上下文接口,提供对系统、模型、工具类的访问以及命令、查询、事件的发送和注册功能 +/// 架构上下文接口,统一暴露框架组件访问、兼容旧命令/查询总线,以及当前推荐的 CQRS 运行时入口。 /// +/// +/// 旧的 GFramework.Core.Abstractions.CommandGFramework.Core.Abstractions.Query 契约会继续通过原有 Command/Query Executor 路径执行,以保证存量代码兼容。 +/// 新的 GFramework.Core.Abstractions.Cqrs 契约由内置 CQRS dispatcher 统一处理,支持 request pipeline、notification publish 与 stream request。 +/// 新功能优先使用 与对应的 CQRS Command/Query 重载;迁移旧代码时可先保留旧入口,再逐步替换为 CQRS 请求模型。 +/// public interface IArchitectureContext { /// @@ -104,87 +109,91 @@ public interface IArchitectureContext IReadOnlyList GetUtilitiesByPriority() where TUtility : class, IUtility; /// - /// 发送一个命令 + /// 发送一个旧版命令。 /// - /// 要发送的命令 + /// 要发送的旧版命令。 void SendCommand(ICommand command); /// - /// 发送一个带返回值的命令 + /// 发送一个旧版带返回值命令。 /// - /// 命令执行结果类型 - /// 要发送的命令 - /// 命令执行结果 - TResult SendCommand(Command.ICommand command); + /// 命令执行结果类型。 + /// 要发送的旧版命令。 + /// 命令执行结果。 + TResult SendCommand(ICommand command); /// - /// [Mediator] 发送命令的同步版本(不推荐,仅用于兼容性) + /// 发送一个新版 CQRS 命令并返回结果。 /// - /// 命令响应类型 - /// 要发送的命令对象 - /// 命令执行结果 - TResponse SendCommand(Mediator.ICommand command); + /// 命令响应类型。 + /// 要发送的 CQRS 命令。 + /// 命令执行结果。 + /// + /// 这是迁移后的推荐命令入口。无返回值命令应实现 IRequest<Unit>,并优先通过 调用。 + /// + TResponse SendCommand(Cqrs.Command.ICommand command); /// - /// 发送并异步执行一个命令 + /// 异步发送一个旧版命令。 /// - /// 要发送的命令 + /// 要发送的旧版命令。 Task SendCommandAsync(IAsyncCommand command); /// - /// [Mediator] 异步发送命令并返回结果 - /// 通过Mediator模式发送命令请求,支持取消操作 + /// 异步发送一个新版 CQRS 命令并返回结果。 /// - /// 命令响应类型 - /// 要发送的命令对象 - /// 取消令牌,用于取消操作 - /// 包含命令执行结果的ValueTask - ValueTask SendCommandAsync(Mediator.ICommand command, + /// 命令响应类型。 + /// 要发送的 CQRS 命令。 + /// 取消令牌。 + /// 包含命令执行结果的值任务。 + ValueTask SendCommandAsync(Cqrs.Command.ICommand command, CancellationToken cancellationToken = default); /// - /// 发送并异步执行一个带返回值的命令 + /// 异步发送一个旧版带返回值命令。 /// - /// 命令执行结果类型 - /// 要发送的命令 - /// 命令执行结果 + /// 命令执行结果类型。 + /// 要发送的旧版命令。 + /// 命令执行结果。 Task SendCommandAsync(IAsyncCommand command); /// - /// 发送一个查询请求 + /// 发送一个旧版查询请求。 /// - /// 查询结果类型 - /// 要发送的查询 - /// 查询结果 - TResult SendQuery(Query.IQuery query); + /// 查询结果类型。 + /// 要发送的旧版查询。 + /// 查询结果。 + TResult SendQuery(IQuery query); /// - /// [Mediator] 发送查询的同步版本(不推荐,仅用于兼容性) + /// 发送一个新版 CQRS 查询并返回结果。 /// - /// 查询响应类型 - /// 要发送的查询对象 - /// 查询结果 - TResponse SendQuery(Mediator.IQuery query); + /// 查询响应类型。 + /// 要发送的 CQRS 查询。 + /// 查询结果。 + /// + /// 这是迁移后的推荐查询入口。新查询应优先实现 GFramework.Core.Abstractions.Cqrs.Query.IQuery<TResponse>。 + /// + TResponse SendQuery(Cqrs.Query.IQuery query); /// - /// 异步发送一个查询请求 + /// 异步发送一个旧版查询请求。 /// - /// 查询结果类型 - /// 要发送的异步查询 - /// 查询结果 + /// 查询结果类型。 + /// 要发送的旧版异步查询。 + /// 查询结果。 Task SendQueryAsync(IAsyncQuery query); /// - /// [Mediator] 异步发送查询并返回结果 - /// 通过Mediator模式发送查询请求,支持取消操作 + /// 异步发送一个新版 CQRS 查询并返回结果。 /// - /// 查询响应类型 - /// 要发送的查询对象 - /// 取消令牌,用于取消操作 - /// 包含查询结果的ValueTask - ValueTask SendQueryAsync(Mediator.IQuery query, + /// 查询响应类型。 + /// 要发送的 CQRS 查询。 + /// 取消令牌。 + /// 包含查询结果的值任务。 + ValueTask SendQueryAsync(Cqrs.Query.IQuery query, CancellationToken cancellationToken = default); /// @@ -216,28 +225,40 @@ public interface IArchitectureContext void UnRegisterEvent(Action onEvent); /// - /// 发送请求(统一处理 Command/Query) + /// 发送新版 CQRS 请求,并统一处理命令与查询。 /// + /// + /// 这是自有 CQRS 运行时的主入口。新代码应优先通过该方法或 进入 dispatcher。 + /// ValueTask SendRequestAsync( IRequest request, CancellationToken cancellationToken = default); /// - /// 发送请求(同步版本,不推荐) + /// 发送新版 CQRS 请求的同步包装版本。 /// + /// + /// 仅为兼容同步调用链保留;新代码应优先使用异步入口,避免阻塞当前线程。 + /// TResponse SendRequest(IRequest request); /// - /// 发布通知(一对多事件) + /// 发布新版 CQRS 通知。 /// + /// + /// 该入口用于一对多通知分发,与框架级 EventBus 事件系统并存,适合围绕请求处理过程传播领域通知。 + /// ValueTask PublishAsync( TNotification notification, CancellationToken cancellationToken = default) where TNotification : INotification; /// - /// 创建流式请求(用于大数据集) + /// 创建新版 CQRS 流式请求。 /// + /// + /// 适用于需要按序惰性产出大量结果的场景。调用方应消费返回的异步序列,而不是回退到旧版查询总线。 + /// IAsyncEnumerable CreateStream( IStreamRequest request, CancellationToken cancellationToken = default); @@ -245,7 +266,7 @@ public interface IArchitectureContext // === 便捷扩展方法 === /// - /// 发送命令(无返回值) + /// 发送一个无返回值的新版 CQRS 命令。 /// ValueTask SendAsync( TCommand command, @@ -253,7 +274,7 @@ public interface IArchitectureContext where TCommand : IRequest; /// - /// 发送命令(有返回值) + /// 发送一个有返回值的新版 CQRS 请求。 /// ValueTask SendAsync( IRequest command, @@ -265,4 +286,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..520f9fee --- /dev/null +++ b/GFramework.Core.Abstractions/Cqrs/MessageHandlerDelegate.cs @@ -0,0 +1,19 @@ +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..bf6566b4 100644 --- a/GFramework.Core.Tests/Architectures/ArchitectureModulesBehaviorTests.cs +++ b/GFramework.Core.Tests/Architectures/ArchitectureModulesBehaviorTests.cs @@ -2,14 +2,13 @@ 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 GfCqrs = GFramework.Core.Abstractions.Cqrs; namespace GFramework.Core.Tests.Architectures; /// -/// 验证 Architecture 通过 ArchitectureModules 暴露出的模块安装与 Mediator 行为注册能力。 -/// 这些测试覆盖模块安装回调和中介管道行为接入,确保模块管理器仍然保持可观察行为不变。 +/// 验证 Architecture 通过 ArchitectureModules 暴露出的模块安装与 CQRS 行为注册能力。 +/// 这些测试覆盖模块安装回调和请求管道行为接入,确保模块管理器仍然保持可观察行为不变。 /// [TestFixture] public class ArchitectureModulesBehaviorTests @@ -57,7 +56,7 @@ public class ArchitectureModulesBehaviorTests } /// - /// 验证注册的 Mediator 行为会参与请求管道执行。 + /// 验证注册的 CQRS 行为会参与请求管道执行。 /// [Test] public async Task RegisterMediatorBehavior_Should_Apply_Pipeline_Behavior_To_Request() @@ -83,12 +82,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 +129,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 +155,8 @@ public sealed class ModuleBehaviorRequestHandler : IRequestHandler /// 请求类型。 /// 响应类型。 -public sealed class TrackingPipelineBehavior : IPipelineBehavior - where TRequest : IRequest +public sealed class TrackingPipelineBehavior : GfCqrs.IPipelineBehavior + where TRequest : GfCqrs.IRequest { /// /// 获取当前测试进程中该请求类型对应的行为触发次数。 @@ -178,11 +171,10 @@ public sealed class TrackingPipelineBehavior : IPipelineBeh /// 取消令牌。 /// 下游处理器的响应结果。 public async ValueTask Handle( - TRequest message, - MessageHandlerDelegate next, + TRequest message, 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/Cqrs/CqrsHandlerRegistrarTests.cs b/GFramework.Core.Tests/Cqrs/CqrsHandlerRegistrarTests.cs new file mode 100644 index 00000000..28b1fb32 --- /dev/null +++ b/GFramework.Core.Tests/Cqrs/CqrsHandlerRegistrarTests.cs @@ -0,0 +1,221 @@ +using System.Reflection; +using GFramework.Core.Abstractions.Cqrs; +using GFramework.Core.Abstractions.Logging; +using GFramework.Core.Architectures; +using GFramework.Core.Ioc; +using GFramework.Core.Logging; +using GFramework.Core.Tests.Logging; + +namespace GFramework.Core.Tests.Cqrs; + +/// +/// 验证 CQRS 处理器自动注册在顺序与容错层面的可观察行为。 +/// +[TestFixture] +internal sealed class CqrsHandlerRegistrarTests +{ + private MicrosoftDiContainer? _container; + private ArchitectureContext? _context; + + /// + /// 初始化测试容器并重置共享状态。 + /// + [SetUp] + public void SetUp() + { + LoggerFactoryResolver.Provider = new ConsoleLoggerFactoryProvider(); + DeterministicNotificationHandlerState.Reset(); + + _container = new MicrosoftDiContainer(); + CqrsTestRuntime.RegisterHandlers( + _container, + typeof(CqrsHandlerRegistrarTests).Assembly); + + _container.Freeze(); + _context = new ArchitectureContext(_container); + } + + /// + /// 清理测试过程中创建的上下文与共享状态。 + /// + [TearDown] + public void TearDown() + { + _context = null; + _container = null; + DeterministicNotificationHandlerState.Reset(); + } + + /// + /// 验证自动扫描到的通知处理器会按稳定名称顺序执行,而不是依赖反射枚举顺序。 + /// + [Test] + public async Task PublishAsync_Should_Run_Notification_Handlers_In_Deterministic_Name_Order() + { + await _context!.PublishAsync(new DeterministicOrderNotification()); + + Assert.That( + DeterministicNotificationHandlerState.InvocationOrder, + Is.EqualTo( + [ + nameof(AlphaDeterministicNotificationHandler), + nameof(ZetaDeterministicNotificationHandler) + ])); + } + + /// + /// 验证部分类型加载失败时仍能保留可加载类型,并记录诊断日志。 + /// + [Test] + public void RegisterHandlers_Should_Register_Loadable_Types_And_Log_Warnings_When_Assembly_Load_Partially_Fails() + { + var originalProvider = LoggerFactoryResolver.Provider; + var capturingProvider = new CapturingLoggerFactoryProvider(LogLevel.Warning); + var reflectionTypeLoadException = new ReflectionTypeLoadException( + [typeof(AlphaDeterministicNotificationHandler), null], + [new TypeLoadException("Missing optional dependency for registrar test.")]); + var partiallyLoadableAssembly = new Mock(); + partiallyLoadableAssembly + .SetupGet(static assembly => assembly.FullName) + .Returns("GFramework.Core.Tests.Cqrs.PartiallyLoadableAssembly, Version=1.0.0.0"); + partiallyLoadableAssembly + .Setup(static assembly => assembly.GetTypes()) + .Throws(reflectionTypeLoadException); + + LoggerFactoryResolver.Provider = capturingProvider; + try + { + var container = new MicrosoftDiContainer(); + CqrsTestRuntime.RegisterHandlers(container, partiallyLoadableAssembly.Object); + container.Freeze(); + + var handlers = container.GetAll>(); + var warningLogs = capturingProvider.Loggers + .SelectMany(static logger => logger.Logs) + .Where(static log => log.Level == LogLevel.Warning) + .ToList(); + + Assert.Multiple(() => + { + Assert.That( + handlers.Select(static handler => handler.GetType()), + Is.EqualTo([typeof(AlphaDeterministicNotificationHandler)])); + Assert.That(warningLogs.Count, Is.GreaterThanOrEqualTo(2)); + Assert.That( + warningLogs.Any(log => log.Message.Contains("partially failed", StringComparison.Ordinal)), + Is.True); + Assert.That( + warningLogs.Any(log => + log.Message.Contains("Missing optional dependency", StringComparison.Ordinal)), + Is.True); + }); + } + finally + { + LoggerFactoryResolver.Provider = originalProvider; + } + } +} + +/// +/// 记录确定性通知处理器的实际执行顺序。 +/// +internal static class DeterministicNotificationHandlerState +{ + /// + /// 获取当前测试中的通知处理器执行顺序。 + /// + public static List InvocationOrder { get; } = []; + + /// + /// 重置共享的执行顺序状态。 + /// + public static void Reset() + { + InvocationOrder.Clear(); + } +} + +/// +/// 用于验证同一通知的多个处理器是否按稳定顺序执行。 +/// +internal sealed record DeterministicOrderNotification : INotification; + +/// +/// 故意放在 Alpha 之前声明,用于验证注册器不会依赖源码声明顺序。 +/// +internal sealed class ZetaDeterministicNotificationHandler : INotificationHandler +{ + /// + /// 记录当前处理器已执行。 + /// + /// 通知实例。 + /// 取消令牌。 + /// 已完成任务。 + public ValueTask Handle(DeterministicOrderNotification notification, CancellationToken cancellationToken) + { + DeterministicNotificationHandlerState.InvocationOrder.Add(nameof(ZetaDeterministicNotificationHandler)); + return ValueTask.CompletedTask; + } +} + +/// +/// 名称排序上应先于 Zeta 处理器执行的通知处理器。 +/// +internal sealed class AlphaDeterministicNotificationHandler : INotificationHandler +{ + /// + /// 记录当前处理器已执行。 + /// + /// 通知实例。 + /// 取消令牌。 + /// 已完成任务。 + public ValueTask Handle(DeterministicOrderNotification notification, CancellationToken cancellationToken) + { + DeterministicNotificationHandlerState.InvocationOrder.Add(nameof(AlphaDeterministicNotificationHandler)); + return ValueTask.CompletedTask; + } +} + +/// +/// 为 CQRS 注册测试捕获真实启动路径中创建的日志记录器。 +/// +/// +/// 处理器注册入口会分别为测试运行时、容器和注册器创建日志器。 +/// 该提供程序统一保留这些测试日志器,以便断言警告是否经由公开入口真正发出。 +/// +internal sealed class CapturingLoggerFactoryProvider : ILoggerFactoryProvider +{ + private readonly List _loggers = []; + + /// + /// 使用指定的最小日志级别初始化一个新的捕获型日志工厂提供程序。 + /// + /// 要应用到新建测试日志器的最小日志级别。 + public CapturingLoggerFactoryProvider(LogLevel minLevel = LogLevel.Info) + { + MinLevel = minLevel; + } + + /// + /// 获取通过当前提供程序创建的全部测试日志器。 + /// + public IReadOnlyList Loggers => _loggers; + + /// + /// 获取或设置新建测试日志器的最小日志级别。 + /// + public LogLevel MinLevel { get; set; } + + /// + /// 创建一个测试日志器并将其纳入捕获集合。 + /// + /// 日志记录器名称。 + /// 用于后续断言的测试日志器。 + public ILogger CreateLogger(string name) + { + var logger = new TestLogger(name, MinLevel); + _loggers.Add(logger); + return logger; + } +} diff --git a/GFramework.Core.Tests/CqrsTestRuntime.cs b/GFramework.Core.Tests/CqrsTestRuntime.cs new file mode 100644 index 00000000..e9664925 --- /dev/null +++ b/GFramework.Core.Tests/CqrsTestRuntime.cs @@ -0,0 +1,56 @@ +using System.Reflection; +using GFramework.Core.Abstractions.Ioc; +using GFramework.Core.Abstractions.Logging; +using GFramework.Core.Architectures; +using GFramework.Core.Ioc; +using GFramework.Core.Logging; + +namespace GFramework.Core.Tests; + +/// +/// 为测试项目提供对 CQRS 处理器真实注册入口的受控访问。 +/// +/// +/// 测试应通过该入口驱动注册流程,而不是直接反射调用注册器的私有辅助方法, +/// 这样可以覆盖生产启动路径中的程序集去重、日志记录与容错恢复行为。 +/// +internal static class CqrsTestRuntime +{ + private static readonly Type CqrsHandlerRegistrarType = typeof(ArchitectureContext).Assembly + .GetType( + "GFramework.Core.Cqrs.Internal.CqrsHandlerRegistrar", + throwOnError: true)! + ?? throw new InvalidOperationException( + "Failed to locate CqrsHandlerRegistrar type."); + + private static readonly MethodInfo RegisterHandlersMethod = CqrsHandlerRegistrarType + .GetMethod( + "RegisterHandlers", + BindingFlags.Public | BindingFlags.NonPublic | + BindingFlags.Static, + binder: null, + [ + typeof(IIocContainer), + typeof(IEnumerable), + typeof(ILogger) + ], + modifiers: null) + ?? throw new InvalidOperationException( + "Failed to locate CqrsHandlerRegistrar.RegisterHandlers."); + + /// + /// 通过与生产代码一致的注册入口扫描并注册指定程序集中的 CQRS 处理器。 + /// + /// 承载处理器映射的测试容器。 + /// 要扫描的程序集集合。 + internal 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]); + } +} diff --git a/GFramework.Core.Tests/Mediator/MediatorAdvancedFeaturesTests.cs b/GFramework.Core.Tests/Mediator/MediatorAdvancedFeaturesTests.cs index bc5cd782..2dc2503a 100644 --- a/GFramework.Core.Tests/Mediator/MediatorAdvancedFeaturesTests.cs +++ b/GFramework.Core.Tests/Mediator/MediatorAdvancedFeaturesTests.cs @@ -1,10 +1,9 @@ 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; namespace GFramework.Core.Tests.Mediator; @@ -15,22 +14,26 @@ namespace GFramework.Core.Tests.Mediator; [TestFixture] public class MediatorAdvancedFeaturesTests { + private MicrosoftDiContainer? _container; + + private ArchitectureContext? _context; + [SetUp] public void SetUp() { LoggerFactoryResolver.Provider = new ConsoleLoggerFactoryProvider(); _container = new MicrosoftDiContainer(); + TestCircuitBreakerHandler.Reset(); var loggerField = typeof(MicrosoftDiContainer).GetField("_logger", BindingFlags.NonPublic | BindingFlags.Instance); 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); @@ -43,9 +46,6 @@ public class MediatorAdvancedFeaturesTests _container = null; } - private ArchitectureContext? _context; - private MicrosoftDiContainer? _container; - [Test] public async Task Request_With_Validation_Behavior_Should_Validate_Input() @@ -136,9 +136,6 @@ public class MediatorAdvancedFeaturesTests [Test] public async Task Circuit_Breaker_Should_Prevent_Cascading_Failures() { - TestCircuitBreakerHandler.FailureCount = 0; - TestCircuitBreakerHandler.SuccessCount = 0; - // 先触发几次失败 for (int i = 0; i < 5; i++) { @@ -276,12 +273,10 @@ public sealed class TestTransientErrorRequestHandler : IRequestHandler { - private static bool _circuitOpen = false; - public ValueTask Handle(TestCircuitBreakerRequest request, CancellationToken cancellationToken) { // 检查断路器状态 - if (_circuitOpen) + if (TestCircuitBreakerHandler.CircuitOpen) { throw new InvalidOperationException("Circuit breaker is open"); } @@ -293,7 +288,7 @@ public sealed class TestCircuitBreakerRequestHandler : IRequestHandler= 5) { - _circuitOpen = true; + TestCircuitBreakerHandler.CircuitOpen = true; } throw new InvalidOperationException("Service unavailable"); @@ -452,6 +447,17 @@ public static class TestCircuitBreakerHandler { public static int FailureCount { get; set; } public static int SuccessCount { get; set; } + public static bool CircuitOpen { get; set; } + + /// + /// 重置断路器测试状态,避免静态字段在测试之间互相污染。 + /// + public static void Reset() + { + FailureCount = 0; + SuccessCount = 0; + CircuitOpen = false; + } } public sealed record TestCircuitBreakerRequest : IRequest @@ -487,4 +493,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..e176cce5 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.Rule; using ICommand = GFramework.Core.Abstractions.Command.ICommand; namespace GFramework.Core.Tests.Mediator; @@ -18,11 +18,17 @@ namespace GFramework.Core.Tests.Mediator; [TestFixture] public class MediatorArchitectureIntegrationTests { + private CommandExecutor? _commandBus; + private MicrosoftDiContainer? _container; + + private ArchitectureContext? _context; + [SetUp] public void SetUp() { LoggerFactoryResolver.Provider = new ConsoleLoggerFactoryProvider(); _container = new MicrosoftDiContainer(); + TestPerDispatchContextAwareHandler.Reset(); var loggerField = typeof(MicrosoftDiContainer).GetField("_logger", BindingFlags.NonPublic | BindingFlags.Instance); @@ -33,11 +39,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); @@ -51,10 +56,6 @@ public class MediatorArchitectureIntegrationTests _commandBus = null; } - private ArchitectureContext? _context; - private MicrosoftDiContainer? _container; - private CommandExecutor? _commandBus; - [Test] public async Task Handler_Can_Access_Architecture_Context() { @@ -292,6 +293,20 @@ public class MediatorArchitectureIntegrationTests Assert.That(traditionalCommand.Executed, Is.True); Assert.That(result, Is.EqualTo(42)); } + + [Test] + public async Task ContextAware_Handler_Should_Use_A_Fresh_Instance_Per_Request() + { + var firstResult = await _context!.SendRequestAsync(new TestPerDispatchContextAwareRequest()); + var secondResult = await _context.SendRequestAsync(new TestPerDispatchContextAwareRequest()); + + Assert.Multiple(() => + { + Assert.That(firstResult, Is.Not.EqualTo(secondResult)); + Assert.That(TestPerDispatchContextAwareHandler.SeenInstanceIds, Is.EqualTo([firstResult, secondResult])); + Assert.That(TestPerDispatchContextAwareHandler.Contexts, Has.All.SameAs(_context)); + }); + } } #region Integration Test Classes @@ -445,6 +460,42 @@ public sealed class TestMediatorRequestHandler : IRequestHandler +/// 用于验证自动扫描到的上下文感知处理器会按请求创建新实例。 +/// +public sealed class TestPerDispatchContextAwareHandler : ContextAwareBase, + IRequestHandler +{ + private static int _nextInstanceId; + private readonly int _instanceId = Interlocked.Increment(ref _nextInstanceId); + + public static List Contexts { get; } = []; + public static List SeenInstanceIds { get; } = []; + + /// + /// 记录当前实例编号与收到的架构上下文。 + /// + /// 请求实例。 + /// 取消令牌。 + /// 当前处理器实例编号。 + public ValueTask Handle(TestPerDispatchContextAwareRequest request, CancellationToken cancellationToken) + { + Contexts.Add(Context); + SeenInstanceIds.Add(_instanceId); + return ValueTask.FromResult(_instanceId); + } + + /// + /// 重置跨测试共享的实例跟踪状态。 + /// + public static void Reset() + { + Contexts.Clear(); + SeenInstanceIds.Clear(); + _nextInstanceId = 0; + } +} + public sealed record TestContextAwareRequest : IRequest; public static class TestContextAwareHandler @@ -545,6 +596,11 @@ public sealed record TestMediatorRequest : IRequest public int Value { get; init; } } +/// +/// 用于验证每次请求分发都会获得新的上下文感知处理器实例。 +/// +public sealed record TestPerDispatchContextAwareRequest : IRequest; + // 传统命令用于混合测试 public class TestTraditionalCommand : ICommand { @@ -559,4 +615,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..27dfed5c 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,22 +11,26 @@ using GFramework.Core.Events; using GFramework.Core.Ioc; using GFramework.Core.Logging; using GFramework.Core.Query; -using Mediator; -using Microsoft.Extensions.DependencyInjection; using ICommand = GFramework.Core.Abstractions.Command.ICommand; - -// ✅ Mediator 库的命名空间 - -// ✅ 使用 global using 或别名来区分 +using Unit = GFramework.Core.Abstractions.Cqrs.Unit; namespace GFramework.Core.Tests.Mediator; [TestFixture] public class MediatorComprehensiveTests { + private AsyncQueryExecutor? _asyncQueryBus; + private CommandExecutor? _commandBus; + private MicrosoftDiContainer? _container; + + private ArchitectureContext? _context; + private DefaultEnvironment? _environment; + private EventBus? _eventBus; + private QueryExecutor? _queryBus; + /// /// 测试初始化方法,在每个测试方法执行前运行。 - /// 负责初始化日志工厂、依赖注入容器、Mediator以及各种总线服务。 + /// 负责初始化日志工厂、依赖注入容器、自有 CQRS 处理器以及各种总线服务。 /// [SetUp] public void SetUp() @@ -51,13 +56,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); @@ -79,14 +82,6 @@ public class MediatorComprehensiveTests _environment = null; } - private ArchitectureContext? _context; - private MicrosoftDiContainer? _container; - private EventBus? _eventBus; - private CommandExecutor? _commandBus; - private QueryExecutor? _queryBus; - private AsyncQueryExecutor? _asyncQueryBus; - private DefaultEnvironment? _environment; - /// /// 测试SendRequestAsync方法在请求有效时返回结果 /// @@ -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,17 +408,17 @@ 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(); _context!.SendCommand(legacyCommand); Assert.That(legacyCommand.Executed, Is.True); - // 使用Mediator方式 + // 使用自有 CQRS 方式 var mediatorCommand = new TestCommandWithResult { ResultValue = 999 }; var result = await _context.SendAsync(mediatorCommand); Assert.That(result, Is.EqualTo(999)); @@ -434,7 +429,7 @@ public class MediatorComprehensiveTests } } -#region Advanced Test Classes for Mediator Features +#region Advanced Test Classes for CQRS Features public sealed record TestLongRunningRequest : IRequest { @@ -628,9 +623,9 @@ public class TestLegacyCommand : ICommand #endregion -#region Test Classes - Mediator (新实现) +#region Test Classes - CQRS Runtime -// ✅ 这些类使用 Mediator.IRequest +// ✅ 这些类使用自有 CQRS IRequest public sealed record TestRequest : IRequest { public int Value { get; init; } @@ -662,7 +657,7 @@ public sealed record TestStreamRequest : IStreamRequest public int[] Values { get; init; } = []; } -// ✅ 这些 Handler 使用 Mediator.IRequestHandler +// ✅ 这些 Handler 使用自有 CQRS IRequestHandler public sealed class TestRequestHandler : IRequestHandler { public ValueTask Handle(TestRequest request, CancellationToken cancellationToken) @@ -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..528de106 100644 --- a/GFramework.Core/Cqrs/Command/AbstractCommandHandler.cs +++ b/GFramework.Core/Cqrs/Command/AbstractCommandHandler.cs @@ -11,17 +11,19 @@ // See the License for the specific language governing permissions and // limitations under the License. +using GFramework.Core.Abstractions.Cqrs; +using GFramework.Core.Abstractions.Cqrs.Command; using GFramework.Core.Rule; -using Mediator; namespace GFramework.Core.Cqrs.Command; /// /// 抽象命令处理器基类 -/// 继承自ContextAwareBase并实现ICommandHandler接口,为具体的命令处理器提供基础功能 +/// 继承自 ContextAwareBase 并实现 IRequestHandler 接口,为具体的命令处理器提供基础功能。 +/// 框架会在每次分发前注入当前架构上下文,因此派生类可以通过 Context 访问架构级服务。 /// /// 命令类型 -public abstract class AbstractCommandHandler : ContextAwareBase, ICommandHandler +public abstract class AbstractCommandHandler : ContextAwareBase, IRequestHandler where TCommand : ICommand { /// @@ -36,12 +38,12 @@ public abstract class AbstractCommandHandler : ContextAwareBase, IComm /// /// 抽象命令处理器基类(带返回值版本) -/// 继承自ContextAwareBase并实现ICommandHandler接口,为具体的命令处理器提供基础功能 -/// 支持泛型命令和结果类型,实现CQRS模式中的命令处理 +/// 继承自 ContextAwareBase 并实现 IRequestHandler 接口,为具体的命令处理器提供基础功能。 +/// 支持泛型命令和结果类型,框架会在每次分发前注入当前架构上下文。 /// /// 命令类型,必须实现ICommand接口 /// 命令执行结果类型 -public abstract class AbstractCommandHandler : ContextAwareBase, ICommandHandler +public abstract class AbstractCommandHandler : ContextAwareBase, IRequestHandler where TCommand : ICommand { /// @@ -52,4 +54,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..847563c4 100644 --- a/GFramework.Core/Cqrs/Command/AbstractStreamCommandHandler.cs +++ b/GFramework.Core/Cqrs/Command/AbstractStreamCommandHandler.cs @@ -11,28 +11,37 @@ // See the License for the specific language governing permissions and // limitations under the License. +using GFramework.Core.Abstractions.Cqrs; +using GFramework.Core.Abstractions.Cqrs.Command; using GFramework.Core.Rule; -using Mediator; namespace GFramework.Core.Cqrs.Command; /// -/// 抽象流式命令处理器基类 -/// 继承自ContextAwareBase并实现IStreamCommandHandler接口,为具体的流式命令处理器提供基础功能 -/// 支持流式处理命令并产生异步可枚举的响应序列 +/// 抽象流式命令处理器基类。 +/// 继承自 并实现 , +/// 为具体的流式命令处理器提供基础功能。 /// -/// 流式命令类型,必须实现IStreamCommand接口 -/// 流式命令响应元素类型 +/// 流式命令类型,必须实现 +/// 流式命令响应元素类型。 +/// +/// 框架会在每次调用 CreateStream 进入实际处理逻辑前,为当前处理器实例注入架构上下文, +/// 因此派生类只能在 执行期间及其返回的异步枚举序列内假定 Context 可用。 +/// 默认注册器会将流式命令处理器注册为瞬态服务,以避免同一个上下文感知实例在多个流或并发请求之间复用。 +/// 派生类不应缓存处理器实例,也不应把依赖当前上下文的可变状态泄漏到流外部。 +/// 传入 的取消令牌同时约束流的创建与后续枚举, +/// 派生类应在启动阶段和每次生成响应前尊重取消请求,避免在调用方停止枚举后继续执行后台工作。 +/// public abstract class AbstractStreamCommandHandler : ContextAwareBase, - IStreamCommandHandler + IStreamRequestHandler where TCommand : IStreamCommand { /// - /// 处理流式命令并返回异步可枚举的响应序列 - /// 由具体的流式命令处理器子类实现流式处理逻辑 + /// 处理流式命令并返回异步可枚举的响应序列。 + /// 由具体的流式命令处理器子类实现流式处理逻辑。 /// - /// 要处理的流式命令对象 - /// 取消令牌,用于取消流式处理操作 - /// 异步可枚举的响应序列,每个元素类型为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..136b76a7 --- /dev/null +++ b/GFramework.Core/Cqrs/Internal/CqrsHandlerRegistrar.cs @@ -0,0 +1,145 @@ +using System.Reflection; +using GFramework.Core.Abstractions.Cqrs; +using GFramework.Core.Abstractions.Ioc; +using GFramework.Core.Abstractions.Logging; + +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 + .Where(static assembly => assembly is not null) + .Distinct() + .OrderBy(GetAssemblySortKey, StringComparer.Ordinal)) + { + RegisterAssemblyHandlers(container.GetServicesUnsafe, assembly, logger); + } + } + + /// + /// 注册单个程序集里的所有 CQRS 处理器映射。 + /// + private static void RegisterAssemblyHandlers(IServiceCollection services, Assembly assembly, ILogger logger) + { + foreach (var implementationType in GetLoadableTypes(assembly, logger).Where(IsConcreteHandlerType)) + { + var handlerInterfaces = implementationType + .GetInterfaces() + .Where(IsSupportedHandlerInterface) + .OrderBy(GetTypeSortKey, StringComparer.Ordinal) + .ToList(); + + if (handlerInterfaces.Count == 0) + continue; + + foreach (var handlerInterface in handlerInterfaces) + { + // Request/notification handlers receive context injection before every dispatch. + // Transient registration avoids sharing mutable Context across concurrent requests. + services.AddTransient(handlerInterface, implementationType); + logger.Debug( + $"Registered CQRS handler {implementationType.FullName} as {handlerInterface.FullName}."); + } + } + } + + /// + /// 安全获取程序集中的可加载类型,并在部分类型加载失败时保留其余处理器注册能力。 + /// + private static IReadOnlyList GetLoadableTypes(Assembly assembly, ILogger logger) + { + try + { + return assembly.GetTypes() + .Where(static type => type is not null) + .OrderBy(GetTypeSortKey, StringComparer.Ordinal) + .ToList(); + } + catch (ReflectionTypeLoadException exception) + { + return RecoverLoadableTypes(assembly, exception, logger); + } + } + + /// + /// 记录部分类型加载失败,并返回仍然可用的类型集合。 + /// + private static IReadOnlyList RecoverLoadableTypes( + Assembly assembly, + ReflectionTypeLoadException exception, + ILogger logger) + { + var assemblyName = GetAssemblySortKey(assembly); + logger.Warn( + $"CQRS handler scan partially failed for assembly {assemblyName}. Continuing with loadable types."); + + foreach (var loaderException in exception.LoaderExceptions.Where(static ex => ex is not null)) + { + logger.Warn( + $"Failed to load one or more types while scanning {assemblyName}: {loaderException!.Message}"); + } + + return exception.Types + .Where(static type => type is not null) + .Cast() + .OrderBy(GetTypeSortKey, StringComparer.Ordinal) + .ToList(); + } + + /// + /// 判断指定类型是否可作为可实例化处理器。 + /// + 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<,>); + } + + /// + /// 生成程序集排序键,保证跨运行环境的处理器注册顺序稳定。 + /// + private static string GetAssemblySortKey(Assembly assembly) + { + return assembly.FullName ?? assembly.GetName().Name ?? assembly.ToString(); + } + + /// + /// 生成类型排序键,保证同一程序集内的处理器与接口映射顺序稳定。 + /// + private static string GetTypeSortKey(Type type) + { + return type.FullName ?? type.Name; + } +} 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..e9a3795f 100644 --- a/GFramework.Core/Cqrs/Query/AbstractQueryHandler.cs +++ b/GFramework.Core/Cqrs/Query/AbstractQueryHandler.cs @@ -11,19 +11,20 @@ // See the License for the specific language governing permissions and // limitations under the License. +using GFramework.Core.Abstractions.Cqrs; +using GFramework.Core.Abstractions.Cqrs.Query; using GFramework.Core.Rule; -using Mediator; namespace GFramework.Core.Cqrs.Query; /// /// 抽象查询处理器基类 -/// 继承自ContextAwareBase并实现IQueryHandler接口,为具体的查询处理器提供基础功能 -/// 支持泛型查询和结果类型,实现CQRS模式中的查询处理 +/// 继承自 ContextAwareBase 并实现 IRequestHandler 接口,为具体的查询处理器提供基础功能。 +/// 框架会在每次分发前注入当前架构上下文,因此派生类可以通过 Context 访问架构级服务。 /// /// 查询类型,必须实现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..6bccf549 100644 --- a/GFramework.Core/Cqrs/Query/QueryBase.cs +++ b/GFramework.Core/Cqrs/Query/QueryBase.cs @@ -12,13 +12,12 @@ // limitations under the License. using GFramework.Core.Abstractions.Cqrs.Query; -using Mediator; namespace GFramework.Core.Cqrs.Query; /// /// 表示一个基础查询类,用于处理带有输入和响应的查询模式实现。 -/// 该类继承自 Mediator.IQuery<TResponse> 接口,提供了通用的查询结构。 +/// 该类实现 IQuery<TResponse> 接口,提供了通用的查询结构。 /// /// 查询输入数据的类型,必须实现 IQueryInput 接口 /// 查询执行后返回结果的类型 @@ -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 +}