Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,8 @@ major 版本中移除。
- `PriorityGenerator` (`[Priority]`): 生成优先级比较相关实现。
- `EnumExtensionsGenerator` (`[GenerateEnumExtensions]`): 生成枚举扩展能力。
- `ContextAwareGenerator` (`[ContextAware]`): 自动实现 `IContextAware` 相关样板逻辑。
- `CqrsHandlerRegistryGenerator`: 为消费端程序集生成 CQRS handler 注册器,运行时优先使用生成产物,无法覆盖时回退到反射扫描。
- `CqrsHandlerRegistryGenerator`: 为消费端程序集生成 CQRS handler 注册器,运行时优先使用生成产物,无法覆盖时回退到反射扫描;非默认程序集可通过
`RegisterCqrsHandlersFromAssembly(...)` / `RegisterCqrsHandlersFromAssemblies(...)` 显式接入同一路径。

这些生成器的目标是减少重复代码,同时保持框架层 API 的一致性与可维护性。

Expand Down
16 changes: 15 additions & 1 deletion GFramework.Core.Abstractions/Architectures/IArchitecture.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
using System.ComponentModel;
using System.Reflection;
using GFramework.Core.Abstractions.Lifecycle;
using GFramework.Core.Abstractions.Model;
using GFramework.Core.Abstractions.Systems;
using GFramework.Core.Abstractions.Utility;
using Microsoft.Extensions.DependencyInjection;

namespace GFramework.Core.Abstractions.Architectures;

Expand Down Expand Up @@ -96,6 +96,20 @@ void RegisterCqrsPipelineBehavior<TBehavior>()
void RegisterMediatorBehavior<TBehavior>()
where TBehavior : class;

/// <summary>
/// 从指定程序集显式注册 CQRS 处理器。
/// 当处理器位于默认架构程序集之外的模块或扩展程序集中时,可在初始化阶段调用该入口接入对应程序集。
/// </summary>
/// <param name="assembly">包含 CQRS 处理器或生成注册器的程序集。</param>
void RegisterCqrsHandlersFromAssembly(Assembly assembly);

/// <summary>
/// 从多个程序集显式注册 CQRS 处理器。
/// 该入口会对程序集集合去重,适用于统一接入多个扩展包或模块程序集。
/// </summary>
/// <param name="assemblies">要接入的程序集集合。</param>
void RegisterCqrsHandlersFromAssemblies(IEnumerable<Assembly> assemblies);
Comment thread
coderabbitai[bot] marked this conversation as resolved.

/// <summary>
/// 安装架构模块
/// </summary>
Expand Down
3 changes: 2 additions & 1 deletion GFramework.Core.Abstractions/GlobalUsings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@
global using System.Runtime;
global using System.Linq;
global using System.Threading;
global using System.Threading.Tasks;
global using System.Threading.Tasks;
global using Microsoft.Extensions.DependencyInjection;
17 changes: 16 additions & 1 deletion GFramework.Core.Abstractions/Ioc/IIocContainer.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using System.ComponentModel;
using System.Reflection;
using GFramework.Core.Abstractions.Rule;
using GFramework.Core.Abstractions.Systems;
using Microsoft.Extensions.DependencyInjection;

namespace GFramework.Core.Abstractions.Ioc;

Expand Down Expand Up @@ -109,6 +109,21 @@ void RegisterCqrsPipelineBehavior<TBehavior>()
void RegisterMediatorBehavior<TBehavior>()
where TBehavior : class;

/// <summary>
/// 从指定程序集显式注册 CQRS 处理器。
/// 该入口适用于处理器不位于默认架构程序集中的场景,例如扩展包、模块程序集或拆分后的业务程序集。
/// 运行时会优先使用程序集级源码生成注册器;若不存在可用注册器,则自动回退到反射扫描。
/// </summary>
/// <param name="assembly">包含 CQRS 处理器或生成注册器的程序集。</param>
void RegisterCqrsHandlersFromAssembly(Assembly assembly);

