fix(cqrs): 硬化 generated invoker descriptor 契约

- 修复 generated request/stream invoker descriptor 枚举阶段对异常、重复 pair 与不一致元数据的防守行为

- 补充 generated invoker provider descriptor 边界回归测试,锁定回退与首条生效语义
This commit is contained in:
gewuyou 2026-05-11 12:41:34 +08:00
parent 8990749d91
commit 7fa9d5ff17
2 changed files with 675 additions and 2 deletions

View File

@ -448,6 +448,126 @@ internal sealed class CqrsGeneratedRequestInvokerProviderTests
Assert.That(results, Is.EqualTo([3, 4]));
}
/// <summary>
/// 验证当 generated request invoker provider 的 descriptor 枚举抛出异常时,
/// registrar 会跳过 generated descriptor 预热并回退到反射路径。
/// </summary>
[Test]
public async Task SendAsync_Should_Fall_Back_To_Runtime_Path_When_Request_Descriptor_Enumeration_Throws()
{
var generatedAssembly = CreateGeneratedAssembly(
typeof(ThrowingEnumeratingRequestInvokerProviderRegistry),
"GFramework.Cqrs.Tests.Cqrs.ThrowingEnumeratingRequestInvokerAssembly, Version=1.0.0.0");
var container = new MicrosoftDiContainer();
CqrsTestRuntime.RegisterHandlers(container, generatedAssembly.Object);
container.Freeze();
var context = new ArchitectureContext(container);
var response = await context.SendRequestAsync(new GeneratedRequestInvokerRequest("payload")).ConfigureAwait(false);
Assert.That(response, Is.EqualTo("runtime:payload"));
}
/// <summary>
/// 验证当 generated stream invoker provider 的 descriptor 枚举抛出异常时,
/// registrar 会跳过 generated descriptor 预热并回退到反射建流路径。
/// </summary>
[Test]
public async Task CreateStream_Should_Fall_Back_To_Runtime_Path_When_Stream_Descriptor_Enumeration_Throws()
{
var generatedAssembly = CreateGeneratedAssembly(
typeof(ThrowingEnumeratingStreamInvokerProviderRegistry),
"GFramework.Cqrs.Tests.Cqrs.ThrowingEnumeratingStreamInvokerAssembly, Version=1.0.0.0");
var container = new MicrosoftDiContainer();
CqrsTestRuntime.RegisterHandlers(container, generatedAssembly.Object);
container.Freeze();
var context = new ArchitectureContext(container);
var results = await DrainAsync(context.CreateStream(new GeneratedStreamInvokerRequest(3))).ConfigureAwait(false);
Assert.That(results, Is.EqualTo([3, 4]));
}
/// <summary>
/// 验证当 request descriptor 枚举返回重复 request-response pair 时,
/// registrar 会稳定保留首个有效描述符,并忽略后续重复项。
/// </summary>
[Test]
public async Task SendAsync_Should_Use_First_Generated_Request_Descriptor_When_Duplicates_Are_Enumerated()
{
var generatedAssembly = CreateGeneratedAssembly(
typeof(DuplicateEnumeratingRequestInvokerProviderRegistry),
"GFramework.Cqrs.Tests.Cqrs.DuplicateEnumeratingRequestInvokerAssembly, Version=1.0.0.0");
var container = new MicrosoftDiContainer();
CqrsTestRuntime.RegisterHandlers(container, generatedAssembly.Object);
container.Freeze();
var context = new ArchitectureContext(container);
var response = await context.SendRequestAsync(new GeneratedRequestInvokerRequest("payload")).ConfigureAwait(false);
Assert.That(response, Is.EqualTo("generated:payload"));
}
/// <summary>
/// 验证当 stream descriptor 枚举返回重复 request-response pair 时,
/// registrar 会稳定保留首个有效描述符,并忽略后续重复项。
/// </summary>
[Test]
public async Task CreateStream_Should_Use_First_Generated_Stream_Descriptor_When_Duplicates_Are_Enumerated()
{
var generatedAssembly = CreateGeneratedAssembly(
typeof(DuplicateEnumeratingStreamInvokerProviderRegistry),
"GFramework.Cqrs.Tests.Cqrs.DuplicateEnumeratingStreamInvokerAssembly, Version=1.0.0.0");
var container = new MicrosoftDiContainer();
CqrsTestRuntime.RegisterHandlers(container, generatedAssembly.Object);
container.Freeze();
var context = new ArchitectureContext(container);
var results = await DrainAsync(context.CreateStream(new GeneratedStreamInvokerRequest(3))).ConfigureAwait(false);
Assert.That(results, Is.EqualTo([30, 31]));
}
/// <summary>
/// 验证当 request descriptor 枚举项与 provider 的 TryGetDescriptor 结果不一致时,
/// registrar 会忽略该坏 descriptor并继续回退到反射路径。
/// </summary>
[Test]
public async Task SendAsync_Should_Fall_Back_To_Runtime_Path_When_Enumerated_Request_Descriptor_Does_Not_Match_Provider()
{
var generatedAssembly = CreateGeneratedAssembly(
typeof(MismatchedEnumeratingRequestInvokerProviderRegistry),
"GFramework.Cqrs.Tests.Cqrs.MismatchedEnumeratingRequestInvokerAssembly, Version=1.0.0.0");
var container = new MicrosoftDiContainer();
CqrsTestRuntime.RegisterHandlers(container, generatedAssembly.Object);
container.Freeze();
var context = new ArchitectureContext(container);
var response = await context.SendRequestAsync(new GeneratedRequestInvokerRequest("payload")).ConfigureAwait(false);
Assert.That(response, Is.EqualTo("runtime:payload"));
}
/// <summary>
/// 验证当 stream descriptor 枚举项与 provider 的 TryGetDescriptor 结果不一致时,
/// registrar 会忽略该坏 descriptor并继续回退到反射建流路径。
/// </summary>
[Test]
public async Task CreateStream_Should_Fall_Back_To_Runtime_Path_When_Enumerated_Stream_Descriptor_Does_Not_Match_Provider()
{
var generatedAssembly = CreateGeneratedAssembly(
typeof(MismatchedEnumeratingStreamInvokerProviderRegistry),
"GFramework.Cqrs.Tests.Cqrs.MismatchedEnumeratingStreamInvokerAssembly, Version=1.0.0.0");
var container = new MicrosoftDiContainer();
CqrsTestRuntime.RegisterHandlers(container, generatedAssembly.Object);
container.Freeze();
var context = new ArchitectureContext(container);
var results = await DrainAsync(context.CreateStream(new GeneratedStreamInvokerRequest(3))).ConfigureAwait(false);
Assert.That(results, Is.EqualTo([3, 4]));
}
/// <summary>
/// 模拟返回实例 request invoker 方法的 generated registry。
/// </summary>
@ -860,6 +980,382 @@ internal sealed class CqrsGeneratedRequestInvokerProviderTests
}
}
/// <summary>
/// 模拟 descriptor 枚举阶段抛出异常的 request invoker provider。
/// </summary>
private sealed class ThrowingEnumeratingRequestInvokerProviderRegistry :
ICqrsHandlerRegistry,
ICqrsRequestInvokerProvider,
IEnumeratesCqrsRequestInvokerDescriptors
{
private static readonly CqrsRequestInvokerDescriptor Descriptor = new(
typeof(IRequestHandler<GeneratedRequestInvokerRequest, string>),
typeof(GeneratedRequestInvokerProviderRegistry).GetMethod(
"InvokeGenerated",
BindingFlags.NonPublic | BindingFlags.Static)!);
/// <inheritdoc />
public void Register(IServiceCollection services, ILogger logger)
{
ArgumentNullException.ThrowIfNull(services);
ArgumentNullException.ThrowIfNull(logger);
services.AddTransient(
typeof(IRequestHandler<GeneratedRequestInvokerRequest, string>),
typeof(GeneratedRequestInvokerRequestHandler));
}
/// <inheritdoc />
public bool TryGetDescriptor(
Type requestType,
Type responseType,
out CqrsRequestInvokerDescriptor? descriptor)
{
ArgumentNullException.ThrowIfNull(requestType);
ArgumentNullException.ThrowIfNull(responseType);
if (requestType == typeof(GeneratedRequestInvokerRequest) && responseType == typeof(string))
{
descriptor = Descriptor;
return true;
}
descriptor = null;
return false;
}
/// <inheritdoc />
public IReadOnlyList<CqrsRequestInvokerDescriptorEntry> GetDescriptors()
{
throw new InvalidOperationException("request descriptors failed");
}
}
/// <summary>
/// 模拟 descriptor 枚举阶段抛出异常的 stream invoker provider。
/// </summary>
private sealed class ThrowingEnumeratingStreamInvokerProviderRegistry :
ICqrsHandlerRegistry,
ICqrsStreamInvokerProvider,
IEnumeratesCqrsStreamInvokerDescriptors
{
private static readonly CqrsStreamInvokerDescriptor Descriptor = new(
typeof(IStreamRequestHandler<GeneratedStreamInvokerRequest, int>),
typeof(GeneratedStreamInvokerProviderRegistry).GetMethod(
"InvokeGenerated",
BindingFlags.NonPublic | BindingFlags.Static)!);
/// <inheritdoc />
public void Register(IServiceCollection services, ILogger logger)
{
ArgumentNullException.ThrowIfNull(services);
ArgumentNullException.ThrowIfNull(logger);
services.AddTransient(
typeof(IStreamRequestHandler<GeneratedStreamInvokerRequest, int>),
typeof(GeneratedStreamInvokerRequestHandler));
}
/// <inheritdoc />
public bool TryGetDescriptor(
Type requestType,
Type responseType,
out CqrsStreamInvokerDescriptor? descriptor)
{
ArgumentNullException.ThrowIfNull(requestType);
ArgumentNullException.ThrowIfNull(responseType);
if (requestType == typeof(GeneratedStreamInvokerRequest) && responseType == typeof(int))
{
descriptor = Descriptor;
return true;
}
descriptor = null;
return false;
}
/// <inheritdoc />
public IReadOnlyList<CqrsStreamInvokerDescriptorEntry> GetDescriptors()
{
throw new InvalidOperationException("stream descriptors failed");
}
}
/// <summary>
/// 模拟返回重复 request descriptor 条目的 generated registry。
/// </summary>
private sealed class DuplicateEnumeratingRequestInvokerProviderRegistry :
ICqrsHandlerRegistry,
ICqrsRequestInvokerProvider,
IEnumeratesCqrsRequestInvokerDescriptors
{
private static readonly CqrsRequestInvokerDescriptor PrimaryDescriptor = new(
typeof(IRequestHandler<GeneratedRequestInvokerRequest, string>),
typeof(GeneratedRequestInvokerProviderRegistry).GetMethod(
"InvokeGenerated",
BindingFlags.NonPublic | BindingFlags.Static)!);
private static readonly CqrsRequestInvokerDescriptor SecondaryDescriptor = new(
typeof(IRequestHandler<GeneratedRequestInvokerRequest, string>),
typeof(DuplicateEnumeratingRequestInvokerProviderRegistry).GetMethod(
nameof(InvokeAlternativeGenerated),
BindingFlags.NonPublic | BindingFlags.Static)!);
/// <inheritdoc />
public void Register(IServiceCollection services, ILogger logger)
{
ArgumentNullException.ThrowIfNull(services);
ArgumentNullException.ThrowIfNull(logger);
services.AddTransient(
typeof(IRequestHandler<GeneratedRequestInvokerRequest, string>),
typeof(GeneratedRequestInvokerRequestHandler));
}
/// <inheritdoc />
public bool TryGetDescriptor(
Type requestType,
Type responseType,
out CqrsRequestInvokerDescriptor? descriptor)
{
ArgumentNullException.ThrowIfNull(requestType);
ArgumentNullException.ThrowIfNull(responseType);
if (requestType == typeof(GeneratedRequestInvokerRequest) && responseType == typeof(string))
{
descriptor = PrimaryDescriptor;
return true;
}
descriptor = null;
return false;
}
/// <inheritdoc />
public IReadOnlyList<CqrsRequestInvokerDescriptorEntry> GetDescriptors()
{
return
[
new CqrsRequestInvokerDescriptorEntry(typeof(GeneratedRequestInvokerRequest), typeof(string), PrimaryDescriptor),
new CqrsRequestInvokerDescriptorEntry(typeof(GeneratedRequestInvokerRequest), typeof(string), SecondaryDescriptor)
];
}
private static ValueTask<string> InvokeAlternativeGenerated(
object handler,
object request,
CancellationToken cancellationToken)
{
return ValueTask.FromResult("duplicate:payload");
}
}
/// <summary>
/// 模拟返回重复 stream descriptor 条目的 generated registry。
/// </summary>
private sealed class DuplicateEnumeratingStreamInvokerProviderRegistry :
ICqrsHandlerRegistry,
ICqrsStreamInvokerProvider,
IEnumeratesCqrsStreamInvokerDescriptors
{
private static readonly CqrsStreamInvokerDescriptor PrimaryDescriptor = new(
typeof(IStreamRequestHandler<GeneratedStreamInvokerRequest, int>),
typeof(GeneratedStreamInvokerProviderRegistry).GetMethod(
"InvokeGenerated",
BindingFlags.NonPublic | BindingFlags.Static)!);
private static readonly CqrsStreamInvokerDescriptor SecondaryDescriptor = new(
typeof(IStreamRequestHandler<GeneratedStreamInvokerRequest, int>),
typeof(DuplicateEnumeratingStreamInvokerProviderRegistry).GetMethod(
nameof(InvokeAlternativeGenerated),
BindingFlags.NonPublic | BindingFlags.Static)!);
/// <inheritdoc />
public void Register(IServiceCollection services, ILogger logger)
{
ArgumentNullException.ThrowIfNull(services);
ArgumentNullException.ThrowIfNull(logger);
services.AddTransient(
typeof(IStreamRequestHandler<GeneratedStreamInvokerRequest, int>),
typeof(GeneratedStreamInvokerRequestHandler));
}
/// <inheritdoc />
public bool TryGetDescriptor(
Type requestType,
Type responseType,
out CqrsStreamInvokerDescriptor? descriptor)
{
ArgumentNullException.ThrowIfNull(requestType);
ArgumentNullException.ThrowIfNull(responseType);
if (requestType == typeof(GeneratedStreamInvokerRequest) && responseType == typeof(int))
{
descriptor = PrimaryDescriptor;
return true;
}
descriptor = null;
return false;
}
/// <inheritdoc />
public IReadOnlyList<CqrsStreamInvokerDescriptorEntry> GetDescriptors()
{
return
[
new CqrsStreamInvokerDescriptorEntry(typeof(GeneratedStreamInvokerRequest), typeof(int), PrimaryDescriptor),
new CqrsStreamInvokerDescriptorEntry(typeof(GeneratedStreamInvokerRequest), typeof(int), SecondaryDescriptor)
];
}
private static object InvokeAlternativeGenerated(object handler, object request, CancellationToken cancellationToken)
{
return new[] { 900, 901 }.ToAsyncEnumerable();
}
}
/// <summary>
/// 模拟枚举出的 request descriptor 与 provider 显式查询结果不一致的 generated registry。
/// </summary>
private sealed class MismatchedEnumeratingRequestInvokerProviderRegistry :
ICqrsHandlerRegistry,
ICqrsRequestInvokerProvider,
IEnumeratesCqrsRequestInvokerDescriptors
{
private static readonly CqrsRequestInvokerDescriptor ProviderDescriptor = new(
typeof(IRequestHandler<GeneratedRequestInvokerRequest, string>),
typeof(GeneratedRequestInvokerProviderRegistry).GetMethod(
"InvokeGenerated",
BindingFlags.NonPublic | BindingFlags.Static)!);
private static readonly CqrsRequestInvokerDescriptor EnumeratedDescriptor = new(
typeof(IRequestHandler<GeneratedRequestInvokerRequest, string>),
typeof(MismatchedEnumeratingRequestInvokerProviderRegistry).GetMethod(
nameof(InvokeAlternativeGenerated),
BindingFlags.NonPublic | BindingFlags.Static)!);
/// <inheritdoc />
public void Register(IServiceCollection services, ILogger logger)
{
ArgumentNullException.ThrowIfNull(services);
ArgumentNullException.ThrowIfNull(logger);
services.AddTransient(
typeof(IRequestHandler<GeneratedRequestInvokerRequest, string>),
typeof(GeneratedRequestInvokerRequestHandler));
}
/// <inheritdoc />
public bool TryGetDescriptor(
Type requestType,
Type responseType,
out CqrsRequestInvokerDescriptor? descriptor)
{
ArgumentNullException.ThrowIfNull(requestType);
ArgumentNullException.ThrowIfNull(responseType);
if (requestType == typeof(GeneratedRequestInvokerRequest) && responseType == typeof(string))
{
descriptor = ProviderDescriptor;
return true;
}
descriptor = null;
return false;
}
/// <inheritdoc />
public IReadOnlyList<CqrsRequestInvokerDescriptorEntry> GetDescriptors()
{
return
[
new CqrsRequestInvokerDescriptorEntry(
typeof(GeneratedRequestInvokerRequest),
typeof(string),
EnumeratedDescriptor)
];
}
private static ValueTask<string> InvokeAlternativeGenerated(
object handler,
object request,
CancellationToken cancellationToken)
{
return ValueTask.FromResult("mismatched:payload");
}
}
/// <summary>
/// 模拟枚举出的 stream descriptor 与 provider 显式查询结果不一致的 generated registry。
/// </summary>
private sealed class MismatchedEnumeratingStreamInvokerProviderRegistry :
ICqrsHandlerRegistry,
ICqrsStreamInvokerProvider,
IEnumeratesCqrsStreamInvokerDescriptors
{
private static readonly CqrsStreamInvokerDescriptor ProviderDescriptor = new(
typeof(IStreamRequestHandler<GeneratedStreamInvokerRequest, int>),
typeof(GeneratedStreamInvokerProviderRegistry).GetMethod(
"InvokeGenerated",
BindingFlags.NonPublic | BindingFlags.Static)!);
private static readonly CqrsStreamInvokerDescriptor EnumeratedDescriptor = new(
typeof(IStreamRequestHandler<GeneratedStreamInvokerRequest, int>),
typeof(MismatchedEnumeratingStreamInvokerProviderRegistry).GetMethod(
nameof(InvokeAlternativeGenerated),
BindingFlags.NonPublic | BindingFlags.Static)!);
/// <inheritdoc />
public void Register(IServiceCollection services, ILogger logger)
{
ArgumentNullException.ThrowIfNull(services);
ArgumentNullException.ThrowIfNull(logger);
services.AddTransient(
typeof(IStreamRequestHandler<GeneratedStreamInvokerRequest, int>),
typeof(GeneratedStreamInvokerRequestHandler));
}
/// <inheritdoc />
public bool TryGetDescriptor(
Type requestType,
Type responseType,
out CqrsStreamInvokerDescriptor? descriptor)
{
ArgumentNullException.ThrowIfNull(requestType);
ArgumentNullException.ThrowIfNull(responseType);
if (requestType == typeof(GeneratedStreamInvokerRequest) && responseType == typeof(int))
{
descriptor = ProviderDescriptor;
return true;
}
descriptor = null;
return false;
}
/// <inheritdoc />
public IReadOnlyList<CqrsStreamInvokerDescriptorEntry> GetDescriptors()
{
return
[
new CqrsStreamInvokerDescriptorEntry(
typeof(GeneratedStreamInvokerRequest),
typeof(int),
EnumeratedDescriptor)
];
}
private static object InvokeAlternativeGenerated(object handler, object request, CancellationToken cancellationToken)
{
return new[] { 700, 701 }.ToAsyncEnumerable();
}
}
/// <summary>
/// 创建带有 generated request invoker registry 元数据的程序集替身。
/// </summary>

