mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-05-11 04:04:29 +08:00
- 优化 generated stream dispatch binding 为按响应类型缓存强类型 invoker 与 pipeline executor,压缩 CreateStream 热路径桥接开销 - 保持 stream 异常契约与行为缓存语义不变,并补齐相关 XML 注释与必要内联说明 - 补充 generated stream binding 与 pipeline executor 复用回归,覆盖 generated invoker 与 stream pipeline 组合场景
1005 lines
43 KiB
C#
1005 lines
43 KiB
C#
// Copyright (c) 2025-2026 GeWuYou
|
||
// SPDX-License-Identifier: Apache-2.0
|
||
|
||
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 会缓存热路径中的 dispatch binding。
|
||
/// </summary>
|
||
[TestFixture]
|
||
[NonParallelizable]
|
||
internal sealed class CqrsDispatcherCacheTests
|
||
{
|
||
private MicrosoftDiContainer? _container;
|
||
private ArchitectureContext? _context;
|
||
|
||
/// <summary>
|
||
/// 初始化测试上下文。
|
||
/// </summary>
|
||
[SetUp]
|
||
public void SetUp()
|
||
{
|
||
LoggerFactoryResolver.Provider = new ConsoleLoggerFactoryProvider();
|
||
_container = new MicrosoftDiContainer();
|
||
ConfigureDispatcherCacheFixture(_container);
|
||
|
||
_container.Freeze();
|
||
_context = new ArchitectureContext(_container);
|
||
DispatcherNotificationContextRefreshState.Reset();
|
||
DispatcherPipelineContextRefreshState.Reset();
|
||
DispatcherStreamPipelineOrderState.Reset();
|
||
DispatcherStreamContextRefreshState.Reset();
|
||
ClearDispatcherCaches();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 清理测试上下文引用。
|
||
/// </summary>
|
||
[TearDown]
|
||
public void TearDown()
|
||
{
|
||
_context = null;
|
||
_container = null;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 验证相同消息类型重复分发时,不会重复扩张 dispatch binding 缓存。
|
||
/// </summary>
|
||
[Test]
|
||
public async Task Dispatcher_Should_Cache_Dispatch_Bindings_After_First_Dispatch()
|
||
{
|
||
var notificationBindings = GetCacheField("NotificationDispatchBindings");
|
||
var requestBindings = GetCacheField("RequestDispatchBindings");
|
||
var streamBindings = GetCacheField("StreamDispatchBindings");
|
||
|
||
Assert.Multiple(() =>
|
||
{
|
||
Assert.That(
|
||
GetSingleKeyCacheValue(notificationBindings, typeof(DispatcherCacheNotification)),
|
||
Is.Null);
|
||
Assert.That(
|
||
GetPairCacheValue(requestBindings, typeof(DispatcherCacheRequest), typeof(int)),
|
||
Is.Null);
|
||
Assert.That(
|
||
GetPairCacheValue(requestBindings, typeof(DispatcherPipelineCacheRequest), typeof(int)),
|
||
Is.Null);
|
||
Assert.That(
|
||
GetPairCacheValue(streamBindings, typeof(DispatcherCacheStreamRequest), typeof(int)),
|
||
Is.Null);
|
||
});
|
||
|
||
await _context!.SendRequestAsync(new DispatcherCacheRequest());
|
||
await _context.SendRequestAsync(new DispatcherPipelineCacheRequest());
|
||
await _context.PublishAsync(new DispatcherCacheNotification());
|
||
await DrainAsync(_context.CreateStream(new DispatcherCacheStreamRequest()));
|
||
|
||
var notificationAfterFirstDispatch =
|
||
GetSingleKeyCacheValue(notificationBindings, typeof(DispatcherCacheNotification));
|
||
var requestAfterFirstDispatch =
|
||
GetPairCacheValue(requestBindings, typeof(DispatcherCacheRequest), typeof(int));
|
||
var pipelineAfterFirstDispatch =
|
||
GetPairCacheValue(requestBindings, typeof(DispatcherPipelineCacheRequest), typeof(int));
|
||
var streamAfterFirstDispatch =
|
||
GetPairCacheValue(streamBindings, typeof(DispatcherCacheStreamRequest), typeof(int));
|
||
|
||
await _context.SendRequestAsync(new DispatcherCacheRequest());
|
||
await _context.SendRequestAsync(new DispatcherPipelineCacheRequest());
|
||
await _context.PublishAsync(new DispatcherCacheNotification());
|
||
await DrainAsync(_context.CreateStream(new DispatcherCacheStreamRequest()));
|
||
|
||
Assert.Multiple(() =>
|
||
{
|
||
Assert.That(notificationAfterFirstDispatch, Is.Not.Null);
|
||
Assert.That(requestAfterFirstDispatch, Is.Not.Null);
|
||
Assert.That(pipelineAfterFirstDispatch, Is.Not.Null);
|
||
Assert.That(streamAfterFirstDispatch, Is.Not.Null);
|
||
|
||
Assert.That(
|
||
GetSingleKeyCacheValue(notificationBindings, typeof(DispatcherCacheNotification)),
|
||
Is.SameAs(notificationAfterFirstDispatch));
|
||
Assert.That(
|
||
GetPairCacheValue(requestBindings, typeof(DispatcherCacheRequest), typeof(int)),
|
||
Is.SameAs(requestAfterFirstDispatch));
|
||
Assert.That(
|
||
GetPairCacheValue(requestBindings, typeof(DispatcherPipelineCacheRequest), typeof(int)),
|
||
Is.SameAs(pipelineAfterFirstDispatch));
|
||
Assert.That(
|
||
GetPairCacheValue(streamBindings, typeof(DispatcherCacheStreamRequest), typeof(int)),
|
||
Is.SameAs(streamAfterFirstDispatch));
|
||
});
|
||
}
|
||
|
||
/// <summary>
|
||
/// 验证 request dispatch binding 会按响应类型分别缓存,避免不同响应类型共用 object 结果桥接。
|
||
/// </summary>
|
||
[Test]
|
||
public async Task Dispatcher_Should_Cache_Request_Dispatch_Bindings_Per_Response_Type()
|
||
{
|
||
var requestBindings = GetCacheField("RequestDispatchBindings");
|
||
|
||
await _context!.SendRequestAsync(new DispatcherCacheRequest());
|
||
await _context.SendRequestAsync(new DispatcherStringCacheRequest());
|
||
|
||
var intAfterFirstDispatch =
|
||
GetPairCacheValue(requestBindings, typeof(DispatcherCacheRequest), typeof(int));
|
||
var stringAfterFirstDispatch =
|
||
GetPairCacheValue(requestBindings, typeof(DispatcherStringCacheRequest), typeof(string));
|
||
|
||
await _context.SendRequestAsync(new DispatcherCacheRequest());
|
||
await _context.SendRequestAsync(new DispatcherStringCacheRequest());
|
||
|
||
Assert.Multiple(() =>
|
||
{
|
||
Assert.That(intAfterFirstDispatch, Is.Not.Null);
|
||
Assert.That(stringAfterFirstDispatch, Is.Not.Null);
|
||
Assert.That(intAfterFirstDispatch, Is.Not.SameAs(stringAfterFirstDispatch));
|
||
Assert.That(
|
||
GetPairCacheValue(requestBindings, typeof(DispatcherCacheRequest), typeof(int)),
|
||
Is.SameAs(intAfterFirstDispatch));
|
||
Assert.That(
|
||
GetPairCacheValue(requestBindings, typeof(DispatcherStringCacheRequest), typeof(string)),
|
||
Is.SameAs(stringAfterFirstDispatch));
|
||
});
|
||
}
|
||
|
||
/// <summary>
|
||
/// 验证 request 的“是否存在 pipeline behavior”判定会按 dispatcher 实例缓存,
|
||
/// 让零行为请求在首次分发后不再重复查询容器,同时不同 dispatcher 不共享该实例级状态。
|
||
/// </summary>
|
||
[Test]
|
||
public async Task Dispatcher_Should_Cache_Zero_Pipeline_Request_Presence_Per_Dispatcher_Instance()
|
||
{
|
||
var firstContext = new ArchitectureContext(_container!);
|
||
var secondContext = new ArchitectureContext(_container!);
|
||
var firstDispatcher = GetDispatcherFromContext(firstContext);
|
||
var secondDispatcher = GetDispatcherFromContext(secondContext);
|
||
using var isolatedContainer = CreateFrozenContainer();
|
||
var isolatedContext = new ArchitectureContext(isolatedContainer);
|
||
var isolatedDispatcher = GetDispatcherFromContext(isolatedContext);
|
||
|
||
AssertRequestBehaviorPresenceIsUnset(firstDispatcher, typeof(IPipelineBehavior<DispatcherCacheRequest, int>));
|
||
AssertRequestBehaviorPresenceIsUnset(secondDispatcher, typeof(IPipelineBehavior<DispatcherCacheRequest, int>));
|
||
AssertRequestBehaviorPresenceIsUnset(isolatedDispatcher, typeof(IPipelineBehavior<DispatcherCacheRequest, int>));
|
||
AssertRequestBehaviorPresenceIsUnset(
|
||
firstDispatcher,
|
||
typeof(IPipelineBehavior<DispatcherPipelineCacheRequest, int>));
|
||
|
||
await firstContext.SendRequestAsync(new DispatcherCacheRequest());
|
||
await firstContext.SendRequestAsync(new DispatcherPipelineCacheRequest());
|
||
|
||
var zeroPipelinePresence = GetRequestBehaviorPresenceCacheValue(
|
||
firstDispatcher,
|
||
typeof(IPipelineBehavior<DispatcherCacheRequest, int>));
|
||
var onePipelinePresence = GetRequestBehaviorPresenceCacheValue(
|
||
firstDispatcher,
|
||
typeof(IPipelineBehavior<DispatcherPipelineCacheRequest, int>));
|
||
|
||
AssertSharedDispatcherCacheState(
|
||
firstDispatcher,
|
||
secondDispatcher,
|
||
isolatedDispatcher,
|
||
zeroPipelinePresence,
|
||
onePipelinePresence);
|
||
|
||
await isolatedContext.SendRequestAsync(new DispatcherCacheRequest());
|
||
|
||
AssertRequestBehaviorPresenceEquals(
|
||
isolatedDispatcher,
|
||
typeof(IPipelineBehavior<DispatcherCacheRequest, int>),
|
||
false);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 验证 stream 的“是否存在 pipeline behavior”判定会按 dispatcher 实例缓存,
|
||
/// 并与当前容器的实际服务可见性保持一致,同时不同 dispatcher 不共享该实例级状态。
|
||
/// </summary>
|
||
[Test]
|
||
public async Task Dispatcher_Should_Cache_Stream_Behavior_Presence_Per_Dispatcher_Instance()
|
||
{
|
||
var firstContext = new ArchitectureContext(_container!);
|
||
var secondContext = new ArchitectureContext(_container!);
|
||
var firstDispatcher = GetDispatcherFromContext(firstContext);
|
||
var secondDispatcher = GetDispatcherFromContext(secondContext);
|
||
using var isolatedContainer = CreateFrozenContainer();
|
||
var isolatedContext = new ArchitectureContext(isolatedContainer);
|
||
var isolatedDispatcher = GetDispatcherFromContext(isolatedContext);
|
||
var zeroPipelineBehaviorType = typeof(IStreamPipelineBehavior<DispatcherZeroPipelineStreamRequest, int>);
|
||
var twoPipelineBehaviorType = typeof(IStreamPipelineBehavior<DispatcherStreamPipelineOrderRequest, int>);
|
||
var expectedZeroPipelinePresence = _container!.HasRegistration(zeroPipelineBehaviorType);
|
||
var expectedTwoPipelinePresence = _container.HasRegistration(twoPipelineBehaviorType);
|
||
|
||
AssertStreamBehaviorPresenceIsUnset(firstDispatcher, zeroPipelineBehaviorType);
|
||
AssertStreamBehaviorPresenceIsUnset(secondDispatcher, zeroPipelineBehaviorType);
|
||
AssertStreamBehaviorPresenceIsUnset(isolatedDispatcher, zeroPipelineBehaviorType);
|
||
AssertStreamBehaviorPresenceIsUnset(firstDispatcher, twoPipelineBehaviorType);
|
||
|
||
await DrainAsync(firstContext.CreateStream(new DispatcherZeroPipelineStreamRequest()));
|
||
await DrainAsync(firstContext.CreateStream(new DispatcherStreamPipelineOrderRequest()));
|
||
|
||
var zeroPipelinePresence = GetStreamBehaviorPresenceCacheValue(
|
||
firstDispatcher,
|
||
zeroPipelineBehaviorType);
|
||
var twoPipelinePresence = GetStreamBehaviorPresenceCacheValue(
|
||
firstDispatcher,
|
||
twoPipelineBehaviorType);
|
||
|
||
AssertSharedStreamDispatcherCacheState(
|
||
firstDispatcher,
|
||
secondDispatcher,
|
||
isolatedDispatcher,
|
||
zeroPipelinePresence,
|
||
twoPipelinePresence,
|
||
zeroPipelineBehaviorType,
|
||
expectedZeroPipelinePresence,
|
||
expectedTwoPipelinePresence);
|
||
|
||
await DrainAsync(isolatedContext.CreateStream(new DispatcherZeroPipelineStreamRequest()));
|
||
|
||
AssertStreamBehaviorPresenceEquals(isolatedDispatcher, zeroPipelineBehaviorType, expectedZeroPipelinePresence);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 验证 request pipeline executor 会按行为数量在 binding 内首次创建并在后续分发中复用。
|
||
/// </summary>
|
||
[Test]
|
||
public async Task Dispatcher_Should_Cache_Request_Pipeline_Executors_Per_Behavior_Count()
|
||
{
|
||
var requestBindings = GetCacheField("RequestDispatchBindings");
|
||
|
||
Assert.Multiple(() =>
|
||
{
|
||
Assert.That(
|
||
GetRequestPipelineExecutorValue(
|
||
requestBindings,
|
||
typeof(DispatcherPipelineCacheRequest),
|
||
typeof(int),
|
||
1),
|
||
Is.Null);
|
||
Assert.That(
|
||
GetRequestPipelineExecutorValue(
|
||
requestBindings,
|
||
typeof(DispatcherPipelineOrderCacheRequest),
|
||
typeof(int),
|
||
2),
|
||
Is.Null);
|
||
});
|
||
|
||
await _context!.SendRequestAsync(new DispatcherPipelineCacheRequest());
|
||
await _context.SendRequestAsync(new DispatcherPipelineOrderCacheRequest());
|
||
|
||
var singleBehaviorExecutor = GetRequestPipelineExecutorValue(
|
||
requestBindings,
|
||
typeof(DispatcherPipelineCacheRequest),
|
||
typeof(int),
|
||
1);
|
||
var twoBehaviorExecutor = GetRequestPipelineExecutorValue(
|
||
requestBindings,
|
||
typeof(DispatcherPipelineOrderCacheRequest),
|
||
typeof(int),
|
||
2);
|
||
|
||
await _context.SendRequestAsync(new DispatcherPipelineCacheRequest());
|
||
await _context.SendRequestAsync(new DispatcherPipelineOrderCacheRequest());
|
||
|
||
Assert.Multiple(() =>
|
||
{
|
||
Assert.That(singleBehaviorExecutor, Is.Not.Null);
|
||
Assert.That(twoBehaviorExecutor, Is.Not.Null);
|
||
Assert.That(singleBehaviorExecutor, Is.Not.SameAs(twoBehaviorExecutor));
|
||
Assert.That(
|
||
GetRequestPipelineExecutorValue(
|
||
requestBindings,
|
||
typeof(DispatcherPipelineCacheRequest),
|
||
typeof(int),
|
||
1),
|
||
Is.SameAs(singleBehaviorExecutor));
|
||
Assert.That(
|
||
GetRequestPipelineExecutorValue(
|
||
requestBindings,
|
||
typeof(DispatcherPipelineOrderCacheRequest),
|
||
typeof(int),
|
||
2),
|
||
Is.SameAs(twoBehaviorExecutor));
|
||
});
|
||
}
|
||
|
||
/// <summary>
|
||
/// 验证 stream pipeline executor 会按行为数量在 binding 内首次创建并在后续建流中复用。
|
||
/// </summary>
|
||
[Test]
|
||
public async Task Dispatcher_Should_Cache_Stream_Pipeline_Executors_Per_Behavior_Count()
|
||
{
|
||
var streamBindings = GetCacheField("StreamDispatchBindings");
|
||
|
||
Assert.Multiple(() =>
|
||
{
|
||
Assert.That(
|
||
GetStreamPipelineExecutorValue(
|
||
streamBindings,
|
||
typeof(DispatcherCacheStreamRequest),
|
||
typeof(int),
|
||
1),
|
||
Is.Null);
|
||
Assert.That(
|
||
GetStreamPipelineExecutorValue(
|
||
streamBindings,
|
||
typeof(DispatcherStreamPipelineOrderRequest),
|
||
typeof(int),
|
||
2),
|
||
Is.Null);
|
||
});
|
||
|
||
await DrainAsync(_context!.CreateStream(new DispatcherCacheStreamRequest()));
|
||
await DrainAsync(_context.CreateStream(new DispatcherStreamPipelineOrderRequest()));
|
||
|
||
var singleBehaviorExecutor = GetStreamPipelineExecutorValue(
|
||
streamBindings,
|
||
typeof(DispatcherCacheStreamRequest),
|
||
typeof(int),
|
||
1);
|
||
var twoBehaviorExecutor = GetStreamPipelineExecutorValue(
|
||
streamBindings,
|
||
typeof(DispatcherStreamPipelineOrderRequest),
|
||
typeof(int),
|
||
2);
|
||
|
||
await DrainAsync(_context.CreateStream(new DispatcherCacheStreamRequest()));
|
||
await DrainAsync(_context.CreateStream(new DispatcherStreamPipelineOrderRequest()));
|
||
|
||
Assert.Multiple(() =>
|
||
{
|
||
Assert.That(singleBehaviorExecutor, Is.Not.Null);
|
||
Assert.That(twoBehaviorExecutor, Is.Not.Null);
|
||
Assert.That(singleBehaviorExecutor, Is.Not.SameAs(twoBehaviorExecutor));
|
||
Assert.That(
|
||
GetStreamPipelineExecutorValue(
|
||
streamBindings,
|
||
typeof(DispatcherCacheStreamRequest),
|
||
typeof(int),
|
||
1),
|
||
Is.SameAs(singleBehaviorExecutor));
|
||
Assert.That(
|
||
GetStreamPipelineExecutorValue(
|
||
streamBindings,
|
||
typeof(DispatcherStreamPipelineOrderRequest),
|
||
typeof(int),
|
||
2),
|
||
Is.SameAs(twoBehaviorExecutor));
|
||
});
|
||
}
|
||
|
||
/// <summary>
|
||
/// 验证复用缓存的 request pipeline executor 后,行为顺序和最终处理器顺序保持不变。
|
||
/// </summary>
|
||
[Test]
|
||
public async Task Dispatcher_Should_Preserve_Request_Pipeline_Order_When_Reusing_Cached_Executor()
|
||
{
|
||
DispatcherPipelineOrderState.Reset();
|
||
|
||
await _context!.SendRequestAsync(new DispatcherPipelineOrderCacheRequest());
|
||
var firstInvocation = DispatcherPipelineOrderState.Steps.ToArray();
|
||
|
||
DispatcherPipelineOrderState.Reset();
|
||
|
||
await _context.SendRequestAsync(new DispatcherPipelineOrderCacheRequest());
|
||
var secondInvocation = DispatcherPipelineOrderState.Steps.ToArray();
|
||
|
||
var expectedOrder = new[]
|
||
{
|
||
"Outer:Before",
|
||
"Inner:Before",
|
||
"Handler",
|
||
"Inner:After",
|
||
"Outer:After"
|
||
};
|
||
|
||
Assert.Multiple(() =>
|
||
{
|
||
Assert.That(firstInvocation, Is.EqualTo(expectedOrder));
|
||
Assert.That(secondInvocation, Is.EqualTo(expectedOrder));
|
||
});
|
||
}
|
||
|
||
/// <summary>
|
||
/// 验证复用缓存的 stream pipeline executor 后,行为顺序和最终处理器顺序保持不变。
|
||
/// </summary>
|
||
[Test]
|
||
public async Task Dispatcher_Should_Preserve_Stream_Pipeline_Order_When_Reusing_Cached_Executor()
|
||
{
|
||
DispatcherStreamPipelineOrderState.Reset();
|
||
|
||
await DrainAsync(_context!.CreateStream(new DispatcherStreamPipelineOrderRequest()));
|
||
var firstInvocation = DispatcherStreamPipelineOrderState.Steps.ToArray();
|
||
|
||
DispatcherStreamPipelineOrderState.Reset();
|
||
|
||
await DrainAsync(_context.CreateStream(new DispatcherStreamPipelineOrderRequest()));
|
||
var secondInvocation = DispatcherStreamPipelineOrderState.Steps.ToArray();
|
||
|
||
var expectedOrder = new[]
|
||
{
|
||
"Outer:Before",
|
||
"Inner:Before",
|
||
"Handler",
|
||
"Inner:After",
|
||
"Outer:After"
|
||
};
|
||
|
||
Assert.Multiple(() =>
|
||
{
|
||
Assert.That(firstInvocation, Is.EqualTo(expectedOrder));
|
||
Assert.That(secondInvocation, Is.EqualTo(expectedOrder));
|
||
});
|
||
}
|
||
|
||
/// <summary>
|
||
/// 验证缓存的 request pipeline executor 在重复分发时仍会重新解析 handler/behavior,
|
||
/// 并为当次实例重新注入当前架构上下文。
|
||
/// </summary>
|
||
[Test]
|
||
public async Task Dispatcher_Should_Reinject_Current_Context_When_Reusing_Cached_Request_Pipeline_Executor()
|
||
{
|
||
DispatcherPipelineContextRefreshState.Reset();
|
||
|
||
var requestBindings = GetCacheField("RequestDispatchBindings");
|
||
var firstContext = new ArchitectureContext(_container!);
|
||
var secondContext = new ArchitectureContext(_container!);
|
||
|
||
await firstContext.SendRequestAsync(new DispatcherPipelineContextRefreshRequest("first"));
|
||
|
||
var executorAfterFirstDispatch = GetRequestPipelineExecutorValue(
|
||
requestBindings,
|
||
typeof(DispatcherPipelineContextRefreshRequest),
|
||
typeof(int),
|
||
1);
|
||
|
||
await secondContext.SendRequestAsync(new DispatcherPipelineContextRefreshRequest("second"));
|
||
|
||
var executorAfterSecondDispatch = GetRequestPipelineExecutorValue(
|
||
requestBindings,
|
||
typeof(DispatcherPipelineContextRefreshRequest),
|
||
typeof(int),
|
||
1);
|
||
var behaviorSnapshots = DispatcherPipelineContextRefreshState.BehaviorSnapshots.ToArray();
|
||
var handlerSnapshots = DispatcherPipelineContextRefreshState.HandlerSnapshots.ToArray();
|
||
|
||
Assert.Multiple(() =>
|
||
{
|
||
Assert.That(executorAfterFirstDispatch, Is.Not.Null);
|
||
Assert.That(executorAfterSecondDispatch, Is.SameAs(executorAfterFirstDispatch));
|
||
|
||
Assert.That(behaviorSnapshots, Has.Length.EqualTo(2));
|
||
Assert.That(handlerSnapshots, Has.Length.EqualTo(2));
|
||
|
||
Assert.That(behaviorSnapshots[0].DispatchId, Is.EqualTo("first"));
|
||
Assert.That(behaviorSnapshots[0].Context, Is.SameAs(firstContext));
|
||
Assert.That(behaviorSnapshots[1].DispatchId, Is.EqualTo("second"));
|
||
Assert.That(behaviorSnapshots[1].Context, Is.SameAs(secondContext));
|
||
Assert.That(behaviorSnapshots[1].Context, Is.Not.SameAs(behaviorSnapshots[0].Context));
|
||
|
||
Assert.That(handlerSnapshots[0].DispatchId, Is.EqualTo("first"));
|
||
Assert.That(handlerSnapshots[0].Context, Is.SameAs(firstContext));
|
||
Assert.That(handlerSnapshots[1].DispatchId, Is.EqualTo("second"));
|
||
Assert.That(handlerSnapshots[1].Context, Is.SameAs(secondContext));
|
||
Assert.That(handlerSnapshots[1].Context, Is.Not.SameAs(handlerSnapshots[0].Context));
|
||
Assert.That(handlerSnapshots[1].InstanceId, Is.Not.EqualTo(handlerSnapshots[0].InstanceId));
|
||
});
|
||
}
|
||
|
||
/// <summary>
|
||
/// 验证缓存的 notification dispatch binding 在重复分发时仍会重新解析 handler,
|
||
/// 并为当次实例重新注入当前架构上下文。
|
||
/// </summary>
|
||
[Test]
|
||
public async Task Dispatcher_Should_Reinject_Current_Context_When_Reusing_Cached_Notification_Dispatch_Binding()
|
||
{
|
||
DispatcherNotificationContextRefreshState.Reset();
|
||
|
||
var notificationBindings = GetCacheField("NotificationDispatchBindings");
|
||
var firstContext = new ArchitectureContext(_container!);
|
||
var secondContext = new ArchitectureContext(_container!);
|
||
|
||
await firstContext.PublishAsync(new DispatcherNotificationContextRefreshNotification("first"));
|
||
|
||
var bindingAfterFirstDispatch = GetSingleKeyCacheValue(
|
||
notificationBindings,
|
||
typeof(DispatcherNotificationContextRefreshNotification));
|
||
|
||
await secondContext.PublishAsync(new DispatcherNotificationContextRefreshNotification("second"));
|
||
|
||
var bindingAfterSecondDispatch = GetSingleKeyCacheValue(
|
||
notificationBindings,
|
||
typeof(DispatcherNotificationContextRefreshNotification));
|
||
var handlerSnapshots = DispatcherNotificationContextRefreshState.HandlerSnapshots.ToArray();
|
||
|
||
Assert.Multiple(() =>
|
||
{
|
||
Assert.That(bindingAfterFirstDispatch, Is.Not.Null);
|
||
Assert.That(bindingAfterSecondDispatch, Is.SameAs(bindingAfterFirstDispatch));
|
||
|
||
Assert.That(handlerSnapshots, Has.Length.EqualTo(2));
|
||
Assert.That(handlerSnapshots[0].DispatchId, Is.EqualTo("first"));
|
||
Assert.That(handlerSnapshots[0].Context, Is.SameAs(firstContext));
|
||
Assert.That(handlerSnapshots[1].DispatchId, Is.EqualTo("second"));
|
||
Assert.That(handlerSnapshots[1].Context, Is.SameAs(secondContext));
|
||
Assert.That(handlerSnapshots[1].Context, Is.Not.SameAs(handlerSnapshots[0].Context));
|
||
Assert.That(handlerSnapshots[1].InstanceId, Is.Not.EqualTo(handlerSnapshots[0].InstanceId));
|
||
});
|
||
}
|
||
|
||
/// <summary>
|
||
/// 验证缓存的 stream dispatch binding 在重复建流时仍会重新解析 handler,
|
||
/// 并为当次实例重新注入当前架构上下文。
|
||
/// </summary>
|
||
[Test]
|
||
public async Task Dispatcher_Should_Reinject_Current_Context_When_Reusing_Cached_Stream_Dispatch_Binding()
|
||
{
|
||
DispatcherStreamContextRefreshState.Reset();
|
||
|
||
var streamBindings = GetCacheField("StreamDispatchBindings");
|
||
var firstContext = new ArchitectureContext(_container!);
|
||
var secondContext = new ArchitectureContext(_container!);
|
||
|
||
var firstStream = firstContext.CreateStream(new DispatcherStreamContextRefreshRequest("first"));
|
||
await DrainAsync(firstStream);
|
||
|
||
var bindingAfterFirstDispatch = GetPairCacheValue(
|
||
streamBindings,
|
||
typeof(DispatcherStreamContextRefreshRequest),
|
||
typeof(int));
|
||
|
||
var secondStream = secondContext.CreateStream(new DispatcherStreamContextRefreshRequest("second"));
|
||
await DrainAsync(secondStream);
|
||
|
||
var bindingAfterSecondDispatch = GetPairCacheValue(
|
||
streamBindings,
|
||
typeof(DispatcherStreamContextRefreshRequest),
|
||
typeof(int));
|
||
var handlerSnapshots = DispatcherStreamContextRefreshState.HandlerSnapshots.ToArray();
|
||
|
||
Assert.Multiple(() =>
|
||
{
|
||
Assert.That(bindingAfterFirstDispatch, Is.Not.Null);
|
||
Assert.That(bindingAfterSecondDispatch, Is.SameAs(bindingAfterFirstDispatch));
|
||
|
||
Assert.That(handlerSnapshots, Has.Length.EqualTo(2));
|
||
Assert.That(handlerSnapshots[0].DispatchId, Is.EqualTo("first"));
|
||
Assert.That(handlerSnapshots[0].Context, Is.SameAs(firstContext));
|
||
Assert.That(handlerSnapshots[1].DispatchId, Is.EqualTo("second"));
|
||
Assert.That(handlerSnapshots[1].Context, Is.SameAs(secondContext));
|
||
Assert.That(handlerSnapshots[1].Context, Is.Not.SameAs(handlerSnapshots[0].Context));
|
||
Assert.That(handlerSnapshots[1].InstanceId, Is.Not.EqualTo(handlerSnapshots[0].InstanceId));
|
||
});
|
||
}
|
||
|
||
/// <summary>
|
||
/// 验证缓存的 stream pipeline executor 在重复建流时仍会重新解析 behavior/handler,
|
||
/// 并为当次实例重新注入当前架构上下文。
|
||
/// </summary>
|
||
[Test]
|
||
public async Task Dispatcher_Should_Reinject_Current_Context_When_Reusing_Cached_Stream_Pipeline_Executor()
|
||
{
|
||
DispatcherStreamContextRefreshState.Reset();
|
||
|
||
var streamBindings = GetCacheField("StreamDispatchBindings");
|
||
var firstContext = new ArchitectureContext(_container!);
|
||
var secondContext = new ArchitectureContext(_container!);
|
||
|
||
await DrainAsync(firstContext.CreateStream(new DispatcherStreamContextRefreshRequest("first")));
|
||
|
||
var executorAfterFirstDispatch = GetStreamPipelineExecutorValue(
|
||
streamBindings,
|
||
typeof(DispatcherStreamContextRefreshRequest),
|
||
typeof(int),
|
||
1);
|
||
|
||
await DrainAsync(secondContext.CreateStream(new DispatcherStreamContextRefreshRequest("second")));
|
||
|
||
var executorAfterSecondDispatch = GetStreamPipelineExecutorValue(
|
||
streamBindings,
|
||
typeof(DispatcherStreamContextRefreshRequest),
|
||
typeof(int),
|
||
1);
|
||
var behaviorSnapshots = DispatcherStreamContextRefreshState.BehaviorSnapshots.ToArray();
|
||
var handlerSnapshots = DispatcherStreamContextRefreshState.HandlerSnapshots.ToArray();
|
||
|
||
Assert.Multiple(() =>
|
||
{
|
||
Assert.That(executorAfterFirstDispatch, Is.Not.Null);
|
||
Assert.That(executorAfterSecondDispatch, Is.SameAs(executorAfterFirstDispatch));
|
||
|
||
Assert.That(behaviorSnapshots, Has.Length.EqualTo(2));
|
||
Assert.That(handlerSnapshots, Has.Length.EqualTo(2));
|
||
|
||
Assert.That(behaviorSnapshots[0].DispatchId, Is.EqualTo("first"));
|
||
Assert.That(behaviorSnapshots[0].Context, Is.SameAs(firstContext));
|
||
Assert.That(behaviorSnapshots[1].DispatchId, Is.EqualTo("second"));
|
||
Assert.That(behaviorSnapshots[1].Context, Is.SameAs(secondContext));
|
||
Assert.That(behaviorSnapshots[1].Context, Is.Not.SameAs(behaviorSnapshots[0].Context));
|
||
|
||
Assert.That(handlerSnapshots[0].DispatchId, Is.EqualTo("first"));
|
||
Assert.That(handlerSnapshots[0].Context, Is.SameAs(firstContext));
|
||
Assert.That(handlerSnapshots[1].DispatchId, Is.EqualTo("second"));
|
||
Assert.That(handlerSnapshots[1].Context, Is.SameAs(secondContext));
|
||
Assert.That(handlerSnapshots[1].Context, Is.Not.SameAs(handlerSnapshots[0].Context));
|
||
Assert.That(handlerSnapshots[1].InstanceId, Is.Not.EqualTo(handlerSnapshots[0].InstanceId));
|
||
});
|
||
}
|
||
|
||
/// <summary>
|
||
/// 通过反射读取 dispatcher 的静态缓存对象。
|
||
/// </summary>
|
||
private static object GetCacheField(string fieldName)
|
||
{
|
||
var dispatcherType = GetDispatcherType();
|
||
var field = dispatcherType.GetField(
|
||
fieldName,
|
||
BindingFlags.NonPublic | BindingFlags.Static);
|
||
|
||
Assert.That(field, Is.Not.Null, $"Missing dispatcher cache field {fieldName}.");
|
||
|
||
return field!.GetValue(null)
|
||
?? throw new InvalidOperationException(
|
||
$"Dispatcher cache field {fieldName} returned null.");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 从架构上下文中解析当前延迟创建的 dispatcher 实例,便于验证其实例级热路径缓存。
|
||
/// </summary>
|
||
private static object GetDispatcherFromContext(ArchitectureContext context)
|
||
{
|
||
ArgumentNullException.ThrowIfNull(context);
|
||
|
||
var lazyRuntimeField = typeof(ArchitectureContext).GetField(
|
||
"_cqrsRuntime",
|
||
BindingFlags.Instance | BindingFlags.NonPublic);
|
||
|
||
Assert.That(lazyRuntimeField, Is.Not.Null, "Missing ArchitectureContext._cqrsRuntime field.");
|
||
|
||
var lazyRuntime = lazyRuntimeField!.GetValue(context)
|
||
?? throw new InvalidOperationException(
|
||
"ArchitectureContext._cqrsRuntime returned null.");
|
||
var lazyValueProperty = lazyRuntime.GetType().GetProperty(
|
||
"Value",
|
||
BindingFlags.Instance | BindingFlags.Public);
|
||
|
||
Assert.That(lazyValueProperty, Is.Not.Null, "Missing Lazy<ICqrsRuntime>.Value accessor.");
|
||
|
||
return lazyValueProperty!.GetValue(lazyRuntime)
|
||
?? throw new InvalidOperationException("Resolved CQRS runtime instance was null.");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 创建与当前 fixture 注册形状一致、但拥有独立 runtime 实例的冻结容器,
|
||
/// 用于验证 dispatcher 的实例级缓存不会跨容器共享。
|
||
/// </summary>
|
||
private static MicrosoftDiContainer CreateFrozenContainer()
|
||
{
|
||
var container = new MicrosoftDiContainer();
|
||
ConfigureDispatcherCacheFixture(container);
|
||
|
||
container.Freeze();
|
||
return container;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 组装当前 fixture 依赖的 CQRS 容器注册形状,确保默认上下文与隔离容器复用同一份装配基线。
|
||
/// </summary>
|
||
/// <param name="container">待补齐 CQRS 注册的目标容器。</param>
|
||
private static void ConfigureDispatcherCacheFixture(MicrosoftDiContainer container)
|
||
{
|
||
container.RegisterCqrsPipelineBehavior<DispatcherPipelineCacheBehavior>();
|
||
container.RegisterCqrsPipelineBehavior<DispatcherPipelineContextRefreshBehavior>();
|
||
container.RegisterCqrsPipelineBehavior<DispatcherPipelineOrderOuterBehavior>();
|
||
container.RegisterCqrsPipelineBehavior<DispatcherPipelineOrderInnerBehavior>();
|
||
container.RegisterCqrsStreamPipelineBehavior<DispatcherStreamPipelineCacheBehavior>();
|
||
container.RegisterCqrsStreamPipelineBehavior<DispatcherStreamPipelineContextRefreshBehavior>();
|
||
container.RegisterCqrsStreamPipelineBehavior<DispatcherStreamPipelineOrderOuterBehavior>();
|
||
container.RegisterCqrsStreamPipelineBehavior<DispatcherStreamPipelineOrderInnerBehavior>();
|
||
|
||
CqrsTestRuntime.RegisterHandlers(
|
||
container,
|
||
typeof(CqrsDispatcherCacheTests).Assembly,
|
||
typeof(ArchitectureContext).Assembly);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 清空本测试依赖的 dispatcher 静态缓存,避免跨用例共享进程级状态导致断言漂移。
|
||
/// </summary>
|
||
private static void ClearDispatcherCaches()
|
||
{
|
||
ClearCache(GetCacheField("NotificationDispatchBindings"));
|
||
ClearCache(GetCacheField("RequestDispatchBindings"));
|
||
ClearCache(GetCacheField("StreamDispatchBindings"));
|
||
}
|
||
|
||
/// <summary>
|
||
/// 读取单键缓存中当前保存的对象。
|
||
/// </summary>
|
||
private static object? GetSingleKeyCacheValue(object cache, Type key)
|
||
{
|
||
return InvokeInstanceMethod(cache, "GetValueOrDefaultForTesting", key);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 读取双键缓存中当前保存的对象。
|
||
/// </summary>
|
||
private static object? GetPairCacheValue(object cache, Type primaryType, Type secondaryType)
|
||
{
|
||
return InvokeInstanceMethod(cache, "GetValueOrDefaultForTesting", primaryType, secondaryType);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 读取指定 dispatcher 实例中当前保存的 request behavior presence 缓存项。
|
||
/// </summary>
|
||
private static object? GetRequestBehaviorPresenceCacheValue(object dispatcher, Type behaviorType)
|
||
{
|
||
var field = dispatcher.GetType().GetField(
|
||
"_requestBehaviorPresenceCache",
|
||
BindingFlags.Instance | BindingFlags.NonPublic);
|
||
|
||
Assert.That(field, Is.Not.Null, "Missing dispatcher request behavior presence cache field.");
|
||
|
||
var cache = field!.GetValue(dispatcher)
|
||
?? throw new InvalidOperationException(
|
||
"Dispatcher request behavior presence cache returned null.");
|
||
var tryGetValueMethod = cache.GetType().GetMethod(
|
||
"TryGetValue",
|
||
BindingFlags.Instance | BindingFlags.Public);
|
||
|
||
Assert.That(tryGetValueMethod, Is.Not.Null, "Missing ConcurrentDictionary.TryGetValue accessor.");
|
||
|
||
object?[] arguments = [behaviorType, null];
|
||
var found = (bool)(tryGetValueMethod!.Invoke(cache, arguments)
|
||
?? throw new InvalidOperationException(
|
||
"ConcurrentDictionary.TryGetValue returned null."));
|
||
return found ? arguments[1] : null;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 读取指定 dispatcher 实例中当前保存的 stream behavior presence 缓存项。
|
||
/// </summary>
|
||
private static object? GetStreamBehaviorPresenceCacheValue(object dispatcher, Type behaviorType)
|
||
{
|
||
var field = dispatcher.GetType().GetField(
|
||
"_streamBehaviorPresenceCache",
|
||
BindingFlags.Instance | BindingFlags.NonPublic);
|
||
|
||
Assert.That(field, Is.Not.Null, "Missing dispatcher stream behavior presence cache field.");
|
||
|
||
var cache = field!.GetValue(dispatcher)
|
||
?? throw new InvalidOperationException(
|
||
"Dispatcher stream behavior presence cache returned null.");
|
||
var tryGetValueMethod = cache.GetType().GetMethod(
|
||
"TryGetValue",
|
||
BindingFlags.Instance | BindingFlags.Public);
|
||
|
||
Assert.That(tryGetValueMethod, Is.Not.Null, "Missing ConcurrentDictionary.TryGetValue accessor.");
|
||
|
||
object?[] arguments = [behaviorType, null];
|
||
var found = (bool)(tryGetValueMethod!.Invoke(cache, arguments)
|
||
?? throw new InvalidOperationException(
|
||
"ConcurrentDictionary.TryGetValue returned null."));
|
||
return found ? arguments[1] : null;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 断言指定 dispatcher 上某个 request behavior presence 缓存项尚未建立。
|
||
/// </summary>
|
||
private static void AssertRequestBehaviorPresenceIsUnset(object dispatcher, Type behaviorType)
|
||
{
|
||
Assert.That(GetRequestBehaviorPresenceCacheValue(dispatcher, behaviorType), Is.Null);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 断言指定 dispatcher 上某个 request behavior presence 缓存项等于预期值。
|
||
/// </summary>
|
||
private static void AssertRequestBehaviorPresenceEquals(object dispatcher, Type behaviorType, bool expected)
|
||
{
|
||
Assert.That(GetRequestBehaviorPresenceCacheValue(dispatcher, behaviorType), Is.EqualTo(expected));
|
||
}
|
||
|
||
/// <summary>
|
||
/// 断言指定 dispatcher 上某个 stream behavior presence 缓存项尚未建立。
|
||
/// </summary>
|
||
private static void AssertStreamBehaviorPresenceIsUnset(object dispatcher, Type behaviorType)
|
||
{
|
||
Assert.That(GetStreamBehaviorPresenceCacheValue(dispatcher, behaviorType), Is.Null);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 断言指定 dispatcher 上某个 stream behavior presence 缓存项等于预期值。
|
||
/// </summary>
|
||
private static void AssertStreamBehaviorPresenceEquals(object dispatcher, Type behaviorType, bool expected)
|
||
{
|
||
Assert.That(GetStreamBehaviorPresenceCacheValue(dispatcher, behaviorType), Is.EqualTo(expected));
|
||
}
|
||
|
||
/// <summary>
|
||
/// 断言同一容器解析出的 dispatcher 会共享实例级缓存,而另一独立容器的 dispatcher 不会提前命中。
|
||
/// </summary>
|
||
private static void AssertSharedDispatcherCacheState(
|
||
object firstDispatcher,
|
||
object secondDispatcher,
|
||
object isolatedDispatcher,
|
||
object? zeroPipelinePresence,
|
||
object? onePipelinePresence)
|
||
{
|
||
Assert.Multiple(() =>
|
||
{
|
||
Assert.That(secondDispatcher, Is.SameAs(firstDispatcher));
|
||
Assert.That(zeroPipelinePresence, Is.EqualTo(false));
|
||
Assert.That(onePipelinePresence, Is.EqualTo(true));
|
||
AssertRequestBehaviorPresenceEquals(
|
||
secondDispatcher,
|
||
typeof(IPipelineBehavior<DispatcherCacheRequest, int>),
|
||
false);
|
||
AssertRequestBehaviorPresenceIsUnset(
|
||
isolatedDispatcher,
|
||
typeof(IPipelineBehavior<DispatcherCacheRequest, int>));
|
||
});
|
||
}
|
||
|
||
/// <summary>
|
||
/// 断言同一容器解析出的 dispatcher 会共享 stream 的实例级缓存,而另一独立容器的 dispatcher 不会提前命中。
|
||
/// </summary>
|
||
private static void AssertSharedStreamDispatcherCacheState(
|
||
object firstDispatcher,
|
||
object secondDispatcher,
|
||
object isolatedDispatcher,
|
||
object? zeroPipelinePresence,
|
||
object? twoPipelinePresence,
|
||
Type zeroPipelineBehaviorType,
|
||
bool expectedZeroPipelinePresence,
|
||
bool expectedTwoPipelinePresence)
|
||
{
|
||
Assert.Multiple(() =>
|
||
{
|
||
Assert.That(secondDispatcher, Is.SameAs(firstDispatcher));
|
||
Assert.That(zeroPipelinePresence, Is.EqualTo(expectedZeroPipelinePresence));
|
||
Assert.That(twoPipelinePresence, Is.EqualTo(expectedTwoPipelinePresence));
|
||
AssertStreamBehaviorPresenceEquals(secondDispatcher, zeroPipelineBehaviorType, expectedZeroPipelinePresence);
|
||
AssertStreamBehaviorPresenceIsUnset(isolatedDispatcher, zeroPipelineBehaviorType);
|
||
});
|
||
}
|
||
|
||
/// <summary>
|
||
/// 读取 request dispatch binding 中指定行为数量的 pipeline executor 缓存项。
|
||
/// </summary>
|
||
/// <param name="requestBindings">dispatcher 内部的 request binding 缓存对象。</param>
|
||
/// <param name="requestType">要读取的请求运行时类型。</param>
|
||
/// <param name="responseType">要读取的响应运行时类型。</param>
|
||
/// <param name="behaviorCount">目标 executor 对应的行为数量。</param>
|
||
/// <returns>已缓存的 executor;若 binding 或 executor 尚未建立则返回 <see langword="null" />。</returns>
|
||
private static object? GetRequestPipelineExecutorValue(
|
||
object requestBindings,
|
||
Type requestType,
|
||
Type responseType,
|
||
int behaviorCount)
|
||
{
|
||
var binding = GetRequestDispatchBindingValue(requestBindings, requestType, responseType);
|
||
return binding is null
|
||
? null
|
||
: InvokeInstanceMethod(binding, "GetPipelineExecutorForTesting", behaviorCount);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 读取 stream dispatch binding 中指定行为数量的 pipeline executor 缓存项。
|
||
/// </summary>
|
||
/// <param name="streamBindings">dispatcher 内部的 stream binding 缓存对象。</param>
|
||
/// <param name="requestType">要读取的流式请求运行时类型。</param>
|
||
/// <param name="responseType">要读取的响应元素类型。</param>
|
||
/// <param name="behaviorCount">目标 executor 对应的行为数量。</param>
|
||
/// <returns>已缓存的 executor;若 binding 或 executor 尚未建立则返回 <see langword="null" />。</returns>
|
||
private static object? GetStreamPipelineExecutorValue(
|
||
object streamBindings,
|
||
Type requestType,
|
||
Type responseType,
|
||
int behaviorCount)
|
||
{
|
||
var binding = GetStreamDispatchBindingValue(streamBindings, requestType, responseType);
|
||
return binding is null
|
||
? null
|
||
: InvokeInstanceMethod(binding, "GetPipelineExecutorForTesting", behaviorCount);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 调用缓存实例上的无参清理方法。
|
||
/// </summary>
|
||
private static void ClearCache(object cache)
|
||
{
|
||
_ = InvokeInstanceMethod(cache, "Clear");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 调用缓存对象上的实例方法。
|
||
/// </summary>
|
||
private static object? InvokeInstanceMethod(object target, string methodName, params object[] arguments)
|
||
{
|
||
var method = target.GetType().GetMethod(
|
||
methodName,
|
||
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
|
||
|
||
Assert.That(method, Is.Not.Null, $"Missing cache method {target.GetType().FullName}.{methodName}.");
|
||
|
||
return method!.Invoke(target, arguments);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 读取指定请求/响应类型对对应的强类型 request dispatch binding。
|
||
/// </summary>
|
||
/// <param name="requestBindings">dispatcher 内部的 request binding 缓存对象。</param>
|
||
/// <param name="requestType">要读取的请求运行时类型。</param>
|
||
/// <param name="responseType">要读取的响应运行时类型。</param>
|
||
/// <returns>强类型 binding;若缓存尚未建立则返回 <see langword="null" />。</returns>
|
||
private static object? GetRequestDispatchBindingValue(object requestBindings, Type requestType, Type responseType)
|
||
{
|
||
var bindingBox = GetPairCacheValue(requestBindings, requestType, responseType);
|
||
if (bindingBox is null)
|
||
{
|
||
return null;
|
||
}
|
||
|
||
var method = bindingBox.GetType().GetMethod(
|
||
"Get",
|
||
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
|
||
|
||
Assert.That(method, Is.Not.Null, $"Missing request binding accessor on {bindingBox.GetType().FullName}.");
|
||
|
||
return method!
|
||
.MakeGenericMethod(responseType)
|
||
.Invoke(bindingBox, Array.Empty<object>());
|
||
}
|
||
|
||
/// <summary>
|
||
/// 读取指定流式请求/响应类型对对应的强类型 stream dispatch binding。
|
||
/// </summary>
|
||
/// <param name="streamBindings">dispatcher 内部的 stream binding 缓存对象。</param>
|
||
/// <param name="requestType">要读取的流式请求运行时类型。</param>
|
||
/// <param name="responseType">要读取的响应元素类型。</param>
|
||
/// <returns>强类型 binding;若缓存尚未建立则返回 <see langword="null" />。</returns>
|
||
private static object? GetStreamDispatchBindingValue(object streamBindings, Type requestType, Type responseType)
|
||
{
|
||
var bindingBox = GetPairCacheValue(streamBindings, requestType, responseType);
|
||
if (bindingBox is null)
|
||
{
|
||
return null;
|
||
}
|
||
|
||
var method = bindingBox.GetType().GetMethod(
|
||
"Get",
|
||
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
|
||
|
||
Assert.That(method, Is.Not.Null, $"Missing stream binding accessor on {bindingBox.GetType().FullName}.");
|
||
|
||
return method!
|
||
.MakeGenericMethod(responseType)
|
||
.Invoke(bindingBox, Array.Empty<object>());
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取 CQRS dispatcher 运行时类型。
|
||
/// </summary>
|
||
private static Type GetDispatcherType()
|
||
{
|
||
return typeof(CqrsReflectionFallbackAttribute).Assembly
|
||
.GetType("GFramework.Cqrs.Internal.CqrsDispatcher", throwOnError: true)!;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 消费整个异步流,确保建流路径被真实执行。
|
||
/// </summary>
|
||
private static async Task DrainAsync<T>(IAsyncEnumerable<T> stream)
|
||
{
|
||
await foreach (var _ in stream.ConfigureAwait(false))
|
||
{
|
||
}
|
||
}
|
||
}
|