From a80ff596317598aff285662ec19141028ef918c4 Mon Sep 17 00:00:00 2001 From: GeWuYou <95328647+GeWuYou@users.noreply.github.com> Date: Wed, 15 Apr 2026 19:42:08 +0800 Subject: [PATCH 01/12] =?UTF-8?q?feat(cqrs):=20=E6=B7=BB=E5=8A=A0CQRS?= =?UTF-8?q?=E8=BF=90=E8=A1=8C=E6=97=B6=E6=A8=A1=E5=9D=97=E5=92=8C=E5=85=BC?= =?UTF-8?q?=E5=AE=B9=E6=80=A7=E6=89=A9=E5=B1=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增ContextAwareMediatorCommandExtensions提供命令扩展方法的兼容性别名 - 新增ContextAwareMediatorExtensions提供CQRS统一接口扩展方法的兼容性别名 - 新增ContextAwareMediatorQueryExtensions提供查询扩展方法的兼容性别名 - 添加CqrsRuntimeModule用于注册CQRS运行时和处理器注册器到依赖注入容器 - 更新IArchitectureContext接口添加新版CQRS请求、命令、查询和通知的统一入口 - 添加架构上下文的CQRS处理器注册相关单元测试 - 配置项目文件以支持多目标框架和包引用管理 --- .../Architectures/IArchitectureContext.cs | 4 +- ...ArchitectureAdditionalCqrsHandlersTests.cs | 2 +- .../ContextAwareMediatorCommandExtensions.cs | 1 + .../ContextAwareMediatorExtensions.cs | 1 + .../ContextAwareMediatorQueryExtensions.cs | 1 + GFramework.Core/GFramework.Core.csproj | 1 + .../Services/Modules/CqrsRuntimeModule.cs | 11 ++-- .../Cqrs/CqrsHandlerRegistrarTests.cs | 1 - .../Command/CommandBase.cs | 2 +- .../CqrsHandlerRegistryAttribute.cs | 2 +- GFramework.Cqrs/CqrsRuntimeFactory.cs | 45 ++++++++++++++++ .../ContextAwareCqrsCommandExtensions.cs | 2 +- .../Extensions/ContextAwareCqrsExtensions.cs | 2 +- .../ContextAwareCqrsQueryExtensions.cs | 2 +- GFramework.Cqrs/GFramework.Cqrs.csproj | 1 + GFramework.Cqrs/GlobalUsings.cs | 6 +++ .../ICqrsHandlerRegistry.cs | 2 +- .../Internal/CqrsDispatcher.cs | 2 +- .../Internal/CqrsHandlerRegistrar.cs | 3 +- .../Internal/DefaultCqrsHandlerRegistrar.cs | 2 +- .../Notification/NotificationBase.cs | 2 +- .../Query/QueryBase.cs | 2 +- .../Request/RequestBase.cs | 2 +- .../ContextAwareCoroutineExtensions.cs | 2 +- .../Constants/PathContests.cs | 12 ++++- .../Cqrs/CqrsHandlerRegistryGeneratorTests.cs | 30 ++++++----- .../Cqrs/CqrsHandlerRegistryGenerator.cs | 20 +++++--- GFramework.Tests.Common/CqrsTestRuntime.cs | 51 ++++--------------- .../GFramework.Tests.Common.csproj | 1 + 29 files changed, 129 insertions(+), 86 deletions(-) rename {GFramework.Core/Cqrs => GFramework.Cqrs}/Command/CommandBase.cs (97%) rename {GFramework.Core.Abstractions/Cqrs => GFramework.Cqrs}/CqrsHandlerRegistryAttribute.cs (94%) create mode 100644 GFramework.Cqrs/CqrsRuntimeFactory.cs rename {GFramework.Core => GFramework.Cqrs}/Extensions/ContextAwareCqrsCommandExtensions.cs (98%) rename {GFramework.Core => GFramework.Cqrs}/Extensions/ContextAwareCqrsExtensions.cs (99%) rename {GFramework.Core => GFramework.Cqrs}/Extensions/ContextAwareCqrsQueryExtensions.cs (98%) create mode 100644 GFramework.Cqrs/GlobalUsings.cs rename {GFramework.Core.Abstractions/Cqrs => GFramework.Cqrs}/ICqrsHandlerRegistry.cs (95%) rename {GFramework.Core/Cqrs => GFramework.Cqrs}/Internal/CqrsDispatcher.cs (99%) rename {GFramework.Core/Cqrs => GFramework.Cqrs}/Internal/CqrsHandlerRegistrar.cs (99%) rename {GFramework.Core/Cqrs => GFramework.Cqrs}/Internal/DefaultCqrsHandlerRegistrar.cs (96%) rename {GFramework.Core/Cqrs => GFramework.Cqrs}/Notification/NotificationBase.cs (96%) rename {GFramework.Core/Cqrs => GFramework.Cqrs}/Query/QueryBase.cs (97%) rename {GFramework.Core/Cqrs => GFramework.Cqrs}/Request/RequestBase.cs (97%) diff --git a/GFramework.Core.Abstractions/Architectures/IArchitectureContext.cs b/GFramework.Core.Abstractions/Architectures/IArchitectureContext.cs index 0eb6a43f..c7b62e2f 100644 --- a/GFramework.Core.Abstractions/Architectures/IArchitectureContext.cs +++ b/GFramework.Core.Abstractions/Architectures/IArchitectureContext.cs @@ -15,7 +15,7 @@ namespace GFramework.Core.Abstractions.Architectures; /// /// /// 旧的 GFramework.Core.Abstractions.CommandGFramework.Core.Abstractions.Query 契约会继续通过原有 Command/Query Executor 路径执行,以保证存量代码兼容。 -/// 新的 GFramework.Core.Abstractions.Cqrs 契约由内置 CQRS dispatcher 统一处理,支持 request pipeline、notification publish 与 stream request。 +/// 新的 GFramework.Cqrs.Abstractions.Cqrs 契约由内置 CQRS dispatcher 统一处理,支持 request pipeline、notification publish 与 stream request。 /// 新功能优先使用 与对应的 CQRS Command/Query 重载;迁移旧代码时可先保留旧入口,再逐步替换为 CQRS 请求模型。 /// public interface IArchitectureContext @@ -175,7 +175,7 @@ public interface IArchitectureContext /// 要发送的 CQRS 查询。 /// 查询结果。 /// - /// 这是迁移后的推荐查询入口。新查询应优先实现 GFramework.Core.Abstractions.Cqrs.Query.IQuery<TResponse>。 + /// 这是迁移后的推荐查询入口。新查询应优先实现 GFramework.Cqrs.Abstractions.Cqrs.Query.IQuery<TResponse>。 /// TResponse SendQuery(GFramework.Cqrs.Abstractions.Cqrs.Query.IQuery query); diff --git a/GFramework.Core.Tests/Architectures/ArchitectureAdditionalCqrsHandlersTests.cs b/GFramework.Core.Tests/Architectures/ArchitectureAdditionalCqrsHandlersTests.cs index 67255b41..ddec08c8 100644 --- a/GFramework.Core.Tests/Architectures/ArchitectureAdditionalCqrsHandlersTests.cs +++ b/GFramework.Core.Tests/Architectures/ArchitectureAdditionalCqrsHandlersTests.cs @@ -1,8 +1,8 @@ using System.Reflection; -using GFramework.Core.Abstractions.Cqrs; using GFramework.Core.Abstractions.Logging; using GFramework.Core.Architectures; using GFramework.Core.Logging; +using GFramework.Cqrs; using GFramework.Cqrs.Abstractions.Cqrs; namespace GFramework.Core.Tests.Architectures; diff --git a/GFramework.Core/Extensions/ContextAwareMediatorCommandExtensions.cs b/GFramework.Core/Extensions/ContextAwareMediatorCommandExtensions.cs index 4ab0692b..d001cb71 100644 --- a/GFramework.Core/Extensions/ContextAwareMediatorCommandExtensions.cs +++ b/GFramework.Core/Extensions/ContextAwareMediatorCommandExtensions.cs @@ -1,6 +1,7 @@ using System.ComponentModel; using GFramework.Core.Abstractions.Rule; using GFramework.Cqrs.Abstractions.Cqrs.Command; +using GFramework.Cqrs.Extensions; namespace GFramework.Core.Extensions; diff --git a/GFramework.Core/Extensions/ContextAwareMediatorExtensions.cs b/GFramework.Core/Extensions/ContextAwareMediatorExtensions.cs index f2294930..c7aec1b6 100644 --- a/GFramework.Core/Extensions/ContextAwareMediatorExtensions.cs +++ b/GFramework.Core/Extensions/ContextAwareMediatorExtensions.cs @@ -1,6 +1,7 @@ using System.ComponentModel; using GFramework.Core.Abstractions.Rule; using GFramework.Cqrs.Abstractions.Cqrs; +using GFramework.Cqrs.Extensions; namespace GFramework.Core.Extensions; diff --git a/GFramework.Core/Extensions/ContextAwareMediatorQueryExtensions.cs b/GFramework.Core/Extensions/ContextAwareMediatorQueryExtensions.cs index 61b02cb9..4eb1b2c7 100644 --- a/GFramework.Core/Extensions/ContextAwareMediatorQueryExtensions.cs +++ b/GFramework.Core/Extensions/ContextAwareMediatorQueryExtensions.cs @@ -1,6 +1,7 @@ using System.ComponentModel; using GFramework.Core.Abstractions.Rule; using GFramework.Cqrs.Abstractions.Cqrs.Query; +using GFramework.Cqrs.Extensions; namespace GFramework.Core.Extensions; diff --git a/GFramework.Core/GFramework.Core.csproj b/GFramework.Core/GFramework.Core.csproj index f3e41eab..2535d4e9 100644 --- a/GFramework.Core/GFramework.Core.csproj +++ b/GFramework.Core/GFramework.Core.csproj @@ -10,6 +10,7 @@ + diff --git a/GFramework.Core/Services/Modules/CqrsRuntimeModule.cs b/GFramework.Core/Services/Modules/CqrsRuntimeModule.cs index 5ca7a909..9e7ad307 100644 --- a/GFramework.Core/Services/Modules/CqrsRuntimeModule.cs +++ b/GFramework.Core/Services/Modules/CqrsRuntimeModule.cs @@ -1,8 +1,8 @@ using GFramework.Core.Abstractions.Architectures; using GFramework.Core.Abstractions.Cqrs; using GFramework.Core.Abstractions.Ioc; -using GFramework.Core.Cqrs.Internal; using GFramework.Core.Logging; +using GFramework.Cqrs; using GFramework.Cqrs.Abstractions.Cqrs; namespace GFramework.Core.Services.Modules; @@ -37,11 +37,12 @@ public sealed class CqrsRuntimeModule : IServiceModule { ArgumentNullException.ThrowIfNull(container); - var dispatcherLogger = LoggerFactoryResolver.Provider.CreateLogger(nameof(CqrsDispatcher)); - var registrarLogger = LoggerFactoryResolver.Provider.CreateLogger(nameof(DefaultCqrsHandlerRegistrar)); + var dispatcherLogger = LoggerFactoryResolver.Provider.CreateLogger("CqrsDispatcher"); + var registrarLogger = LoggerFactoryResolver.Provider.CreateLogger("DefaultCqrsHandlerRegistrar"); - container.Register(new CqrsDispatcher(container, dispatcherLogger)); - container.Register(new DefaultCqrsHandlerRegistrar(container, registrarLogger)); + container.Register(CqrsRuntimeFactory.CreateRuntime(container, dispatcherLogger)); + container.Register( + CqrsRuntimeFactory.CreateHandlerRegistrar(container, registrarLogger)); } /// diff --git a/GFramework.Cqrs.Tests/Cqrs/CqrsHandlerRegistrarTests.cs b/GFramework.Cqrs.Tests/Cqrs/CqrsHandlerRegistrarTests.cs index 360fe97b..3100ce84 100644 --- a/GFramework.Cqrs.Tests/Cqrs/CqrsHandlerRegistrarTests.cs +++ b/GFramework.Cqrs.Tests/Cqrs/CqrsHandlerRegistrarTests.cs @@ -1,4 +1,3 @@ -using GFramework.Core.Abstractions.Cqrs; using GFramework.Core.Abstractions.Logging; using GFramework.Core.Architectures; using GFramework.Core.Ioc; diff --git a/GFramework.Core/Cqrs/Command/CommandBase.cs b/GFramework.Cqrs/Command/CommandBase.cs similarity index 97% rename from GFramework.Core/Cqrs/Command/CommandBase.cs rename to GFramework.Cqrs/Command/CommandBase.cs index d8232608..57703c47 100644 --- a/GFramework.Core/Cqrs/Command/CommandBase.cs +++ b/GFramework.Cqrs/Command/CommandBase.cs @@ -13,7 +13,7 @@ using GFramework.Cqrs.Abstractions.Cqrs.Command; -namespace GFramework.Core.Cqrs.Command; +namespace GFramework.Cqrs.Command; /// /// 表示一个基础命令类,用于处理带有输入和响应的命令模式实现。 diff --git a/GFramework.Core.Abstractions/Cqrs/CqrsHandlerRegistryAttribute.cs b/GFramework.Cqrs/CqrsHandlerRegistryAttribute.cs similarity index 94% rename from GFramework.Core.Abstractions/Cqrs/CqrsHandlerRegistryAttribute.cs rename to GFramework.Cqrs/CqrsHandlerRegistryAttribute.cs index e073fe5c..a64875f3 100644 --- a/GFramework.Core.Abstractions/Cqrs/CqrsHandlerRegistryAttribute.cs +++ b/GFramework.Cqrs/CqrsHandlerRegistryAttribute.cs @@ -1,4 +1,4 @@ -namespace GFramework.Core.Abstractions.Cqrs; +namespace GFramework.Cqrs; /// /// 声明程序集内可供运行时直接调用的 CQRS 处理器注册器类型。 diff --git a/GFramework.Cqrs/CqrsRuntimeFactory.cs b/GFramework.Cqrs/CqrsRuntimeFactory.cs new file mode 100644 index 00000000..45dbb07a --- /dev/null +++ b/GFramework.Cqrs/CqrsRuntimeFactory.cs @@ -0,0 +1,45 @@ +using GFramework.Core.Abstractions.Cqrs; +using GFramework.Core.Abstractions.Ioc; +using GFramework.Core.Abstractions.Logging; +using GFramework.Cqrs.Abstractions.Cqrs; +using GFramework.Cqrs.Internal; + +namespace GFramework.Cqrs; + +/// +/// 提供 CQRS runtime 默认实现的跨程序集创建入口。 +/// +/// +/// 需要在不暴露内部实现细节的前提下接入默认 CQRS runtime, +/// 因此通过该工厂返回抽象接口,而不是直接公开内部 dispatcher / registrar 类型。 +/// +public static class CqrsRuntimeFactory +{ + /// + /// 创建默认 CQRS runtime 分发器。 + /// + /// 目标依赖注入容器。 + /// 用于 runtime 诊断的日志器。 + /// 默认 CQRS runtime。 + public static ICqrsRuntime CreateRuntime(IIocContainer container, ILogger logger) + { + ArgumentNullException.ThrowIfNull(container); + ArgumentNullException.ThrowIfNull(logger); + + return new CqrsDispatcher(container, logger); + } + + /// + /// 创建默认 CQRS 处理器注册器。 + /// + /// 目标依赖注入容器。 + /// 用于注册阶段诊断的日志器。 + /// 默认 CQRS handler registrar。 + public static ICqrsHandlerRegistrar CreateHandlerRegistrar(IIocContainer container, ILogger logger) + { + ArgumentNullException.ThrowIfNull(container); + ArgumentNullException.ThrowIfNull(logger); + + return new DefaultCqrsHandlerRegistrar(container, logger); + } +} diff --git a/GFramework.Core/Extensions/ContextAwareCqrsCommandExtensions.cs b/GFramework.Cqrs/Extensions/ContextAwareCqrsCommandExtensions.cs similarity index 98% rename from GFramework.Core/Extensions/ContextAwareCqrsCommandExtensions.cs rename to GFramework.Cqrs/Extensions/ContextAwareCqrsCommandExtensions.cs index f99fae25..b3c2bde8 100644 --- a/GFramework.Core/Extensions/ContextAwareCqrsCommandExtensions.cs +++ b/GFramework.Cqrs/Extensions/ContextAwareCqrsCommandExtensions.cs @@ -1,7 +1,7 @@ using GFramework.Core.Abstractions.Rule; using GFramework.Cqrs.Abstractions.Cqrs.Command; -namespace GFramework.Core.Extensions; +namespace GFramework.Cqrs.Extensions; /// /// 提供对 接口的 CQRS 命令扩展方法。 diff --git a/GFramework.Core/Extensions/ContextAwareCqrsExtensions.cs b/GFramework.Cqrs/Extensions/ContextAwareCqrsExtensions.cs similarity index 99% rename from GFramework.Core/Extensions/ContextAwareCqrsExtensions.cs rename to GFramework.Cqrs/Extensions/ContextAwareCqrsExtensions.cs index 6db156f5..ef5eb247 100644 --- a/GFramework.Core/Extensions/ContextAwareCqrsExtensions.cs +++ b/GFramework.Cqrs/Extensions/ContextAwareCqrsExtensions.cs @@ -1,7 +1,7 @@ using GFramework.Core.Abstractions.Rule; using GFramework.Cqrs.Abstractions.Cqrs; -namespace GFramework.Core.Extensions; +namespace GFramework.Cqrs.Extensions; /// /// 提供对 接口的 CQRS 统一扩展方法。 diff --git a/GFramework.Core/Extensions/ContextAwareCqrsQueryExtensions.cs b/GFramework.Cqrs/Extensions/ContextAwareCqrsQueryExtensions.cs similarity index 98% rename from GFramework.Core/Extensions/ContextAwareCqrsQueryExtensions.cs rename to GFramework.Cqrs/Extensions/ContextAwareCqrsQueryExtensions.cs index 40ec0f7d..21c1e952 100644 --- a/GFramework.Core/Extensions/ContextAwareCqrsQueryExtensions.cs +++ b/GFramework.Cqrs/Extensions/ContextAwareCqrsQueryExtensions.cs @@ -1,7 +1,7 @@ using GFramework.Core.Abstractions.Rule; using GFramework.Cqrs.Abstractions.Cqrs.Query; -namespace GFramework.Core.Extensions; +namespace GFramework.Cqrs.Extensions; /// /// 提供对 接口的 CQRS 查询扩展方法。 diff --git a/GFramework.Cqrs/GFramework.Cqrs.csproj b/GFramework.Cqrs/GFramework.Cqrs.csproj index 9f002283..86323d6e 100644 --- a/GFramework.Cqrs/GFramework.Cqrs.csproj +++ b/GFramework.Cqrs/GFramework.Cqrs.csproj @@ -11,6 +11,7 @@ + diff --git a/GFramework.Cqrs/GlobalUsings.cs b/GFramework.Cqrs/GlobalUsings.cs new file mode 100644 index 00000000..7f8c14d6 --- /dev/null +++ b/GFramework.Cqrs/GlobalUsings.cs @@ -0,0 +1,6 @@ +global using System; +global using System.Collections.Generic; +global using System.Linq; +global using System.Threading; +global using System.Threading.Tasks; +global using Microsoft.Extensions.DependencyInjection; diff --git a/GFramework.Core.Abstractions/Cqrs/ICqrsHandlerRegistry.cs b/GFramework.Cqrs/ICqrsHandlerRegistry.cs similarity index 95% rename from GFramework.Core.Abstractions/Cqrs/ICqrsHandlerRegistry.cs rename to GFramework.Cqrs/ICqrsHandlerRegistry.cs index 91af6be7..db3775de 100644 --- a/GFramework.Core.Abstractions/Cqrs/ICqrsHandlerRegistry.cs +++ b/GFramework.Cqrs/ICqrsHandlerRegistry.cs @@ -1,6 +1,6 @@ using GFramework.Core.Abstractions.Logging; -namespace GFramework.Core.Abstractions.Cqrs; +namespace GFramework.Cqrs; /// /// 定义由源码生成器产出的 CQRS 处理器注册器契约。 diff --git a/GFramework.Core/Cqrs/Internal/CqrsDispatcher.cs b/GFramework.Cqrs/Internal/CqrsDispatcher.cs similarity index 99% rename from GFramework.Core/Cqrs/Internal/CqrsDispatcher.cs rename to GFramework.Cqrs/Internal/CqrsDispatcher.cs index 0cae34c4..dafea402 100644 --- a/GFramework.Core/Cqrs/Internal/CqrsDispatcher.cs +++ b/GFramework.Cqrs/Internal/CqrsDispatcher.cs @@ -7,7 +7,7 @@ using GFramework.Core.Abstractions.Logging; using GFramework.Core.Abstractions.Rule; using GFramework.Cqrs.Abstractions.Cqrs; -namespace GFramework.Core.Cqrs.Internal; +namespace GFramework.Cqrs.Internal; /// /// GFramework 自有 CQRS 运行时分发器。 diff --git a/GFramework.Core/Cqrs/Internal/CqrsHandlerRegistrar.cs b/GFramework.Cqrs/Internal/CqrsHandlerRegistrar.cs similarity index 99% rename from GFramework.Core/Cqrs/Internal/CqrsHandlerRegistrar.cs rename to GFramework.Cqrs/Internal/CqrsHandlerRegistrar.cs index 65b1cbcf..85a95509 100644 --- a/GFramework.Core/Cqrs/Internal/CqrsHandlerRegistrar.cs +++ b/GFramework.Cqrs/Internal/CqrsHandlerRegistrar.cs @@ -1,10 +1,9 @@ using System.Reflection; -using GFramework.Core.Abstractions.Cqrs; using GFramework.Core.Abstractions.Ioc; using GFramework.Core.Abstractions.Logging; using GFramework.Cqrs.Abstractions.Cqrs; -namespace GFramework.Core.Cqrs.Internal; +namespace GFramework.Cqrs.Internal; /// /// 在架构初始化期间扫描并注册 CQRS 处理器。 diff --git a/GFramework.Core/Cqrs/Internal/DefaultCqrsHandlerRegistrar.cs b/GFramework.Cqrs/Internal/DefaultCqrsHandlerRegistrar.cs similarity index 96% rename from GFramework.Core/Cqrs/Internal/DefaultCqrsHandlerRegistrar.cs rename to GFramework.Cqrs/Internal/DefaultCqrsHandlerRegistrar.cs index ddf0c06a..70d33562 100644 --- a/GFramework.Core/Cqrs/Internal/DefaultCqrsHandlerRegistrar.cs +++ b/GFramework.Cqrs/Internal/DefaultCqrsHandlerRegistrar.cs @@ -3,7 +3,7 @@ using GFramework.Core.Abstractions.Ioc; using GFramework.Core.Abstractions.Logging; using GFramework.Cqrs.Abstractions.Cqrs; -namespace GFramework.Core.Cqrs.Internal; +namespace GFramework.Cqrs.Internal; /// /// 默认的 CQRS 处理器注册器实现。 diff --git a/GFramework.Core/Cqrs/Notification/NotificationBase.cs b/GFramework.Cqrs/Notification/NotificationBase.cs similarity index 96% rename from GFramework.Core/Cqrs/Notification/NotificationBase.cs rename to GFramework.Cqrs/Notification/NotificationBase.cs index 05db2de7..06390406 100644 --- a/GFramework.Core/Cqrs/Notification/NotificationBase.cs +++ b/GFramework.Cqrs/Notification/NotificationBase.cs @@ -14,7 +14,7 @@ using GFramework.Cqrs.Abstractions.Cqrs; using GFramework.Cqrs.Abstractions.Cqrs.Notification; -namespace GFramework.Core.Cqrs.Notification; +namespace GFramework.Cqrs.Notification; /// /// 表示一个基础通知类,用于处理带有输入的通知模式实现。 diff --git a/GFramework.Core/Cqrs/Query/QueryBase.cs b/GFramework.Cqrs/Query/QueryBase.cs similarity index 97% rename from GFramework.Core/Cqrs/Query/QueryBase.cs rename to GFramework.Cqrs/Query/QueryBase.cs index 759b8df1..d0f491b1 100644 --- a/GFramework.Core/Cqrs/Query/QueryBase.cs +++ b/GFramework.Cqrs/Query/QueryBase.cs @@ -13,7 +13,7 @@ using GFramework.Cqrs.Abstractions.Cqrs.Query; -namespace GFramework.Core.Cqrs.Query; +namespace GFramework.Cqrs.Query; /// /// 表示一个基础查询类,用于处理带有输入和响应的查询模式实现。 diff --git a/GFramework.Core/Cqrs/Request/RequestBase.cs b/GFramework.Cqrs/Request/RequestBase.cs similarity index 97% rename from GFramework.Core/Cqrs/Request/RequestBase.cs rename to GFramework.Cqrs/Request/RequestBase.cs index 5ff18a04..8c534c86 100644 --- a/GFramework.Core/Cqrs/Request/RequestBase.cs +++ b/GFramework.Cqrs/Request/RequestBase.cs @@ -14,7 +14,7 @@ using GFramework.Cqrs.Abstractions.Cqrs; using GFramework.Cqrs.Abstractions.Cqrs.Request; -namespace GFramework.Core.Cqrs.Request; +namespace GFramework.Cqrs.Request; /// /// 表示一个基础请求类,用于处理带有输入和响应的请求模式实现。 diff --git a/GFramework.Godot/Coroutine/ContextAwareCoroutineExtensions.cs b/GFramework.Godot/Coroutine/ContextAwareCoroutineExtensions.cs index 6805d2e7..15ee4561 100644 --- a/GFramework.Godot/Coroutine/ContextAwareCoroutineExtensions.cs +++ b/GFramework.Godot/Coroutine/ContextAwareCoroutineExtensions.cs @@ -1,10 +1,10 @@ using GFramework.Core.Abstractions.Rule; using GFramework.Core.Coroutine; using GFramework.Core.Coroutine.Extensions; -using GFramework.Core.Extensions; using GFramework.Cqrs.Abstractions.Cqrs; using GFramework.Cqrs.Abstractions.Cqrs.Command; using GFramework.Cqrs.Abstractions.Cqrs.Query; +using GFramework.Cqrs.Extensions; namespace GFramework.Godot.Coroutine; diff --git a/GFramework.SourceGenerators.Common/Constants/PathContests.cs b/GFramework.SourceGenerators.Common/Constants/PathContests.cs index 3facd8b6..a9416fbe 100644 --- a/GFramework.SourceGenerators.Common/Constants/PathContests.cs +++ b/GFramework.SourceGenerators.Common/Constants/PathContests.cs @@ -15,6 +15,11 @@ public static class PathContests /// public const string CoreNamespace = $"{BaseNamespace}.Core"; + /// + /// GFramework CQRS runtime 命名空间 + /// + public const string CqrsNamespace = $"{BaseNamespace}.Cqrs"; + /// /// GFramework Godot模块命名空间 /// @@ -45,4 +50,9 @@ public static class PathContests /// GFramework核心抽象层命名空间 /// public const string CoreAbstractionsNamespace = $"{CoreNamespace}.Abstractions"; -} \ No newline at end of file + + /// + /// GFramework CQRS 抽象层命名空间 + /// + public const string CqrsAbstractionsNamespace = $"{CqrsNamespace}.Abstractions"; +} diff --git a/GFramework.SourceGenerators.Tests/Cqrs/CqrsHandlerRegistryGeneratorTests.cs b/GFramework.SourceGenerators.Tests/Cqrs/CqrsHandlerRegistryGeneratorTests.cs index e2b7ffb1..0392ac8a 100644 --- a/GFramework.SourceGenerators.Tests/Cqrs/CqrsHandlerRegistryGeneratorTests.cs +++ b/GFramework.SourceGenerators.Tests/Cqrs/CqrsHandlerRegistryGeneratorTests.cs @@ -38,7 +38,7 @@ public class CqrsHandlerRegistryGeneratorTests } } - namespace GFramework.Core.Abstractions.Cqrs + namespace GFramework.Cqrs.Abstractions.Cqrs { public interface IRequest { } public interface INotification { } @@ -47,7 +47,10 @@ public class CqrsHandlerRegistryGeneratorTests public interface IRequestHandler where TRequest : IRequest { } public interface INotificationHandler where TNotification : INotification { } public interface IStreamRequestHandler where TRequest : IStreamRequest { } + } + namespace GFramework.Cqrs + { public interface ICqrsHandlerRegistry { void Register(Microsoft.Extensions.DependencyInjection.IServiceCollection services, GFramework.Core.Abstractions.Logging.ILogger logger); @@ -62,7 +65,7 @@ public class CqrsHandlerRegistryGeneratorTests namespace TestApp { - using GFramework.Core.Abstractions.Cqrs; + using GFramework.Cqrs.Abstractions.Cqrs; public sealed record PingQuery() : IRequest; public sealed record DomainEvent() : INotification; @@ -78,11 +81,11 @@ public class CqrsHandlerRegistryGeneratorTests // #nullable enable - [assembly: global::GFramework.Core.Abstractions.Cqrs.CqrsHandlerRegistryAttribute(typeof(global::GFramework.Generated.Cqrs.__GFrameworkGeneratedCqrsHandlerRegistry))] + [assembly: global::GFramework.Cqrs.CqrsHandlerRegistryAttribute(typeof(global::GFramework.Generated.Cqrs.__GFrameworkGeneratedCqrsHandlerRegistry))] namespace GFramework.Generated.Cqrs; - internal sealed class __GFrameworkGeneratedCqrsHandlerRegistry : global::GFramework.Core.Abstractions.Cqrs.ICqrsHandlerRegistry + internal sealed class __GFrameworkGeneratedCqrsHandlerRegistry : global::GFramework.Cqrs.ICqrsHandlerRegistry { public void Register(global::Microsoft.Extensions.DependencyInjection.IServiceCollection services, global::GFramework.Core.Abstractions.Logging.ILogger logger) { @@ -93,19 +96,19 @@ public class CqrsHandlerRegistryGeneratorTests global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddTransient( services, - typeof(global::GFramework.Core.Abstractions.Cqrs.IRequestHandler), + typeof(global::GFramework.Cqrs.Abstractions.Cqrs.IRequestHandler), typeof(global::TestApp.AlphaQueryHandler)); - logger.Debug("Registered CQRS handler TestApp.AlphaQueryHandler as GFramework.Core.Abstractions.Cqrs.IRequestHandler."); + logger.Debug("Registered CQRS handler TestApp.AlphaQueryHandler as GFramework.Cqrs.Abstractions.Cqrs.IRequestHandler."); global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddTransient( services, - typeof(global::GFramework.Core.Abstractions.Cqrs.IStreamRequestHandler), + typeof(global::GFramework.Cqrs.Abstractions.Cqrs.IStreamRequestHandler), typeof(global::TestApp.StreamHandler)); - logger.Debug("Registered CQRS handler TestApp.StreamHandler as GFramework.Core.Abstractions.Cqrs.IStreamRequestHandler."); + logger.Debug("Registered CQRS handler TestApp.StreamHandler as GFramework.Cqrs.Abstractions.Cqrs.IStreamRequestHandler."); global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddTransient( services, - typeof(global::GFramework.Core.Abstractions.Cqrs.INotificationHandler), + typeof(global::GFramework.Cqrs.Abstractions.Cqrs.INotificationHandler), typeof(global::TestApp.ZetaNotificationHandler)); - logger.Debug("Registered CQRS handler TestApp.ZetaNotificationHandler as GFramework.Core.Abstractions.Cqrs.INotificationHandler."); + logger.Debug("Registered CQRS handler TestApp.ZetaNotificationHandler as GFramework.Cqrs.Abstractions.Cqrs.INotificationHandler."); } } @@ -143,7 +146,7 @@ public class CqrsHandlerRegistryGeneratorTests } } - namespace GFramework.Core.Abstractions.Cqrs + namespace GFramework.Cqrs.Abstractions.Cqrs { public interface IRequest { } public interface INotification { } @@ -152,7 +155,10 @@ public class CqrsHandlerRegistryGeneratorTests public interface IRequestHandler where TRequest : IRequest { } public interface INotificationHandler where TNotification : INotification { } public interface IStreamRequestHandler where TRequest : IStreamRequest { } + } + namespace GFramework.Cqrs + { public interface ICqrsHandlerRegistry { void Register(Microsoft.Extensions.DependencyInjection.IServiceCollection services, GFramework.Core.Abstractions.Logging.ILogger logger); @@ -167,7 +173,7 @@ public class CqrsHandlerRegistryGeneratorTests namespace TestApp { - using GFramework.Core.Abstractions.Cqrs; + using GFramework.Cqrs.Abstractions.Cqrs; public sealed record VisibleRequest() : IRequest; diff --git a/GFramework.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.cs b/GFramework.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.cs index 39c3aadb..65aad69a 100644 --- a/GFramework.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.cs +++ b/GFramework.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.cs @@ -8,13 +8,17 @@ namespace GFramework.SourceGenerators.Cqrs; [Generator] public sealed class CqrsHandlerRegistryGenerator : IIncrementalGenerator { - private const string CqrsNamespace = $"{PathContests.CoreAbstractionsNamespace}.Cqrs"; + private const string CqrsContractsNamespace = $"{PathContests.CqrsAbstractionsNamespace}.Cqrs"; + private const string CqrsRuntimeNamespace = PathContests.CqrsNamespace; private const string LoggingNamespace = $"{PathContests.CoreAbstractionsNamespace}.Logging"; - private const string IRequestHandlerMetadataName = $"{CqrsNamespace}.IRequestHandler`2"; - private const string INotificationHandlerMetadataName = $"{CqrsNamespace}.INotificationHandler`1"; - private const string IStreamRequestHandlerMetadataName = $"{CqrsNamespace}.IStreamRequestHandler`2"; - private const string ICqrsHandlerRegistryMetadataName = $"{CqrsNamespace}.ICqrsHandlerRegistry"; - private const string CqrsHandlerRegistryAttributeMetadataName = $"{CqrsNamespace}.CqrsHandlerRegistryAttribute"; + private const string IRequestHandlerMetadataName = $"{CqrsContractsNamespace}.IRequestHandler`2"; + private const string INotificationHandlerMetadataName = $"{CqrsContractsNamespace}.INotificationHandler`1"; + private const string IStreamRequestHandlerMetadataName = $"{CqrsContractsNamespace}.IStreamRequestHandler`2"; + private const string ICqrsHandlerRegistryMetadataName = $"{CqrsRuntimeNamespace}.ICqrsHandlerRegistry"; + + private const string CqrsHandlerRegistryAttributeMetadataName = + $"{CqrsRuntimeNamespace}.CqrsHandlerRegistryAttribute"; + private const string ILoggerMetadataName = $"{LoggingNamespace}.ILogger"; private const string IServiceCollectionMetadataName = "Microsoft.Extensions.DependencyInjection.IServiceCollection"; private const string GeneratedNamespace = "GFramework.Generated.Cqrs"; @@ -273,7 +277,7 @@ public sealed class CqrsHandlerRegistryGenerator : IIncrementalGenerator builder.AppendLine("#nullable enable"); builder.AppendLine(); builder.Append("[assembly: global::"); - builder.Append(CqrsNamespace); + builder.Append(CqrsRuntimeNamespace); builder.Append(".CqrsHandlerRegistryAttribute(typeof(global::"); builder.Append(GeneratedNamespace); builder.Append('.'); @@ -287,7 +291,7 @@ public sealed class CqrsHandlerRegistryGenerator : IIncrementalGenerator builder.Append("internal sealed class "); builder.Append(GeneratedTypeName); builder.Append(" : global::"); - builder.Append(CqrsNamespace); + builder.Append(CqrsRuntimeNamespace); builder.AppendLine(".ICqrsHandlerRegistry"); builder.AppendLine("{"); builder.Append( diff --git a/GFramework.Tests.Common/CqrsTestRuntime.cs b/GFramework.Tests.Common/CqrsTestRuntime.cs index 57d4effd..f5913c6e 100644 --- a/GFramework.Tests.Common/CqrsTestRuntime.cs +++ b/GFramework.Tests.Common/CqrsTestRuntime.cs @@ -4,10 +4,11 @@ using System.Reflection; using GFramework.Core.Abstractions.Cqrs; using GFramework.Core.Abstractions.Ioc; using GFramework.Core.Abstractions.Logging; -using GFramework.Core.Architectures; using GFramework.Core.Ioc; using GFramework.Core.Logging; +using GFramework.Cqrs; using GFramework.Cqrs.Abstractions.Cqrs; +using GFramework.Cqrs.Command; namespace GFramework.Tests.Common; @@ -20,7 +21,9 @@ namespace GFramework.Tests.Common; /// public static class CqrsTestRuntime { - private static readonly Type CqrsHandlerRegistrarType = typeof(ArchitectureContext).Assembly + private static readonly Assembly CqrsRuntimeAssembly = typeof(CommandBase<,>).Assembly; + + private static readonly Type CqrsHandlerRegistrarType = CqrsRuntimeAssembly .GetType( "GFramework.Core.Cqrs.Internal.CqrsHandlerRegistrar", throwOnError: true)!; @@ -40,41 +43,6 @@ public static class CqrsTestRuntime ?? throw new InvalidOperationException( "Failed to locate CqrsHandlerRegistrar.RegisterHandlers."); - private static readonly Type CqrsDispatcherType = typeof(ArchitectureContext).Assembly - .GetType( - "GFramework.Core.Cqrs.Internal.CqrsDispatcher", - throwOnError: true)!; - - private static readonly ConstructorInfo CqrsDispatcherConstructor = CqrsDispatcherType.GetConstructor( - BindingFlags.Instance | - BindingFlags.Public | - BindingFlags.NonPublic, - binder: null, - [ - typeof(IIocContainer), - typeof(ILogger) - ], - modifiers: null) - ?? throw new InvalidOperationException( - "Failed to locate CqrsDispatcher constructor."); - - private static readonly Type DefaultCqrsHandlerRegistrarType = typeof(ArchitectureContext).Assembly - .GetType( - "GFramework.Core.Cqrs.Internal.DefaultCqrsHandlerRegistrar", - throwOnError: true)!; - - private static readonly ConstructorInfo DefaultCqrsHandlerRegistrarConstructor = - DefaultCqrsHandlerRegistrarType.GetConstructor( - BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, - binder: null, - [ - typeof(IIocContainer), - typeof(ILogger) - ], - modifiers: null) - ?? throw new InvalidOperationException( - "Failed to locate DefaultCqrsHandlerRegistrar constructor."); - /// /// 为裸测试容器补齐默认 CQRS runtime seam。 /// @@ -92,16 +60,15 @@ public static class CqrsTestRuntime if (container.Get() is null) { - var runtimeLogger = LoggerFactoryResolver.Provider.CreateLogger(CqrsDispatcherType.Name); - var runtime = (ICqrsRuntime)CqrsDispatcherConstructor.Invoke([container, runtimeLogger]); + var runtimeLogger = LoggerFactoryResolver.Provider.CreateLogger("CqrsDispatcher"); + var runtime = CqrsRuntimeFactory.CreateRuntime(container, runtimeLogger); container.Register(runtime); } if (container.Get() is null) { - var registrarLogger = LoggerFactoryResolver.Provider.CreateLogger(DefaultCqrsHandlerRegistrarType.Name); - var registrar = - (ICqrsHandlerRegistrar)DefaultCqrsHandlerRegistrarConstructor.Invoke([container, registrarLogger]); + var registrarLogger = LoggerFactoryResolver.Provider.CreateLogger("DefaultCqrsHandlerRegistrar"); + var registrar = CqrsRuntimeFactory.CreateHandlerRegistrar(container, registrarLogger); container.Register(registrar); } } diff --git a/GFramework.Tests.Common/GFramework.Tests.Common.csproj b/GFramework.Tests.Common/GFramework.Tests.Common.csproj index 51f8fba9..ddd8c02c 100644 --- a/GFramework.Tests.Common/GFramework.Tests.Common.csproj +++ b/GFramework.Tests.Common/GFramework.Tests.Common.csproj @@ -10,6 +10,7 @@ + From e36c80229a8719de411a7a4be42a2b3d947fd90f Mon Sep 17 00:00:00 2001 From: GeWuYou <95328647+GeWuYou@users.noreply.github.com> Date: Wed, 15 Apr 2026 20:34:08 +0800 Subject: [PATCH 02/12] =?UTF-8?q?feat(cqrs):=20=E6=B7=BB=E5=8A=A0CQRS?= =?UTF-8?q?=E5=A4=84=E7=90=86=E5=99=A8=E5=9F=BA=E7=B1=BB=E5=92=8C=E6=B5=8B?= =?UTF-8?q?=E8=AF=95=E8=BF=90=E8=A1=8C=E6=97=B6=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 引入AbstractCommandHandler、AbstractQueryHandler等各类处理器基类 - 实现CqrsContextAwareHandlerBase提供上下文感知功能 - 添加CqrsTestRuntime为测试项目提供CQRS运行时访问入口 - 创建AbstractCqrsHandlerContextTests验证上下文注入行为 - 支持命令、查询、通知及流式处理的各种抽象基类实现 --- .../Cqrs/AbstractCqrsHandlerContextTests.cs | 74 +++++++++++++++++++ .../Cqrs/Command/AbstractCommandHandler.cs | 12 +-- .../Command/AbstractStreamCommandHandler.cs | 7 +- .../Cqrs/CqrsContextAwareHandlerBase.cs | 59 +++++++++++++++ .../AbstractNotificationHandler.cs | 8 +- .../Cqrs/Query/AbstractQueryHandler.cs | 8 +- .../Cqrs/Query/AbstractStreamQueryHandler.cs | 7 +- .../Cqrs/Request/AbstractRequestHandler.cs | 11 ++- .../Request/AbstractStreamRequestHandler.cs | 7 +- GFramework.Tests.Common/CqrsTestRuntime.cs | 2 +- 10 files changed, 162 insertions(+), 33 deletions(-) create mode 100644 GFramework.Cqrs.Tests/Cqrs/AbstractCqrsHandlerContextTests.cs rename {GFramework.Core => GFramework.Cqrs}/Cqrs/Command/AbstractCommandHandler.cs (83%) rename {GFramework.Core => GFramework.Cqrs}/Cqrs/Command/AbstractStreamCommandHandler.cs (92%) create mode 100644 GFramework.Cqrs/Cqrs/CqrsContextAwareHandlerBase.cs rename {GFramework.Core => GFramework.Cqrs}/Cqrs/Notification/AbstractNotificationHandler.cs (85%) rename {GFramework.Core => GFramework.Cqrs}/Cqrs/Query/AbstractQueryHandler.cs (83%) rename {GFramework.Core => GFramework.Cqrs}/Cqrs/Query/AbstractStreamQueryHandler.cs (89%) rename {GFramework.Core => GFramework.Cqrs}/Cqrs/Request/AbstractRequestHandler.cs (86%) rename {GFramework.Core => GFramework.Cqrs}/Cqrs/Request/AbstractStreamRequestHandler.cs (88%) diff --git a/GFramework.Cqrs.Tests/Cqrs/AbstractCqrsHandlerContextTests.cs b/GFramework.Cqrs.Tests/Cqrs/AbstractCqrsHandlerContextTests.cs new file mode 100644 index 00000000..58e86f89 --- /dev/null +++ b/GFramework.Cqrs.Tests/Cqrs/AbstractCqrsHandlerContextTests.cs @@ -0,0 +1,74 @@ +using GFramework.Core.Abstractions.Architectures; +using GFramework.Core.Abstractions.Rule; +using GFramework.Cqrs.Abstractions.Cqrs; +using GFramework.Cqrs.Abstractions.Cqrs.Command; +using GFramework.Cqrs.Cqrs.Command; + +namespace GFramework.Cqrs.Tests.Cqrs; + +/// +/// 验证 CQRS handler 基类在脱离 dispatcher 使用时会显式失败,并在注入上下文后保持可观察行为。 +/// +[TestFixture] +internal sealed class AbstractCqrsHandlerContextTests +{ + /// + /// 验证新的轻量 handler 基类不会再偷偷回退到全局 GameContext。 + /// + [Test] + public void GetContext_Should_Throw_When_Handler_Has_Not_Been_Initialized_By_Runtime() + { + var handler = new TestCommandHandler(); + + var exception = Assert.Throws(() => ((IContextAware)handler).GetContext()); + + Assert.That( + exception!.Message, + Does.Contain("has not been initialized").IgnoreCase); + } + + /// + /// 验证 runtime 注入上下文后,派生 handler 可以继续访问 Context 并收到 OnContextReady 回调。 + /// + [Test] + public async Task Handle_Should_Observe_Injected_Context_And_OnContextReady_Callback() + { + var handler = new TestCommandHandler(); + var context = new Mock(MockBehavior.Strict).Object; + + ((IContextAware)handler).SetContext(context); + await handler.Handle(new TestCommand(), CancellationToken.None); + + Assert.Multiple(() => + { + Assert.That(handler.OnContextReadyCallCount, Is.EqualTo(1)); + Assert.That(handler.LastObservedContext, Is.SameAs(context)); + }); + } + + /// + /// 用于验证上下文注入行为的最小 CQRS 命令。 + /// + private sealed record TestCommand : ICommand; + + /// + /// 暴露基类上下文访问与初始化回调的测试处理器。 + /// + private sealed class TestCommandHandler : AbstractCommandHandler + { + public int OnContextReadyCallCount { get; private set; } + + public IArchitectureContext? LastObservedContext { get; private set; } + + protected override void OnContextReady() + { + OnContextReadyCallCount++; + } + + public override ValueTask Handle(TestCommand command, CancellationToken cancellationToken) + { + LastObservedContext = Context; + return ValueTask.FromResult(Unit.Value); + } + } +} diff --git a/GFramework.Core/Cqrs/Command/AbstractCommandHandler.cs b/GFramework.Cqrs/Cqrs/Command/AbstractCommandHandler.cs similarity index 83% rename from GFramework.Core/Cqrs/Command/AbstractCommandHandler.cs rename to GFramework.Cqrs/Cqrs/Command/AbstractCommandHandler.cs index d7ebb117..825737bf 100644 --- a/GFramework.Core/Cqrs/Command/AbstractCommandHandler.cs +++ b/GFramework.Cqrs/Cqrs/Command/AbstractCommandHandler.cs @@ -11,19 +11,18 @@ // See the License for the specific language governing permissions and // limitations under the License. -using GFramework.Core.Rule; using GFramework.Cqrs.Abstractions.Cqrs; using GFramework.Cqrs.Abstractions.Cqrs.Command; -namespace GFramework.Core.Cqrs.Command; +namespace GFramework.Cqrs.Cqrs.Command; /// /// 抽象命令处理器基类 -/// 继承自 ContextAwareBase 并实现 IRequestHandler 接口,为具体的命令处理器提供基础功能。 +/// 继承自轻量 CQRS 上下文基类并实现 IRequestHandler 接口,为具体的命令处理器提供基础功能。 /// 框架会在每次分发前注入当前架构上下文,因此派生类可以通过 Context 访问架构级服务。 /// /// 命令类型 -public abstract class AbstractCommandHandler : ContextAwareBase, IRequestHandler +public abstract class AbstractCommandHandler : CqrsContextAwareHandlerBase, IRequestHandler where TCommand : ICommand { /// @@ -38,12 +37,13 @@ public abstract class AbstractCommandHandler : ContextAwareBase, IRequ /// /// 抽象命令处理器基类(带返回值版本) -/// 继承自 ContextAwareBase 并实现 IRequestHandler 接口,为具体的命令处理器提供基础功能。 +/// 继承自轻量 CQRS 上下文基类并实现 IRequestHandler 接口,为具体的命令处理器提供基础功能。 /// 支持泛型命令和结果类型,框架会在每次分发前注入当前架构上下文。 /// /// 命令类型,必须实现ICommand接口 /// 命令执行结果类型 -public abstract class AbstractCommandHandler : ContextAwareBase, IRequestHandler +public abstract class AbstractCommandHandler : CqrsContextAwareHandlerBase, + IRequestHandler where TCommand : ICommand { /// diff --git a/GFramework.Core/Cqrs/Command/AbstractStreamCommandHandler.cs b/GFramework.Cqrs/Cqrs/Command/AbstractStreamCommandHandler.cs similarity index 92% rename from GFramework.Core/Cqrs/Command/AbstractStreamCommandHandler.cs rename to GFramework.Cqrs/Cqrs/Command/AbstractStreamCommandHandler.cs index 223a9cc5..cb4ccb6b 100644 --- a/GFramework.Core/Cqrs/Command/AbstractStreamCommandHandler.cs +++ b/GFramework.Cqrs/Cqrs/Command/AbstractStreamCommandHandler.cs @@ -11,15 +11,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -using GFramework.Core.Rule; using GFramework.Cqrs.Abstractions.Cqrs; using GFramework.Cqrs.Abstractions.Cqrs.Command; -namespace GFramework.Core.Cqrs.Command; +namespace GFramework.Cqrs.Cqrs.Command; /// /// 抽象流式命令处理器基类。 -/// 继承自 并实现 , +/// 继承自轻量 CQRS 上下文基类并实现 , /// 为具体的流式命令处理器提供基础功能。 /// /// 流式命令类型,必须实现 @@ -32,7 +31,7 @@ namespace GFramework.Core.Cqrs.Command; /// 传入 的取消令牌同时约束流的创建与后续枚举, /// 派生类应在启动阶段和每次生成响应前尊重取消请求,避免在调用方停止枚举后继续执行后台工作。 /// -public abstract class AbstractStreamCommandHandler : ContextAwareBase, +public abstract class AbstractStreamCommandHandler : CqrsContextAwareHandlerBase, IStreamRequestHandler where TCommand : IStreamCommand { diff --git a/GFramework.Cqrs/Cqrs/CqrsContextAwareHandlerBase.cs b/GFramework.Cqrs/Cqrs/CqrsContextAwareHandlerBase.cs new file mode 100644 index 00000000..73e25ff1 --- /dev/null +++ b/GFramework.Cqrs/Cqrs/CqrsContextAwareHandlerBase.cs @@ -0,0 +1,59 @@ +using GFramework.Core.Abstractions.Architectures; +using GFramework.Core.Abstractions.Rule; + +namespace GFramework.Cqrs.Cqrs; + +/// +/// 为 CQRS 处理器提供最小化的上下文感知基类实现。 +/// +/// +/// 该基类只承接 CQRS runtime 在分发前注入的 , +/// 不再像 ContextAwareBase 那样回退到 GameContext 全局查找。 +/// 这样可以让 GFramework.Cqrs 保持对 GFramework.Core 运行时实现的零依赖, +/// 同时在处理器被错误地脱离 dispatcher 使用时以显式异常快速失败。 +/// +public abstract class CqrsContextAwareHandlerBase : IContextAware +{ + private IArchitectureContext? _context; + + /// + /// 获取当前分发周期内已注入的架构上下文。 + /// + /// + /// 当前处理器尚未被 CQRS runtime 注入上下文。 + /// + protected IArchitectureContext Context => _context ?? throw new InvalidOperationException( + "The CQRS handler context has not been initialized. Ensure the handler is invoked through the CQRS runtime."); + + /// + /// 由 runtime 在分发前注入当前架构上下文。 + /// + /// 当前架构上下文。 + void IContextAware.SetContext(IArchitectureContext context) + { + ArgumentNullException.ThrowIfNull(context); + + _context = context; + OnContextReady(); + } + + /// + /// 获取当前处理器实例已绑定的架构上下文。 + /// + /// 当前分发周期内的架构上下文。 + IArchitectureContext IContextAware.GetContext() + { + return Context; + } + + /// + /// 当上下文注入完成后执行额外初始化。 + /// + /// + /// 该钩子保留与旧 ContextAwareBase 相近的扩展点, + /// 便于处理器在迁移后继续承接分发前的派生类初始化逻辑。 + /// + protected virtual void OnContextReady() + { + } +} diff --git a/GFramework.Core/Cqrs/Notification/AbstractNotificationHandler.cs b/GFramework.Cqrs/Cqrs/Notification/AbstractNotificationHandler.cs similarity index 85% rename from GFramework.Core/Cqrs/Notification/AbstractNotificationHandler.cs rename to GFramework.Cqrs/Cqrs/Notification/AbstractNotificationHandler.cs index da6f5281..16246d16 100644 --- a/GFramework.Core/Cqrs/Notification/AbstractNotificationHandler.cs +++ b/GFramework.Cqrs/Cqrs/Notification/AbstractNotificationHandler.cs @@ -11,18 +11,18 @@ // See the License for the specific language governing permissions and // limitations under the License. -using GFramework.Core.Rule; using GFramework.Cqrs.Abstractions.Cqrs; -namespace GFramework.Core.Cqrs.Notification; +namespace GFramework.Cqrs.Cqrs.Notification; /// /// 抽象通知处理器基类 -/// 继承自ContextAwareBase并实现INotificationHandler接口,为具体的通知处理器提供基础功能 +/// 继承自轻量 CQRS 上下文基类并实现INotificationHandler接口,为具体的通知处理器提供基础功能 /// 用于处理CQRS模式中的通知消息,支持异步处理 /// /// 通知类型,必须实现INotification接口 -public abstract class AbstractNotificationHandler : ContextAwareBase, INotificationHandler +public abstract class AbstractNotificationHandler : CqrsContextAwareHandlerBase, + INotificationHandler where TNotification : INotification { /// diff --git a/GFramework.Core/Cqrs/Query/AbstractQueryHandler.cs b/GFramework.Cqrs/Cqrs/Query/AbstractQueryHandler.cs similarity index 83% rename from GFramework.Core/Cqrs/Query/AbstractQueryHandler.cs rename to GFramework.Cqrs/Cqrs/Query/AbstractQueryHandler.cs index 85c86425..5096f4b7 100644 --- a/GFramework.Core/Cqrs/Query/AbstractQueryHandler.cs +++ b/GFramework.Cqrs/Cqrs/Query/AbstractQueryHandler.cs @@ -11,20 +11,20 @@ // See the License for the specific language governing permissions and // limitations under the License. -using GFramework.Core.Rule; using GFramework.Cqrs.Abstractions.Cqrs; using GFramework.Cqrs.Abstractions.Cqrs.Query; -namespace GFramework.Core.Cqrs.Query; +namespace GFramework.Cqrs.Cqrs.Query; /// /// 抽象查询处理器基类 -/// 继承自 ContextAwareBase 并实现 IRequestHandler 接口,为具体的查询处理器提供基础功能。 +/// 继承自轻量 CQRS 上下文基类并实现 IRequestHandler 接口,为具体的查询处理器提供基础功能。 /// 框架会在每次分发前注入当前架构上下文,因此派生类可以通过 Context 访问架构级服务。 /// /// 查询类型,必须实现IQuery接口 /// 查询结果类型 -public abstract class AbstractQueryHandler : ContextAwareBase, IRequestHandler +public abstract class AbstractQueryHandler : CqrsContextAwareHandlerBase, + IRequestHandler where TQuery : IQuery { /// diff --git a/GFramework.Core/Cqrs/Query/AbstractStreamQueryHandler.cs b/GFramework.Cqrs/Cqrs/Query/AbstractStreamQueryHandler.cs similarity index 89% rename from GFramework.Core/Cqrs/Query/AbstractStreamQueryHandler.cs rename to GFramework.Cqrs/Cqrs/Query/AbstractStreamQueryHandler.cs index 9695dc42..7d301009 100644 --- a/GFramework.Core/Cqrs/Query/AbstractStreamQueryHandler.cs +++ b/GFramework.Cqrs/Cqrs/Query/AbstractStreamQueryHandler.cs @@ -11,20 +11,19 @@ // See the License for the specific language governing permissions and // limitations under the License. -using GFramework.Core.Rule; using GFramework.Cqrs.Abstractions.Cqrs; using GFramework.Cqrs.Abstractions.Cqrs.Query; -namespace GFramework.Core.Cqrs.Query; +namespace GFramework.Cqrs.Cqrs.Query; /// /// 抽象流式查询处理器基类 -/// 继承自ContextAwareBase并实现IStreamQueryHandler接口,为具体的流式查询处理器提供基础功能 +/// 继承自轻量 CQRS 上下文基类并实现IStreamQueryHandler接口,为具体的流式查询处理器提供基础功能 /// 支持流式处理查询并产生异步可枚举的响应序列,适用于大数据量或实时数据查询场景 /// /// 流式查询类型,必须实现IStreamQuery接口 /// 流式查询响应元素类型 -public abstract class AbstractStreamQueryHandler : ContextAwareBase, +public abstract class AbstractStreamQueryHandler : CqrsContextAwareHandlerBase, IStreamRequestHandler where TQuery : IStreamQuery { diff --git a/GFramework.Core/Cqrs/Request/AbstractRequestHandler.cs b/GFramework.Cqrs/Cqrs/Request/AbstractRequestHandler.cs similarity index 86% rename from GFramework.Core/Cqrs/Request/AbstractRequestHandler.cs rename to GFramework.Cqrs/Cqrs/Request/AbstractRequestHandler.cs index f26c7cfa..8343576b 100644 --- a/GFramework.Core/Cqrs/Request/AbstractRequestHandler.cs +++ b/GFramework.Cqrs/Cqrs/Request/AbstractRequestHandler.cs @@ -11,17 +11,16 @@ // See the License for the specific language governing permissions and // limitations under the License. -using GFramework.Core.Rule; using GFramework.Cqrs.Abstractions.Cqrs; -namespace GFramework.Core.Cqrs.Request; +namespace GFramework.Cqrs.Cqrs.Request; /// /// 抽象请求处理器基类,用于处理不返回具体响应的请求 -/// 继承自ContextAwareBase并实现IRequestHandler接口 +/// 继承自轻量 CQRS 上下文基类并实现IRequestHandler接口 /// /// 请求类型,必须实现IRequest[Unit]接口 -public abstract class AbstractRequestHandler : ContextAwareBase, IRequestHandler +public abstract class AbstractRequestHandler : CqrsContextAwareHandlerBase, IRequestHandler where TRequest : IRequest { /// @@ -35,11 +34,11 @@ public abstract class AbstractRequestHandler : ContextAwareBase, IRequ /// /// 抽象请求处理器基类,用于处理需要返回具体响应的请求 -/// 继承自ContextAwareBase并实现IRequestHandler接口 +/// 继承自轻量 CQRS 上下文基类并实现IRequestHandler接口 /// /// 请求类型,必须实现IRequest[TResponse]接口 /// 响应类型 -public abstract class AbstractRequestHandler : ContextAwareBase, +public abstract class AbstractRequestHandler : CqrsContextAwareHandlerBase, IRequestHandler where TRequest : IRequest { /// diff --git a/GFramework.Core/Cqrs/Request/AbstractStreamRequestHandler.cs b/GFramework.Cqrs/Cqrs/Request/AbstractStreamRequestHandler.cs similarity index 88% rename from GFramework.Core/Cqrs/Request/AbstractStreamRequestHandler.cs rename to GFramework.Cqrs/Cqrs/Request/AbstractStreamRequestHandler.cs index 2cbf438d..42b968f4 100644 --- a/GFramework.Core/Cqrs/Request/AbstractStreamRequestHandler.cs +++ b/GFramework.Cqrs/Cqrs/Request/AbstractStreamRequestHandler.cs @@ -11,19 +11,18 @@ // See the License for the specific language governing permissions and // limitations under the License. -using GFramework.Core.Rule; using GFramework.Cqrs.Abstractions.Cqrs; -namespace GFramework.Core.Cqrs.Request; +namespace GFramework.Cqrs.Cqrs.Request; /// /// 抽象流式请求处理器基类 -/// 继承自ContextAwareBase并实现IStreamRequestHandler接口,为具体的流式请求处理器提供基础功能 +/// 继承自轻量 CQRS 上下文基类并实现IStreamRequestHandler接口,为具体的流式请求处理器提供基础功能 /// 支持流式处理请求并产生异步可枚举的响应序列,适用于需要逐步返回结果的请求处理场景 /// /// 流式请求类型,必须实现IStreamRequest接口 /// 流式请求响应元素类型 -public abstract class AbstractStreamRequestHandler : ContextAwareBase, +public abstract class AbstractStreamRequestHandler : CqrsContextAwareHandlerBase, IStreamRequestHandler where TRequest : IStreamRequest { diff --git a/GFramework.Tests.Common/CqrsTestRuntime.cs b/GFramework.Tests.Common/CqrsTestRuntime.cs index f5913c6e..f044bbc8 100644 --- a/GFramework.Tests.Common/CqrsTestRuntime.cs +++ b/GFramework.Tests.Common/CqrsTestRuntime.cs @@ -25,7 +25,7 @@ public static class CqrsTestRuntime private static readonly Type CqrsHandlerRegistrarType = CqrsRuntimeAssembly .GetType( - "GFramework.Core.Cqrs.Internal.CqrsHandlerRegistrar", + "GFramework.Cqrs.Internal.CqrsHandlerRegistrar", throwOnError: true)!; private static readonly MethodInfo RegisterHandlersMethod = CqrsHandlerRegistrarType From 1c7558aeb829970e8a7bd063f3ed4145fc2d57ab Mon Sep 17 00:00:00 2001 From: GeWuYou <95328647+GeWuYou@users.noreply.github.com> Date: Wed, 15 Apr 2026 21:40:58 +0800 Subject: [PATCH 03/12] =?UTF-8?q?refactor(cqrs):=20=E7=A7=BB=E9=99=A4?= =?UTF-8?q?=E6=97=A7=E6=97=A5=E5=BF=97=E8=A1=8C=E4=B8=BA=E5=B9=B6=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0CQRS=E8=BF=90=E8=A1=8C=E6=97=B6=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 删除 LoggingBehavior 类及其相关实现 - 新增 CqrsRuntimeModule 用于注册CQRS运行时组件 - 添加 ArchitectureComponentRegistryBehaviorTests 测试组件注册行为 - 添加 ArchitectureContextTests 测试架构上下文功能 - 完善CQRS运行时的并发安全性和生命周期管理 --- .../Logging/LoggerFactoryResolver.cs | 250 ++++++++++++++++++ ...hitectureComponentRegistryBehaviorTests.cs | 4 +- .../Architectures/ArchitectureContextTests.cs | 1 + ...ArchitectureInitializationPipelineTests.cs | 4 +- .../ArchitectureLifecycleBehaviorTests.cs | 4 +- .../ArchitectureModulesBehaviorTests.cs | 1 + .../Architectures/PriorityServiceTests.cs | 3 +- .../Ioc/MicrosoftDiContainerTests.cs | 1 + .../State/StateMachineSystemTests.cs | 3 +- .../Logging/LoggerFactoryResolver.cs | 26 -- GFramework.Core/Properties/TypeForwarders.cs | 4 + .../Services/Modules/CqrsRuntimeModule.cs | 2 +- GFramework.Cqrs.Tests/GlobalUsings.cs | 1 + .../Mediator/MediatorAdvancedFeaturesTests.cs | 1 + .../MediatorArchitectureIntegrationTests.cs | 1 + .../Mediator/MediatorComprehensiveTests.cs | 1 + .../Cqrs/Behaviors/LoggingBehavior.cs | 26 +- .../Cqrs/Behaviors/PerformanceBehavior.cs | 32 +-- GFramework.Cqrs/GlobalUsings.cs | 1 + 19 files changed, 302 insertions(+), 64 deletions(-) create mode 100644 GFramework.Core.Abstractions/Logging/LoggerFactoryResolver.cs delete mode 100644 GFramework.Core/Logging/LoggerFactoryResolver.cs create mode 100644 GFramework.Core/Properties/TypeForwarders.cs rename {GFramework.Core => GFramework.Cqrs}/Cqrs/Behaviors/LoggingBehavior.cs (71%) rename {GFramework.Core => GFramework.Cqrs}/Cqrs/Behaviors/PerformanceBehavior.cs (61%) diff --git a/GFramework.Core.Abstractions/Logging/LoggerFactoryResolver.cs b/GFramework.Core.Abstractions/Logging/LoggerFactoryResolver.cs new file mode 100644 index 00000000..a87a18da --- /dev/null +++ b/GFramework.Core.Abstractions/Logging/LoggerFactoryResolver.cs @@ -0,0 +1,250 @@ +namespace GFramework.Core.Abstractions.Logging; + +/// +/// 提供全局日志工厂访问入口。 +/// +/// +/// 该类型位于抽象层,是为了让上层模块可以在不依赖 GFramework.Core 实现程序集的前提下 +/// 获取日志记录器。默认 provider 会优先通过反射解析 GFramework.Core 中的控制台实现, +/// 若宿主未加载该程序集,则退回到静默 provider,避免抽象层形成实现层循环依赖。 +/// +public static class LoggerFactoryResolver +{ + private const string DefaultProviderTypeName = + "GFramework.Core.Logging.ConsoleLoggerFactoryProvider, GFramework.Core"; + + /// + /// 获取或设置当前日志工厂提供程序。 + /// + /// + /// 当赋值为 时抛出。 + /// + public static ILoggerFactoryProvider Provider + { + get => field ??= CreateDefaultProvider(); + set => field = value ?? throw new ArgumentNullException(nameof(value)); + } + + /// + /// 获取或设置新创建日志记录器的最小日志级别。 + /// + /// + /// 该属性直接代理到当前 ,确保调用方调整级别后立即影响后续创建的日志器。 + /// + public static LogLevel MinLevel + { + get => Provider.MinLevel; + set => Provider.MinLevel = value; + } + + private static ILoggerFactoryProvider CreateDefaultProvider() + { + if (Type.GetType(DefaultProviderTypeName, throwOnError: false) is { } providerType && + Activator.CreateInstance(providerType) is ILoggerFactoryProvider provider) + { + provider.MinLevel = LogLevel.Info; + return provider; + } + + return new SilentLoggerFactoryProvider(); + } + + /// + /// 当宿主未提供默认日志实现时使用的静默 provider。 + /// + private sealed class SilentLoggerFactoryProvider : ILoggerFactoryProvider + { + public LogLevel MinLevel { get; set; } = LogLevel.Info; + + public ILogger CreateLogger(string name) + { + return new SilentLogger(name); + } + } + + /// + /// 默认日志实现不可用时的 no-op 日志器。 + /// + private sealed class SilentLogger(string name) : ILogger + { + public string Name() + { + return name; + } + + public bool IsTraceEnabled() + { + return false; + } + + public bool IsDebugEnabled() + { + return false; + } + + public bool IsInfoEnabled() + { + return false; + } + + public bool IsWarnEnabled() + { + return false; + } + + public bool IsErrorEnabled() + { + return false; + } + + public bool IsFatalEnabled() + { + return false; + } + + public bool IsEnabledForLevel(LogLevel level) + { + return false; + } + + public void Trace(string msg) + { + } + + public void Trace(string format, object arg) + { + } + + public void Trace(string format, object arg1, object arg2) + { + } + + public void Trace(string format, params object[] arguments) + { + } + + public void Trace(string msg, Exception t) + { + } + + public void Debug(string msg) + { + } + + public void Debug(string format, object arg) + { + } + + public void Debug(string format, object arg1, object arg2) + { + } + + public void Debug(string format, params object[] arguments) + { + } + + public void Debug(string msg, Exception t) + { + } + + public void Info(string msg) + { + } + + public void Info(string format, object arg) + { + } + + public void Info(string format, object arg1, object arg2) + { + } + + public void Info(string format, params object[] arguments) + { + } + + public void Info(string msg, Exception t) + { + } + + public void Warn(string msg) + { + } + + public void Warn(string format, object arg) + { + } + + public void Warn(string format, object arg1, object arg2) + { + } + + public void Warn(string format, params object[] arguments) + { + } + + public void Warn(string msg, Exception t) + { + } + + public void Error(string msg) + { + } + + public void Error(string format, object arg) + { + } + + public void Error(string format, object arg1, object arg2) + { + } + + public void Error(string format, params object[] arguments) + { + } + + public void Error(string msg, Exception t) + { + } + + public void Fatal(string msg) + { + } + + public void Fatal(string format, object arg) + { + } + + public void Fatal(string format, object arg1, object arg2) + { + } + + public void Fatal(string format, params object[] arguments) + { + } + + public void Fatal(string msg, Exception t) + { + } + + public void Log(LogLevel level, string message) + { + } + + public void Log(LogLevel level, string format, object arg) + { + } + + public void Log(LogLevel level, string format, object arg1, object arg2) + { + } + + public void Log(LogLevel level, string format, params object[] arguments) + { + } + + public void Log(LogLevel level, string message, Exception exception) + { + } + } +} diff --git a/GFramework.Core.Tests/Architectures/ArchitectureComponentRegistryBehaviorTests.cs b/GFramework.Core.Tests/Architectures/ArchitectureComponentRegistryBehaviorTests.cs index 583c30c8..f6cbdafa 100644 --- a/GFramework.Core.Tests/Architectures/ArchitectureComponentRegistryBehaviorTests.cs +++ b/GFramework.Core.Tests/Architectures/ArchitectureComponentRegistryBehaviorTests.cs @@ -1,11 +1,11 @@ using GFramework.Core.Abstractions.Architectures; using GFramework.Core.Abstractions.Enums; +using GFramework.Core.Abstractions.Logging; using GFramework.Core.Abstractions.Model; using GFramework.Core.Abstractions.Systems; using GFramework.Core.Abstractions.Utility; using GFramework.Core.Architectures; using GFramework.Core.Logging; -using Microsoft.Extensions.DependencyInjection; namespace GFramework.Core.Tests.Architectures; @@ -714,4 +714,4 @@ public class ArchitectureComponentRegistryBehaviorTests return _context; } } -} \ No newline at end of file +} diff --git a/GFramework.Core.Tests/Architectures/ArchitectureContextTests.cs b/GFramework.Core.Tests/Architectures/ArchitectureContextTests.cs index 87978cd2..cdcde44d 100644 --- a/GFramework.Core.Tests/Architectures/ArchitectureContextTests.cs +++ b/GFramework.Core.Tests/Architectures/ArchitectureContextTests.cs @@ -5,6 +5,7 @@ using GFramework.Core.Abstractions.Cqrs; using GFramework.Core.Abstractions.Enums; using GFramework.Core.Abstractions.Environment; 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; diff --git a/GFramework.Core.Tests/Architectures/ArchitectureInitializationPipelineTests.cs b/GFramework.Core.Tests/Architectures/ArchitectureInitializationPipelineTests.cs index 717e1b18..10945ad2 100644 --- a/GFramework.Core.Tests/Architectures/ArchitectureInitializationPipelineTests.cs +++ b/GFramework.Core.Tests/Architectures/ArchitectureInitializationPipelineTests.cs @@ -1,9 +1,9 @@ using GFramework.Core.Abstractions.Enums; using GFramework.Core.Abstractions.Events; +using GFramework.Core.Abstractions.Logging; using GFramework.Core.Architectures; using GFramework.Core.Environment; using GFramework.Core.Logging; -using Microsoft.Extensions.DependencyInjection; namespace GFramework.Core.Tests.Architectures; @@ -185,4 +185,4 @@ public class ArchitectureInitializationPipelineTests private sealed class BootstrapMarker { } -} \ No newline at end of file +} diff --git a/GFramework.Core.Tests/Architectures/ArchitectureLifecycleBehaviorTests.cs b/GFramework.Core.Tests/Architectures/ArchitectureLifecycleBehaviorTests.cs index b0ef7262..943e2bfd 100644 --- a/GFramework.Core.Tests/Architectures/ArchitectureLifecycleBehaviorTests.cs +++ b/GFramework.Core.Tests/Architectures/ArchitectureLifecycleBehaviorTests.cs @@ -2,12 +2,12 @@ using System.Reflection; using GFramework.Core.Abstractions.Architectures; using GFramework.Core.Abstractions.Enums; using GFramework.Core.Abstractions.Lifecycle; +using GFramework.Core.Abstractions.Logging; using GFramework.Core.Abstractions.Model; using GFramework.Core.Abstractions.Systems; using GFramework.Core.Abstractions.Utility; using GFramework.Core.Architectures; using GFramework.Core.Logging; -using Microsoft.Extensions.DependencyInjection; namespace GFramework.Core.Tests.Architectures; @@ -460,4 +460,4 @@ public class ArchitectureLifecycleBehaviorTests return _context; } } -} \ No newline at end of file +} diff --git a/GFramework.Core.Tests/Architectures/ArchitectureModulesBehaviorTests.cs b/GFramework.Core.Tests/Architectures/ArchitectureModulesBehaviorTests.cs index cfe0db79..493f3590 100644 --- a/GFramework.Core.Tests/Architectures/ArchitectureModulesBehaviorTests.cs +++ b/GFramework.Core.Tests/Architectures/ArchitectureModulesBehaviorTests.cs @@ -1,4 +1,5 @@ using GFramework.Core.Abstractions.Architectures; +using GFramework.Core.Abstractions.Logging; using GFramework.Core.Abstractions.Utility; using GFramework.Core.Architectures; using GFramework.Core.Logging; diff --git a/GFramework.Core.Tests/Architectures/PriorityServiceTests.cs b/GFramework.Core.Tests/Architectures/PriorityServiceTests.cs index 24fd2681..0499a4ab 100644 --- a/GFramework.Core.Tests/Architectures/PriorityServiceTests.cs +++ b/GFramework.Core.Tests/Architectures/PriorityServiceTests.cs @@ -1,5 +1,6 @@ using System.Reflection; using GFramework.Core.Abstractions.Bases; +using GFramework.Core.Abstractions.Logging; using GFramework.Core.Abstractions.Model; using GFramework.Core.Abstractions.Systems; using GFramework.Core.Abstractions.Utility; @@ -244,4 +245,4 @@ public class PriorityTestUtilityC : IPriorityTestUtility, IPrioritized public int Priority => 30; } -#endregion \ No newline at end of file +#endregion diff --git a/GFramework.Core.Tests/Ioc/MicrosoftDiContainerTests.cs b/GFramework.Core.Tests/Ioc/MicrosoftDiContainerTests.cs index 0e03059e..4104ccb1 100644 --- a/GFramework.Core.Tests/Ioc/MicrosoftDiContainerTests.cs +++ b/GFramework.Core.Tests/Ioc/MicrosoftDiContainerTests.cs @@ -1,6 +1,7 @@ using System.Reflection; using GFramework.Core.Abstractions.Bases; using GFramework.Core.Abstractions.Cqrs; +using GFramework.Core.Abstractions.Logging; using GFramework.Core.Ioc; using GFramework.Core.Logging; using GFramework.Core.Tests.Cqrs; diff --git a/GFramework.Core.Tests/State/StateMachineSystemTests.cs b/GFramework.Core.Tests/State/StateMachineSystemTests.cs index 0ea62de6..2c234238 100644 --- a/GFramework.Core.Tests/State/StateMachineSystemTests.cs +++ b/GFramework.Core.Tests/State/StateMachineSystemTests.cs @@ -1,5 +1,6 @@ using System.Reflection; using GFramework.Core.Abstractions.Enums; +using GFramework.Core.Abstractions.Logging; using GFramework.Core.Abstractions.State; using GFramework.Core.Abstractions.Systems; using GFramework.Core.Architectures; @@ -373,4 +374,4 @@ public class TestStateV5_2 : IState } } -#endregion \ No newline at end of file +#endregion diff --git a/GFramework.Core/Logging/LoggerFactoryResolver.cs b/GFramework.Core/Logging/LoggerFactoryResolver.cs deleted file mode 100644 index c15378d1..00000000 --- a/GFramework.Core/Logging/LoggerFactoryResolver.cs +++ /dev/null @@ -1,26 +0,0 @@ -using GFramework.Core.Abstractions.Logging; - -namespace GFramework.Core.Logging; - -/// -/// 日志工厂提供程序解析器,用于管理和提供日志工厂提供程序实例 -/// -public static class LoggerFactoryResolver -{ - /// - /// 获取或设置当前的日志工厂提供程序 - /// - /// - /// 日志工厂提供程序实例,默认为控制台日志工厂提供程序 - /// - public static ILoggerFactoryProvider Provider { get; set; } - = new ConsoleLoggerFactoryProvider(); - - /// - /// 获取或设置日志记录的最小级别 - /// - /// - /// 日志级别枚举值,默认为Info级别 - /// - public static LogLevel MinLevel { get; set; } = LogLevel.Info; -} \ No newline at end of file diff --git a/GFramework.Core/Properties/TypeForwarders.cs b/GFramework.Core/Properties/TypeForwarders.cs new file mode 100644 index 00000000..a71d9af2 --- /dev/null +++ b/GFramework.Core/Properties/TypeForwarders.cs @@ -0,0 +1,4 @@ +using System.Runtime.CompilerServices; +using GFramework.Core.Abstractions.Logging; + +[assembly: TypeForwardedTo(typeof(LoggerFactoryResolver))] diff --git a/GFramework.Core/Services/Modules/CqrsRuntimeModule.cs b/GFramework.Core/Services/Modules/CqrsRuntimeModule.cs index 9e7ad307..3fe37558 100644 --- a/GFramework.Core/Services/Modules/CqrsRuntimeModule.cs +++ b/GFramework.Core/Services/Modules/CqrsRuntimeModule.cs @@ -1,7 +1,7 @@ using GFramework.Core.Abstractions.Architectures; using GFramework.Core.Abstractions.Cqrs; using GFramework.Core.Abstractions.Ioc; -using GFramework.Core.Logging; +using GFramework.Core.Abstractions.Logging; using GFramework.Cqrs; using GFramework.Cqrs.Abstractions.Cqrs; diff --git a/GFramework.Cqrs.Tests/GlobalUsings.cs b/GFramework.Cqrs.Tests/GlobalUsings.cs index c47473b0..9f52a3dd 100644 --- a/GFramework.Cqrs.Tests/GlobalUsings.cs +++ b/GFramework.Cqrs.Tests/GlobalUsings.cs @@ -20,6 +20,7 @@ global using System.Reflection; global using System.Runtime.CompilerServices; global using System.Threading; global using System.Threading.Tasks; +global using System.Diagnostics; global using GFramework.Tests.Common; global using Microsoft.Extensions.DependencyInjection; global using Moq; diff --git a/GFramework.Cqrs.Tests/Mediator/MediatorAdvancedFeaturesTests.cs b/GFramework.Cqrs.Tests/Mediator/MediatorAdvancedFeaturesTests.cs index 257809f0..fe8f4413 100644 --- a/GFramework.Cqrs.Tests/Mediator/MediatorAdvancedFeaturesTests.cs +++ b/GFramework.Cqrs.Tests/Mediator/MediatorAdvancedFeaturesTests.cs @@ -1,3 +1,4 @@ +using GFramework.Core.Abstractions.Logging; using GFramework.Core.Architectures; using GFramework.Core.Ioc; using GFramework.Core.Logging; diff --git a/GFramework.Cqrs.Tests/Mediator/MediatorArchitectureIntegrationTests.cs b/GFramework.Cqrs.Tests/Mediator/MediatorArchitectureIntegrationTests.cs index e403735e..728f005a 100644 --- a/GFramework.Cqrs.Tests/Mediator/MediatorArchitectureIntegrationTests.cs +++ b/GFramework.Cqrs.Tests/Mediator/MediatorArchitectureIntegrationTests.cs @@ -1,4 +1,5 @@ using GFramework.Core.Abstractions.Architectures; +using GFramework.Core.Abstractions.Logging; using GFramework.Core.Architectures; using GFramework.Core.Command; using GFramework.Core.Ioc; diff --git a/GFramework.Cqrs.Tests/Mediator/MediatorComprehensiveTests.cs b/GFramework.Cqrs.Tests/Mediator/MediatorComprehensiveTests.cs index b0b510d6..423b1c9b 100644 --- a/GFramework.Cqrs.Tests/Mediator/MediatorComprehensiveTests.cs +++ b/GFramework.Cqrs.Tests/Mediator/MediatorComprehensiveTests.cs @@ -1,5 +1,6 @@ using GFramework.Core.Abstractions.Architectures; using GFramework.Core.Abstractions.Events; +using GFramework.Core.Abstractions.Logging; using GFramework.Core.Architectures; using GFramework.Core.Command; using GFramework.Core.Environment; diff --git a/GFramework.Core/Cqrs/Behaviors/LoggingBehavior.cs b/GFramework.Cqrs/Cqrs/Behaviors/LoggingBehavior.cs similarity index 71% rename from GFramework.Core/Cqrs/Behaviors/LoggingBehavior.cs rename to GFramework.Cqrs/Cqrs/Behaviors/LoggingBehavior.cs index 7230f53d..ccd9f0bf 100644 --- a/GFramework.Core/Cqrs/Behaviors/LoggingBehavior.cs +++ b/GFramework.Cqrs/Cqrs/Behaviors/LoggingBehavior.cs @@ -11,19 +11,20 @@ // See the License for the specific language governing permissions and // limitations under the License. -using System.Diagnostics; using GFramework.Core.Abstractions.Logging; -using GFramework.Core.Logging; using GFramework.Cqrs.Abstractions.Cqrs; -namespace GFramework.Core.Cqrs.Behaviors; +namespace GFramework.Cqrs.Cqrs.Behaviors; /// -/// 日志记录行为类,用于在CQRS管道中记录请求处理的日志信息 -/// 实现IPipelineBehavior接口,为请求处理提供日志记录功能 +/// 在 CQRS 请求管道中记录请求开始、完成、取消与失败日志。 /// -/// 请求类型,必须实现IRequest接口 -/// 响应类型 +/// 请求类型。 +/// 响应类型。 +/// +/// 该行为保留在 GFramework.Core.Cqrs.Behaviors 命名空间以兼容现有调用点, +/// 但实现已迁入 GFramework.Cqrs 程序集,避免继续由 GFramework.Core 承载 CQRS runtime 细节。 +/// public sealed class LoggingBehavior : IPipelineBehavior where TRequest : IRequest { @@ -31,13 +32,12 @@ public sealed class LoggingBehavior : IPipelineBehavior)); /// - /// 处理请求并记录日志 - /// 在请求处理前后记录调试信息,处理异常时记录错误日志 + /// 执行日志包装后的下一段请求处理逻辑。 /// - /// 要处理的请求消息 - /// 下一个处理委托,用于继续管道执行 - /// 取消令牌,用于取消操作 - /// 处理结果的ValueTask + /// 当前请求消息。 + /// 后续处理委托。 + /// 取消令牌。 + /// 请求处理结果。 public async ValueTask Handle( TRequest message, MessageHandlerDelegate next, diff --git a/GFramework.Core/Cqrs/Behaviors/PerformanceBehavior.cs b/GFramework.Cqrs/Cqrs/Behaviors/PerformanceBehavior.cs similarity index 61% rename from GFramework.Core/Cqrs/Behaviors/PerformanceBehavior.cs rename to GFramework.Cqrs/Cqrs/Behaviors/PerformanceBehavior.cs index 35ab2978..1d13319d 100644 --- a/GFramework.Core/Cqrs/Behaviors/PerformanceBehavior.cs +++ b/GFramework.Cqrs/Cqrs/Behaviors/PerformanceBehavior.cs @@ -11,33 +11,34 @@ // See the License for the specific language governing permissions and // limitations under the License. -using System.Diagnostics; using GFramework.Core.Abstractions.Logging; -using GFramework.Core.Logging; using GFramework.Cqrs.Abstractions.Cqrs; -namespace GFramework.Core.Cqrs.Behaviors; +namespace GFramework.Cqrs.Cqrs.Behaviors; /// -/// 性能监控行为类,用于监控CQRS请求的执行时间 -/// 实现IPipelineBehavior接口,检测并记录执行时间过长的请求 +/// 在 CQRS 请求管道中监控处理耗时,并对长耗时请求发出告警。 /// -/// 请求类型,必须实现IRequest接口 -/// 响应类型 +/// 请求类型。 +/// 响应类型。 +/// +/// 该行为保留现有公开命名空间以维持消费端兼容性,但实现已迁入 GFramework.Cqrs 程序集。 +/// public sealed class PerformanceBehavior : IPipelineBehavior where TRequest : IRequest { + private const double SlowRequestThresholdMilliseconds = 500; + private readonly ILogger _logger = LoggerFactoryResolver.Provider.CreateLogger(nameof(PerformanceBehavior)); /// - /// 处理请求并监控执行时间 - /// 使用Stopwatch测量请求处理耗时,超过500ms时记录警告日志 + /// 统计当前请求处理耗时,并在超阈值时记录警告日志。 /// - /// 要处理的请求消息 - /// 下一个处理委托,用于继续管道执行 - /// 取消令牌,用于取消操作 - /// 处理结果的ValueTask + /// 当前请求消息。 + /// 后续处理委托。 + /// 取消令牌。 + /// 请求处理结果。 public async ValueTask Handle( TRequest message, MessageHandlerDelegate next, @@ -53,11 +54,10 @@ public sealed class PerformanceBehavior : IPipelineBehavior { var elapsed = Stopwatch.GetElapsedTime(start); - if (elapsed.TotalMilliseconds > 500) + if (elapsed.TotalMilliseconds > SlowRequestThresholdMilliseconds) { var requestName = typeof(TRequest).Name; - _logger.Warn( - $"Long Running Request: {requestName} ({elapsed.TotalMilliseconds:F2} ms)"); + _logger.Warn($"Long Running Request: {requestName} ({elapsed.TotalMilliseconds:F2} ms)"); } } } diff --git a/GFramework.Cqrs/GlobalUsings.cs b/GFramework.Cqrs/GlobalUsings.cs index 7f8c14d6..97f2d13a 100644 --- a/GFramework.Cqrs/GlobalUsings.cs +++ b/GFramework.Cqrs/GlobalUsings.cs @@ -4,3 +4,4 @@ global using System.Linq; global using System.Threading; global using System.Threading.Tasks; global using Microsoft.Extensions.DependencyInjection; +global using System.Diagnostics; From 18337c59952466b91f2c7f02d9aeb7755ac71099 Mon Sep 17 00:00:00 2001 From: GeWuYou <95328647+GeWuYou@users.noreply.github.com> Date: Wed, 15 Apr 2026 21:54:29 +0800 Subject: [PATCH 04/12] =?UTF-8?q?refactor(tests):=20=E7=A7=BB=E9=99=A4?= =?UTF-8?q?=E6=9C=AA=E4=BD=BF=E7=94=A8=E7=9A=84=20System.Diagnostics=20?= =?UTF-8?q?=E5=BC=95=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 从 GlobalUsings.cs 中删除未使用的 System.Diagnostics 全局引用 - 保持测试项目的全局引用列表整洁 - 减少不必要的命名空间导入 --- GFramework.Cqrs.Tests/GlobalUsings.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/GFramework.Cqrs.Tests/GlobalUsings.cs b/GFramework.Cqrs.Tests/GlobalUsings.cs index 9f52a3dd..c47473b0 100644 --- a/GFramework.Cqrs.Tests/GlobalUsings.cs +++ b/GFramework.Cqrs.Tests/GlobalUsings.cs @@ -20,7 +20,6 @@ global using System.Reflection; global using System.Runtime.CompilerServices; global using System.Threading; global using System.Threading.Tasks; -global using System.Diagnostics; global using GFramework.Tests.Common; global using Microsoft.Extensions.DependencyInjection; global using Moq; From 005c32d84f7ca0fedf6a46eafe8a6f89841a5d2e Mon Sep 17 00:00:00 2001 From: GeWuYou <95328647+GeWuYou@users.noreply.github.com> Date: Wed, 15 Apr 2026 22:27:09 +0800 Subject: [PATCH 05/12] =?UTF-8?q?feat(cqrs):=20=E6=B7=BB=E5=8A=A0CQRS?= =?UTF-8?q?=E6=89=A9=E5=B1=95=E6=96=B9=E6=B3=95=E5=85=BC=E5=AE=B9=E5=B1=82?= =?UTF-8?q?=E5=92=8C=E6=97=A5=E5=BF=97=E5=B7=A5=E5=8E=82=E8=A7=A3=E6=9E=90?= =?UTF-8?q?=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增ContextAwareMediatorCommandExtensions提供命令扩展方法兼容性支持 - 新增ContextAwareMediatorQueryExtensions提供查询扩展方法兼容性支持 - 添加LoggerFactoryResolver实现全局日志工厂访问入口 - 实现TypeForwarders将核心类型转发到正确程序集 - 添加MediatorCompatibilityDeprecationTests验证弃用策略 - 扩展LoggerFactoryTests覆盖并发初始化和回退逻辑 - 迁移CommandBase到Core.Cqrs.Command命名空间 - 移动LoggingBehavior到GFramework.Cqrs.Cqrs.Behaviors - 添加AbstractStreamQueryHandler支持流式查询处理 - 创建NotificationBase提供通知基类实现 --- .../Logging/LoggerFactoryResolver.cs | 45 +++- .../CqrsPublicNamespaceCompatibilityTests.cs | 76 ++++++ .../MediatorCompatibilityDeprecationTests.cs | 4 +- .../Logging/LoggerFactoryTests.cs | 238 ++++++++++++++++-- .../ContextAwareMediatorCommandExtensions.cs | 2 +- .../ContextAwareMediatorQueryExtensions.cs | 2 +- GFramework.Core/Properties/TypeForwarders.cs | 8 + GFramework.Cqrs/Command/CommandBase.cs | 20 +- .../Cqrs/Behaviors/LoggingBehavior.cs | 4 +- .../Cqrs/Query/AbstractStreamQueryHandler.cs | 22 +- GFramework.Cqrs/CqrsRuntimeFactory.cs | 6 + .../Notification/NotificationBase.cs | 18 +- GFramework.Cqrs/Query/QueryBase.cs | 20 +- GFramework.Cqrs/Request/RequestBase.cs | 20 +- GFramework.Tests.Common/CqrsTestRuntime.cs | 4 +- 15 files changed, 409 insertions(+), 80 deletions(-) create mode 100644 GFramework.Core.Tests/Cqrs/CqrsPublicNamespaceCompatibilityTests.cs diff --git a/GFramework.Core.Abstractions/Logging/LoggerFactoryResolver.cs b/GFramework.Core.Abstractions/Logging/LoggerFactoryResolver.cs index a87a18da..fdffe5b2 100644 --- a/GFramework.Core.Abstractions/Logging/LoggerFactoryResolver.cs +++ b/GFramework.Core.Abstractions/Logging/LoggerFactoryResolver.cs @@ -10,19 +10,42 @@ namespace GFramework.Core.Abstractions.Logging; /// public static class LoggerFactoryResolver { - private const string DefaultProviderTypeName = + private static readonly object ProviderLock = new(); + + private static string DefaultProviderTypeName = "GFramework.Core.Logging.ConsoleLoggerFactoryProvider, GFramework.Core"; + private static ILoggerFactoryProvider? _provider; + /// /// 获取或设置当前日志工厂提供程序。 /// + /// + /// 读取与赋值都会通过同一把锁串行化,确保并发调用方观察到确定的 provider 引用。 + /// 当调用方未显式赋值时,会在首次访问时尝试解析默认实现;若解析失败,则退回静默 provider。 + /// /// /// 当赋值为 时抛出。 /// public static ILoggerFactoryProvider Provider { - get => field ??= CreateDefaultProvider(); - set => field = value ?? throw new ArgumentNullException(nameof(value)); + get + { + lock (ProviderLock) + { + _provider ??= CreateDefaultProvider(); + return _provider; + } + } + set + { + var provider = value ?? throw new ArgumentNullException(nameof(value)); + + lock (ProviderLock) + { + _provider = provider; + } + } } /// @@ -39,11 +62,19 @@ public static class LoggerFactoryResolver private static ILoggerFactoryProvider CreateDefaultProvider() { - if (Type.GetType(DefaultProviderTypeName, throwOnError: false) is { } providerType && - Activator.CreateInstance(providerType) is ILoggerFactoryProvider provider) + try { - provider.MinLevel = LogLevel.Info; - return provider; + if (Type.GetType(DefaultProviderTypeName, throwOnError: false) is { } providerType && + Activator.CreateInstance(providerType) is ILoggerFactoryProvider provider) + { + provider.MinLevel = LogLevel.Info; + return provider; + } + } + catch (Exception) + { + // The default provider is optional. Any load or activation failure must degrade to the silent provider so + // abstractions-only hosts can continue bootstrapping without the concrete logging assembly. } return new SilentLoggerFactoryProvider(); diff --git a/GFramework.Core.Tests/Cqrs/CqrsPublicNamespaceCompatibilityTests.cs b/GFramework.Core.Tests/Cqrs/CqrsPublicNamespaceCompatibilityTests.cs new file mode 100644 index 00000000..2f02dbb7 --- /dev/null +++ b/GFramework.Core.Tests/Cqrs/CqrsPublicNamespaceCompatibilityTests.cs @@ -0,0 +1,76 @@ +using GFramework.Core.Cqrs.Command; +using GFramework.Core.Cqrs.Notification; +using GFramework.Core.Cqrs.Query; +using GFramework.Core.Cqrs.Request; +using GFramework.Cqrs.Abstractions.Cqrs; +using GFramework.Cqrs.Abstractions.Cqrs.Command; +using GFramework.Cqrs.Abstractions.Cqrs.Notification; +using GFramework.Cqrs.Abstractions.Cqrs.Query; +using GFramework.Cqrs.Abstractions.Cqrs.Request; + +namespace GFramework.Core.Tests.Cqrs; + +/// +/// 锁定 CQRS 基础消息类型在 runtime 拆分后的公开命名空间与程序集兼容性。 +/// +[TestFixture] +public sealed class CqrsPublicNamespaceCompatibilityTests +{ + /// + /// 验证基础消息类型继续暴露在历史 Core.Cqrs 命名空间,同时由独立 runtime 程序集承载实现。 + /// + [Test] + public void Base_Message_Types_Should_Remain_In_Legacy_Namespaces_While_Living_In_Runtime_Assembly() + { + Assert.Multiple(() => + { + AssertLegacyType(typeof(CommandBase), "GFramework.Core.Cqrs.Command"); + AssertLegacyType(typeof(QueryBase), "GFramework.Core.Cqrs.Query"); + AssertLegacyType(typeof(RequestBase), "GFramework.Core.Cqrs.Request"); + AssertLegacyType(typeof(NotificationBase), "GFramework.Core.Cqrs.Notification"); + }); + } + + /// + /// 验证旧的 GFramework.Core 程序集限定名仍可解析到迁移后的 runtime 实现类型。 + /// + [Test] + public void GFramework_Core_Assembly_Should_Forward_Legacy_Base_Types_To_Runtime_Assembly() + { + Assert.Multiple(() => + { + AssertForwardedType("GFramework.Core.Cqrs.Command.CommandBase`2, GFramework.Core"); + AssertForwardedType("GFramework.Core.Cqrs.Query.QueryBase`2, GFramework.Core"); + AssertForwardedType("GFramework.Core.Cqrs.Request.RequestBase`2, GFramework.Core"); + AssertForwardedType("GFramework.Core.Cqrs.Notification.NotificationBase`1, GFramework.Core"); + }); + } + + private static void AssertLegacyType(Type type, string expectedNamespace) + { + Assert.Multiple(() => + { + Assert.That(type.Namespace, Is.EqualTo(expectedNamespace)); + Assert.That(type.Assembly.GetName().Name, Is.EqualTo("GFramework.Cqrs")); + }); + } + + private static void AssertForwardedType(string assemblyQualifiedTypeName) + { + var resolvedType = Type.GetType(assemblyQualifiedTypeName, throwOnError: true); + + Assert.Multiple(() => + { + Assert.That(resolvedType, Is.Not.Null); + Assert.That(resolvedType!.Assembly.GetName().Name, Is.EqualTo("GFramework.Cqrs")); + }); + } + + private sealed record TestCommandInput : ICommandInput; + + private sealed record TestQueryInput : IQueryInput; + + private sealed record TestRequestInput : IRequestInput; + + private sealed record TestNotificationInput : INotificationInput; +} diff --git a/GFramework.Core.Tests/Cqrs/MediatorCompatibilityDeprecationTests.cs b/GFramework.Core.Tests/Cqrs/MediatorCompatibilityDeprecationTests.cs index b25806f0..a6cc927f 100644 --- a/GFramework.Core.Tests/Cqrs/MediatorCompatibilityDeprecationTests.cs +++ b/GFramework.Core.Tests/Cqrs/MediatorCompatibilityDeprecationTests.cs @@ -38,10 +38,10 @@ public class MediatorCompatibilityDeprecationTests "Use GFramework.Core.Extensions.ContextAwareCqrsExtensions instead."); AssertLegacyType( typeof(ContextAwareMediatorCommandExtensions), - "Use GFramework.Core.Extensions.ContextAwareCqrsCommandExtensions instead."); + "Use GFramework.Cqrs.Extensions.ContextAwareCqrsCommandExtensions instead."); AssertLegacyType( typeof(ContextAwareMediatorQueryExtensions), - "Use GFramework.Core.Extensions.ContextAwareCqrsQueryExtensions instead."); + "Use GFramework.Cqrs.Extensions.ContextAwareCqrsQueryExtensions instead."); AssertLegacyType( typeof(MediatorCoroutineExtensions), "Use GFramework.Core.Coroutine.Extensions.CqrsCoroutineExtensions instead."); diff --git a/GFramework.Core.Tests/Logging/LoggerFactoryTests.cs b/GFramework.Core.Tests/Logging/LoggerFactoryTests.cs index 8ba7ac91..d175b32f 100644 --- a/GFramework.Core.Tests/Logging/LoggerFactoryTests.cs +++ b/GFramework.Core.Tests/Logging/LoggerFactoryTests.cs @@ -1,18 +1,19 @@ using System.IO; +using System.Reflection; using GFramework.Core.Abstractions.Logging; using GFramework.Core.Logging; -using NUnit.Framework; namespace GFramework.Core.Tests.Logging; /// -/// 测试LoggerFactory相关功能的测试类 +/// 测试 LoggerFactory 相关功能的测试类。 /// [TestFixture] +[NonParallelizable] public class LoggerFactoryTests { /// - /// 测试ConsoleLoggerFactory的GetLogger方法是否返回ConsoleLogger实例 + /// 测试 ConsoleLoggerFactory 的 GetLogger 方法是否返回 ConsoleLogger 实例。 /// [Test] public void ConsoleLoggerFactory_GetLogger_ShouldReturnConsoleLogger() @@ -26,7 +27,7 @@ public class LoggerFactoryTests } /// - /// 测试ConsoleLoggerFactory使用不同名称获取不同的logger实例 + /// 测试 ConsoleLoggerFactory 使用不同名称获取不同的 logger 实例。 /// [Test] public void ConsoleLoggerFactory_GetLogger_WithDifferentNames_ShouldReturnDifferentLoggers() @@ -40,7 +41,7 @@ public class LoggerFactoryTests } /// - /// 测试ConsoleLoggerFactory使用默认最小级别时的行为(默认为Info级别) + /// 测试 ConsoleLoggerFactory 使用默认最小级别时的行为。 /// [Test] public void ConsoleLoggerFactory_GetLogger_WithDefaultMinLevel_ShouldUseInfo() @@ -51,7 +52,6 @@ public class LoggerFactoryTests var stringWriter = new StringWriter(); var testLogger = new ConsoleLogger("TestLogger", LogLevel.Info, stringWriter, false); - // 验证Debug消息不会被记录,但Info消息会被记录 testLogger.Debug("Debug message"); testLogger.Info("Info message"); @@ -61,7 +61,7 @@ public class LoggerFactoryTests } /// - /// 测试ConsoleLoggerFactoryProvider创建logger时使用提供者的最小级别设置 + /// 测试 ConsoleLoggerFactoryProvider 创建 logger 时使用提供者的最小级别设置。 /// [Test] public void ConsoleLoggerFactoryProvider_CreateLogger_ShouldReturnLoggerWithProviderMinLevel() @@ -72,7 +72,6 @@ public class LoggerFactoryTests var stringWriter = new StringWriter(); var testLogger = new ConsoleLogger("TestLogger", LogLevel.Debug, stringWriter, false); - // 验证Debug消息会被记录,但Trace消息不会被记录 testLogger.Debug("Debug message"); testLogger.Trace("Trace message"); @@ -82,7 +81,7 @@ public class LoggerFactoryTests } /// - /// 测试ConsoleLoggerFactoryProvider创建logger时使用提供的名称 + /// 测试 ConsoleLoggerFactoryProvider 创建 logger 时使用提供的名称。 /// [Test] public void ConsoleLoggerFactoryProvider_CreateLogger_ShouldUseProvidedName() @@ -94,7 +93,7 @@ public class LoggerFactoryTests } /// - /// 测试LoggerFactoryResolver的Provider属性是否有默认值 + /// 测试 LoggerFactoryResolver 的 Provider 属性是否有默认值。 /// [Test] public void LoggerFactoryResolver_Provider_ShouldHaveDefaultValue() @@ -104,7 +103,7 @@ public class LoggerFactoryTests } /// - /// 测试LoggerFactoryResolver的Provider属性可以被更改 + /// 测试 LoggerFactoryResolver 的 Provider 属性可以被更改。 /// [Test] public void LoggerFactoryResolver_Provider_CanBeChanged() @@ -120,7 +119,7 @@ public class LoggerFactoryTests } /// - /// 测试LoggerFactoryResolver的MinLevel属性是否有默认值 + /// 测试 LoggerFactoryResolver 的 MinLevel 属性是否有默认值。 /// [Test] public void LoggerFactoryResolver_MinLevel_ShouldHaveDefaultValue() @@ -129,7 +128,7 @@ public class LoggerFactoryTests } /// - /// 测试LoggerFactoryResolver的MinLevel属性可以被更改 + /// 测试 LoggerFactoryResolver 的 MinLevel 属性可以被更改。 /// [Test] public void LoggerFactoryResolver_MinLevel_CanBeChanged() @@ -144,7 +143,7 @@ public class LoggerFactoryTests } /// - /// 测试ConsoleLoggerFactoryProvider的MinLevel属性是否有默认值 + /// 测试 ConsoleLoggerFactoryProvider 的 MinLevel 属性是否有默认值。 /// [Test] public void ConsoleLoggerFactoryProvider_MinLevel_ShouldHaveDefaultValue() @@ -155,7 +154,7 @@ public class LoggerFactoryTests } /// - /// 测试ConsoleLoggerFactoryProvider的MinLevel属性可以被更改 + /// 测试 ConsoleLoggerFactoryProvider 的 MinLevel 属性可以被更改。 /// [Test] public void ConsoleLoggerFactoryProvider_MinLevel_CanBeChanged() @@ -168,7 +167,7 @@ public class LoggerFactoryTests } /// - /// 测试LoggerFactoryResolver的Provider创建logger时使用提供者设置 + /// 测试 LoggerFactoryResolver 的 Provider 创建 logger 时使用提供者设置。 /// [Test] public void LoggerFactoryResolver_Provider_CreateLogger_ShouldUseProviderSettings() @@ -183,7 +182,6 @@ public class LoggerFactoryTests var stringWriter = new StringWriter(); var testLogger = new ConsoleLogger("TestLogger", LogLevel.Warning, stringWriter, false); - // 验证Warn消息会被记录,但Info消息不会被记录 testLogger.Warn("Warn message"); testLogger.Info("Info message"); @@ -195,7 +193,7 @@ public class LoggerFactoryTests } /// - /// 测试LoggerFactoryResolver的MinLevel属性影响新创建的logger + /// 测试 LoggerFactoryResolver 的 MinLevel 属性影响新创建的 logger。 /// [Test] public void LoggerFactoryResolver_MinLevel_AffectsNewLoggers() @@ -210,7 +208,6 @@ public class LoggerFactoryTests var stringWriter = new StringWriter(); var testLogger = new ConsoleLogger("TestLogger", LogLevel.Error, stringWriter, false); - // 验证Error消息会被记录,但Warn消息不会被记录 testLogger.Error("Error message"); testLogger.Warn("Warn message"); @@ -222,7 +219,93 @@ public class LoggerFactoryTests } /// - /// 测试ConsoleLoggerFactory创建的多个logger实例是独立的 + /// 验证默认 provider 激活失败时会回退到静默 provider。 + /// + [Test] + public void + LoggerFactoryResolver_Provider_Should_Fall_Back_To_SilentProvider_When_DefaultProvider_Activation_Fails() + { + var originalProvider = LoggerFactoryResolver.Provider; + var originalTypeName = GetDefaultProviderTypeName(); + + try + { + ResetProvider(); + SetDefaultProviderTypeName(typeof(ThrowingLoggerFactoryProvider).AssemblyQualifiedName!); + + var provider = LoggerFactoryResolver.Provider; + var logger = provider.CreateLogger("Fallback"); + + Assert.Multiple(() => + { + Assert.That(provider.GetType().Name, Is.EqualTo("SilentLoggerFactoryProvider")); + Assert.That(provider.MinLevel, Is.EqualTo(LogLevel.Info)); + Assert.That(logger.IsEnabledForLevel(LogLevel.Error), Is.False); + }); + } + finally + { + SetDefaultProviderTypeName(originalTypeName); + LoggerFactoryResolver.Provider = originalProvider; + } + } + + /// + /// 验证并发首次访问默认 provider 时只会创建一个实例,并向所有调用方返回相同引用。 + /// + [Test] + public async Task + LoggerFactoryResolver_Provider_Should_Create_A_Single_Default_Instance_When_Accessed_Concurrently() + { + var originalProvider = LoggerFactoryResolver.Provider; + var originalTypeName = GetDefaultProviderTypeName(); + + try + { + BlockingLoggerFactoryProvider.Reset(); + ResetProvider(); + SetDefaultProviderTypeName(typeof(BlockingLoggerFactoryProvider).AssemblyQualifiedName!); + + var startGate = new ManualResetEventSlim(false); + var tasks = Enumerable.Range(0, 8) + .Select(_ => Task.Run(() => + { + startGate.Wait(); + return LoggerFactoryResolver.Provider; + })) + .ToArray(); + + startGate.Set(); + + Assert.That( + SpinWait.SpinUntil( + () => BlockingLoggerFactoryProvider.ConstructionCount >= 1, + TimeSpan.FromSeconds(2)), + Is.True, + "The test provider should start construction after concurrent access begins."); + + BlockingLoggerFactoryProvider.ReleaseConstruction(); + + var providers = await Task.WhenAll(tasks); + + Assert.Multiple(() => + { + Assert.That(BlockingLoggerFactoryProvider.ConstructionCount, Is.EqualTo(1)); + Assert.That(providers.Distinct().Count(), Is.EqualTo(1)); + Assert.That(LoggerFactoryResolver.Provider, Is.SameAs(providers[0])); + }); + } + finally + { + BlockingLoggerFactoryProvider.ReleaseConstruction(); + BlockingLoggerFactoryProvider.Reset(); + SetDefaultProviderTypeName(originalTypeName); + LoggerFactoryResolver.Provider = originalProvider; + } + } + + /// + /// 测试 ConsoleLoggerFactory 创建的多个 logger 实例是独立的。 /// [Test] public void ConsoleLoggerFactory_MultipleLoggers_ShouldBeIndependent() @@ -236,7 +319,7 @@ public class LoggerFactoryTests } /// - /// 测试ConsoleLoggerFactoryProvider的MinLevel不会影响已创建的logger + /// 测试 ConsoleLoggerFactoryProvider 的 MinLevel 不会影响已创建的 logger。 /// [Test] public void ConsoleLoggerFactoryProvider_MinLevel_DoesNotAffectCreatedLogger() @@ -247,7 +330,6 @@ public class LoggerFactoryTests var stringWriter = new StringWriter(); var testLogger = new ConsoleLogger("TestLogger", LogLevel.Error, stringWriter, false); - // 验证Error和Fatal消息都会被记录 testLogger.Error("Error message"); testLogger.Fatal("Fatal message"); @@ -255,4 +337,114 @@ public class LoggerFactoryTests Assert.That(output, Does.Contain("Error message")); Assert.That(output, Does.Contain("Fatal message")); } -} \ No newline at end of file + + private static string GetDefaultProviderTypeName() + { + return (string)GetResolverField("DefaultProviderTypeName").GetValue(null)!; + } + + private static void SetDefaultProviderTypeName(string typeName) + { + GetResolverField("DefaultProviderTypeName").SetValue(null, typeName); + } + + private static void ResetProvider() + { + GetResolverField("_provider").SetValue(null, null); + } + + private static FieldInfo GetResolverField(string fieldName) + { + return typeof(LoggerFactoryResolver).GetField( + fieldName, + BindingFlags.NonPublic | BindingFlags.Static) + ?? throw new InvalidOperationException( + $"Failed to locate LoggerFactoryResolver.{fieldName}."); + } + + /// + /// 用于触发默认 provider 激活失败回退路径的测试桩。 + /// + public sealed class ThrowingLoggerFactoryProvider : ILoggerFactoryProvider + { + /// + /// 初始化一个始终抛出异常的 provider。 + /// + /// 始终抛出,用于覆盖回退路径。 + public ThrowingLoggerFactoryProvider() + { + throw new InvalidOperationException("Simulated provider activation failure."); + } + + /// + /// 获取或设置最小日志级别。 + /// + public LogLevel MinLevel { get; set; } = LogLevel.Info; + + /// + /// 创建日志器。 + /// + /// 日志器名称。 + /// 该测试桩永远不会成功创建日志器。 + /// 始终抛出,因为该方法不应被调用。 + public ILogger CreateLogger(string name) + { + throw new NotSupportedException(); + } + } + + /// + /// 用于验证并发首次初始化路径只创建单个 provider 实例的测试桩。 + /// + public sealed class BlockingLoggerFactoryProvider : ILoggerFactoryProvider + { + private static int _constructionCount; + private static ManualResetEventSlim _constructionGate = new(false); + + /// + /// 初始化一个会阻塞构造完成的 provider,用于放大并发首次访问竞争窗口。 + /// + public BlockingLoggerFactoryProvider() + { + Interlocked.Increment(ref _constructionCount); + _constructionGate.Wait(TimeSpan.FromSeconds(5)); + } + + /// + /// 获取已经发生的构造次数。 + /// + public static int ConstructionCount => Volatile.Read(ref _constructionCount); + + /// + /// 获取或设置最小日志级别。 + /// + public LogLevel MinLevel { get; set; } = LogLevel.Info; + + /// + /// 创建测试日志器。 + /// + /// 日志器名称。 + /// 带有当前最小级别设置的控制台日志器。 + public ILogger CreateLogger(string name) + { + return new ConsoleLogger(name, MinLevel, TextWriter.Null, false); + } + + /// + /// 重置该测试桩的并发观测状态。 + /// + public static void Reset() + { + _constructionGate = new ManualResetEventSlim(false); + Interlocked.Exchange(ref _constructionCount, 0); + } + + /// + /// 释放当前被阻塞的 provider 构造过程。 + /// + public static void ReleaseConstruction() + { + _constructionGate.Set(); + } + } +} diff --git a/GFramework.Core/Extensions/ContextAwareMediatorCommandExtensions.cs b/GFramework.Core/Extensions/ContextAwareMediatorCommandExtensions.cs index d001cb71..24490239 100644 --- a/GFramework.Core/Extensions/ContextAwareMediatorCommandExtensions.cs +++ b/GFramework.Core/Extensions/ContextAwareMediatorCommandExtensions.cs @@ -12,7 +12,7 @@ namespace GFramework.Core.Extensions; /// [EditorBrowsable(EditorBrowsableState.Never)] [Obsolete( - "Use GFramework.Core.Extensions.ContextAwareCqrsCommandExtensions instead. This compatibility alias will be removed in a future major version.")] + "Use GFramework.Cqrs.Extensions.ContextAwareCqrsCommandExtensions instead. This compatibility alias will be removed in a future major version.")] public static class ContextAwareMediatorCommandExtensions { /// diff --git a/GFramework.Core/Extensions/ContextAwareMediatorQueryExtensions.cs b/GFramework.Core/Extensions/ContextAwareMediatorQueryExtensions.cs index 4eb1b2c7..cf0b4513 100644 --- a/GFramework.Core/Extensions/ContextAwareMediatorQueryExtensions.cs +++ b/GFramework.Core/Extensions/ContextAwareMediatorQueryExtensions.cs @@ -12,7 +12,7 @@ namespace GFramework.Core.Extensions; /// [EditorBrowsable(EditorBrowsableState.Never)] [Obsolete( - "Use GFramework.Core.Extensions.ContextAwareCqrsQueryExtensions instead. This compatibility alias will be removed in a future major version.")] + "Use GFramework.Cqrs.Extensions.ContextAwareCqrsQueryExtensions instead. This compatibility alias will be removed in a future major version.")] public static class ContextAwareMediatorQueryExtensions { /// diff --git a/GFramework.Core/Properties/TypeForwarders.cs b/GFramework.Core/Properties/TypeForwarders.cs index a71d9af2..a27d2bf4 100644 --- a/GFramework.Core/Properties/TypeForwarders.cs +++ b/GFramework.Core/Properties/TypeForwarders.cs @@ -1,4 +1,12 @@ using System.Runtime.CompilerServices; using GFramework.Core.Abstractions.Logging; +using GFramework.Core.Cqrs.Command; +using GFramework.Core.Cqrs.Notification; +using GFramework.Core.Cqrs.Query; +using GFramework.Core.Cqrs.Request; [assembly: TypeForwardedTo(typeof(LoggerFactoryResolver))] +[assembly: TypeForwardedTo(typeof(CommandBase<,>))] +[assembly: TypeForwardedTo(typeof(QueryBase<,>))] +[assembly: TypeForwardedTo(typeof(RequestBase<,>))] +[assembly: TypeForwardedTo(typeof(NotificationBase<>))] diff --git a/GFramework.Cqrs/Command/CommandBase.cs b/GFramework.Cqrs/Command/CommandBase.cs index 57703c47..01351332 100644 --- a/GFramework.Cqrs/Command/CommandBase.cs +++ b/GFramework.Cqrs/Command/CommandBase.cs @@ -13,19 +13,23 @@ using GFramework.Cqrs.Abstractions.Cqrs.Command; -namespace GFramework.Cqrs.Command; +namespace GFramework.Core.Cqrs.Command; /// -/// 表示一个基础命令类,用于处理带有输入和响应的命令模式实现。 -/// 该类实现了 ICommand<TResponse> 接口,提供了通用的命令结构。 +/// 为携带输入模型的 CQRS 命令提供统一基类。 /// -/// 命令输入数据的类型 -/// 命令执行后返回结果的类型 -/// 命令执行所需的输入数据 -public abstract class CommandBase(TInput input) : ICommand where TInput : ICommandInput +/// 命令输入类型。 +/// 命令响应类型。 +/// 命令执行所需的输入对象。 +/// +/// 该类型继续保留在历史公开命名空间中,以避免调用方因 runtime 程序集拆分而批量修改继承层次。 +/// 具体实现现由 GFramework.Cqrs 程序集承载,并通过 type forward 维持旧程序集兼容性。 +/// +public abstract class CommandBase(TInput input) : ICommand + where TInput : ICommandInput { /// - /// 获取命令的输入数据。 + /// 获取命令执行时携带的输入对象。 /// public TInput Input => input; } diff --git a/GFramework.Cqrs/Cqrs/Behaviors/LoggingBehavior.cs b/GFramework.Cqrs/Cqrs/Behaviors/LoggingBehavior.cs index ccd9f0bf..63896c92 100644 --- a/GFramework.Cqrs/Cqrs/Behaviors/LoggingBehavior.cs +++ b/GFramework.Cqrs/Cqrs/Behaviors/LoggingBehavior.cs @@ -22,8 +22,8 @@ namespace GFramework.Cqrs.Cqrs.Behaviors; /// 请求类型。 /// 响应类型。 /// -/// 该行为保留在 GFramework.Core.Cqrs.Behaviors 命名空间以兼容现有调用点, -/// 但实现已迁入 GFramework.Cqrs 程序集,避免继续由 GFramework.Core 承载 CQRS runtime 细节。 +/// 该行为已迁移到 GFramework.Cqrs.Cqrs.Behaviors 命名空间, +/// 实现位于 GFramework.Cqrs 程序集,用于承载 CQRS runtime 细节并与旧层解耦。 /// public sealed class LoggingBehavior : IPipelineBehavior where TRequest : IRequest diff --git a/GFramework.Cqrs/Cqrs/Query/AbstractStreamQueryHandler.cs b/GFramework.Cqrs/Cqrs/Query/AbstractStreamQueryHandler.cs index 7d301009..f532f80d 100644 --- a/GFramework.Cqrs/Cqrs/Query/AbstractStreamQueryHandler.cs +++ b/GFramework.Cqrs/Cqrs/Query/AbstractStreamQueryHandler.cs @@ -17,22 +17,24 @@ using GFramework.Cqrs.Abstractions.Cqrs.Query; namespace GFramework.Cqrs.Cqrs.Query; /// -/// 抽象流式查询处理器基类 -/// 继承自轻量 CQRS 上下文基类并实现IStreamQueryHandler接口,为具体的流式查询处理器提供基础功能 -/// 支持流式处理查询并产生异步可枚举的响应序列,适用于大数据量或实时数据查询场景 +/// 为流式查询处理器提供共享的 CQRS 上下文访问基类。 /// -/// 流式查询类型,必须实现IStreamQuery接口 -/// 流式查询响应元素类型 +/// 流式查询类型,必须实现 +/// 流式查询响应元素类型。 +/// +/// 该基类复用 的上下文注入能力,并实现 +/// 契约,让派生类只需聚焦于结果流的生成逻辑。 +/// 适用于需要逐步产出大量结果或长生命周期响应流的查询场景。 +/// public abstract class AbstractStreamQueryHandler : CqrsContextAwareHandlerBase, IStreamRequestHandler where TQuery : IStreamQuery { /// - /// 处理流式查询并返回异步可枚举的响应序列 - /// 由具体的流式查询处理器子类实现流式查询处理逻辑 + /// 处理流式查询并返回异步可枚举的响应序列。 /// - /// 要处理的流式查询对象 - /// 取消令牌,用于取消流式查询操作 - /// 异步可枚举的响应序列,每个元素类型为TResponse + /// 要处理的流式查询对象。 + /// 用于停止结果流生成的取消令牌。 + /// 按需生成的异步响应序列。 public abstract IAsyncEnumerable Handle(TQuery query, CancellationToken cancellationToken); } diff --git a/GFramework.Cqrs/CqrsRuntimeFactory.cs b/GFramework.Cqrs/CqrsRuntimeFactory.cs index 45dbb07a..0a0f86ce 100644 --- a/GFramework.Cqrs/CqrsRuntimeFactory.cs +++ b/GFramework.Cqrs/CqrsRuntimeFactory.cs @@ -21,6 +21,9 @@ public static class CqrsRuntimeFactory /// 目标依赖注入容器。 /// 用于 runtime 诊断的日志器。 /// 默认 CQRS runtime。 + /// + /// 。 + /// public static ICqrsRuntime CreateRuntime(IIocContainer container, ILogger logger) { ArgumentNullException.ThrowIfNull(container); @@ -35,6 +38,9 @@ public static class CqrsRuntimeFactory /// 目标依赖注入容器。 /// 用于注册阶段诊断的日志器。 /// 默认 CQRS handler registrar。 + /// + /// 。 + /// public static ICqrsHandlerRegistrar CreateHandlerRegistrar(IIocContainer container, ILogger logger) { ArgumentNullException.ThrowIfNull(container); diff --git a/GFramework.Cqrs/Notification/NotificationBase.cs b/GFramework.Cqrs/Notification/NotificationBase.cs index 06390406..93daea8f 100644 --- a/GFramework.Cqrs/Notification/NotificationBase.cs +++ b/GFramework.Cqrs/Notification/NotificationBase.cs @@ -14,18 +14,22 @@ using GFramework.Cqrs.Abstractions.Cqrs; using GFramework.Cqrs.Abstractions.Cqrs.Notification; -namespace GFramework.Cqrs.Notification; +namespace GFramework.Core.Cqrs.Notification; /// -/// 表示一个基础通知类,用于处理带有输入的通知模式实现。 -/// 该类实现了 INotification 接口,提供了通用的通知结构。 +/// 为携带输入模型的 CQRS 通知提供统一基类。 /// -/// 通知输入数据的类型,必须实现 INotificationInput 接口 -/// 通知执行所需的输入数据 -public abstract class NotificationBase(TInput input) : INotification where TInput : INotificationInput +/// 通知输入类型,必须实现 +/// 通知广播时携带的输入对象。 +/// +/// 该类型继续保留在历史公开命名空间中,以避免调用方因 runtime 程序集拆分而批量修改继承层次。 +/// 具体实现现由 GFramework.Cqrs 程序集承载,并通过 type forward 维持旧程序集兼容性。 +/// +public abstract class NotificationBase(TInput input) : INotification + where TInput : INotificationInput { /// - /// 获取通知的输入数据。 + /// 获取通知广播时携带的输入对象。 /// public TInput Input => input; } diff --git a/GFramework.Cqrs/Query/QueryBase.cs b/GFramework.Cqrs/Query/QueryBase.cs index d0f491b1..9d15e028 100644 --- a/GFramework.Cqrs/Query/QueryBase.cs +++ b/GFramework.Cqrs/Query/QueryBase.cs @@ -13,19 +13,23 @@ using GFramework.Cqrs.Abstractions.Cqrs.Query; -namespace GFramework.Cqrs.Query; +namespace GFramework.Core.Cqrs.Query; /// -/// 表示一个基础查询类,用于处理带有输入和响应的查询模式实现。 -/// 该类实现 IQuery<TResponse> 接口,提供了通用的查询结构。 +/// 为携带输入模型的 CQRS 查询提供统一基类。 /// -/// 查询输入数据的类型,必须实现 IQueryInput 接口 -/// 查询执行后返回结果的类型 -/// 查询执行所需的输入数据 -public abstract class QueryBase(TInput input) : IQuery where TInput : IQueryInput +/// 查询输入类型,必须实现 +/// 查询响应类型。 +/// 查询执行所需的输入对象。 +/// +/// 该类型继续保留在历史公开命名空间中,以避免调用方因 runtime 程序集拆分而批量修改继承层次。 +/// 具体实现现由 GFramework.Cqrs 程序集承载,并通过 type forward 维持旧程序集兼容性。 +/// +public abstract class QueryBase(TInput input) : IQuery + where TInput : IQueryInput { /// - /// 获取查询的输入数据。 + /// 获取查询执行时携带的输入对象。 /// public TInput Input => input; } diff --git a/GFramework.Cqrs/Request/RequestBase.cs b/GFramework.Cqrs/Request/RequestBase.cs index 8c534c86..40d7a44d 100644 --- a/GFramework.Cqrs/Request/RequestBase.cs +++ b/GFramework.Cqrs/Request/RequestBase.cs @@ -14,19 +14,23 @@ using GFramework.Cqrs.Abstractions.Cqrs; using GFramework.Cqrs.Abstractions.Cqrs.Request; -namespace GFramework.Cqrs.Request; +namespace GFramework.Core.Cqrs.Request; /// -/// 表示一个基础请求类,用于处理带有输入和响应的请求模式实现。 -/// 该类实现了 IRequest<TResponse> 接口,提供了通用的请求结构。 +/// 为携带输入模型的通用 CQRS 请求提供统一基类。 /// -/// 请求输入数据的类型,必须实现 IRequestInput 接口 -/// 请求执行后返回结果的类型 -/// 请求执行所需的输入数据 -public abstract class RequestBase(TInput input) : IRequest where TInput : IRequestInput +/// 请求输入类型,必须实现 +/// 请求响应类型。 +/// 请求执行所需的输入对象。 +/// +/// 该类型继续保留在历史公开命名空间中,以避免调用方因 runtime 程序集拆分而批量修改继承层次。 +/// 具体实现现由 GFramework.Cqrs 程序集承载,并通过 type forward 维持旧程序集兼容性。 +/// +public abstract class RequestBase(TInput input) : IRequest + where TInput : IRequestInput { /// - /// 获取请求的输入数据。 + /// 获取请求执行时携带的输入对象。 /// public TInput Input => input; } diff --git a/GFramework.Tests.Common/CqrsTestRuntime.cs b/GFramework.Tests.Common/CqrsTestRuntime.cs index f044bbc8..ad02120e 100644 --- a/GFramework.Tests.Common/CqrsTestRuntime.cs +++ b/GFramework.Tests.Common/CqrsTestRuntime.cs @@ -4,11 +4,10 @@ using System.Reflection; using GFramework.Core.Abstractions.Cqrs; using GFramework.Core.Abstractions.Ioc; using GFramework.Core.Abstractions.Logging; +using GFramework.Core.Cqrs.Command; using GFramework.Core.Ioc; -using GFramework.Core.Logging; using GFramework.Cqrs; using GFramework.Cqrs.Abstractions.Cqrs; -using GFramework.Cqrs.Command; namespace GFramework.Tests.Common; @@ -48,7 +47,6 @@ public static class CqrsTestRuntime /// /// 目标测试容器。 /// - /// 反射调用底层 CQRS runtime 或注册器构造函数失败时抛出。 /// /// 这使仅使用 的测试环境也能观察与生产路径一致的 runtime 行为, /// 而无需完整启动服务模块管理器。 From 12c9c8a9ec24698792ab0cd5ed4304a0d4df00dc Mon Sep 17 00:00:00 2001 From: GeWuYou <95328647+GeWuYou@users.noreply.github.com> Date: Wed, 15 Apr 2026 22:27:55 +0800 Subject: [PATCH 06/12] =?UTF-8?q?refactor(cqrs):=20=E8=BF=81=E7=A7=BBCQRS?= =?UTF-8?q?=E5=9F=BA=E7=A1=80=E7=B1=BB=E5=9E=8B=E5=AE=9E=E7=8E=B0=E5=B9=B6?= =?UTF-8?q?=E7=BB=B4=E6=8A=A4=E7=A8=8B=E5=BA=8F=E9=9B=86=E5=85=BC=E5=AE=B9?= =?UTF-8?q?=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将CommandBase、QueryBase、RequestBase、NotificationBase类型从Core模块迁移到独立Cqrs模块 - 在GFramework.Core中添加TypeForwarder维持向后兼容性 - 创建CqrsPublicNamespaceCompatibilityTests验证运行时程序集转发功能 - 更新CqrsTestRuntime移除对已迁移类型的直接引用 - 为所有基础类型添加统一输入模型支持和命名空间兼容性注释 - 实现完整的CQRS抽象基类重构以支持模块化架构 --- .../Cqrs/CqrsPublicNamespaceCompatibilityTests.cs | 4 ---- GFramework.Core/Properties/TypeForwarders.cs | 4 ---- GFramework.Cqrs/Command/CommandBase.cs | 2 +- GFramework.Cqrs/Notification/NotificationBase.cs | 4 ++-- GFramework.Cqrs/Query/QueryBase.cs | 4 ++-- GFramework.Cqrs/Request/RequestBase.cs | 4 ++-- GFramework.Tests.Common/CqrsTestRuntime.cs | 1 - 7 files changed, 7 insertions(+), 16 deletions(-) diff --git a/GFramework.Core.Tests/Cqrs/CqrsPublicNamespaceCompatibilityTests.cs b/GFramework.Core.Tests/Cqrs/CqrsPublicNamespaceCompatibilityTests.cs index 2f02dbb7..20470dd8 100644 --- a/GFramework.Core.Tests/Cqrs/CqrsPublicNamespaceCompatibilityTests.cs +++ b/GFramework.Core.Tests/Cqrs/CqrsPublicNamespaceCompatibilityTests.cs @@ -1,7 +1,3 @@ -using GFramework.Core.Cqrs.Command; -using GFramework.Core.Cqrs.Notification; -using GFramework.Core.Cqrs.Query; -using GFramework.Core.Cqrs.Request; using GFramework.Cqrs.Abstractions.Cqrs; using GFramework.Cqrs.Abstractions.Cqrs.Command; using GFramework.Cqrs.Abstractions.Cqrs.Notification; diff --git a/GFramework.Core/Properties/TypeForwarders.cs b/GFramework.Core/Properties/TypeForwarders.cs index a27d2bf4..df43a698 100644 --- a/GFramework.Core/Properties/TypeForwarders.cs +++ b/GFramework.Core/Properties/TypeForwarders.cs @@ -1,9 +1,5 @@ using System.Runtime.CompilerServices; using GFramework.Core.Abstractions.Logging; -using GFramework.Core.Cqrs.Command; -using GFramework.Core.Cqrs.Notification; -using GFramework.Core.Cqrs.Query; -using GFramework.Core.Cqrs.Request; [assembly: TypeForwardedTo(typeof(LoggerFactoryResolver))] [assembly: TypeForwardedTo(typeof(CommandBase<,>))] diff --git a/GFramework.Cqrs/Command/CommandBase.cs b/GFramework.Cqrs/Command/CommandBase.cs index 01351332..486e4136 100644 --- a/GFramework.Cqrs/Command/CommandBase.cs +++ b/GFramework.Cqrs/Command/CommandBase.cs @@ -13,7 +13,7 @@ using GFramework.Cqrs.Abstractions.Cqrs.Command; -namespace GFramework.Core.Cqrs.Command; +namespace GFramework.Cqrs.Command; /// /// 为携带输入模型的 CQRS 命令提供统一基类。 diff --git a/GFramework.Cqrs/Notification/NotificationBase.cs b/GFramework.Cqrs/Notification/NotificationBase.cs index 93daea8f..b585a9a4 100644 --- a/GFramework.Cqrs/Notification/NotificationBase.cs +++ b/GFramework.Cqrs/Notification/NotificationBase.cs @@ -14,12 +14,12 @@ using GFramework.Cqrs.Abstractions.Cqrs; using GFramework.Cqrs.Abstractions.Cqrs.Notification; -namespace GFramework.Core.Cqrs.Notification; +namespace GFramework.Cqrs.Notification; /// /// 为携带输入模型的 CQRS 通知提供统一基类。 /// -/// 通知输入类型,必须实现 +/// 通知输入类型,必须实现 /// 通知广播时携带的输入对象。 /// /// 该类型继续保留在历史公开命名空间中,以避免调用方因 runtime 程序集拆分而批量修改继承层次。 diff --git a/GFramework.Cqrs/Query/QueryBase.cs b/GFramework.Cqrs/Query/QueryBase.cs index 9d15e028..880abf0c 100644 --- a/GFramework.Cqrs/Query/QueryBase.cs +++ b/GFramework.Cqrs/Query/QueryBase.cs @@ -13,12 +13,12 @@ using GFramework.Cqrs.Abstractions.Cqrs.Query; -namespace GFramework.Core.Cqrs.Query; +namespace GFramework.Cqrs.Query; /// /// 为携带输入模型的 CQRS 查询提供统一基类。 /// -/// 查询输入类型,必须实现 +/// 查询输入类型,必须实现 /// 查询响应类型。 /// 查询执行所需的输入对象。 /// diff --git a/GFramework.Cqrs/Request/RequestBase.cs b/GFramework.Cqrs/Request/RequestBase.cs index 40d7a44d..93af04ae 100644 --- a/GFramework.Cqrs/Request/RequestBase.cs +++ b/GFramework.Cqrs/Request/RequestBase.cs @@ -14,12 +14,12 @@ using GFramework.Cqrs.Abstractions.Cqrs; using GFramework.Cqrs.Abstractions.Cqrs.Request; -namespace GFramework.Core.Cqrs.Request; +namespace GFramework.Cqrs.Request; /// /// 为携带输入模型的通用 CQRS 请求提供统一基类。 /// -/// 请求输入类型,必须实现 +/// 请求输入类型,必须实现 /// 请求响应类型。 /// 请求执行所需的输入对象。 /// diff --git a/GFramework.Tests.Common/CqrsTestRuntime.cs b/GFramework.Tests.Common/CqrsTestRuntime.cs index ad02120e..aec866df 100644 --- a/GFramework.Tests.Common/CqrsTestRuntime.cs +++ b/GFramework.Tests.Common/CqrsTestRuntime.cs @@ -4,7 +4,6 @@ using System.Reflection; using GFramework.Core.Abstractions.Cqrs; using GFramework.Core.Abstractions.Ioc; using GFramework.Core.Abstractions.Logging; -using GFramework.Core.Cqrs.Command; using GFramework.Core.Ioc; using GFramework.Cqrs; using GFramework.Cqrs.Abstractions.Cqrs; From b747787b8754d62c4dafcdf01103fa700039161b Mon Sep 17 00:00:00 2001 From: GeWuYou <95328647+GeWuYou@users.noreply.github.com> Date: Wed, 15 Apr 2026 22:36:55 +0800 Subject: [PATCH 07/12] =?UTF-8?q?refactor(core):=20=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E7=B1=BB=E5=9E=8B=E8=BD=AC=E5=8F=91=E5=99=A8=E5=92=8C=E6=B5=8B?= =?UTF-8?q?=E8=AF=95=E6=96=87=E4=BB=B6=E7=9A=84=E5=91=BD=E5=90=8D=E7=A9=BA?= =?UTF-8?q?=E9=97=B4=E5=BC=95=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在 TypeForwarders.cs 中添加 Cqrs 相关命名空间引用 - 在 CqrsPublicNamespaceCompatibilityTests.cs 中同步更新命名空间导入 - 在 CqrsTestRuntime.cs 中补充 Command 命名空间引用 - 确保所有测试运行时环境的命名空间一致性 --- .../Cqrs/CqrsPublicNamespaceCompatibilityTests.cs | 4 ++++ GFramework.Core/Properties/TypeForwarders.cs | 4 ++++ GFramework.Tests.Common/CqrsTestRuntime.cs | 1 + 3 files changed, 9 insertions(+) diff --git a/GFramework.Core.Tests/Cqrs/CqrsPublicNamespaceCompatibilityTests.cs b/GFramework.Core.Tests/Cqrs/CqrsPublicNamespaceCompatibilityTests.cs index 20470dd8..40506524 100644 --- a/GFramework.Core.Tests/Cqrs/CqrsPublicNamespaceCompatibilityTests.cs +++ b/GFramework.Core.Tests/Cqrs/CqrsPublicNamespaceCompatibilityTests.cs @@ -3,6 +3,10 @@ using GFramework.Cqrs.Abstractions.Cqrs.Command; using GFramework.Cqrs.Abstractions.Cqrs.Notification; using GFramework.Cqrs.Abstractions.Cqrs.Query; using GFramework.Cqrs.Abstractions.Cqrs.Request; +using GFramework.Cqrs.Command; +using GFramework.Cqrs.Notification; +using GFramework.Cqrs.Query; +using GFramework.Cqrs.Request; namespace GFramework.Core.Tests.Cqrs; diff --git a/GFramework.Core/Properties/TypeForwarders.cs b/GFramework.Core/Properties/TypeForwarders.cs index df43a698..2c260462 100644 --- a/GFramework.Core/Properties/TypeForwarders.cs +++ b/GFramework.Core/Properties/TypeForwarders.cs @@ -1,5 +1,9 @@ using System.Runtime.CompilerServices; using GFramework.Core.Abstractions.Logging; +using GFramework.Cqrs.Command; +using GFramework.Cqrs.Notification; +using GFramework.Cqrs.Query; +using GFramework.Cqrs.Request; [assembly: TypeForwardedTo(typeof(LoggerFactoryResolver))] [assembly: TypeForwardedTo(typeof(CommandBase<,>))] diff --git a/GFramework.Tests.Common/CqrsTestRuntime.cs b/GFramework.Tests.Common/CqrsTestRuntime.cs index aec866df..e7c971db 100644 --- a/GFramework.Tests.Common/CqrsTestRuntime.cs +++ b/GFramework.Tests.Common/CqrsTestRuntime.cs @@ -7,6 +7,7 @@ using GFramework.Core.Abstractions.Logging; using GFramework.Core.Ioc; using GFramework.Cqrs; using GFramework.Cqrs.Abstractions.Cqrs; +using GFramework.Cqrs.Command; namespace GFramework.Tests.Common; From 7e402d91d3b118e81e8f79321044472edeb563ca Mon Sep 17 00:00:00 2001 From: GeWuYou <95328647+GeWuYou@users.noreply.github.com> Date: Wed, 15 Apr 2026 22:50:21 +0800 Subject: [PATCH 08/12] =?UTF-8?q?docs(cqrs):=20=E4=BF=AE=E6=AD=A3=E6=B3=9B?= =?UTF-8?q?=E5=9E=8B=E7=B1=BB=E5=9E=8B=E5=8F=82=E6=95=B0=E7=9A=84XML?= =?UTF-8?q?=E6=96=87=E6=A1=A3=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修正NotificationBase中TInput类型的XML文档注释,将input更正为INotificationInput - 修正QueryBase中TInput类型的XML文档注释,将TResponse更正为IQueryInput - 修正RequestBase中TInput类型的XML文档注释,将TResponse更正为IRequestInput --- GFramework.Cqrs/Notification/NotificationBase.cs | 2 +- GFramework.Cqrs/Query/QueryBase.cs | 2 +- GFramework.Cqrs/Request/RequestBase.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/GFramework.Cqrs/Notification/NotificationBase.cs b/GFramework.Cqrs/Notification/NotificationBase.cs index b585a9a4..653a22f2 100644 --- a/GFramework.Cqrs/Notification/NotificationBase.cs +++ b/GFramework.Cqrs/Notification/NotificationBase.cs @@ -19,7 +19,7 @@ namespace GFramework.Cqrs.Notification; /// /// 为携带输入模型的 CQRS 通知提供统一基类。 /// -/// 通知输入类型,必须实现 +/// 通知输入类型,必须实现 /// 通知广播时携带的输入对象。 /// /// 该类型继续保留在历史公开命名空间中,以避免调用方因 runtime 程序集拆分而批量修改继承层次。 diff --git a/GFramework.Cqrs/Query/QueryBase.cs b/GFramework.Cqrs/Query/QueryBase.cs index 880abf0c..4da82bf3 100644 --- a/GFramework.Cqrs/Query/QueryBase.cs +++ b/GFramework.Cqrs/Query/QueryBase.cs @@ -18,7 +18,7 @@ namespace GFramework.Cqrs.Query; /// /// 为携带输入模型的 CQRS 查询提供统一基类。 /// -/// 查询输入类型,必须实现 +/// 查询输入类型,必须实现 /// 查询响应类型。 /// 查询执行所需的输入对象。 /// diff --git a/GFramework.Cqrs/Request/RequestBase.cs b/GFramework.Cqrs/Request/RequestBase.cs index 93af04ae..08ceabe4 100644 --- a/GFramework.Cqrs/Request/RequestBase.cs +++ b/GFramework.Cqrs/Request/RequestBase.cs @@ -19,7 +19,7 @@ namespace GFramework.Cqrs.Request; /// /// 为携带输入模型的通用 CQRS 请求提供统一基类。 /// -/// 请求输入类型,必须实现 +/// 请求输入类型,必须实现 /// 请求响应类型。 /// 请求执行所需的输入对象。 /// From 7a2127b50e7414865bdafbc755a5d3c0bab9b539 Mon Sep 17 00:00:00 2001 From: GeWuYou <95328647+GeWuYou@users.noreply.github.com> Date: Wed, 15 Apr 2026 22:51:10 +0800 Subject: [PATCH 09/12] =?UTF-8?q?refactor(cqrs):=20=E6=9B=B4=E6=96=B0Cqrs?= =?UTF-8?q?=E5=91=BD=E5=90=8D=E7=A9=BA=E9=97=B4=E8=B7=AF=E5=BE=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将GFramework.Core.Cqrs.Command更改为GFramework.Cqrs.Command - 将GFramework.Core.Cqrs.Query更改为GFramework.Cqrs.Query - 将GFramework.Core.Cqrs.Request更改为GFramework.Cqrs.Request - 将GFramework.Core.Cqrs.Notification更改为GFramework.Cqrs.Notification --- .../Cqrs/CqrsPublicNamespaceCompatibilityTests.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/GFramework.Core.Tests/Cqrs/CqrsPublicNamespaceCompatibilityTests.cs b/GFramework.Core.Tests/Cqrs/CqrsPublicNamespaceCompatibilityTests.cs index 40506524..dd119413 100644 --- a/GFramework.Core.Tests/Cqrs/CqrsPublicNamespaceCompatibilityTests.cs +++ b/GFramework.Core.Tests/Cqrs/CqrsPublicNamespaceCompatibilityTests.cs @@ -24,10 +24,10 @@ public sealed class CqrsPublicNamespaceCompatibilityTests { Assert.Multiple(() => { - AssertLegacyType(typeof(CommandBase), "GFramework.Core.Cqrs.Command"); - AssertLegacyType(typeof(QueryBase), "GFramework.Core.Cqrs.Query"); - AssertLegacyType(typeof(RequestBase), "GFramework.Core.Cqrs.Request"); - AssertLegacyType(typeof(NotificationBase), "GFramework.Core.Cqrs.Notification"); + AssertLegacyType(typeof(CommandBase), "GFramework.Cqrs.Command"); + AssertLegacyType(typeof(QueryBase), "GFramework.Cqrs.Query"); + AssertLegacyType(typeof(RequestBase), "GFramework.Cqrs.Request"); + AssertLegacyType(typeof(NotificationBase), "GFramework.Cqrs.Notification"); }); } From ff9b01063920f3e27c26e84b7d0c1825eb99084e Mon Sep 17 00:00:00 2001 From: GeWuYou <95328647+GeWuYou@users.noreply.github.com> Date: Wed, 15 Apr 2026 22:51:31 +0800 Subject: [PATCH 10/12] =?UTF-8?q?refactor(cqrs):=20=E8=B0=83=E6=95=B4?= =?UTF-8?q?=E5=9F=BA=E7=A1=80=E6=B6=88=E6=81=AF=E7=B1=BB=E5=9E=8B=E7=9A=84?= =?UTF-8?q?=E5=91=BD=E5=90=8D=E7=A9=BA=E9=97=B4=E5=92=8C=E7=A8=8B=E5=BA=8F?= =?UTF-8?q?=E9=9B=86=E5=B8=83=E5=B1=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将基础消息类型从遗留的Core.Cqrs命名空间迁移到新的Cqrs命名空间 - 保持运行时程序集承载实现的架构设计 - 更新测试方法名称以反映新的命名空间结构 - 确保公共API兼容性的同时优化组件组织结构 --- .../Cqrs/CqrsPublicNamespaceCompatibilityTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GFramework.Core.Tests/Cqrs/CqrsPublicNamespaceCompatibilityTests.cs b/GFramework.Core.Tests/Cqrs/CqrsPublicNamespaceCompatibilityTests.cs index dd119413..dd99d8dc 100644 --- a/GFramework.Core.Tests/Cqrs/CqrsPublicNamespaceCompatibilityTests.cs +++ b/GFramework.Core.Tests/Cqrs/CqrsPublicNamespaceCompatibilityTests.cs @@ -20,7 +20,7 @@ public sealed class CqrsPublicNamespaceCompatibilityTests /// 验证基础消息类型继续暴露在历史 Core.Cqrs 命名空间,同时由独立 runtime 程序集承载实现。 /// [Test] - public void Base_Message_Types_Should_Remain_In_Legacy_Namespaces_While_Living_In_Runtime_Assembly() + public void Base_Message_Types_Should_Live_In_Cqrs_Namespaces_And_Runtime_Assembly() { Assert.Multiple(() => { From 7b63a65f51196200118f68f18bcd9795bd166080 Mon Sep 17 00:00:00 2001 From: GeWuYou <95328647+GeWuYou@users.noreply.github.com> Date: Wed, 15 Apr 2026 22:52:26 +0800 Subject: [PATCH 11/12] =?UTF-8?q?refactor(tests):=20=E9=87=8D=E6=9E=84CQRS?= =?UTF-8?q?=E7=B1=BB=E5=9E=8B=E8=BD=AC=E5=8F=91=E6=B5=8B=E8=AF=95=E4=BB=A5?= =?UTF-8?q?=E4=BD=BF=E7=94=A8=E6=96=B0=E5=91=BD=E5=90=8D=E7=A9=BA=E9=97=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 更新测试方法名称从 GFramework_Core_Assembly_Should_Forward_Legacy_Base_Types_To_Runtime_Assembly 为 Type_Forwarding_Should_Resolve_Cqrs_Types_From_Core_Assembly - 将断言中的程序集限定名从 GFramework.Core.Cqrs.* 更新为 GFramework.Cqrs.* - 保持对命令、查询、请求和通知基类型的验证逻辑不变 --- .../Cqrs/CqrsPublicNamespaceCompatibilityTests.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/GFramework.Core.Tests/Cqrs/CqrsPublicNamespaceCompatibilityTests.cs b/GFramework.Core.Tests/Cqrs/CqrsPublicNamespaceCompatibilityTests.cs index dd99d8dc..f89ccddc 100644 --- a/GFramework.Core.Tests/Cqrs/CqrsPublicNamespaceCompatibilityTests.cs +++ b/GFramework.Core.Tests/Cqrs/CqrsPublicNamespaceCompatibilityTests.cs @@ -35,14 +35,14 @@ public sealed class CqrsPublicNamespaceCompatibilityTests /// 验证旧的 GFramework.Core 程序集限定名仍可解析到迁移后的 runtime 实现类型。 /// [Test] - public void GFramework_Core_Assembly_Should_Forward_Legacy_Base_Types_To_Runtime_Assembly() + public void Type_Forwarding_Should_Resolve_Cqrs_Types_From_Core_Assembly() { Assert.Multiple(() => { - AssertForwardedType("GFramework.Core.Cqrs.Command.CommandBase`2, GFramework.Core"); - AssertForwardedType("GFramework.Core.Cqrs.Query.QueryBase`2, GFramework.Core"); - AssertForwardedType("GFramework.Core.Cqrs.Request.RequestBase`2, GFramework.Core"); - AssertForwardedType("GFramework.Core.Cqrs.Notification.NotificationBase`1, GFramework.Core"); + AssertForwardedType("GFramework.Cqrs.Command.CommandBase`2, GFramework.Core"); + AssertForwardedType("GFramework.Cqrs.Query.QueryBase`2, GFramework.Core"); + AssertForwardedType("GFramework.Cqrs.Request.RequestBase`2, GFramework.Core"); + AssertForwardedType("GFramework.Cqrs.Notification.NotificationBase`1, GFramework.Core"); }); } From 922ad43b5e0728259b9523c7d67fe09c4dc7fa31 Mon Sep 17 00:00:00 2001 From: GeWuYou <95328647+GeWuYou@users.noreply.github.com> Date: Wed, 15 Apr 2026 22:59:46 +0800 Subject: [PATCH 12/12] =?UTF-8?q?fix(cqrs):=20=E4=BF=AE=E5=A4=8DCQRS?= =?UTF-8?q?=E5=91=BD=E5=90=8D=E7=A9=BA=E9=97=B4=E5=85=BC=E5=AE=B9=E6=80=A7?= =?UTF-8?q?=E6=B5=8B=E8=AF=95=E4=B8=AD=E7=9A=84=E7=B1=BB=E5=9E=8B=E8=A7=A3?= =?UTF-8?q?=E6=9E=90=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 更新注释以更准确描述基础消息类型的命名空间暴露方式 - 将Type.GetType的throwOnError参数从true改为false以避免异常抛出 - 调整测试逻辑以更好地处理类型解析场景 --- .../Cqrs/CqrsPublicNamespaceCompatibilityTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/GFramework.Core.Tests/Cqrs/CqrsPublicNamespaceCompatibilityTests.cs b/GFramework.Core.Tests/Cqrs/CqrsPublicNamespaceCompatibilityTests.cs index f89ccddc..64f69f02 100644 --- a/GFramework.Core.Tests/Cqrs/CqrsPublicNamespaceCompatibilityTests.cs +++ b/GFramework.Core.Tests/Cqrs/CqrsPublicNamespaceCompatibilityTests.cs @@ -17,7 +17,7 @@ namespace GFramework.Core.Tests.Cqrs; public sealed class CqrsPublicNamespaceCompatibilityTests { /// - /// 验证基础消息类型继续暴露在历史 Core.Cqrs 命名空间,同时由独立 runtime 程序集承载实现。 + /// 验证基础消息类型继续暴露在历史公开 CQRS 命名空间(GFramework.Cqrs.*),同时由独立 runtime 程序集承载实现。 /// [Test] public void Base_Message_Types_Should_Live_In_Cqrs_Namespaces_And_Runtime_Assembly() @@ -57,7 +57,7 @@ public sealed class CqrsPublicNamespaceCompatibilityTests private static void AssertForwardedType(string assemblyQualifiedTypeName) { - var resolvedType = Type.GetType(assemblyQualifiedTypeName, throwOnError: true); + var resolvedType = Type.GetType(assemblyQualifiedTypeName, throwOnError: false); Assert.Multiple(() => {