View File

@ -16,6 +16,13 @@ namespace GFramework.Cqrs.Internal;
/// </summary>
internal static class CqrsHandlerRegistrar
{
/// <summary>
/// 描述 generated invoker descriptor 在 registrar 预热阶段使用的 request/response 类型对键。
/// </summary>
/// <param name="RequestType">请求运行时类型。</param>
/// <param name="ResponseType">响应运行时类型。</param>
private readonly record struct InvokerDescriptorKey(Type RequestType, Type ResponseType);
// 卸载安全的进程级缓存:程序集元数据只按弱键复用。
// 若程序集来自 collectible AssemblyLoadContext被回收后会重新分析而不会被静态缓存永久钉住。
private static readonly WeakKeyCache<Assembly, AssemblyRegistrationMetadata> AssemblyMetadataCache =
@ -321,8 +328,48 @@ internal static class CqrsHandlerRegistrar
if (provider is not IEnumeratesCqrsRequestInvokerDescriptors descriptorSource)
return;
foreach (var descriptorEntry in descriptorSource.GetDescriptors())
IReadOnlyList<CqrsRequestInvokerDescriptorEntry>? descriptors;
try
{
descriptors = descriptorSource.GetDescriptors();
}
catch (Exception exception)
{
logger.Warn(
$"Failed to enumerate generated CQRS request invoker descriptors from provider {provider.GetType().FullName} in assembly {assemblyName}. Falling back to runtime reflection for request invokers: {exception.Message}");
return;
}
if (descriptors is null)
{
logger.Warn(
$"Ignoring generated CQRS request invoker descriptors from provider {provider.GetType().FullName} in assembly {assemblyName} because GetDescriptors() returned null.");
return;
}
var registeredKeys = new HashSet<InvokerDescriptorKey>();
foreach (var descriptorEntry in descriptors)
{
if (descriptorEntry is null)
{
logger.Warn(
$"Ignoring null generated CQRS request invoker descriptor entry from provider {provider.GetType().FullName} in assembly {assemblyName}.");
continue;
}
var descriptorKey = new InvokerDescriptorKey(
descriptorEntry.RequestType,
descriptorEntry.ResponseType);
if (!registeredKeys.Add(descriptorKey))
{
logger.Warn(
$"Ignoring duplicate generated CQRS request invoker descriptor for {descriptorEntry.RequestType.FullName} -> {descriptorEntry.ResponseType.FullName} from provider {provider.GetType().FullName} in assembly {assemblyName}.");
continue;
}
if (!TryValidateEnumeratedRequestInvokerDescriptor(provider, descriptorEntry, assemblyName, logger))
continue;
CqrsDispatcher.RegisterGeneratedRequestInvokerDescriptor(
descriptorEntry.RequestType,
descriptorEntry.ResponseType,
@ -376,8 +423,48 @@ internal static class CqrsHandlerRegistrar
if (provider is not IEnumeratesCqrsStreamInvokerDescriptors descriptorSource)
return;
foreach (var descriptorEntry in descriptorSource.GetDescriptors())
IReadOnlyList<CqrsStreamInvokerDescriptorEntry>? descriptors;
try
{
descriptors = descriptorSource.GetDescriptors();
}
catch (Exception exception)
{
logger.Warn(
$"Failed to enumerate generated CQRS stream invoker descriptors from provider {provider.GetType().FullName} in assembly {assemblyName}. Falling back to runtime reflection for stream invokers: {exception.Message}");
return;
}
if (descriptors is null)
{
logger.Warn(
$"Ignoring generated CQRS stream invoker descriptors from provider {provider.GetType().FullName} in assembly {assemblyName} because GetDescriptors() returned null.");
return;
}
var registeredKeys = new HashSet<InvokerDescriptorKey>();
foreach (var descriptorEntry in descriptors)
{
if (descriptorEntry is null)
{
logger.Warn(
$"Ignoring null generated CQRS stream invoker descriptor entry from provider {provider.GetType().FullName} in assembly {assemblyName}.");
continue;
}
var descriptorKey = new InvokerDescriptorKey(
descriptorEntry.RequestType,
descriptorEntry.ResponseType);
if (!registeredKeys.Add(descriptorKey))
{
logger.Warn(
$"Ignoring duplicate generated CQRS stream invoker descriptor for {descriptorEntry.RequestType.FullName} -> {descriptorEntry.ResponseType.FullName} from provider {provider.GetType().FullName} in assembly {assemblyName}.");
continue;
}
if (!TryValidateEnumeratedStreamInvokerDescriptor(provider, descriptorEntry, assemblyName, logger))
continue;
CqrsDispatcher.RegisterGeneratedStreamInvokerDescriptor(
descriptorEntry.RequestType,
descriptorEntry.ResponseType,
@ -387,6 +474,96 @@ internal static class CqrsHandlerRegistrar
}
}
/// <summary>
/// 校验 request descriptor 枚举项是否与 provider 的显式查询结果保持一致。
/// </summary>
/// <param name="provider">当前正在预热的 request invoker provider。</param>
/// <param name="descriptorEntry">当前枚举到的描述符条目。</param>
/// <param name="assemblyName">当前程序集的稳定名称。</param>
/// <param name="logger">日志记录器。</param>
/// <returns>当该枚举项可安全写入 dispatcher 缓存时返回 <see langword="true" />;否则返回 <see langword="false" />。</returns>
private static bool TryValidateEnumeratedRequestInvokerDescriptor(
ICqrsRequestInvokerProvider provider,
CqrsRequestInvokerDescriptorEntry descriptorEntry,
string assemblyName,
ILogger logger)
{
try
{
if (!provider.TryGetDescriptor(
descriptorEntry.RequestType,
descriptorEntry.ResponseType,
out var resolvedDescriptor) ||
resolvedDescriptor is null)
{
logger.Warn(
$"Ignoring generated CQRS request invoker descriptor for {descriptorEntry.RequestType.FullName} -> {descriptorEntry.ResponseType.FullName} from provider {provider.GetType().FullName} in assembly {assemblyName} because TryGetDescriptor did not return a matching descriptor.");
return false;
}
if (!ReferenceEquals(resolvedDescriptor.InvokerMethod, descriptorEntry.Descriptor.InvokerMethod) ||
resolvedDescriptor.HandlerType != descriptorEntry.Descriptor.HandlerType)
{
logger.Warn(
$"Ignoring generated CQRS request invoker descriptor for {descriptorEntry.RequestType.FullName} -> {descriptorEntry.ResponseType.FullName} from provider {provider.GetType().FullName} in assembly {assemblyName} because the enumerated descriptor does not match TryGetDescriptor.");
return false;
}
return true;
}
catch (Exception exception)
{
logger.Warn(
$"Ignoring generated CQRS request invoker descriptor for {descriptorEntry.RequestType.FullName} -> {descriptorEntry.ResponseType.FullName} from provider {provider.GetType().FullName} in assembly {assemblyName} because TryGetDescriptor threw: {exception.Message}");
return false;
}
}
/// <summary>
/// 校验 stream descriptor 枚举项是否与 provider 的显式查询结果保持一致。
/// </summary>
/// <param name="provider">当前正在预热的 stream invoker provider。</param>
/// <param name="descriptorEntry">当前枚举到的描述符条目。</param>
/// <param name="assemblyName">当前程序集的稳定名称。</param>
/// <param name="logger">日志记录器。</param>
/// <returns>当该枚举项可安全写入 dispatcher 缓存时返回 <see langword="true" />;否则返回 <see langword="false" />。</returns>
private static bool TryValidateEnumeratedStreamInvokerDescriptor(
ICqrsStreamInvokerProvider provider,
CqrsStreamInvokerDescriptorEntry descriptorEntry,
string assemblyName,
ILogger logger)
{
try
{
if (!provider.TryGetDescriptor(
descriptorEntry.RequestType,
descriptorEntry.ResponseType,
out var resolvedDescriptor) ||
resolvedDescriptor is null)
{
logger.Warn(
$"Ignoring generated CQRS stream invoker descriptor for {descriptorEntry.RequestType.FullName} -> {descriptorEntry.ResponseType.FullName} from provider {provider.GetType().FullName} in assembly {assemblyName} because TryGetDescriptor did not return a matching descriptor.");
return false;
}
if (!ReferenceEquals(resolvedDescriptor.InvokerMethod, descriptorEntry.Descriptor.InvokerMethod) ||
resolvedDescriptor.HandlerType != descriptorEntry.Descriptor.HandlerType)
{
logger.Warn(
$"Ignoring generated CQRS stream invoker descriptor for {descriptorEntry.RequestType.FullName} -> {descriptorEntry.ResponseType.FullName} from provider {provider.GetType().FullName} in assembly {assemblyName} because the enumerated descriptor does not match TryGetDescriptor.");
return false;
}
return true;
}
catch (Exception exception)
{
logger.Warn(
$"Ignoring generated CQRS stream invoker descriptor for {descriptorEntry.RequestType.FullName} -> {descriptorEntry.ResponseType.FullName} from provider {provider.GetType().FullName} in assembly {assemblyName} because TryGetDescriptor threw: {exception.Message}");
return false;
}
}
/// <summary>
/// 将 generated registry 的 fallback 元数据转换为统一的注册结果,并记录下一阶段是定向补扫还是整程序集扫描。
/// </summary>