mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-05-07 00:39:00 +08:00
feat(arch): 添加架构基础类和依赖注入容器实现
- 创建 Architecture 基类提供系统、模型、工具等组件的注册与管理功能 - 实现架构生命周期管理、初始化流程控制和阶段转换功能 - 添加 ArchitectureModules 模块管理器负责 CQRS 行为注册和模块安装 - 实现 MicrosoftDiContainer 依赖注入容器适配器 - 支持单例、瞬态、作用域服务注册和工厂方法注册 - 添加 CQRS 请求管道行为和处理器注册功能 - 实现线程安全的读写锁保护容器操作 - 提供服务获取、排序和优先级管理功能
This commit is contained in:
parent
4db7923512
commit
27266d037d
@ -101,6 +101,8 @@ public interface IArchitecture : IAsyncInitializable, IAsyncDestroyable, IInitia
|
||||
/// 当处理器位于默认架构程序集之外的模块或扩展程序集中时,可在初始化阶段调用该入口接入对应程序集。
|
||||
/// </summary>
|
||||
/// <param name="assembly">包含 CQRS 处理器或生成注册器的程序集。</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="assembly" /> 为 <see langword="null" />。</exception>
|
||||
/// <exception cref="InvalidOperationException">当前架构的底层容器已冻结,无法继续注册处理器。</exception>
|
||||
void RegisterCqrsHandlersFromAssembly(Assembly assembly);
|
||||
|
||||
/// <summary>
|
||||
@ -108,6 +110,8 @@ public interface IArchitecture : IAsyncInitializable, IAsyncDestroyable, IInitia
|
||||
/// 该入口会对程序集集合去重,适用于统一接入多个扩展包或模块程序集。
|
||||
/// </summary>
|
||||
/// <param name="assemblies">要接入的程序集集合。</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="assemblies" /> 为 <see langword="null" />。</exception>
|
||||
/// <exception cref="InvalidOperationException">当前架构的底层容器已冻结,无法继续注册处理器。</exception>
|
||||
void RegisterCqrsHandlersFromAssemblies(IEnumerable<Assembly> assemblies);
|
||||
|
||||
/// <summary>
|
||||
|
||||
@ -115,6 +115,8 @@ public interface IIocContainer : IContextAware
|
||||
/// 运行时会优先使用程序集级源码生成注册器;若不存在可用注册器,则自动回退到反射扫描。
|
||||
/// </summary>
|
||||
/// <param name="assembly">包含 CQRS 处理器或生成注册器的程序集。</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="assembly" /> 为 <see langword="null" />。</exception>
|
||||
/// <exception cref="InvalidOperationException">容器已冻结,无法继续注册 CQRS 处理器。</exception>
|
||||
void RegisterCqrsHandlersFromAssembly(Assembly assembly);
|
||||
|
||||
/// <summary>
|
||||
@ -122,6 +124,8 @@ public interface IIocContainer : IContextAware
|
||||
/// 容器会按稳定程序集键去重,避免默认启动路径与扩展模块重复接入同一程序集时产生重复 handler 映射。
|
||||
/// </summary>
|
||||
/// <param name="assemblies">要接入的程序集集合。</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="assemblies" /> 为 <see langword="null" />。</exception>
|
||||
/// <exception cref="InvalidOperationException">容器已冻结,无法继续注册 CQRS 处理器。</exception>
|
||||
void RegisterCqrsHandlersFromAssemblies(IEnumerable<Assembly> assemblies);
|
||||
|
||||
|
||||
|
||||
@ -18,9 +18,10 @@ public sealed class ArchitectureAdditionalCqrsHandlersTests
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
_previousLoggerFactoryProvider = LoggerFactoryResolver.Provider;
|
||||
LoggerFactoryResolver.Provider = new ConsoleLoggerFactoryProvider();
|
||||
GameContext.Clear();
|
||||
AdditionalAssemblyNotificationHandler.Reset();
|
||||
AdditionalAssemblyNotificationHandlerState.Reset();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -29,10 +30,15 @@ public sealed class ArchitectureAdditionalCqrsHandlersTests
|
||||
[TearDown]
|
||||
public void TearDown()
|
||||
{
|
||||
AdditionalAssemblyNotificationHandler.Reset();
|
||||
AdditionalAssemblyNotificationHandlerState.Reset();
|
||||
GameContext.Clear();
|
||||
LoggerFactoryResolver.Provider = _previousLoggerFactoryProvider
|
||||
?? throw new InvalidOperationException(
|
||||
"LoggerFactoryResolver.Provider should be captured during setup.");
|
||||
}
|
||||
|
||||
private ILoggerFactoryProvider? _previousLoggerFactoryProvider;
|
||||
|
||||
/// <summary>
|
||||
/// 验证显式声明的额外程序集会在初始化阶段接入当前架构容器。
|
||||
/// </summary>
|
||||
@ -44,11 +50,16 @@ public sealed class ArchitectureAdditionalCqrsHandlersTests
|
||||
target.RegisterCqrsHandlersFromAssembly(generatedAssembly.Object));
|
||||
|
||||
await architecture.InitializeAsync();
|
||||
await architecture.Context.PublishAsync(new AdditionalAssemblyNotification());
|
||||
try
|
||||
{
|
||||
await architecture.Context.PublishAsync(new AdditionalAssemblyNotification());
|
||||
|
||||
Assert.That(AdditionalAssemblyNotificationHandler.InvocationCount, Is.EqualTo(1));
|
||||
|
||||
await architecture.DestroyAsync();
|
||||
Assert.That(AdditionalAssemblyNotificationHandlerState.InvocationCount, Is.EqualTo(1));
|
||||
}
|
||||
finally
|
||||
{
|
||||
await architecture.DestroyAsync();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -65,11 +76,16 @@ public sealed class ArchitectureAdditionalCqrsHandlersTests
|
||||
});
|
||||
|
||||
await architecture.InitializeAsync();
|
||||
await architecture.Context.PublishAsync(new AdditionalAssemblyNotification());
|
||||
try
|
||||
{
|
||||
await architecture.Context.PublishAsync(new AdditionalAssemblyNotification());
|
||||
|
||||
Assert.That(AdditionalAssemblyNotificationHandler.InvocationCount, Is.EqualTo(1));
|
||||
|
||||
await architecture.DestroyAsync();
|
||||
Assert.That(AdditionalAssemblyNotificationHandlerState.InvocationCount, Is.EqualTo(1));
|
||||
}
|
||||
finally
|
||||
{
|
||||
await architecture.DestroyAsync();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -111,25 +127,26 @@ public sealed class ArchitectureAdditionalCqrsHandlersTests
|
||||
public sealed record AdditionalAssemblyNotification : INotification;
|
||||
|
||||
/// <summary>
|
||||
/// 由模拟扩展程序集的生成注册器挂入当前容器的通知处理器。
|
||||
/// 记录模拟扩展程序集通知处理器的执行次数。
|
||||
/// </summary>
|
||||
public sealed class AdditionalAssemblyNotificationHandler : INotificationHandler<AdditionalAssemblyNotification>
|
||||
public static class AdditionalAssemblyNotificationHandlerState
|
||||
{
|
||||
private static int _invocationCount;
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前测试进程中该处理器的执行次数。
|
||||
/// </summary>
|
||||
public static int InvocationCount { get; private set; }
|
||||
/// <remarks>
|
||||
/// 该计数器通过原子读写维护,以支持 NUnit 并行执行环境中的并发访问。
|
||||
/// </remarks>
|
||||
public static int InvocationCount => Volatile.Read(ref _invocationCount);
|
||||
|
||||
/// <summary>
|
||||
/// 记录一次通知处理,供测试断言显式程序集接入后的运行时行为。
|
||||
/// </summary>
|
||||
/// <param name="notification">通知实例。</param>
|
||||
/// <param name="cancellationToken">取消令牌。</param>
|
||||
/// <returns>已完成任务。</returns>
|
||||
public ValueTask Handle(AdditionalAssemblyNotification notification, CancellationToken cancellationToken)
|
||||
public static void RecordInvocation()
|
||||
{
|
||||
InvocationCount++;
|
||||
return ValueTask.CompletedTask;
|
||||
Interlocked.Increment(ref _invocationCount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -137,7 +154,7 @@ public sealed class AdditionalAssemblyNotificationHandler : INotificationHandler
|
||||
/// </summary>
|
||||
public static void Reset()
|
||||
{
|
||||
InvocationCount = 0;
|
||||
Interlocked.Exchange(ref _invocationCount, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@ -156,10 +173,25 @@ internal sealed class AdditionalAssemblyNotificationHandlerRegistry : ICqrsHandl
|
||||
ArgumentNullException.ThrowIfNull(services);
|
||||
ArgumentNullException.ThrowIfNull(logger);
|
||||
|
||||
services
|
||||
.AddTransient<INotificationHandler<AdditionalAssemblyNotification>,
|
||||
AdditionalAssemblyNotificationHandler>();
|
||||
services.AddTransient<INotificationHandler<AdditionalAssemblyNotification>>(_ => CreateHandler());
|
||||
logger.Debug(
|
||||
$"Registered CQRS handler {typeof(AdditionalAssemblyNotificationHandler).FullName} as {typeof(INotificationHandler<AdditionalAssemblyNotification>).FullName}.");
|
||||
$"Registered CQRS handler proxy for {typeof(INotificationHandler<AdditionalAssemblyNotification>).FullName}.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个仅供显式程序集注册路径使用的动态通知处理器。
|
||||
/// </summary>
|
||||
/// <returns>用于记录通知触发次数的测试替身处理器。</returns>
|
||||
private static INotificationHandler<AdditionalAssemblyNotification> CreateHandler()
|
||||
{
|
||||
var handler = new Mock<INotificationHandler<AdditionalAssemblyNotification>>();
|
||||
handler
|
||||
.Setup(target => target.Handle(It.IsAny<AdditionalAssemblyNotification>(), It.IsAny<CancellationToken>()))
|
||||
.Returns(() =>
|
||||
{
|
||||
AdditionalAssemblyNotificationHandlerState.RecordInvocation();
|
||||
return ValueTask.CompletedTask;
|
||||
});
|
||||
return handler.Object;
|
||||
}
|
||||
}
|
||||
|
||||
@ -186,11 +186,21 @@ public class TestArchitectureWithRegistry : IArchitecture
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试替身未实现显式程序集 CQRS 处理器接入入口。
|
||||
/// </summary>
|
||||
/// <param name="assembly">包含 CQRS 处理器或生成注册器的程序集。</param>
|
||||
/// <exception cref="NotImplementedException">该测试替身不参与 CQRS 程序集接入路径验证。</exception>
|
||||
public void RegisterCqrsHandlersFromAssembly(Assembly assembly)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试替身未实现显式程序集 CQRS 处理器接入入口。
|
||||
/// </summary>
|
||||
/// <param name="assemblies">要接入的程序集集合。</param>
|
||||
/// <exception cref="NotImplementedException">该测试替身不参与 CQRS 程序集接入路径验证。</exception>
|
||||
public void RegisterCqrsHandlersFromAssemblies(IEnumerable<Assembly> assemblies)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
@ -327,11 +337,21 @@ public class TestArchitectureWithoutRegistry : IArchitecture
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试替身未实现显式程序集 CQRS 处理器接入入口。
|
||||
/// </summary>
|
||||
/// <param name="assembly">包含 CQRS 处理器或生成注册器的程序集。</param>
|
||||
/// <exception cref="NotImplementedException">该测试替身不参与 CQRS 程序集接入路径验证。</exception>
|
||||
public void RegisterCqrsHandlersFromAssembly(Assembly assembly)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试替身未实现显式程序集 CQRS 处理器接入入口。
|
||||
/// </summary>
|
||||
/// <param name="assemblies">要接入的程序集集合。</param>
|
||||
/// <exception cref="NotImplementedException">该测试替身不参与 CQRS 程序集接入路径验证。</exception>
|
||||
public void RegisterCqrsHandlersFromAssemblies(IEnumerable<Assembly> assemblies)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
using System.Reflection;
|
||||
using GFramework.Core.Abstractions.Bases;
|
||||
using GFramework.Core.Abstractions.Cqrs;
|
||||
using GFramework.Core.Ioc;
|
||||
using GFramework.Core.Logging;
|
||||
using GFramework.Core.Tests.Cqrs;
|
||||
using GFramework.Core.Tests.Systems;
|
||||
|
||||
namespace GFramework.Core.Tests.Ioc;
|
||||
@ -306,6 +308,34 @@ public class MicrosoftDiContainerTests
|
||||
Assert.That(_container.Contains<TestService>(), Is.False);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试清空容器后可以重新接入同一程序集中的 CQRS 处理器。
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void Clear_Should_Reset_Cqrs_Assembly_Deduplication_State()
|
||||
{
|
||||
var assembly = typeof(CqrsHandlerRegistrarTests).Assembly;
|
||||
|
||||
_container.RegisterCqrsHandlersFromAssembly(assembly);
|
||||
Assert.That(
|
||||
_container.GetServicesUnsafe.Any(static descriptor =>
|
||||
descriptor.ServiceType == typeof(INotificationHandler<DeterministicOrderNotification>)),
|
||||
Is.True);
|
||||
|
||||
_container.Clear();
|
||||
Assert.That(
|
||||
_container.GetServicesUnsafe.Any(static descriptor =>
|
||||
descriptor.ServiceType == typeof(INotificationHandler<DeterministicOrderNotification>)),
|
||||
Is.False);
|
||||
|
||||
_container.RegisterCqrsHandlersFromAssembly(assembly);
|
||||
|
||||
Assert.That(
|
||||
_container.GetServicesUnsafe.Any(static descriptor =>
|
||||
descriptor.ServiceType == typeof(INotificationHandler<DeterministicOrderNotification>)),
|
||||
Is.True);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试冻结容器以防止进一步注册的功能
|
||||
/// </summary>
|
||||
@ -676,4 +706,4 @@ public sealed class PrioritizedService : IPrioritizedService, IMixedService
|
||||
public sealed class NonPrioritizedService : IMixedService
|
||||
{
|
||||
public string? Name { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@ -175,6 +175,8 @@ public abstract class Architecture : IArchitecture
|
||||
/// 该入口适用于把拆分到其他模块或扩展包程序集中的 handlers 接入当前架构。
|
||||
/// </summary>
|
||||
/// <param name="assembly">包含 CQRS 处理器或生成注册器的程序集。</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="assembly" /> 为 <see langword="null" />。</exception>
|
||||
/// <exception cref="InvalidOperationException">当前架构的底层容器已冻结,无法继续注册处理器。</exception>
|
||||
public void RegisterCqrsHandlersFromAssembly(Assembly assembly)
|
||||
{
|
||||
_modules.RegisterCqrsHandlersFromAssembly(assembly);
|
||||
@ -185,6 +187,8 @@ public abstract class Architecture : IArchitecture
|
||||
/// 适用于在初始化阶段批量接入多个扩展程序集,并沿用容器的去重策略避免重复注册。
|
||||
/// </summary>
|
||||
/// <param name="assemblies">要接入的程序集集合。</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="assemblies" /> 为 <see langword="null" />。</exception>
|
||||
/// <exception cref="InvalidOperationException">当前架构的底层容器已冻结,无法继续注册处理器。</exception>
|
||||
public void RegisterCqrsHandlersFromAssemblies(IEnumerable<Assembly> assemblies)
|
||||
{
|
||||
_modules.RegisterCqrsHandlersFromAssemblies(assemblies);
|
||||
|
||||
@ -44,8 +44,11 @@ internal sealed class ArchitectureModules(
|
||||
/// 该入口用于把默认架构程序集之外的扩展处理器接入当前架构容器。
|
||||
/// </summary>
|
||||
/// <param name="assembly">包含 CQRS 处理器或生成注册器的程序集。</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="assembly" /> 为 <see langword="null" />。</exception>
|
||||
/// <exception cref="InvalidOperationException">底层容器已冻结,无法继续注册处理器。</exception>
|
||||
public void RegisterCqrsHandlersFromAssembly(Assembly assembly)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(assembly);
|
||||
logger.Debug($"Registering CQRS handlers from assembly: {assembly.FullName ?? assembly.GetName().Name}");
|
||||
services.Container.RegisterCqrsHandlersFromAssembly(assembly);
|
||||
}
|
||||
@ -55,8 +58,11 @@ internal sealed class ArchitectureModules(
|
||||
/// 它会复用容器级去重逻辑,避免模块重复接入相同程序集时重复注册 handler。
|
||||
/// </summary>
|
||||
/// <param name="assemblies">要接入的程序集集合。</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="assemblies" /> 为 <see langword="null" />。</exception>
|
||||
/// <exception cref="InvalidOperationException">底层容器已冻结,无法继续注册处理器。</exception>
|
||||
public void RegisterCqrsHandlersFromAssemblies(IEnumerable<Assembly> assemblies)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(assemblies);
|
||||
logger.Debug("Registering CQRS handlers from additional assemblies.");
|
||||
services.Container.RegisterCqrsHandlersFromAssemblies(assemblies);
|
||||
}
|
||||
|
||||
@ -384,6 +384,8 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
|
||||
/// 从指定程序集显式注册 CQRS 处理器。
|
||||
/// </summary>
|
||||
/// <param name="assembly">包含 CQRS 处理器或生成注册器的程序集。</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="assembly" /> 为 <see langword="null" />。</exception>
|
||||
/// <exception cref="InvalidOperationException">容器已冻结,无法继续注册 CQRS 处理器。</exception>
|
||||
public void RegisterCqrsHandlersFromAssembly(Assembly assembly)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(assembly);
|
||||
@ -395,6 +397,8 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
|
||||
/// 同一程序集只会被接入一次,避免默认启动路径与扩展模块重复注册相同 handlers。
|
||||
/// </summary>
|
||||
/// <param name="assemblies">要接入的程序集集合。</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="assemblies" /> 为 <see langword="null" />。</exception>
|
||||
/// <exception cref="InvalidOperationException">容器已冻结,无法继续注册 CQRS 处理器。</exception>
|
||||
public void RegisterCqrsHandlersFromAssemblies(IEnumerable<Assembly> assemblies)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(assemblies);
|
||||
@ -803,6 +807,7 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
|
||||
|
||||
GetServicesUnsafe.Clear();
|
||||
_registeredInstances.Clear();
|
||||
_registeredCqrsHandlerAssemblyKeys.Clear();
|
||||
_provider = null;
|
||||
_logger.Info("Container cleared");
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user