/// <summary>
/// 从多个程序集显式注册 CQRS 处理器。
/// 容器会按稳定程序集键去重,避免默认启动路径与扩展模块重复接入同一程序集时产生重复 handler 映射。
/// </summary>
/// <param name="assemblies">要接入的程序集集合。</param>
void RegisterCqrsHandlersFromAssemblies(IEnumerable<Assembly> assemblies);
Comment thread
coderabbitai[bot] marked this conversation as resolved.


/// <summary>
/// 配置服务
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
using System.Reflection;
using GFramework.Core.Abstractions.Cqrs;
using GFramework.Core.Abstractions.Logging;
using GFramework.Core.Architectures;
using GFramework.Core.Logging;

namespace GFramework.Core.Tests.Architectures;

/// <summary>
/// 验证架构初始化阶段可以显式接入默认程序集之外的 CQRS handlers。
/// </summary>
[TestFixture]
public sealed class ArchitectureAdditionalCqrsHandlersTests
{
/// <summary>
/// 初始化日志工厂和共享测试状态。
/// </summary>
[SetUp]
public void SetUp()
{
LoggerFactoryResolver.Provider = new ConsoleLoggerFactoryProvider();
GameContext.Clear();
AdditionalAssemblyNotificationHandler.Reset();
}

/// <summary>
/// 清理测试过程中写入的共享状态。
/// </summary>
[TearDown]
public void TearDown()
{
AdditionalAssemblyNotificationHandler.Reset();
GameContext.Clear();
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

/// <summary>
/// 验证显式声明的额外程序集会在初始化阶段接入当前架构容器。
/// </summary>
[Test]
public async Task RegisterCqrsHandlersFromAssembly_Should_Register_Handlers_From_Explicit_Assembly()
{
Comment thread
coderabbitai[bot] marked this conversation as resolved.
var generatedAssembly = CreateGeneratedHandlerAssembly();
var architecture = new AdditionalHandlersTestArchitecture(target =>
target.RegisterCqrsHandlersFromAssembly(generatedAssembly.Object));

await architecture.InitializeAsync();
await architecture.Context.PublishAsync(new AdditionalAssemblyNotification());

Assert.That(AdditionalAssemblyNotificationHandler.InvocationCount, Is.EqualTo(1));

await architecture.DestroyAsync();
}

/// <summary>
/// 验证同一额外程序集被重复声明时,不会向容器重复写入相同 handler 映射。
/// </summary>
[Test]
public async Task RegisterCqrsHandlersFromAssembly_Should_Deduplicate_Repeated_Assembly_Registration()
{
var generatedAssembly = CreateGeneratedHandlerAssembly();
var architecture = new AdditionalHandlersTestArchitecture(target =>
{
target.RegisterCqrsHandlersFromAssembly(generatedAssembly.Object);
target.RegisterCqrsHandlersFromAssemblies([generatedAssembly.Object]);
});

await architecture.InitializeAsync();
await architecture.Context.PublishAsync(new AdditionalAssemblyNotification());

Assert.That(AdditionalAssemblyNotificationHandler.InvocationCount, Is.EqualTo(1));

await architecture.DestroyAsync();
}

/// <summary>
/// 创建一个仅暴露程序集级 CQRS registry 元数据的 mocked Assembly。
/// 该测试替身模拟“扩展程序集已经挂接 source-generator,运行时只需显式接入该程序集”的真实路径。
/// </summary>
/// <returns>包含程序集级 handler registry 元数据的 mocked Assembly。</returns>
private static Mock<Assembly> CreateGeneratedHandlerAssembly()
{
var generatedAssembly = new Mock<Assembly>();
generatedAssembly
.SetupGet(static assembly => assembly.FullName)
.Returns("GFramework.Core.Tests.Architectures.ExplicitAdditionalHandlers, Version=1.0.0.0");
generatedAssembly
.Setup(static assembly => assembly.GetCustomAttributes(typeof(CqrsHandlerRegistryAttribute), false))
.Returns([new CqrsHandlerRegistryAttribute(typeof(AdditionalAssemblyNotificationHandlerRegistry))]);
return generatedAssembly;
}

/// <summary>
/// 用于测试额外程序集注册入口的最小架构实现。
/// </summary>
private sealed class AdditionalHandlersTestArchitecture(Action<AdditionalHandlersTestArchitecture> configure) :
Architecture
{
/// <summary>
/// 在初始化阶段执行测试注入的额外 CQRS 程序集接入逻辑。
/// </summary>
protected override void OnInitialize()
{
configure(this);
}
}
}

/// <summary>
/// 用于验证额外程序集接入是否成功的测试通知。
/// </summary>
public sealed record AdditionalAssemblyNotification : INotification;

/// <summary>
/// 由模拟扩展程序集的生成注册器挂入当前容器的通知处理器。
/// </summary>
public sealed class AdditionalAssemblyNotificationHandler : INotificationHandler<AdditionalAssemblyNotification>
{
/// <summary>
/// 获取当前测试进程中该处理器的执行次数。
/// </summary>
public static int InvocationCount { get; private set; }

/// <summary>
/// 记录一次通知处理,供测试断言显式程序集接入后的运行时行为。
/// </summary>
/// <param name="notification">通知实例。</param>
/// <param name="cancellationToken">取消令牌。</param>
/// <returns>已完成任务。</returns>
public ValueTask Handle(AdditionalAssemblyNotification notification, CancellationToken cancellationToken)
{
InvocationCount++;
return ValueTask.CompletedTask;
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

/// <summary>
/// 清理共享计数器,避免测试间相互污染。
/// </summary>
public static void Reset()
{
InvocationCount = 0;
}
}

/// <summary>
/// 模拟由 source-generator 为扩展程序集生成的 CQRS handler registry。
/// </summary>
internal sealed class AdditionalAssemblyNotificationHandlerRegistry : ICqrsHandlerRegistry
{
/// <summary>
/// 将扩展程序集中的通知处理器映射写入服务集合。
/// </summary>
/// <param name="services">目标服务集合。</param>
/// <param name="logger">日志记录器。</param>
public void Register(IServiceCollection services, ILogger logger)
{
ArgumentNullException.ThrowIfNull(services);
ArgumentNullException.ThrowIfNull(logger);

Comment thread
GeWuYou marked this conversation as resolved.
services
.AddTransient<INotificationHandler<AdditionalAssemblyNotification>,
AdditionalAssemblyNotificationHandler>();
logger.Debug(
$"Registered CQRS handler {typeof(AdditionalAssemblyNotificationHandler).FullName} as {typeof(INotificationHandler<AdditionalAssemblyNotification>).FullName}.");
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Reflection;
using GFramework.Core.Abstractions.Architectures;
using GFramework.Core.Abstractions.Enums;
using GFramework.Core.Abstractions.Lifecycle;
Expand Down Expand Up @@ -185,6 +186,16 @@ public void RegisterCqrsPipelineBehavior<TBehavior>() where TBehavior : class
throw new NotImplementedException();
}

public void RegisterCqrsHandlersFromAssembly(Assembly assembly)
{
throw new NotImplementedException();
}

public void RegisterCqrsHandlersFromAssemblies(IEnumerable<Assembly> assemblies)
{
throw new NotImplementedException();
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

[Obsolete("Use RegisterCqrsPipelineBehavior<TBehavior>() instead.")]
public void RegisterMediatorBehavior<TBehavior>() where TBehavior : class
{
Expand Down Expand Up @@ -316,6 +327,16 @@ public void RegisterCqrsPipelineBehavior<TBehavior>() where TBehavior : class
throw new NotImplementedException();
}

public void RegisterCqrsHandlersFromAssembly(Assembly assembly)
{
throw new NotImplementedException();
}

public void RegisterCqrsHandlersFromAssemblies(IEnumerable<Assembly> assemblies)
{
throw new NotImplementedException();
}

[Obsolete("Use RegisterCqrsPipelineBehavior<TBehavior>() instead.")]
public void RegisterMediatorBehavior<TBehavior>() where TBehavior : class
{
Expand Down
21 changes: 21 additions & 0 deletions GFramework.Core/Architectures/Architecture.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.ComponentModel;
using System.Reflection;
using GFramework.Core.Abstractions.Architectures;
using GFramework.Core.Abstractions.Enums;
using GFramework.Core.Abstractions.Environment;
Expand Down Expand Up @@ -169,6 +170,26 @@ public void RegisterMediatorBehavior<TBehavior>() where TBehavior : class
RegisterCqrsPipelineBehavior<TBehavior>();
}

/// <summary>
/// 从指定程序集显式注册 CQRS 处理器。
/// 该入口适用于把拆分到其他模块或扩展包程序集中的 handlers 接入当前架构。
/// </summary>
/// <param name="assembly">包含 CQRS 处理器或生成注册器的程序集。</param>
public void RegisterCqrsHandlersFromAssembly(Assembly assembly)
{
_modules.RegisterCqrsHandlersFromAssembly(assembly);
}

/// <summary>
/// 从多个程序集显式注册 CQRS 处理器。
/// 适用于在初始化阶段批量接入多个扩展程序集,并沿用容器的去重策略避免重复注册。
/// </summary>
/// <param name="assemblies">要接入的程序集集合。</param>
public void RegisterCqrsHandlersFromAssemblies(IEnumerable<Assembly> assemblies)
{
_modules.RegisterCqrsHandlersFromAssemblies(assemblies);
}

/// <summary>
/// 安装架构模块
/// </summary>
Expand Down
10 changes: 5 additions & 5 deletions GFramework.Core/Architectures/ArchitectureBootstrapper.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
using GFramework.Core.Abstractions.Architectures;
using GFramework.Core.Abstractions.Environment;
using GFramework.Core.Abstractions.Logging;
using GFramework.Core.Cqrs.Internal;

namespace GFramework.Core.Architectures;

Expand Down Expand Up @@ -99,10 +98,11 @@ private IArchitectureContext EnsureContext(IArchitectureContext? existingContext
private void ConfigureServices(IArchitectureContext context, Action<IServiceCollection>? configurator)
{
services.SetContext(context);
CqrsHandlerRegistrar.RegisterHandlers(
services.Container,
[architectureType.Assembly, typeof(ArchitectureContext).Assembly],
logger);
services.Container.RegisterCqrsHandlersFromAssemblies(
[
architectureType.Assembly,
typeof(ArchitectureContext).Assembly
]);

if (configurator is null)
logger.Debug("No external service configurator provided. Using built-in CQRS runtime registration only.");
Expand Down
23 changes: 23 additions & 0 deletions GFramework.Core/Architectures/ArchitectureModules.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.ComponentModel;
using System.Reflection;
using GFramework.Core.Abstractions.Architectures;
using GFramework.Core.Abstractions.Logging;

Expand Down Expand Up @@ -38,6 +39,28 @@ public void RegisterMediatorBehavior<TBehavior>() where TBehavior : class
RegisterCqrsPipelineBehavior<TBehavior>();
}

/// <summary>
/// 从指定程序集显式注册 CQRS 处理器。
/// 该入口用于把默认架构程序集之外的扩展处理器接入当前架构容器。
/// </summary>
/// <param name="assembly">包含 CQRS 处理器或生成注册器的程序集。</param>
public void RegisterCqrsHandlersFromAssembly(Assembly assembly)
{
logger.Debug($"Registering CQRS handlers from assembly: {assembly.FullName ?? assembly.GetName().Name}");
services.Container.RegisterCqrsHandlersFromAssembly(assembly);
}

/// <summary>
/// 从多个程序集显式注册 CQRS 处理器。
/// 它会复用容器级去重逻辑,避免模块重复接入相同程序集时重复注册 handler。
/// </summary>
/// <param name="assemblies">要接入的程序集集合。</param>
public void RegisterCqrsHandlersFromAssemblies(IEnumerable<Assembly> assemblies)
{
logger.Debug("Registering CQRS handlers from additional assemblies.");
services.Container.RegisterCqrsHandlersFromAssemblies(assemblies);
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

/// <summary>
/// 安装架构模块
/// </summary>
Expand Down
Loading
Loading