mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-05-07 00:39:00 +08:00
feat(cqrs): 添加CQRS扩展方法兼容层和日志工厂解析器
- 新增ContextAwareMediatorCommandExtensions提供命令扩展方法兼容性支持 - 新增ContextAwareMediatorQueryExtensions提供查询扩展方法兼容性支持 - 添加LoggerFactoryResolver实现全局日志工厂访问入口 - 实现TypeForwarders将核心类型转发到正确程序集 - 添加MediatorCompatibilityDeprecationTests验证弃用策略 - 扩展LoggerFactoryTests覆盖并发初始化和回退逻辑 - 迁移CommandBase到Core.Cqrs.Command命名空间 - 移动LoggingBehavior到GFramework.Cqrs.Cqrs.Behaviors - 添加AbstractStreamQueryHandler支持流式查询处理 - 创建NotificationBase提供通知基类实现
This commit is contained in:
parent
18337c5995
commit
005c32d84f
@ -10,19 +10,42 @@ namespace GFramework.Core.Abstractions.Logging;
|
||||
/// </remarks>
|
||||
public static class LoggerFactoryResolver
|
||||
{
|
||||
private const string DefaultProviderTypeName =
|
||||
private static readonly object ProviderLock = new();
|
||||
|
||||
private static string DefaultProviderTypeName =
|
||||
"GFramework.Core.Logging.ConsoleLoggerFactoryProvider, GFramework.Core";
|
||||
|
||||
private static ILoggerFactoryProvider? _provider;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置当前日志工厂提供程序。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 读取与赋值都会通过同一把锁串行化,确保并发调用方观察到确定的 provider 引用。
|
||||
/// 当调用方未显式赋值时,会在首次访问时尝试解析默认实现;若解析失败,则退回静默 provider。
|
||||
/// </remarks>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// 当赋值为 <see langword="null" /> 时抛出。
|
||||
/// </exception>
|
||||
public static ILoggerFactoryProvider Provider
|
||||
{
|
||||
get => field ??= CreateDefaultProvider();
|
||||
set => field = value ?? throw new ArgumentNullException(nameof(value));
|
||||
get
|
||||
{
|
||||
lock (ProviderLock)
|
||||
{
|
||||
_provider ??= CreateDefaultProvider();
|
||||
return _provider;
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
var provider = value ?? throw new ArgumentNullException(nameof(value));
|
||||
|
||||
lock (ProviderLock)
|
||||
{
|
||||
_provider = provider;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -39,11 +62,19 @@ public static class LoggerFactoryResolver
|
||||
|
||||
private static ILoggerFactoryProvider CreateDefaultProvider()
|
||||
{
|
||||
if (Type.GetType(DefaultProviderTypeName, throwOnError: false) is { } providerType &&
|
||||
Activator.CreateInstance(providerType) is ILoggerFactoryProvider provider)
|
||||
try
|
||||
{
|
||||
provider.MinLevel = LogLevel.Info;
|
||||
return provider;
|
||||
if (Type.GetType(DefaultProviderTypeName, throwOnError: false) is { } providerType &&
|
||||
Activator.CreateInstance(providerType) is ILoggerFactoryProvider provider)
|
||||
{
|
||||
provider.MinLevel = LogLevel.Info;
|
||||
return provider;
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// The default provider is optional. Any load or activation failure must degrade to the silent provider so
|
||||
// abstractions-only hosts can continue bootstrapping without the concrete logging assembly.
|
||||
}
|
||||
|
||||
return new SilentLoggerFactoryProvider();
|
||||
|
||||
@ -0,0 +1,76 @@
|
||||
using GFramework.Core.Cqrs.Command;
|
||||
using GFramework.Core.Cqrs.Notification;
|
||||
using GFramework.Core.Cqrs.Query;
|
||||
using GFramework.Core.Cqrs.Request;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs.Command;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs.Notification;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs.Query;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs.Request;
|
||||
|
||||
namespace GFramework.Core.Tests.Cqrs;
|
||||
|
||||
/// <summary>
|
||||
/// 锁定 CQRS 基础消息类型在 runtime 拆分后的公开命名空间与程序集兼容性。
|
||||
/// </summary>
|
||||
[TestFixture]
|
||||
public sealed class CqrsPublicNamespaceCompatibilityTests
|
||||
{
|
||||
/// <summary>
|
||||
/// 验证基础消息类型继续暴露在历史 Core.Cqrs 命名空间,同时由独立 runtime 程序集承载实现。
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void Base_Message_Types_Should_Remain_In_Legacy_Namespaces_While_Living_In_Runtime_Assembly()
|
||||
{
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
AssertLegacyType(typeof(CommandBase<TestCommandInput, Unit>), "GFramework.Core.Cqrs.Command");
|
||||
AssertLegacyType(typeof(QueryBase<TestQueryInput, string>), "GFramework.Core.Cqrs.Query");
|
||||
AssertLegacyType(typeof(RequestBase<TestRequestInput, string>), "GFramework.Core.Cqrs.Request");
|
||||
AssertLegacyType(typeof(NotificationBase<TestNotificationInput>), "GFramework.Core.Cqrs.Notification");
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证旧的 GFramework.Core 程序集限定名仍可解析到迁移后的 runtime 实现类型。
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void GFramework_Core_Assembly_Should_Forward_Legacy_Base_Types_To_Runtime_Assembly()
|
||||
{
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
AssertForwardedType("GFramework.Core.Cqrs.Command.CommandBase`2, GFramework.Core");
|
||||
AssertForwardedType("GFramework.Core.Cqrs.Query.QueryBase`2, GFramework.Core");
|
||||
AssertForwardedType("GFramework.Core.Cqrs.Request.RequestBase`2, GFramework.Core");
|
||||
AssertForwardedType("GFramework.Core.Cqrs.Notification.NotificationBase`1, GFramework.Core");
|
||||
});
|
||||
}
|
||||
|
||||
private static void AssertLegacyType(Type type, string expectedNamespace)
|
||||
{
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(type.Namespace, Is.EqualTo(expectedNamespace));
|
||||
Assert.That(type.Assembly.GetName().Name, Is.EqualTo("GFramework.Cqrs"));
|
||||
});
|
||||
}
|
||||
|
||||
private static void AssertForwardedType(string assemblyQualifiedTypeName)
|
||||
{
|
||||
var resolvedType = Type.GetType(assemblyQualifiedTypeName, throwOnError: true);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(resolvedType, Is.Not.Null);
|
||||
Assert.That(resolvedType!.Assembly.GetName().Name, Is.EqualTo("GFramework.Cqrs"));
|
||||
});
|
||||
}
|
||||
|
||||
private sealed record TestCommandInput : ICommandInput;
|
||||
|
||||
private sealed record TestQueryInput : IQueryInput;
|
||||
|
||||
private sealed record TestRequestInput : IRequestInput;
|
||||
|
||||
private sealed record TestNotificationInput : INotificationInput;
|
||||
}
|
||||
@ -38,10 +38,10 @@ public class MediatorCompatibilityDeprecationTests
|
||||
"Use GFramework.Core.Extensions.ContextAwareCqrsExtensions instead.");
|
||||
AssertLegacyType(
|
||||
typeof(ContextAwareMediatorCommandExtensions),
|
||||
"Use GFramework.Core.Extensions.ContextAwareCqrsCommandExtensions instead.");
|
||||
"Use GFramework.Cqrs.Extensions.ContextAwareCqrsCommandExtensions instead.");
|
||||
AssertLegacyType(
|
||||
typeof(ContextAwareMediatorQueryExtensions),
|
||||
"Use GFramework.Core.Extensions.ContextAwareCqrsQueryExtensions instead.");
|
||||
"Use GFramework.Cqrs.Extensions.ContextAwareCqrsQueryExtensions instead.");
|
||||
AssertLegacyType(
|
||||
typeof(MediatorCoroutineExtensions),
|
||||
"Use GFramework.Core.Coroutine.Extensions.CqrsCoroutineExtensions instead.");
|
||||
|
||||
@ -1,18 +1,19 @@
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using GFramework.Core.Abstractions.Logging;
|
||||
using GFramework.Core.Logging;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace GFramework.Core.Tests.Logging;
|
||||
|
||||
/// <summary>
|
||||
/// 测试LoggerFactory相关功能的测试类
|
||||
/// 测试 LoggerFactory 相关功能的测试类。
|
||||
/// </summary>
|
||||
[TestFixture]
|
||||
[NonParallelizable]
|
||||
public class LoggerFactoryTests
|
||||
{
|
||||
/// <summary>
|
||||
/// 测试ConsoleLoggerFactory的GetLogger方法是否返回ConsoleLogger实例
|
||||
/// 测试 ConsoleLoggerFactory 的 GetLogger 方法是否返回 ConsoleLogger 实例。
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void ConsoleLoggerFactory_GetLogger_ShouldReturnConsoleLogger()
|
||||
@ -26,7 +27,7 @@ public class LoggerFactoryTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试ConsoleLoggerFactory使用不同名称获取不同的logger实例
|
||||
/// 测试 ConsoleLoggerFactory 使用不同名称获取不同的 logger 实例。
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void ConsoleLoggerFactory_GetLogger_WithDifferentNames_ShouldReturnDifferentLoggers()
|
||||
@ -40,7 +41,7 @@ public class LoggerFactoryTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试ConsoleLoggerFactory使用默认最小级别时的行为(默认为Info级别)
|
||||
/// 测试 ConsoleLoggerFactory 使用默认最小级别时的行为。
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void ConsoleLoggerFactory_GetLogger_WithDefaultMinLevel_ShouldUseInfo()
|
||||
@ -51,7 +52,6 @@ public class LoggerFactoryTests
|
||||
var stringWriter = new StringWriter();
|
||||
var testLogger = new ConsoleLogger("TestLogger", LogLevel.Info, stringWriter, false);
|
||||
|
||||
// 验证Debug消息不会被记录,但Info消息会被记录
|
||||
testLogger.Debug("Debug message");
|
||||
testLogger.Info("Info message");
|
||||
|
||||
@ -61,7 +61,7 @@ public class LoggerFactoryTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试ConsoleLoggerFactoryProvider创建logger时使用提供者的最小级别设置
|
||||
/// 测试 ConsoleLoggerFactoryProvider 创建 logger 时使用提供者的最小级别设置。
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void ConsoleLoggerFactoryProvider_CreateLogger_ShouldReturnLoggerWithProviderMinLevel()
|
||||
@ -72,7 +72,6 @@ public class LoggerFactoryTests
|
||||
var stringWriter = new StringWriter();
|
||||
var testLogger = new ConsoleLogger("TestLogger", LogLevel.Debug, stringWriter, false);
|
||||
|
||||
// 验证Debug消息会被记录,但Trace消息不会被记录
|
||||
testLogger.Debug("Debug message");
|
||||
testLogger.Trace("Trace message");
|
||||
|
||||
@ -82,7 +81,7 @@ public class LoggerFactoryTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试ConsoleLoggerFactoryProvider创建logger时使用提供的名称
|
||||
/// 测试 ConsoleLoggerFactoryProvider 创建 logger 时使用提供的名称。
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void ConsoleLoggerFactoryProvider_CreateLogger_ShouldUseProvidedName()
|
||||
@ -94,7 +93,7 @@ public class LoggerFactoryTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试LoggerFactoryResolver的Provider属性是否有默认值
|
||||
/// 测试 LoggerFactoryResolver 的 Provider 属性是否有默认值。
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void LoggerFactoryResolver_Provider_ShouldHaveDefaultValue()
|
||||
@ -104,7 +103,7 @@ public class LoggerFactoryTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试LoggerFactoryResolver的Provider属性可以被更改
|
||||
/// 测试 LoggerFactoryResolver 的 Provider 属性可以被更改。
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void LoggerFactoryResolver_Provider_CanBeChanged()
|
||||
@ -120,7 +119,7 @@ public class LoggerFactoryTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试LoggerFactoryResolver的MinLevel属性是否有默认值
|
||||
/// 测试 LoggerFactoryResolver 的 MinLevel 属性是否有默认值。
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void LoggerFactoryResolver_MinLevel_ShouldHaveDefaultValue()
|
||||
@ -129,7 +128,7 @@ public class LoggerFactoryTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试LoggerFactoryResolver的MinLevel属性可以被更改
|
||||
/// 测试 LoggerFactoryResolver 的 MinLevel 属性可以被更改。
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void LoggerFactoryResolver_MinLevel_CanBeChanged()
|
||||
@ -144,7 +143,7 @@ public class LoggerFactoryTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试ConsoleLoggerFactoryProvider的MinLevel属性是否有默认值
|
||||
/// 测试 ConsoleLoggerFactoryProvider 的 MinLevel 属性是否有默认值。
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void ConsoleLoggerFactoryProvider_MinLevel_ShouldHaveDefaultValue()
|
||||
@ -155,7 +154,7 @@ public class LoggerFactoryTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试ConsoleLoggerFactoryProvider的MinLevel属性可以被更改
|
||||
/// 测试 ConsoleLoggerFactoryProvider 的 MinLevel 属性可以被更改。
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void ConsoleLoggerFactoryProvider_MinLevel_CanBeChanged()
|
||||
@ -168,7 +167,7 @@ public class LoggerFactoryTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试LoggerFactoryResolver的Provider创建logger时使用提供者设置
|
||||
/// 测试 LoggerFactoryResolver 的 Provider 创建 logger 时使用提供者设置。
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void LoggerFactoryResolver_Provider_CreateLogger_ShouldUseProviderSettings()
|
||||
@ -183,7 +182,6 @@ public class LoggerFactoryTests
|
||||
var stringWriter = new StringWriter();
|
||||
var testLogger = new ConsoleLogger("TestLogger", LogLevel.Warning, stringWriter, false);
|
||||
|
||||
// 验证Warn消息会被记录,但Info消息不会被记录
|
||||
testLogger.Warn("Warn message");
|
||||
testLogger.Info("Info message");
|
||||
|
||||
@ -195,7 +193,7 @@ public class LoggerFactoryTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试LoggerFactoryResolver的MinLevel属性影响新创建的logger
|
||||
/// 测试 LoggerFactoryResolver 的 MinLevel 属性影响新创建的 logger。
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void LoggerFactoryResolver_MinLevel_AffectsNewLoggers()
|
||||
@ -210,7 +208,6 @@ public class LoggerFactoryTests
|
||||
var stringWriter = new StringWriter();
|
||||
var testLogger = new ConsoleLogger("TestLogger", LogLevel.Error, stringWriter, false);
|
||||
|
||||
// 验证Error消息会被记录,但Warn消息不会被记录
|
||||
testLogger.Error("Error message");
|
||||
testLogger.Warn("Warn message");
|
||||
|
||||
@ -222,7 +219,93 @@ public class LoggerFactoryTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试ConsoleLoggerFactory创建的多个logger实例是独立的
|
||||
/// 验证默认 provider 激活失败时会回退到静默 provider。
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void
|
||||
LoggerFactoryResolver_Provider_Should_Fall_Back_To_SilentProvider_When_DefaultProvider_Activation_Fails()
|
||||
{
|
||||
var originalProvider = LoggerFactoryResolver.Provider;
|
||||
var originalTypeName = GetDefaultProviderTypeName();
|
||||
|
||||
try
|
||||
{
|
||||
ResetProvider();
|
||||
SetDefaultProviderTypeName(typeof(ThrowingLoggerFactoryProvider).AssemblyQualifiedName!);
|
||||
|
||||
var provider = LoggerFactoryResolver.Provider;
|
||||
var logger = provider.CreateLogger("Fallback");
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(provider.GetType().Name, Is.EqualTo("SilentLoggerFactoryProvider"));
|
||||
Assert.That(provider.MinLevel, Is.EqualTo(LogLevel.Info));
|
||||
Assert.That(logger.IsEnabledForLevel(LogLevel.Error), Is.False);
|
||||
});
|
||||
}
|
||||
finally
|
||||
{
|
||||
SetDefaultProviderTypeName(originalTypeName);
|
||||
LoggerFactoryResolver.Provider = originalProvider;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证并发首次访问默认 provider 时只会创建一个实例,并向所有调用方返回相同引用。
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task
|
||||
LoggerFactoryResolver_Provider_Should_Create_A_Single_Default_Instance_When_Accessed_Concurrently()
|
||||
{
|
||||
var originalProvider = LoggerFactoryResolver.Provider;
|
||||
var originalTypeName = GetDefaultProviderTypeName();
|
||||
|
||||
try
|
||||
{
|
||||
BlockingLoggerFactoryProvider.Reset();
|
||||
ResetProvider();
|
||||
SetDefaultProviderTypeName(typeof(BlockingLoggerFactoryProvider).AssemblyQualifiedName!);
|
||||
|
||||
var startGate = new ManualResetEventSlim(false);
|
||||
var tasks = Enumerable.Range(0, 8)
|
||||
.Select(_ => Task.Run(() =>
|
||||
{
|
||||
startGate.Wait();
|
||||
return LoggerFactoryResolver.Provider;
|
||||
}))
|
||||
.ToArray();
|
||||
|
||||
startGate.Set();
|
||||
|
||||
Assert.That(
|
||||
SpinWait.SpinUntil(
|
||||
() => BlockingLoggerFactoryProvider.ConstructionCount >= 1,
|
||||
TimeSpan.FromSeconds(2)),
|
||||
Is.True,
|
||||
"The test provider should start construction after concurrent access begins.");
|
||||
|
||||
BlockingLoggerFactoryProvider.ReleaseConstruction();
|
||||
|
||||
var providers = await Task.WhenAll(tasks);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(BlockingLoggerFactoryProvider.ConstructionCount, Is.EqualTo(1));
|
||||
Assert.That(providers.Distinct().Count(), Is.EqualTo(1));
|
||||
Assert.That(LoggerFactoryResolver.Provider, Is.SameAs(providers[0]));
|
||||
});
|
||||
}
|
||||
finally
|
||||
{
|
||||
BlockingLoggerFactoryProvider.ReleaseConstruction();
|
||||
BlockingLoggerFactoryProvider.Reset();
|
||||
SetDefaultProviderTypeName(originalTypeName);
|
||||
LoggerFactoryResolver.Provider = originalProvider;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试 ConsoleLoggerFactory 创建的多个 logger 实例是独立的。
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void ConsoleLoggerFactory_MultipleLoggers_ShouldBeIndependent()
|
||||
@ -236,7 +319,7 @@ public class LoggerFactoryTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试ConsoleLoggerFactoryProvider的MinLevel不会影响已创建的logger
|
||||
/// 测试 ConsoleLoggerFactoryProvider 的 MinLevel 不会影响已创建的 logger。
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void ConsoleLoggerFactoryProvider_MinLevel_DoesNotAffectCreatedLogger()
|
||||
@ -247,7 +330,6 @@ public class LoggerFactoryTests
|
||||
var stringWriter = new StringWriter();
|
||||
var testLogger = new ConsoleLogger("TestLogger", LogLevel.Error, stringWriter, false);
|
||||
|
||||
// 验证Error和Fatal消息都会被记录
|
||||
testLogger.Error("Error message");
|
||||
testLogger.Fatal("Fatal message");
|
||||
|
||||
@ -255,4 +337,114 @@ public class LoggerFactoryTests
|
||||
Assert.That(output, Does.Contain("Error message"));
|
||||
Assert.That(output, Does.Contain("Fatal message"));
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetDefaultProviderTypeName()
|
||||
{
|
||||
return (string)GetResolverField("DefaultProviderTypeName").GetValue(null)!;
|
||||
}
|
||||
|
||||
private static void SetDefaultProviderTypeName(string typeName)
|
||||
{
|
||||
GetResolverField("DefaultProviderTypeName").SetValue(null, typeName);
|
||||
}
|
||||
|
||||
private static void ResetProvider()
|
||||
{
|
||||
GetResolverField("_provider").SetValue(null, null);
|
||||
}
|
||||
|
||||
private static FieldInfo GetResolverField(string fieldName)
|
||||
{
|
||||
return typeof(LoggerFactoryResolver).GetField(
|
||||
fieldName,
|
||||
BindingFlags.NonPublic | BindingFlags.Static)
|
||||
?? throw new InvalidOperationException(
|
||||
$"Failed to locate LoggerFactoryResolver.{fieldName}.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 用于触发默认 provider 激活失败回退路径的测试桩。
|
||||
/// </summary>
|
||||
public sealed class ThrowingLoggerFactoryProvider : ILoggerFactoryProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// 初始化一个始终抛出异常的 provider。
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidOperationException">始终抛出,用于覆盖回退路径。</exception>
|
||||
public ThrowingLoggerFactoryProvider()
|
||||
{
|
||||
throw new InvalidOperationException("Simulated provider activation failure.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置最小日志级别。
|
||||
/// </summary>
|
||||
public LogLevel MinLevel { get; set; } = LogLevel.Info;
|
||||
|
||||
/// <summary>
|
||||
/// 创建日志器。
|
||||
/// </summary>
|
||||
/// <param name="name">日志器名称。</param>
|
||||
/// <returns>该测试桩永远不会成功创建日志器。</returns>
|
||||
/// <exception cref="NotSupportedException">始终抛出,因为该方法不应被调用。</exception>
|
||||
public ILogger CreateLogger(string name)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 用于验证并发首次初始化路径只创建单个 provider 实例的测试桩。
|
||||
/// </summary>
|
||||
public sealed class BlockingLoggerFactoryProvider : ILoggerFactoryProvider
|
||||
{
|
||||
private static int _constructionCount;
|
||||
private static ManualResetEventSlim _constructionGate = new(false);
|
||||
|
||||
/// <summary>
|
||||
/// 初始化一个会阻塞构造完成的 provider,用于放大并发首次访问竞争窗口。
|
||||
/// </summary>
|
||||
public BlockingLoggerFactoryProvider()
|
||||
{
|
||||
Interlocked.Increment(ref _constructionCount);
|
||||
_constructionGate.Wait(TimeSpan.FromSeconds(5));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取已经发生的构造次数。
|
||||
/// </summary>
|
||||
public static int ConstructionCount => Volatile.Read(ref _constructionCount);
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置最小日志级别。
|
||||
/// </summary>
|
||||
public LogLevel MinLevel { get; set; } = LogLevel.Info;
|
||||
|
||||
/// <summary>
|
||||
/// 创建测试日志器。
|
||||
/// </summary>
|
||||
/// <param name="name">日志器名称。</param>
|
||||
/// <returns>带有当前最小级别设置的控制台日志器。</returns>
|
||||
public ILogger CreateLogger(string name)
|
||||
{
|
||||
return new ConsoleLogger(name, MinLevel, TextWriter.Null, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重置该测试桩的并发观测状态。
|
||||
/// </summary>
|
||||
public static void Reset()
|
||||
{
|
||||
_constructionGate = new ManualResetEventSlim(false);
|
||||
Interlocked.Exchange(ref _constructionCount, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放当前被阻塞的 provider 构造过程。
|
||||
/// </summary>
|
||||
public static void ReleaseConstruction()
|
||||
{
|
||||
_constructionGate.Set();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -12,7 +12,7 @@ namespace GFramework.Core.Extensions;
|
||||
/// </summary>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
[Obsolete(
|
||||
"Use GFramework.Core.Extensions.ContextAwareCqrsCommandExtensions instead. This compatibility alias will be removed in a future major version.")]
|
||||
"Use GFramework.Cqrs.Extensions.ContextAwareCqrsCommandExtensions instead. This compatibility alias will be removed in a future major version.")]
|
||||
public static class ContextAwareMediatorCommandExtensions
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@ -12,7 +12,7 @@ namespace GFramework.Core.Extensions;
|
||||
/// </summary>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
[Obsolete(
|
||||
"Use GFramework.Core.Extensions.ContextAwareCqrsQueryExtensions instead. This compatibility alias will be removed in a future major version.")]
|
||||
"Use GFramework.Cqrs.Extensions.ContextAwareCqrsQueryExtensions instead. This compatibility alias will be removed in a future major version.")]
|
||||
public static class ContextAwareMediatorQueryExtensions
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@ -1,4 +1,12 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using GFramework.Core.Abstractions.Logging;
|
||||
using GFramework.Core.Cqrs.Command;
|
||||
using GFramework.Core.Cqrs.Notification;
|
||||
using GFramework.Core.Cqrs.Query;
|
||||
using GFramework.Core.Cqrs.Request;
|
||||
|
||||
[assembly: TypeForwardedTo(typeof(LoggerFactoryResolver))]
|
||||
[assembly: TypeForwardedTo(typeof(CommandBase<,>))]
|
||||
[assembly: TypeForwardedTo(typeof(QueryBase<,>))]
|
||||
[assembly: TypeForwardedTo(typeof(RequestBase<,>))]
|
||||
[assembly: TypeForwardedTo(typeof(NotificationBase<>))]
|
||||
|
||||
@ -13,19 +13,23 @@
|
||||
|
||||
using GFramework.Cqrs.Abstractions.Cqrs.Command;
|
||||
|
||||
namespace GFramework.Cqrs.Command;
|
||||
namespace GFramework.Core.Cqrs.Command;
|
||||
|
||||
/// <summary>
|
||||
/// 表示一个基础命令类,用于处理带有输入和响应的命令模式实现。
|
||||
/// 该类实现了 ICommand<TResponse> 接口,提供了通用的命令结构。
|
||||
/// 为携带输入模型的 CQRS 命令提供统一基类。
|
||||
/// </summary>
|
||||
/// <typeparam name="TInput">命令输入数据的类型</typeparam>
|
||||
/// <typeparam name="TResponse">命令执行后返回结果的类型</typeparam>
|
||||
/// <param name="input">命令执行所需的输入数据</param>
|
||||
public abstract class CommandBase<TInput, TResponse>(TInput input) : ICommand<TResponse> where TInput : ICommandInput
|
||||
/// <typeparam name="TInput">命令输入类型。</typeparam>
|
||||
/// <typeparam name="TResponse">命令响应类型。</typeparam>
|
||||
/// <param name="input">命令执行所需的输入对象。</param>
|
||||
/// <remarks>
|
||||
/// 该类型继续保留在历史公开命名空间中,以避免调用方因 runtime 程序集拆分而批量修改继承层次。
|
||||
/// 具体实现现由 <c>GFramework.Cqrs</c> 程序集承载,并通过 type forward 维持旧程序集兼容性。
|
||||
/// </remarks>
|
||||
public abstract class CommandBase<TInput, TResponse>(TInput input) : ICommand<TResponse>
|
||||
where TInput : ICommandInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取命令的输入数据。
|
||||
/// 获取命令执行时携带的输入对象。
|
||||
/// </summary>
|
||||
public TInput Input => input;
|
||||
}
|
||||
|
||||
@ -22,8 +22,8 @@ namespace GFramework.Cqrs.Cqrs.Behaviors;
|
||||
/// <typeparam name="TRequest">请求类型。</typeparam>
|
||||
/// <typeparam name="TResponse">响应类型。</typeparam>
|
||||
/// <remarks>
|
||||
/// 该行为保留在 <c>GFramework.Core.Cqrs.Behaviors</c> 命名空间以兼容现有调用点,
|
||||
/// 但实现已迁入 <c>GFramework.Cqrs</c> 程序集,避免继续由 <c>GFramework.Core</c> 承载 CQRS runtime 细节。
|
||||
/// 该行为已迁移到 <c>GFramework.Cqrs.Cqrs.Behaviors</c> 命名空间,
|
||||
/// 实现位于 <c>GFramework.Cqrs</c> 程序集,用于承载 CQRS runtime 细节并与旧层解耦。
|
||||
/// </remarks>
|
||||
public sealed class LoggingBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
|
||||
where TRequest : IRequest<TResponse>
|
||||
|
||||
@ -17,22 +17,24 @@ using GFramework.Cqrs.Abstractions.Cqrs.Query;
|
||||
namespace GFramework.Cqrs.Cqrs.Query;
|
||||
|
||||
/// <summary>
|
||||
/// 抽象流式查询处理器基类
|
||||
/// 继承自轻量 CQRS 上下文基类并实现IStreamQueryHandler接口,为具体的流式查询处理器提供基础功能
|
||||
/// 支持流式处理查询并产生异步可枚举的响应序列,适用于大数据量或实时数据查询场景
|
||||
/// 为流式查询处理器提供共享的 CQRS 上下文访问基类。
|
||||
/// </summary>
|
||||
/// <typeparam name="TQuery">流式查询类型,必须实现IStreamQuery接口</typeparam>
|
||||
/// <typeparam name="TResponse">流式查询响应元素类型</typeparam>
|
||||
/// <typeparam name="TQuery">流式查询类型,必须实现 <see cref="IStreamQuery{TResponse}" />。</typeparam>
|
||||
/// <typeparam name="TResponse">流式查询响应元素类型。</typeparam>
|
||||
/// <remarks>
|
||||
/// 该基类复用 <see cref="CqrsContextAwareHandlerBase" /> 的上下文注入能力,并实现
|
||||
/// <see cref="IStreamRequestHandler{TQuery,TResponse}" /> 契约,让派生类只需聚焦于结果流的生成逻辑。
|
||||
/// 适用于需要逐步产出大量结果或长生命周期响应流的查询场景。
|
||||
/// </remarks>
|
||||
public abstract class AbstractStreamQueryHandler<TQuery, TResponse> : CqrsContextAwareHandlerBase,
|
||||
IStreamRequestHandler<TQuery, TResponse>
|
||||
where TQuery : IStreamQuery<TResponse>
|
||||
{
|
||||
/// <summary>
|
||||
/// 处理流式查询并返回异步可枚举的响应序列
|
||||
/// 由具体的流式查询处理器子类实现流式查询处理逻辑
|
||||
/// 处理流式查询并返回异步可枚举的响应序列。
|
||||
/// </summary>
|
||||
/// <param name="query">要处理的流式查询对象</param>
|
||||
/// <param name="cancellationToken">取消令牌,用于取消流式查询操作</param>
|
||||
/// <returns>异步可枚举的响应序列,每个元素类型为TResponse</returns>
|
||||
/// <param name="query">要处理的流式查询对象。</param>
|
||||
/// <param name="cancellationToken">用于停止结果流生成的取消令牌。</param>
|
||||
/// <returns>按需生成的异步响应序列。</returns>
|
||||
public abstract IAsyncEnumerable<TResponse> Handle(TQuery query, CancellationToken cancellationToken);
|
||||
}
|
||||
|
||||
@ -21,6 +21,9 @@ public static class CqrsRuntimeFactory
|
||||
/// <param name="container">目标依赖注入容器。</param>
|
||||
/// <param name="logger">用于 runtime 诊断的日志器。</param>
|
||||
/// <returns>默认 CQRS runtime。</returns>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// <paramref name="container" /> 或 <paramref name="logger" /> 为 <see langword="null" />。
|
||||
/// </exception>
|
||||
public static ICqrsRuntime CreateRuntime(IIocContainer container, ILogger logger)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(container);
|
||||
@ -35,6 +38,9 @@ public static class CqrsRuntimeFactory
|
||||
/// <param name="container">目标依赖注入容器。</param>
|
||||
/// <param name="logger">用于注册阶段诊断的日志器。</param>
|
||||
/// <returns>默认 CQRS handler registrar。</returns>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// <paramref name="container" /> 或 <paramref name="logger" /> 为 <see langword="null" />。
|
||||
/// </exception>
|
||||
public static ICqrsHandlerRegistrar CreateHandlerRegistrar(IIocContainer container, ILogger logger)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(container);
|
||||
|
||||
@ -14,18 +14,22 @@
|
||||
using GFramework.Cqrs.Abstractions.Cqrs;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs.Notification;
|
||||
|
||||
namespace GFramework.Cqrs.Notification;
|
||||
namespace GFramework.Core.Cqrs.Notification;
|
||||
|
||||
/// <summary>
|
||||
/// 表示一个基础通知类,用于处理带有输入的通知模式实现。
|
||||
/// 该类实现了 INotification 接口,提供了通用的通知结构。
|
||||
/// 为携带输入模型的 CQRS 通知提供统一基类。
|
||||
/// </summary>
|
||||
/// <typeparam name="TInput">通知输入数据的类型,必须实现 INotificationInput 接口</typeparam>
|
||||
/// <param name="input">通知执行所需的输入数据</param>
|
||||
public abstract class NotificationBase<TInput>(TInput input) : INotification where TInput : INotificationInput
|
||||
/// <typeparam name="TInput">通知输入类型,必须实现 <see cref="INotificationInput" />。</typeparam>
|
||||
/// <param name="input">通知广播时携带的输入对象。</param>
|
||||
/// <remarks>
|
||||
/// 该类型继续保留在历史公开命名空间中,以避免调用方因 runtime 程序集拆分而批量修改继承层次。
|
||||
/// 具体实现现由 <c>GFramework.Cqrs</c> 程序集承载,并通过 type forward 维持旧程序集兼容性。
|
||||
/// </remarks>
|
||||
public abstract class NotificationBase<TInput>(TInput input) : INotification
|
||||
where TInput : INotificationInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取通知的输入数据。
|
||||
/// 获取通知广播时携带的输入对象。
|
||||
/// </summary>
|
||||
public TInput Input => input;
|
||||
}
|
||||
|
||||
@ -13,19 +13,23 @@
|
||||
|
||||
using GFramework.Cqrs.Abstractions.Cqrs.Query;
|
||||
|
||||
namespace GFramework.Cqrs.Query;
|
||||
namespace GFramework.Core.Cqrs.Query;
|
||||
|
||||
/// <summary>
|
||||
/// 表示一个基础查询类,用于处理带有输入和响应的查询模式实现。
|
||||
/// 该类实现 IQuery<TResponse> 接口,提供了通用的查询结构。
|
||||
/// 为携带输入模型的 CQRS 查询提供统一基类。
|
||||
/// </summary>
|
||||
/// <typeparam name="TInput">查询输入数据的类型,必须实现 IQueryInput 接口</typeparam>
|
||||
/// <typeparam name="TResponse">查询执行后返回结果的类型</typeparam>
|
||||
/// <param name="input">查询执行所需的输入数据</param>
|
||||
public abstract class QueryBase<TInput, TResponse>(TInput input) : IQuery<TResponse> where TInput : IQueryInput
|
||||
/// <typeparam name="TInput">查询输入类型,必须实现 <see cref="IQueryInput" />。</typeparam>
|
||||
/// <typeparam name="TResponse">查询响应类型。</typeparam>
|
||||
/// <param name="input">查询执行所需的输入对象。</param>
|
||||
/// <remarks>
|
||||
/// 该类型继续保留在历史公开命名空间中,以避免调用方因 runtime 程序集拆分而批量修改继承层次。
|
||||
/// 具体实现现由 <c>GFramework.Cqrs</c> 程序集承载,并通过 type forward 维持旧程序集兼容性。
|
||||
/// </remarks>
|
||||
public abstract class QueryBase<TInput, TResponse>(TInput input) : IQuery<TResponse>
|
||||
where TInput : IQueryInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取查询的输入数据。
|
||||
/// 获取查询执行时携带的输入对象。
|
||||
/// </summary>
|
||||
public TInput Input => input;
|
||||
}
|
||||
|
||||
@ -14,19 +14,23 @@
|
||||
using GFramework.Cqrs.Abstractions.Cqrs;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs.Request;
|
||||
|
||||
namespace GFramework.Cqrs.Request;
|
||||
namespace GFramework.Core.Cqrs.Request;
|
||||
|
||||
/// <summary>
|
||||
/// 表示一个基础请求类,用于处理带有输入和响应的请求模式实现。
|
||||
/// 该类实现了 IRequest<TResponse> 接口,提供了通用的请求结构。
|
||||
/// 为携带输入模型的通用 CQRS 请求提供统一基类。
|
||||
/// </summary>
|
||||
/// <typeparam name="TInput">请求输入数据的类型,必须实现 IRequestInput 接口</typeparam>
|
||||
/// <typeparam name="TResponse">请求执行后返回结果的类型</typeparam>
|
||||
/// <param name="input">请求执行所需的输入数据</param>
|
||||
public abstract class RequestBase<TInput, TResponse>(TInput input) : IRequest<TResponse> where TInput : IRequestInput
|
||||
/// <typeparam name="TInput">请求输入类型,必须实现 <see cref="IRequestInput" />。</typeparam>
|
||||
/// <typeparam name="TResponse">请求响应类型。</typeparam>
|
||||
/// <param name="input">请求执行所需的输入对象。</param>
|
||||
/// <remarks>
|
||||
/// 该类型继续保留在历史公开命名空间中,以避免调用方因 runtime 程序集拆分而批量修改继承层次。
|
||||
/// 具体实现现由 <c>GFramework.Cqrs</c> 程序集承载,并通过 type forward 维持旧程序集兼容性。
|
||||
/// </remarks>
|
||||
public abstract class RequestBase<TInput, TResponse>(TInput input) : IRequest<TResponse>
|
||||
where TInput : IRequestInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取请求的输入数据。
|
||||
/// 获取请求执行时携带的输入对象。
|
||||
/// </summary>
|
||||
public TInput Input => input;
|
||||
}
|
||||
|
||||
@ -4,11 +4,10 @@ using System.Reflection;
|
||||
using GFramework.Core.Abstractions.Cqrs;
|
||||
using GFramework.Core.Abstractions.Ioc;
|
||||
using GFramework.Core.Abstractions.Logging;
|
||||
using GFramework.Core.Cqrs.Command;
|
||||
using GFramework.Core.Ioc;
|
||||
using GFramework.Core.Logging;
|
||||
using GFramework.Cqrs;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs;
|
||||
using GFramework.Cqrs.Command;
|
||||
|
||||
namespace GFramework.Tests.Common;
|
||||
|
||||
@ -48,7 +47,6 @@ public static class CqrsTestRuntime
|
||||
/// </summary>
|
||||
/// <param name="container">目标测试容器。</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="container" /> 为 <see langword="null" />。</exception>
|
||||
/// <exception cref="TargetInvocationException">反射调用底层 CQRS runtime 或注册器构造函数失败时抛出。</exception>
|
||||
/// <remarks>
|
||||
/// 这使仅使用 <see cref="MicrosoftDiContainer" /> 的测试环境也能观察与生产路径一致的 runtime 行为,
|
||||
/// 而无需完整启动服务模块管理器。
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user