mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-05-06 16:16:44 +08:00
feat(cqrs): 添加CQRS调度器实现和改进处理器注册机制
- 实现GFramework自有CQRS运行时分发器,支持请求/通知/流式请求处理 - 添加进程级缓存机制优化反射调用性能,包括请求、通知、流水线调用委托缓存 - 重构CqrsHandlerRegistrar使用ReflectionFallbackMetadata替代字符串类型名 - 引入CqrsReflectionFallbackAttribute支持运行时补充反射扫描的处理器类型 - 添加完整的CQRS处理器注册单元测试,验证有序执行和容错行为 - 修复MicrosoftDiContainer中异常消息的格式化空白问题 - 实现上下文感知处理器的CQRS分发上下文注入功能
This commit is contained in:
parent
391e3e9813
commit
06f95db593
@ -400,7 +400,7 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
|
||||
/// <param name="assemblies">要接入的程序集集合。</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="assemblies" /> 为 <see langword="null" />。</exception>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="assemblies" /> 中存在 <see langword="null" /> 元素。</exception>
|
||||
/// <exception cref="InvalidOperationException">容器已冻结,无法继续注册 CQRS 处理器。</exception>
|
||||
/// <exception cref="InvalidOperationException"> 容器已冻结,无法继续注册 CQRS 处理器。</exception>
|
||||
public void RegisterCqrsHandlersFromAssemblies(IEnumerable<Assembly> assemblies)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(assemblies);
|
||||
|
||||
172
GFramework.Cqrs.Tests/Cqrs/CqrsDispatcherCacheTests.cs
Normal file
172
GFramework.Cqrs.Tests/Cqrs/CqrsDispatcherCacheTests.cs
Normal file
@ -0,0 +1,172 @@
|
||||
using GFramework.Core.Abstractions.Logging;
|
||||
using GFramework.Core.Architectures;
|
||||
using GFramework.Core.Ioc;
|
||||
using GFramework.Core.Logging;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs;
|
||||
|
||||
namespace GFramework.Cqrs.Tests.Cqrs;
|
||||
|
||||
/// <summary>
|
||||
/// 验证 CQRS dispatcher 会缓存热路径中的服务类型构造结果。
|
||||
/// </summary>
|
||||
[TestFixture]
|
||||
internal sealed class CqrsDispatcherCacheTests
|
||||
{
|
||||
private MicrosoftDiContainer? _container;
|
||||
private ArchitectureContext? _context;
|
||||
|
||||
/// <summary>
|
||||
/// 初始化测试上下文。
|
||||
/// </summary>
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
LoggerFactoryResolver.Provider = new ConsoleLoggerFactoryProvider();
|
||||
_container = new MicrosoftDiContainer();
|
||||
|
||||
CqrsTestRuntime.RegisterHandlers(
|
||||
_container,
|
||||
typeof(CqrsDispatcherCacheTests).Assembly,
|
||||
typeof(ArchitectureContext).Assembly);
|
||||
|
||||
_container.Freeze();
|
||||
_context = new ArchitectureContext(_container);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清理测试上下文引用。
|
||||
/// </summary>
|
||||
[TearDown]
|
||||
public void TearDown()
|
||||
{
|
||||
_context = null;
|
||||
_container = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证相同消息类型重复分发时,不会重复扩张服务类型缓存。
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task Dispatcher_Should_Cache_Service_Types_After_First_Dispatch()
|
||||
{
|
||||
var notificationServiceTypes = GetCacheField("NotificationHandlerServiceTypes");
|
||||
var requestServiceTypes = GetCacheField("RequestServiceTypes");
|
||||
var streamServiceTypes = GetCacheField("StreamHandlerServiceTypes");
|
||||
|
||||
var notificationBefore = notificationServiceTypes.Count;
|
||||
var requestBefore = requestServiceTypes.Count;
|
||||
var streamBefore = streamServiceTypes.Count;
|
||||
|
||||
await _context!.SendRequestAsync(new DispatcherCacheRequest());
|
||||
await _context.PublishAsync(new DispatcherCacheNotification());
|
||||
await DrainAsync(_context.CreateStream(new DispatcherCacheStreamRequest()));
|
||||
|
||||
var notificationAfterFirstDispatch = notificationServiceTypes.Count;
|
||||
var requestAfterFirstDispatch = requestServiceTypes.Count;
|
||||
var streamAfterFirstDispatch = streamServiceTypes.Count;
|
||||
|
||||
await _context.SendRequestAsync(new DispatcherCacheRequest());
|
||||
await _context.PublishAsync(new DispatcherCacheNotification());
|
||||
await DrainAsync(_context.CreateStream(new DispatcherCacheStreamRequest()));
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(notificationAfterFirstDispatch, Is.EqualTo(notificationBefore + 1));
|
||||
Assert.That(requestAfterFirstDispatch, Is.EqualTo(requestBefore + 1));
|
||||
Assert.That(streamAfterFirstDispatch, Is.EqualTo(streamBefore + 1));
|
||||
|
||||
Assert.That(notificationServiceTypes.Count, Is.EqualTo(notificationAfterFirstDispatch));
|
||||
Assert.That(requestServiceTypes.Count, Is.EqualTo(requestAfterFirstDispatch));
|
||||
Assert.That(streamServiceTypes.Count, Is.EqualTo(streamAfterFirstDispatch));
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 通过反射读取 dispatcher 的静态缓存字典。
|
||||
/// </summary>
|
||||
private static IDictionary GetCacheField(string fieldName)
|
||||
{
|
||||
var dispatcherType = typeof(CqrsReflectionFallbackAttribute).Assembly
|
||||
.GetType("GFramework.Cqrs.Internal.CqrsDispatcher", throwOnError: true)!;
|
||||
|
||||
var field = dispatcherType.GetField(
|
||||
fieldName,
|
||||
BindingFlags.NonPublic | BindingFlags.Static);
|
||||
|
||||
Assert.That(field, Is.Not.Null, $"Missing dispatcher cache field {fieldName}.");
|
||||
|
||||
return field!.GetValue(null) as IDictionary
|
||||
?? throw new InvalidOperationException(
|
||||
$"Dispatcher cache field {fieldName} does not implement IDictionary.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 消费整个异步流,确保建流路径被真实执行。
|
||||
/// </summary>
|
||||
private static async Task DrainAsync<T>(IAsyncEnumerable<T> stream)
|
||||
{
|
||||
await foreach (var _ in stream)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 用于验证 request 服务类型缓存的测试请求。
|
||||
/// </summary>
|
||||
internal sealed record DispatcherCacheRequest : IRequest<int>;
|
||||
|
||||
/// <summary>
|
||||
/// 用于验证 notification 服务类型缓存的测试通知。
|
||||
/// </summary>
|
||||
internal sealed record DispatcherCacheNotification : INotification;
|
||||
|
||||
/// <summary>
|
||||
/// 用于验证 stream 服务类型缓存的测试请求。
|
||||
/// </summary>
|
||||
internal sealed record DispatcherCacheStreamRequest : IStreamRequest<int>;
|
||||
|
||||
/// <summary>
|
||||
/// 处理 <see cref="DispatcherCacheRequest" />。
|
||||
/// </summary>
|
||||
internal sealed class DispatcherCacheRequestHandler : IRequestHandler<DispatcherCacheRequest, int>
|
||||
{
|
||||
/// <summary>
|
||||
/// 返回固定结果,供缓存测试验证 dispatcher 请求路径。
|
||||
/// </summary>
|
||||
public ValueTask<int> Handle(DispatcherCacheRequest request, CancellationToken cancellationToken)
|
||||
{
|
||||
return ValueTask.FromResult(1);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理 <see cref="DispatcherCacheNotification" />。
|
||||
/// </summary>
|
||||
internal sealed class DispatcherCacheNotificationHandler : INotificationHandler<DispatcherCacheNotification>
|
||||
{
|
||||
/// <summary>
|
||||
/// 消费通知,不执行额外副作用。
|
||||
/// </summary>
|
||||
public ValueTask Handle(DispatcherCacheNotification notification, CancellationToken cancellationToken)
|
||||
{
|
||||
return ValueTask.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理 <see cref="DispatcherCacheStreamRequest" />。
|
||||
/// </summary>
|
||||
internal sealed class DispatcherCacheStreamHandler : IStreamRequestHandler<DispatcherCacheStreamRequest, int>
|
||||
{
|
||||
/// <summary>
|
||||
/// 返回一个最小流,供缓存测试命中 stream 分发路径。
|
||||
/// </summary>
|
||||
public async IAsyncEnumerable<int> Handle(
|
||||
DispatcherCacheStreamRequest request,
|
||||
[EnumeratorCancellation] CancellationToken cancellationToken)
|
||||
{
|
||||
yield return 1;
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
@ -243,6 +243,55 @@ internal sealed class CqrsHandlerRegistrarTests
|
||||
Times.Once);
|
||||
generatedAssembly.Verify(static assembly => assembly.GetTypes(), Times.Never);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证手写 fallback metadata 直接提供 handler 类型时,运行时会复用这些类型,
|
||||
/// 而不会再通过程序集名称查找或整程序集扫描补齐映射。
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void RegisterHandlers_Should_Use_Direct_Fallback_Types_Without_GetType_Or_GetTypes()
|
||||
{
|
||||
var generatedAssembly = new Mock<Assembly>();
|
||||
generatedAssembly
|
||||
.SetupGet(static assembly => assembly.FullName)
|
||||
.Returns(ReflectionFallbackNotificationContainer.ReflectionOnlyHandlerType.Assembly.FullName);
|
||||
generatedAssembly
|
||||
.Setup(static assembly => assembly.GetCustomAttributes(typeof(CqrsHandlerRegistryAttribute), false))
|
||||
.Returns([new CqrsHandlerRegistryAttribute(typeof(PartialGeneratedNotificationHandlerRegistry))]);
|
||||
generatedAssembly
|
||||
.Setup(static assembly => assembly.GetCustomAttributes(typeof(CqrsReflectionFallbackAttribute), false))
|
||||
.Returns(
|
||||
[
|
||||
new CqrsReflectionFallbackAttribute(
|
||||
ReflectionFallbackNotificationContainer.ReflectionOnlyHandlerType)
|
||||
]);
|
||||
|
||||
var container = new MicrosoftDiContainer();
|
||||
CqrsTestRuntime.RegisterHandlers(container, generatedAssembly.Object);
|
||||
|
||||
var registrations = container.GetServicesUnsafe
|
||||
.Where(static descriptor =>
|
||||
descriptor.ServiceType == typeof(INotificationHandler<GeneratedRegistryNotification>) &&
|
||||
descriptor.ImplementationType is not null)
|
||||
.Select(static descriptor => descriptor.ImplementationType!)
|
||||
.ToList();
|
||||
|
||||
Assert.That(
|
||||
registrations,
|
||||
Is.EqualTo(
|
||||
[
|
||||
typeof(GeneratedRegistryNotificationHandler),
|
||||
ReflectionFallbackNotificationContainer.ReflectionOnlyHandlerType
|
||||
]));
|
||||
|
||||
generatedAssembly.Verify(
|
||||
static assembly => assembly.GetType(
|
||||
ReflectionFallbackNotificationContainer.ReflectionOnlyHandlerType.FullName!,
|
||||
false,
|
||||
false),
|
||||
Times.Never);
|
||||
generatedAssembly.Verify(static assembly => assembly.GetTypes(), Times.Never);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@ -11,6 +11,15 @@ namespace GFramework.Cqrs;
|
||||
[AttributeUsage(AttributeTargets.Assembly)]
|
||||
public sealed class CqrsReflectionFallbackAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// 初始化 <see cref="CqrsReflectionFallbackAttribute" />,保留旧版“仅标记需要补扫”的语义。
|
||||
/// </summary>
|
||||
public CqrsReflectionFallbackAttribute()
|
||||
{
|
||||
FallbackHandlerTypeNames = [];
|
||||
FallbackHandlerTypes = [];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 初始化 <see cref="CqrsReflectionFallbackAttribute" />。
|
||||
/// </summary>
|
||||
@ -27,10 +36,36 @@ public sealed class CqrsReflectionFallbackAttribute : Attribute
|
||||
.Distinct(StringComparer.Ordinal)
|
||||
.OrderBy(static typeName => typeName, StringComparer.Ordinal)
|
||||
.ToArray();
|
||||
FallbackHandlerTypes = [];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 初始化 <see cref="CqrsReflectionFallbackAttribute" />。
|
||||
/// </summary>
|
||||
/// <param name="fallbackHandlerTypes">
|
||||
/// 需要运行时补充反射注册的处理器类型。
|
||||
/// 该重载适合手写或第三方程序集显式声明可直接引用的 fallback handlers,
|
||||
/// 避免再通过字符串名称回查程序集元数据。
|
||||
/// </param>
|
||||
public CqrsReflectionFallbackAttribute(params Type[] fallbackHandlerTypes)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(fallbackHandlerTypes);
|
||||
|
||||
FallbackHandlerTypeNames = [];
|
||||
FallbackHandlerTypes = fallbackHandlerTypes
|
||||
.Where(static type => type is not null)
|
||||
.Distinct()
|
||||
.OrderBy(static type => type.FullName ?? type.Name, StringComparer.Ordinal)
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取需要运行时补充反射注册的处理器类型全名集合。
|
||||
/// </summary>
|
||||
public IReadOnlyList<string> FallbackHandlerTypeNames { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取可直接供运行时补充反射注册的处理器类型集合。
|
||||
/// </summary>
|
||||
public IReadOnlyList<Type> FallbackHandlerTypes { get; }
|
||||
}
|
||||
|
||||
@ -1,5 +1,3 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Reflection;
|
||||
using GFramework.Core.Abstractions.Architectures;
|
||||
using GFramework.Core.Abstractions.Ioc;
|
||||
using GFramework.Core.Abstractions.Logging;
|
||||
@ -30,10 +28,22 @@ internal sealed class CqrsDispatcher(
|
||||
// 进程级缓存:缓存通知调用委托,复用并发安全字典以支撑多线程发布路径。
|
||||
private static readonly ConcurrentDictionary<Type, NotificationInvoker> NotificationInvokers = new();
|
||||
|
||||
// 进程级缓存:缓存通知处理器服务类型,避免每次发布都重复 MakeGenericType。
|
||||
private static readonly ConcurrentDictionary<Type, Type> NotificationHandlerServiceTypes = new();
|
||||
|
||||
// 进程级缓存:缓存流式请求调用委托,避免每次创建流时重复解析反射签名。
|
||||
private static readonly ConcurrentDictionary<(Type RequestType, Type ResponseType), StreamInvoker> StreamInvokers =
|
||||
new();
|
||||
|
||||
// 进程级缓存:缓存请求处理器与 pipeline 行为的服务类型,减少热路径中的泛型类型构造。
|
||||
private static readonly ConcurrentDictionary<(Type RequestType, Type ResponseType), RequestServiceTypeSet>
|
||||
RequestServiceTypes = new();
|
||||
|
||||
// 进程级缓存:缓存流式请求处理器服务类型,避免每次建流时重复 MakeGenericType。
|
||||
private static readonly ConcurrentDictionary<(Type RequestType, Type ResponseType), Type>
|
||||
StreamHandlerServiceTypes =
|
||||
new();
|
||||
|
||||
/// <summary>
|
||||
/// 发布通知到所有已注册处理器。
|
||||
/// </summary>
|
||||
@ -51,7 +61,9 @@ internal sealed class CqrsDispatcher(
|
||||
ArgumentNullException.ThrowIfNull(notification);
|
||||
|
||||
var notificationType = notification.GetType();
|
||||
var handlerType = typeof(INotificationHandler<>).MakeGenericType(notificationType);
|
||||
var handlerType = NotificationHandlerServiceTypes.GetOrAdd(
|
||||
notificationType,
|
||||
static type => typeof(INotificationHandler<>).MakeGenericType(type));
|
||||
var handlers = container.GetAll(handlerType);
|
||||
|
||||
if (handlers.Count == 0)
|
||||
@ -88,14 +100,18 @@ internal sealed class CqrsDispatcher(
|
||||
ArgumentNullException.ThrowIfNull(request);
|
||||
|
||||
var requestType = request.GetType();
|
||||
var handlerType = typeof(IRequestHandler<,>).MakeGenericType(requestType, typeof(TResponse));
|
||||
var serviceTypes = RequestServiceTypes.GetOrAdd(
|
||||
(requestType, typeof(TResponse)),
|
||||
static key => new RequestServiceTypeSet(
|
||||
typeof(IRequestHandler<,>).MakeGenericType(key.RequestType, key.ResponseType),
|
||||
typeof(IPipelineBehavior<,>).MakeGenericType(key.RequestType, key.ResponseType)));
|
||||
var handlerType = serviceTypes.HandlerType;
|
||||
var handler = container.Get(handlerType)
|
||||
?? throw new InvalidOperationException(
|
||||
$"No CQRS request handler registered for {requestType.FullName}.");
|
||||
|
||||
PrepareHandler(handler, context);
|
||||
var behaviorType = typeof(IPipelineBehavior<,>).MakeGenericType(requestType, typeof(TResponse));
|
||||
var behaviors = container.GetAll(behaviorType);
|
||||
var behaviors = container.GetAll(serviceTypes.BehaviorType);
|
||||
|
||||
foreach (var behavior in behaviors)
|
||||
PrepareHandler(behavior, context);
|
||||
@ -135,7 +151,9 @@ internal sealed class CqrsDispatcher(
|
||||
ArgumentNullException.ThrowIfNull(request);
|
||||
|
||||
var requestType = request.GetType();
|
||||
var handlerType = typeof(IStreamRequestHandler<,>).MakeGenericType(requestType, typeof(TResponse));
|
||||
var handlerType = StreamHandlerServiceTypes.GetOrAdd(
|
||||
(requestType, typeof(TResponse)),
|
||||
static key => typeof(IStreamRequestHandler<,>).MakeGenericType(key.RequestType, key.ResponseType));
|
||||
var handler = container.Get(handlerType)
|
||||
?? throw new InvalidOperationException(
|
||||
$"No CQRS stream handler registered for {requestType.FullName}.");
|
||||
@ -293,4 +311,6 @@ internal sealed class CqrsDispatcher(
|
||||
CancellationToken cancellationToken);
|
||||
|
||||
private delegate object StreamInvoker(object handler, object request, CancellationToken cancellationToken);
|
||||
|
||||
private readonly record struct RequestServiceTypeSet(Type HandlerType, Type BehaviorType);
|
||||
}
|
||||
|
||||
@ -40,7 +40,7 @@ internal static class CqrsHandlerRegistrar
|
||||
container.GetServicesUnsafe,
|
||||
assembly,
|
||||
logger,
|
||||
generatedRegistrationResult.ReflectionFallbackTypeNames);
|
||||
generatedRegistrationResult.ReflectionFallbackMetadata);
|
||||
}
|
||||
}
|
||||
|
||||
@ -106,13 +106,13 @@ internal static class CqrsHandlerRegistrar
|
||||
registry.Register(services, logger);
|
||||
}
|
||||
|
||||
var reflectionFallbackTypeNames = GetReflectionFallbackTypeNames(assembly);
|
||||
if (reflectionFallbackTypeNames is not null)
|
||||
var reflectionFallbackMetadata = GetReflectionFallbackMetadata(assembly, logger);
|
||||
if (reflectionFallbackMetadata is not null)
|
||||
{
|
||||
if (reflectionFallbackTypeNames.Count > 0)
|
||||
if (reflectionFallbackMetadata.HasExplicitTypes)
|
||||
{
|
||||
logger.Debug(
|
||||
$"Generated CQRS registry for assembly {assemblyName} requested targeted reflection fallback for {reflectionFallbackTypeNames.Count} unsupported handler type(s).");
|
||||
$"Generated CQRS registry for assembly {assemblyName} requested targeted reflection fallback for {reflectionFallbackMetadata.Types.Count} unsupported handler type(s).");
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -120,7 +120,7 @@ internal static class CqrsHandlerRegistrar
|
||||
$"Generated CQRS registry for assembly {assemblyName} requested full reflection fallback for unsupported handlers.");
|
||||
}
|
||||
|
||||
return GeneratedRegistrationResult.WithReflectionFallback(reflectionFallbackTypeNames);
|
||||
return GeneratedRegistrationResult.WithReflectionFallback(reflectionFallbackMetadata);
|
||||
}
|
||||
|
||||
return GeneratedRegistrationResult.FullyHandled();
|
||||
@ -142,9 +142,9 @@ internal static class CqrsHandlerRegistrar
|
||||
IServiceCollection services,
|
||||
Assembly assembly,
|
||||
ILogger logger,
|
||||
IReadOnlyList<string>? reflectionFallbackTypeNames)
|
||||
ReflectionFallbackMetadata? reflectionFallbackMetadata)
|
||||
{
|
||||
foreach (var implementationType in GetCandidateHandlerTypes(assembly, logger, reflectionFallbackTypeNames)
|
||||
foreach (var implementationType in GetCandidateHandlerTypes(assembly, logger, reflectionFallbackMetadata)
|
||||
.Where(IsConcreteHandlerType))
|
||||
{
|
||||
var handlerInterfaces = implementationType
|
||||
@ -180,24 +180,51 @@ internal static class CqrsHandlerRegistrar
|
||||
private static IReadOnlyList<Type> GetCandidateHandlerTypes(
|
||||
Assembly assembly,
|
||||
ILogger logger,
|
||||
IReadOnlyList<string>? reflectionFallbackTypeNames)
|
||||
ReflectionFallbackMetadata? reflectionFallbackMetadata)
|
||||
{
|
||||
return reflectionFallbackTypeNames is { Count: > 0 }
|
||||
? GetNamedFallbackTypes(assembly, reflectionFallbackTypeNames, logger)
|
||||
return reflectionFallbackMetadata is { HasExplicitTypes: true }
|
||||
? reflectionFallbackMetadata.Types
|
||||
: GetLoadableTypes(assembly, logger);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据生成器记录的类型全名,精确解析仍需运行时补充注册的处理器类型。
|
||||
/// 获取生成注册器要求运行时继续补充反射扫描的 handler 元数据。
|
||||
/// </summary>
|
||||
private static IReadOnlyList<Type> GetNamedFallbackTypes(
|
||||
private static ReflectionFallbackMetadata? GetReflectionFallbackMetadata(
|
||||
Assembly assembly,
|
||||
IReadOnlyList<string> reflectionFallbackTypeNames,
|
||||
ILogger logger)
|
||||
{
|
||||
var assemblyName = GetAssemblySortKey(assembly);
|
||||
var resolvedTypes = new List<Type>(reflectionFallbackTypeNames.Count);
|
||||
foreach (var typeName in reflectionFallbackTypeNames
|
||||
var fallbackAttributes = assembly
|
||||
.GetCustomAttributes(typeof(CqrsReflectionFallbackAttribute), inherit: false)
|
||||
.OfType<CqrsReflectionFallbackAttribute>()
|
||||
.ToList();
|
||||
|
||||
if (fallbackAttributes.Count == 0)
|
||||
return null;
|
||||
|
||||
var resolvedTypes = new List<Type>();
|
||||
foreach (var fallbackType in fallbackAttributes
|
||||
.SelectMany(static attribute => attribute.FallbackHandlerTypes)
|
||||
.Where(static type => type is not null)
|
||||
.Distinct()
|
||||
.OrderBy(GetTypeSortKey, StringComparer.Ordinal))
|
||||
{
|
||||
if (!string.Equals(
|
||||
GetAssemblySortKey(fallbackType.Assembly),
|
||||
assemblyName,
|
||||
StringComparison.Ordinal))
|
||||
{
|
||||
logger.Warn(
|
||||
$"Generated CQRS reflection fallback type {fallbackType.FullName} was declared on assembly {assemblyName} but belongs to assembly {GetAssemblySortKey(fallbackType.Assembly)}. Skipping mismatched fallback entry.");
|
||||
continue;
|
||||
}
|
||||
|
||||
resolvedTypes.Add(fallbackType);
|
||||
}
|
||||
|
||||
foreach (var typeName in fallbackAttributes
|
||||
.SelectMany(static attribute => attribute.FallbackHandlerTypeNames)
|
||||
.Where(static name => !string.IsNullOrWhiteSpace(name))
|
||||
.Distinct(StringComparer.Ordinal)
|
||||
.OrderBy(static name => name, StringComparer.Ordinal))
|
||||
@ -221,9 +248,11 @@ internal static class CqrsHandlerRegistrar
|
||||
}
|
||||
}
|
||||
|
||||
return resolvedTypes
|
||||
.OrderBy(GetTypeSortKey, StringComparer.Ordinal)
|
||||
.ToList();
|
||||
return new ReflectionFallbackMetadata(
|
||||
resolvedTypes
|
||||
.Distinct()
|
||||
.OrderBy(GetTypeSortKey, StringComparer.Ordinal)
|
||||
.ToArray());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -291,27 +320,6 @@ internal static class CqrsHandlerRegistrar
|
||||
definition == typeof(IStreamRequestHandler<,>);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取生成注册器要求运行时继续补充反射扫描的 handler 类型名清单。
|
||||
/// </summary>
|
||||
private static IReadOnlyList<string>? GetReflectionFallbackTypeNames(Assembly assembly)
|
||||
{
|
||||
var fallbackAttributes = assembly
|
||||
.GetCustomAttributes(typeof(CqrsReflectionFallbackAttribute), inherit: false)
|
||||
.OfType<CqrsReflectionFallbackAttribute>()
|
||||
.ToList();
|
||||
|
||||
if (fallbackAttributes.Count == 0)
|
||||
return null;
|
||||
|
||||
return fallbackAttributes
|
||||
.SelectMany(static attribute => attribute.FallbackHandlerTypeNames)
|
||||
.Where(static typeName => !string.IsNullOrWhiteSpace(typeName))
|
||||
.Distinct(StringComparer.Ordinal)
|
||||
.OrderBy(static typeName => typeName, StringComparer.Ordinal)
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断同一 handler 映射是否已经由生成注册器或先前扫描步骤写入服务集合。
|
||||
/// </summary>
|
||||
@ -346,14 +354,14 @@ internal static class CqrsHandlerRegistrar
|
||||
private readonly record struct GeneratedRegistrationResult(
|
||||
bool UsedGeneratedRegistry,
|
||||
bool RequiresReflectionFallback,
|
||||
IReadOnlyList<string>? ReflectionFallbackTypeNames)
|
||||
ReflectionFallbackMetadata? ReflectionFallbackMetadata)
|
||||
{
|
||||
public static GeneratedRegistrationResult NoGeneratedRegistry()
|
||||
{
|
||||
return new GeneratedRegistrationResult(
|
||||
UsedGeneratedRegistry: false,
|
||||
RequiresReflectionFallback: false,
|
||||
ReflectionFallbackTypeNames: null);
|
||||
ReflectionFallbackMetadata: null);
|
||||
}
|
||||
|
||||
public static GeneratedRegistrationResult FullyHandled()
|
||||
@ -361,18 +369,25 @@ internal static class CqrsHandlerRegistrar
|
||||
return new GeneratedRegistrationResult(
|
||||
UsedGeneratedRegistry: true,
|
||||
RequiresReflectionFallback: false,
|
||||
ReflectionFallbackTypeNames: null);
|
||||
ReflectionFallbackMetadata: null);
|
||||
}
|
||||
|
||||
public static GeneratedRegistrationResult WithReflectionFallback(
|
||||
IReadOnlyList<string> reflectionFallbackTypeNames)
|
||||
ReflectionFallbackMetadata reflectionFallbackMetadata)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(reflectionFallbackTypeNames);
|
||||
ArgumentNullException.ThrowIfNull(reflectionFallbackMetadata);
|
||||
|
||||
return new GeneratedRegistrationResult(
|
||||
UsedGeneratedRegistry: true,
|
||||
RequiresReflectionFallback: true,
|
||||
ReflectionFallbackTypeNames: reflectionFallbackTypeNames);
|
||||
ReflectionFallbackMetadata: reflectionFallbackMetadata);
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class ReflectionFallbackMetadata(IReadOnlyList<Type> types)
|
||||
{
|
||||
public IReadOnlyList<Type> Types { get; } = types ?? throw new ArgumentNullException(nameof(types));
|
||||
|
||||
public bool HasExplicitTypes => Types.Count > 0;
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user