From a7604de804a25e9e4b6c29bf8f5c64e01ef8c61e Mon Sep 17 00:00:00 2001 From: GeWuYou <95328647+GeWuYou@users.noreply.github.com> Date: Thu, 16 Apr 2026 08:37:40 +0800 Subject: [PATCH] =?UTF-8?q?feat(ioc):=20=E6=B7=BB=E5=8A=A0=20Microsoft=20D?= =?UTF-8?q?I=20=E5=AE=B9=E5=99=A8=E9=80=82=E9=85=8D=E5=99=A8=E5=92=8C=20CQ?= =?UTF-8?q?RS=20=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 - 实现 MicrosoftDiContainer 类,提供对 Microsoft.Extensions.DependencyInjection 的适配 - 添加线程安全的依赖注入容器功能,支持单例、瞬态和作用域服务注册 - 实现 CqrsRuntimeModule 模块,用于注册 CQRS 运行时组件 - 添加 CqrsRuntimeFactory 工厂类,提供 CQRS 运行时实现的创建入口 - 实现 DefaultCqrsRegistrationService,处理 CQRS 处理器的程序集注册 - 添加 CqrsTestRuntime 测试工具类,为测试环境提供 CQRS 运行时访问 - 支持多种注册方式包括实例注册、类型映射和工厂方法 - 实现服务获取、查询和生命周期管理功能 - 添加容器冻结机制以构建服务提供者 - 支持 CQRS 管道行为和处理器的批量注册功能 --- GFramework.Core/Ioc/MicrosoftDiContainer.cs | 56 ++++-------------- .../Services/Modules/CqrsRuntimeModule.cs | 7 ++- GFramework.Cqrs/CqrsRuntimeFactory.cs | 17 ++++++ GFramework.Cqrs/ICqrsRegistrationService.cs | 19 ++++++ .../DefaultCqrsRegistrationService.cs | 59 +++++++++++++++++++ GFramework.Tests.Common/CqrsTestRuntime.cs | 8 +++ 6 files changed, 118 insertions(+), 48 deletions(-) create mode 100644 GFramework.Cqrs/ICqrsRegistrationService.cs create mode 100644 GFramework.Cqrs/Internal/DefaultCqrsRegistrationService.cs diff --git a/GFramework.Core/Ioc/MicrosoftDiContainer.cs b/GFramework.Core/Ioc/MicrosoftDiContainer.cs index 390f4c91..78295ed4 100644 --- a/GFramework.Core/Ioc/MicrosoftDiContainer.cs +++ b/GFramework.Core/Ioc/MicrosoftDiContainer.cs @@ -5,6 +5,7 @@ using GFramework.Core.Abstractions.Ioc; using GFramework.Core.Abstractions.Logging; using GFramework.Core.Abstractions.Systems; using GFramework.Core.Rule; +using GFramework.Cqrs; using GFramework.Cqrs.Abstractions.Cqrs; namespace GFramework.Core.Ioc; @@ -56,12 +57,6 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null) /// private readonly HashSet _registeredInstances = []; - /// - /// 已接入 CQRS handler 注册流程的程序集键集合。 - /// 使用稳定字符串键而不是 Assembly 引用本身,以避免默认路径和显式扩展路径使用不同 Assembly 对象时重复注册。 - /// - private readonly HashSet _registeredCqrsHandlerAssemblyKeys = new(StringComparer.Ordinal); - /// /// 日志记录器,用于记录容器操作日志 /// @@ -405,26 +400,7 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null) try { ThrowIfFrozen(); - - var processedAssemblyKeys = new HashSet(StringComparer.Ordinal); - foreach (var assembly in assemblies - .Where(static assembly => assembly is not null) - .OrderBy(GetCqrsAssemblyRegistrationKey, StringComparer.Ordinal)) - { - var assemblyKey = GetCqrsAssemblyRegistrationKey(assembly); - if (!processedAssemblyKeys.Add(assemblyKey)) - continue; - - if (_registeredCqrsHandlerAssemblyKeys.Contains(assemblyKey)) - { - _logger.Debug( - $"Skipping CQRS handler registration for assembly {assemblyKey} because it was already registered."); - continue; - } - - ResolveCqrsHandlerRegistrar().RegisterHandlers([assembly]); - _registeredCqrsHandlerAssemblyKeys.Add(assemblyKey); - } + ResolveCqrsRegistrationService().RegisterHandlers(assemblies); } finally { @@ -455,22 +431,22 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null) #region Get /// - /// 获取当前容器中已注册的 CQRS 处理器注册器。 + /// 获取当前容器中已注册的 CQRS 程序集注册协调器。 /// 该方法仅供容器内部在注册阶段使用,因此直接读取服务描述符中的实例绑定, /// 避免在容器未冻结前依赖完整的服务提供者构建流程。 /// - /// 已注册的 CQRS 处理器注册器实例。 - /// 未找到可用的 CQRS 处理器注册器实例时抛出。 - private ICqrsHandlerRegistrar ResolveCqrsHandlerRegistrar() + /// 已注册的 CQRS 程序集注册协调器实例。 + /// 未找到可用的 CQRS 程序集注册协调器实例时抛出。 + private ICqrsRegistrationService ResolveCqrsRegistrationService() { var descriptor = GetServicesUnsafe.LastOrDefault(static service => - service.ServiceType == typeof(ICqrsHandlerRegistrar)); + service.ServiceType == typeof(ICqrsRegistrationService)); - if (descriptor?.ImplementationInstance is ICqrsHandlerRegistrar registrar) - return registrar; + if (descriptor?.ImplementationInstance is ICqrsRegistrationService registrationService) + return registrationService; const string errorMessage = - "ICqrsHandlerRegistrar not registered. Ensure the CQRS runtime module has been installed before registering handlers."; + "ICqrsRegistrationService not registered. Ensure the CQRS runtime module has been installed before registering handlers."; _logger.Error(errorMessage); throw new InvalidOperationException(errorMessage); } @@ -832,7 +808,6 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null) GetServicesUnsafe.Clear(); _registeredInstances.Clear(); - _registeredCqrsHandlerAssemblyKeys.Clear(); _provider = null; _logger.Info("Container cleared"); } @@ -904,16 +879,5 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null) } } - /// - /// 生成 CQRS handler 注册用的稳定程序集键。 - /// 该键需要同时兼顾真实程序集与测试中使用的 mocked Assembly,避免仅靠引用比较导致重复接入。 - /// - /// 目标程序集。 - /// 稳定的程序集标识字符串。 - private static string GetCqrsAssemblyRegistrationKey(Assembly assembly) - { - return assembly.FullName ?? assembly.GetName().Name ?? assembly.ToString(); - } - #endregion } diff --git a/GFramework.Core/Services/Modules/CqrsRuntimeModule.cs b/GFramework.Core/Services/Modules/CqrsRuntimeModule.cs index 9a36d003..1da8f684 100644 --- a/GFramework.Core/Services/Modules/CqrsRuntimeModule.cs +++ b/GFramework.Core/Services/Modules/CqrsRuntimeModule.cs @@ -39,12 +39,15 @@ public sealed class CqrsRuntimeModule : IServiceModule var dispatcherLogger = LoggerFactoryResolver.Provider.CreateLogger("CqrsDispatcher"); var registrarLogger = LoggerFactoryResolver.Provider.CreateLogger("DefaultCqrsHandlerRegistrar"); + var registrationLogger = LoggerFactoryResolver.Provider.CreateLogger("DefaultCqrsRegistrationService"); var runtime = CqrsRuntimeFactory.CreateRuntime(container, dispatcherLogger); + var registrar = CqrsRuntimeFactory.CreateHandlerRegistrar(container, registrarLogger); container.Register(runtime); container.Register((LegacyICqrsRuntime)runtime); - container.Register( - CqrsRuntimeFactory.CreateHandlerRegistrar(container, registrarLogger)); + container.Register(registrar); + container.Register( + CqrsRuntimeFactory.CreateRegistrationService(registrar, registrationLogger)); } /// diff --git a/GFramework.Cqrs/CqrsRuntimeFactory.cs b/GFramework.Cqrs/CqrsRuntimeFactory.cs index 1357975d..cbed68aa 100644 --- a/GFramework.Cqrs/CqrsRuntimeFactory.cs +++ b/GFramework.Cqrs/CqrsRuntimeFactory.cs @@ -47,4 +47,21 @@ public static class CqrsRuntimeFactory return new DefaultCqrsHandlerRegistrar(container, logger); } + + /// + /// 创建默认的 CQRS 程序集注册协调器。 + /// + /// 底层 handler 注册器。 + /// 用于注册阶段诊断的日志器。 + /// 默认 CQRS 程序集注册协调器。 + /// + /// 。 + /// + public static ICqrsRegistrationService CreateRegistrationService(ICqrsHandlerRegistrar registrar, ILogger logger) + { + ArgumentNullException.ThrowIfNull(registrar); + ArgumentNullException.ThrowIfNull(logger); + + return new DefaultCqrsRegistrationService(registrar, logger); + } } diff --git a/GFramework.Cqrs/ICqrsRegistrationService.cs b/GFramework.Cqrs/ICqrsRegistrationService.cs new file mode 100644 index 00000000..89a0a9d1 --- /dev/null +++ b/GFramework.Cqrs/ICqrsRegistrationService.cs @@ -0,0 +1,19 @@ +using System.Reflection; + +namespace GFramework.Cqrs; + +/// +/// 协调 CQRS 处理器程序集的接入流程。 +/// +/// +/// 该服务封装“程序集去重 + 生成注册器优先 + 反射回退”的默认接入语义, +/// 让 GFramework.Core 的容器层只保留公开入口,而不再直接维护 CQRS handler 注册细节。 +/// +public interface ICqrsRegistrationService +{ + /// + /// 注册一个或多个程序集中的 CQRS 处理器。 + /// + /// 要接入的程序集集合。 + void RegisterHandlers(IEnumerable assemblies); +} diff --git a/GFramework.Cqrs/Internal/DefaultCqrsRegistrationService.cs b/GFramework.Cqrs/Internal/DefaultCqrsRegistrationService.cs new file mode 100644 index 00000000..712ebea6 --- /dev/null +++ b/GFramework.Cqrs/Internal/DefaultCqrsRegistrationService.cs @@ -0,0 +1,59 @@ +using System.Reflection; +using GFramework.Core.Abstractions.Logging; +using GFramework.Cqrs.Abstractions.Cqrs; + +namespace GFramework.Cqrs.Internal; + +/// +/// 默认的 CQRS 程序集注册协调器。 +/// +/// +/// 该实现把“按稳定程序集键去重”和“委托给 handler registrar 执行实际映射注册”收敛到 CQRS runtime 内部, +/// 避免外层容器继续了解 handler 注册流水线的内部结构。 +/// +internal sealed class DefaultCqrsRegistrationService(ICqrsHandlerRegistrar registrar, ILogger logger) + : ICqrsRegistrationService +{ + private readonly ILogger _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + private readonly HashSet _registeredAssemblyKeys = new(StringComparer.Ordinal); + private readonly ICqrsHandlerRegistrar _registrar = registrar ?? throw new ArgumentNullException(nameof(registrar)); + + /// + /// 注册指定程序集中的 CQRS handlers。 + /// + /// 要接入的程序集集合。 + public void RegisterHandlers(IEnumerable assemblies) + { + ArgumentNullException.ThrowIfNull(assemblies); + + var processedAssemblyKeys = new HashSet(StringComparer.Ordinal); + foreach (var assembly in assemblies + .Where(static assembly => assembly is not null) + .OrderBy(GetAssemblyRegistrationKey, StringComparer.Ordinal)) + { + var assemblyKey = GetAssemblyRegistrationKey(assembly); + if (!processedAssemblyKeys.Add(assemblyKey)) + continue; + + if (_registeredAssemblyKeys.Contains(assemblyKey)) + { + _logger.Debug( + $"Skipping CQRS handler registration for assembly {assemblyKey} because it was already registered."); + continue; + } + + _registrar.RegisterHandlers([assembly]); + _registeredAssemblyKeys.Add(assemblyKey); + } + } + + /// + /// 生成稳定程序集键,避免相同程序集被不同 实例重复接入。 + /// + /// 目标程序集。 + /// 稳定的程序集标识。 + private static string GetAssemblyRegistrationKey(Assembly assembly) + { + return assembly.FullName ?? assembly.GetName().Name ?? assembly.ToString(); + } +} diff --git a/GFramework.Tests.Common/CqrsTestRuntime.cs b/GFramework.Tests.Common/CqrsTestRuntime.cs index 7acbbe35..6109bd54 100644 --- a/GFramework.Tests.Common/CqrsTestRuntime.cs +++ b/GFramework.Tests.Common/CqrsTestRuntime.cs @@ -74,6 +74,14 @@ public static class CqrsTestRuntime var registrar = CqrsRuntimeFactory.CreateHandlerRegistrar(container, registrarLogger); container.Register(registrar); } + + if (container.Get() is null) + { + var registrationLogger = LoggerFactoryResolver.Provider.CreateLogger("DefaultCqrsRegistrationService"); + var registrar = container.GetRequired(); + var registrationService = CqrsRuntimeFactory.CreateRegistrationService(registrar, registrationLogger); + container.Register(registrationService); + } } ///