From 005c32d84f7ca0fedf6a46eafe8a6f89841a5d2e Mon Sep 17 00:00:00 2001 From: GeWuYou <95328647+GeWuYou@users.noreply.github.com> Date: Wed, 15 Apr 2026 22:27:09 +0800 Subject: [PATCH] =?UTF-8?q?feat(cqrs):=20=E6=B7=BB=E5=8A=A0CQRS=E6=89=A9?= =?UTF-8?q?=E5=B1=95=E6=96=B9=E6=B3=95=E5=85=BC=E5=AE=B9=E5=B1=82=E5=92=8C?= =?UTF-8?q?=E6=97=A5=E5=BF=97=E5=B7=A5=E5=8E=82=E8=A7=A3=E6=9E=90=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增ContextAwareMediatorCommandExtensions提供命令扩展方法兼容性支持 - 新增ContextAwareMediatorQueryExtensions提供查询扩展方法兼容性支持 - 添加LoggerFactoryResolver实现全局日志工厂访问入口 - 实现TypeForwarders将核心类型转发到正确程序集 - 添加MediatorCompatibilityDeprecationTests验证弃用策略 - 扩展LoggerFactoryTests覆盖并发初始化和回退逻辑 - 迁移CommandBase到Core.Cqrs.Command命名空间 - 移动LoggingBehavior到GFramework.Cqrs.Cqrs.Behaviors - 添加AbstractStreamQueryHandler支持流式查询处理 - 创建NotificationBase提供通知基类实现 --- .../Logging/LoggerFactoryResolver.cs | 45 +++- .../CqrsPublicNamespaceCompatibilityTests.cs | 76 ++++++ .../MediatorCompatibilityDeprecationTests.cs | 4 +- .../Logging/LoggerFactoryTests.cs | 238 ++++++++++++++++-- .../ContextAwareMediatorCommandExtensions.cs | 2 +- .../ContextAwareMediatorQueryExtensions.cs | 2 +- GFramework.Core/Properties/TypeForwarders.cs | 8 + GFramework.Cqrs/Command/CommandBase.cs | 20 +- .../Cqrs/Behaviors/LoggingBehavior.cs | 4 +- .../Cqrs/Query/AbstractStreamQueryHandler.cs | 22 +- GFramework.Cqrs/CqrsRuntimeFactory.cs | 6 + .../Notification/NotificationBase.cs | 18 +- GFramework.Cqrs/Query/QueryBase.cs | 20 +- GFramework.Cqrs/Request/RequestBase.cs | 20 +- GFramework.Tests.Common/CqrsTestRuntime.cs | 4 +- 15 files changed, 409 insertions(+), 80 deletions(-) create mode 100644 GFramework.Core.Tests/Cqrs/CqrsPublicNamespaceCompatibilityTests.cs diff --git a/GFramework.Core.Abstractions/Logging/LoggerFactoryResolver.cs b/GFramework.Core.Abstractions/Logging/LoggerFactoryResolver.cs index a87a18da..fdffe5b2 100644 --- a/GFramework.Core.Abstractions/Logging/LoggerFactoryResolver.cs +++ b/GFramework.Core.Abstractions/Logging/LoggerFactoryResolver.cs @@ -10,19 +10,42 @@ namespace GFramework.Core.Abstractions.Logging; /// 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; + /// /// 获取或设置当前日志工厂提供程序。 /// + /// + /// 读取与赋值都会通过同一把锁串行化,确保并发调用方观察到确定的 provider 引用。 + /// 当调用方未显式赋值时,会在首次访问时尝试解析默认实现;若解析失败,则退回静默 provider。 + /// /// /// 当赋值为 时抛出。 /// 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; + } + } } /// @@ -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(); diff --git a/GFramework.Core.Tests/Cqrs/CqrsPublicNamespaceCompatibilityTests.cs b/GFramework.Core.Tests/Cqrs/CqrsPublicNamespaceCompatibilityTests.cs new file mode 100644 index 00000000..2f02dbb7 --- /dev/null +++ b/GFramework.Core.Tests/Cqrs/CqrsPublicNamespaceCompatibilityTests.cs @@ -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; + +/// +/// 锁定 CQRS 基础消息类型在 runtime 拆分后的公开命名空间与程序集兼容性。 +/// +[TestFixture] +public sealed class CqrsPublicNamespaceCompatibilityTests +{ + /// + /// 验证基础消息类型继续暴露在历史 Core.Cqrs 命名空间,同时由独立 runtime 程序集承载实现。 + /// + [Test] + public void Base_Message_Types_Should_Remain_In_Legacy_Namespaces_While_Living_In_Runtime_Assembly() + { + Assert.Multiple(() => + { + AssertLegacyType(typeof(CommandBase), "GFramework.Core.Cqrs.Command"); + AssertLegacyType(typeof(QueryBase), "GFramework.Core.Cqrs.Query"); + AssertLegacyType(typeof(RequestBase), "GFramework.Core.Cqrs.Request"); + AssertLegacyType(typeof(NotificationBase), "GFramework.Core.Cqrs.Notification"); + }); + } + + /// + /// 验证旧的 GFramework.Core 程序集限定名仍可解析到迁移后的 runtime 实现类型。 + /// + [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; +} diff --git a/GFramework.Core.Tests/Cqrs/MediatorCompatibilityDeprecationTests.cs b/GFramework.Core.Tests/Cqrs/MediatorCompatibilityDeprecationTests.cs index b25806f0..a6cc927f 100644 --- a/GFramework.Core.Tests/Cqrs/MediatorCompatibilityDeprecationTests.cs +++ b/GFramework.Core.Tests/Cqrs/MediatorCompatibilityDeprecationTests.cs @@ -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."); diff --git a/GFramework.Core.Tests/Logging/LoggerFactoryTests.cs b/GFramework.Core.Tests/Logging/LoggerFactoryTests.cs index 8ba7ac91..d175b32f 100644 --- a/GFramework.Core.Tests/Logging/LoggerFactoryTests.cs +++ b/GFramework.Core.Tests/Logging/LoggerFactoryTests.cs @@ -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; /// -/// 测试LoggerFactory相关功能的测试类 +/// 测试 LoggerFactory 相关功能的测试类。 /// [TestFixture] +[NonParallelizable] public class LoggerFactoryTests { /// - /// 测试ConsoleLoggerFactory的GetLogger方法是否返回ConsoleLogger实例 + /// 测试 ConsoleLoggerFactory 的 GetLogger 方法是否返回 ConsoleLogger 实例。 /// [Test] public void ConsoleLoggerFactory_GetLogger_ShouldReturnConsoleLogger() @@ -26,7 +27,7 @@ public class LoggerFactoryTests } /// - /// 测试ConsoleLoggerFactory使用不同名称获取不同的logger实例 + /// 测试 ConsoleLoggerFactory 使用不同名称获取不同的 logger 实例。 /// [Test] public void ConsoleLoggerFactory_GetLogger_WithDifferentNames_ShouldReturnDifferentLoggers() @@ -40,7 +41,7 @@ public class LoggerFactoryTests } /// - /// 测试ConsoleLoggerFactory使用默认最小级别时的行为(默认为Info级别) + /// 测试 ConsoleLoggerFactory 使用默认最小级别时的行为。 /// [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 } /// - /// 测试ConsoleLoggerFactoryProvider创建logger时使用提供者的最小级别设置 + /// 测试 ConsoleLoggerFactoryProvider 创建 logger 时使用提供者的最小级别设置。 /// [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 } /// - /// 测试ConsoleLoggerFactoryProvider创建logger时使用提供的名称 + /// 测试 ConsoleLoggerFactoryProvider 创建 logger 时使用提供的名称。 /// [Test] public void ConsoleLoggerFactoryProvider_CreateLogger_ShouldUseProvidedName() @@ -94,7 +93,7 @@ public class LoggerFactoryTests } /// - /// 测试LoggerFactoryResolver的Provider属性是否有默认值 + /// 测试 LoggerFactoryResolver 的 Provider 属性是否有默认值。 /// [Test] public void LoggerFactoryResolver_Provider_ShouldHaveDefaultValue() @@ -104,7 +103,7 @@ public class LoggerFactoryTests } /// - /// 测试LoggerFactoryResolver的Provider属性可以被更改 + /// 测试 LoggerFactoryResolver 的 Provider 属性可以被更改。 /// [Test] public void LoggerFactoryResolver_Provider_CanBeChanged() @@ -120,7 +119,7 @@ public class LoggerFactoryTests } /// - /// 测试LoggerFactoryResolver的MinLevel属性是否有默认值 + /// 测试 LoggerFactoryResolver 的 MinLevel 属性是否有默认值。 /// [Test] public void LoggerFactoryResolver_MinLevel_ShouldHaveDefaultValue() @@ -129,7 +128,7 @@ public class LoggerFactoryTests } /// - /// 测试LoggerFactoryResolver的MinLevel属性可以被更改 + /// 测试 LoggerFactoryResolver 的 MinLevel 属性可以被更改。 /// [Test] public void LoggerFactoryResolver_MinLevel_CanBeChanged() @@ -144,7 +143,7 @@ public class LoggerFactoryTests } /// - /// 测试ConsoleLoggerFactoryProvider的MinLevel属性是否有默认值 + /// 测试 ConsoleLoggerFactoryProvider 的 MinLevel 属性是否有默认值。 /// [Test] public void ConsoleLoggerFactoryProvider_MinLevel_ShouldHaveDefaultValue() @@ -155,7 +154,7 @@ public class LoggerFactoryTests } /// - /// 测试ConsoleLoggerFactoryProvider的MinLevel属性可以被更改 + /// 测试 ConsoleLoggerFactoryProvider 的 MinLevel 属性可以被更改。 /// [Test] public void ConsoleLoggerFactoryProvider_MinLevel_CanBeChanged() @@ -168,7 +167,7 @@ public class LoggerFactoryTests } /// - /// 测试LoggerFactoryResolver的Provider创建logger时使用提供者设置 + /// 测试 LoggerFactoryResolver 的 Provider 创建 logger 时使用提供者设置。 /// [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 } /// - /// 测试LoggerFactoryResolver的MinLevel属性影响新创建的logger + /// 测试 LoggerFactoryResolver 的 MinLevel 属性影响新创建的 logger。 /// [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 } /// - /// 测试ConsoleLoggerFactory创建的多个logger实例是独立的 + /// 验证默认 provider 激活失败时会回退到静默 provider。 + /// + [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; + } + } + + /// + /// 验证并发首次访问默认 provider 时只会创建一个实例,并向所有调用方返回相同引用。 + /// + [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; + } + } + + /// + /// 测试 ConsoleLoggerFactory 创建的多个 logger 实例是独立的。 /// [Test] public void ConsoleLoggerFactory_MultipleLoggers_ShouldBeIndependent() @@ -236,7 +319,7 @@ public class LoggerFactoryTests } /// - /// 测试ConsoleLoggerFactoryProvider的MinLevel不会影响已创建的logger + /// 测试 ConsoleLoggerFactoryProvider 的 MinLevel 不会影响已创建的 logger。 /// [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")); } -} \ No newline at end of file + + 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}."); + } + + /// + /// 用于触发默认 provider 激活失败回退路径的测试桩。 + /// + public sealed class ThrowingLoggerFactoryProvider : ILoggerFactoryProvider + { + /// + /// 初始化一个始终抛出异常的 provider。 + /// + /// 始终抛出,用于覆盖回退路径。 + public ThrowingLoggerFactoryProvider() + { + throw new InvalidOperationException("Simulated provider activation failure."); + } + + /// + /// 获取或设置最小日志级别。 + /// + public LogLevel MinLevel { get; set; } = LogLevel.Info; + + /// + /// 创建日志器。 + /// + /// 日志器名称。 + /// 该测试桩永远不会成功创建日志器。 + /// 始终抛出,因为该方法不应被调用。 + public ILogger CreateLogger(string name) + { + throw new NotSupportedException(); + } + } + + /// + /// 用于验证并发首次初始化路径只创建单个 provider 实例的测试桩。 + /// + public sealed class BlockingLoggerFactoryProvider : ILoggerFactoryProvider + { + private static int _constructionCount; + private static ManualResetEventSlim _constructionGate = new(false); + + /// + /// 初始化一个会阻塞构造完成的 provider,用于放大并发首次访问竞争窗口。 + /// + public BlockingLoggerFactoryProvider() + { + Interlocked.Increment(ref _constructionCount); + _constructionGate.Wait(TimeSpan.FromSeconds(5)); + } + + /// + /// 获取已经发生的构造次数。 + /// + public static int ConstructionCount => Volatile.Read(ref _constructionCount); + + /// + /// 获取或设置最小日志级别。 + /// + public LogLevel MinLevel { get; set; } = LogLevel.Info; + + /// + /// 创建测试日志器。 + /// + /// 日志器名称。 + /// 带有当前最小级别设置的控制台日志器。 + public ILogger CreateLogger(string name) + { + return new ConsoleLogger(name, MinLevel, TextWriter.Null, false); + } + + /// + /// 重置该测试桩的并发观测状态。 + /// + public static void Reset() + { + _constructionGate = new ManualResetEventSlim(false); + Interlocked.Exchange(ref _constructionCount, 0); + } + + /// + /// 释放当前被阻塞的 provider 构造过程。 + /// + public static void ReleaseConstruction() + { + _constructionGate.Set(); + } + } +} diff --git a/GFramework.Core/Extensions/ContextAwareMediatorCommandExtensions.cs b/GFramework.Core/Extensions/ContextAwareMediatorCommandExtensions.cs index d001cb71..24490239 100644 --- a/GFramework.Core/Extensions/ContextAwareMediatorCommandExtensions.cs +++ b/GFramework.Core/Extensions/ContextAwareMediatorCommandExtensions.cs @@ -12,7 +12,7 @@ namespace GFramework.Core.Extensions; /// [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 { /// diff --git a/GFramework.Core/Extensions/ContextAwareMediatorQueryExtensions.cs b/GFramework.Core/Extensions/ContextAwareMediatorQueryExtensions.cs index 4eb1b2c7..cf0b4513 100644 --- a/GFramework.Core/Extensions/ContextAwareMediatorQueryExtensions.cs +++ b/GFramework.Core/Extensions/ContextAwareMediatorQueryExtensions.cs @@ -12,7 +12,7 @@ namespace GFramework.Core.Extensions; /// [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 { /// diff --git a/GFramework.Core/Properties/TypeForwarders.cs b/GFramework.Core/Properties/TypeForwarders.cs index a71d9af2..a27d2bf4 100644 --- a/GFramework.Core/Properties/TypeForwarders.cs +++ b/GFramework.Core/Properties/TypeForwarders.cs @@ -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<>))] diff --git a/GFramework.Cqrs/Command/CommandBase.cs b/GFramework.Cqrs/Command/CommandBase.cs index 57703c47..01351332 100644 --- a/GFramework.Cqrs/Command/CommandBase.cs +++ b/GFramework.Cqrs/Command/CommandBase.cs @@ -13,19 +13,23 @@ using GFramework.Cqrs.Abstractions.Cqrs.Command; -namespace GFramework.Cqrs.Command; +namespace GFramework.Core.Cqrs.Command; /// -/// 表示一个基础命令类,用于处理带有输入和响应的命令模式实现。 -/// 该类实现了 ICommand<TResponse> 接口,提供了通用的命令结构。 +/// 为携带输入模型的 CQRS 命令提供统一基类。 /// -/// 命令输入数据的类型 -/// 命令执行后返回结果的类型 -/// 命令执行所需的输入数据 -public abstract class CommandBase(TInput input) : ICommand where TInput : ICommandInput +/// 命令输入类型。 +/// 命令响应类型。 +/// 命令执行所需的输入对象。 +/// +/// 该类型继续保留在历史公开命名空间中,以避免调用方因 runtime 程序集拆分而批量修改继承层次。 +/// 具体实现现由 GFramework.Cqrs 程序集承载,并通过 type forward 维持旧程序集兼容性。 +/// +public abstract class CommandBase(TInput input) : ICommand + where TInput : ICommandInput { /// - /// 获取命令的输入数据。 + /// 获取命令执行时携带的输入对象。 /// public TInput Input => input; } diff --git a/GFramework.Cqrs/Cqrs/Behaviors/LoggingBehavior.cs b/GFramework.Cqrs/Cqrs/Behaviors/LoggingBehavior.cs index ccd9f0bf..63896c92 100644 --- a/GFramework.Cqrs/Cqrs/Behaviors/LoggingBehavior.cs +++ b/GFramework.Cqrs/Cqrs/Behaviors/LoggingBehavior.cs @@ -22,8 +22,8 @@ namespace GFramework.Cqrs.Cqrs.Behaviors; /// 请求类型。 /// 响应类型。 /// -/// 该行为保留在 GFramework.Core.Cqrs.Behaviors 命名空间以兼容现有调用点, -/// 但实现已迁入 GFramework.Cqrs 程序集,避免继续由 GFramework.Core 承载 CQRS runtime 细节。 +/// 该行为已迁移到 GFramework.Cqrs.Cqrs.Behaviors 命名空间, +/// 实现位于 GFramework.Cqrs 程序集,用于承载 CQRS runtime 细节并与旧层解耦。 /// public sealed class LoggingBehavior : IPipelineBehavior where TRequest : IRequest diff --git a/GFramework.Cqrs/Cqrs/Query/AbstractStreamQueryHandler.cs b/GFramework.Cqrs/Cqrs/Query/AbstractStreamQueryHandler.cs index 7d301009..f532f80d 100644 --- a/GFramework.Cqrs/Cqrs/Query/AbstractStreamQueryHandler.cs +++ b/GFramework.Cqrs/Cqrs/Query/AbstractStreamQueryHandler.cs @@ -17,22 +17,24 @@ using GFramework.Cqrs.Abstractions.Cqrs.Query; namespace GFramework.Cqrs.Cqrs.Query; /// -/// 抽象流式查询处理器基类 -/// 继承自轻量 CQRS 上下文基类并实现IStreamQueryHandler接口,为具体的流式查询处理器提供基础功能 -/// 支持流式处理查询并产生异步可枚举的响应序列,适用于大数据量或实时数据查询场景 +/// 为流式查询处理器提供共享的 CQRS 上下文访问基类。 /// -/// 流式查询类型,必须实现IStreamQuery接口 -/// 流式查询响应元素类型 +/// 流式查询类型,必须实现 +/// 流式查询响应元素类型。 +/// +/// 该基类复用 的上下文注入能力,并实现 +/// 契约,让派生类只需聚焦于结果流的生成逻辑。 +/// 适用于需要逐步产出大量结果或长生命周期响应流的查询场景。 +/// public abstract class AbstractStreamQueryHandler : CqrsContextAwareHandlerBase, IStreamRequestHandler where TQuery : IStreamQuery { /// - /// 处理流式查询并返回异步可枚举的响应序列 - /// 由具体的流式查询处理器子类实现流式查询处理逻辑 + /// 处理流式查询并返回异步可枚举的响应序列。 /// - /// 要处理的流式查询对象 - /// 取消令牌,用于取消流式查询操作 - /// 异步可枚举的响应序列,每个元素类型为TResponse + /// 要处理的流式查询对象。 + /// 用于停止结果流生成的取消令牌。 + /// 按需生成的异步响应序列。 public abstract IAsyncEnumerable Handle(TQuery query, CancellationToken cancellationToken); } diff --git a/GFramework.Cqrs/CqrsRuntimeFactory.cs b/GFramework.Cqrs/CqrsRuntimeFactory.cs index 45dbb07a..0a0f86ce 100644 --- a/GFramework.Cqrs/CqrsRuntimeFactory.cs +++ b/GFramework.Cqrs/CqrsRuntimeFactory.cs @@ -21,6 +21,9 @@ public static class CqrsRuntimeFactory /// 目标依赖注入容器。 /// 用于 runtime 诊断的日志器。 /// 默认 CQRS runtime。 + /// + /// 。 + /// public static ICqrsRuntime CreateRuntime(IIocContainer container, ILogger logger) { ArgumentNullException.ThrowIfNull(container); @@ -35,6 +38,9 @@ public static class CqrsRuntimeFactory /// 目标依赖注入容器。 /// 用于注册阶段诊断的日志器。 /// 默认 CQRS handler registrar。 + /// + /// 。 + /// public static ICqrsHandlerRegistrar CreateHandlerRegistrar(IIocContainer container, ILogger logger) { ArgumentNullException.ThrowIfNull(container); diff --git a/GFramework.Cqrs/Notification/NotificationBase.cs b/GFramework.Cqrs/Notification/NotificationBase.cs index 06390406..93daea8f 100644 --- a/GFramework.Cqrs/Notification/NotificationBase.cs +++ b/GFramework.Cqrs/Notification/NotificationBase.cs @@ -14,18 +14,22 @@ using GFramework.Cqrs.Abstractions.Cqrs; using GFramework.Cqrs.Abstractions.Cqrs.Notification; -namespace GFramework.Cqrs.Notification; +namespace GFramework.Core.Cqrs.Notification; /// -/// 表示一个基础通知类,用于处理带有输入的通知模式实现。 -/// 该类实现了 INotification 接口,提供了通用的通知结构。 +/// 为携带输入模型的 CQRS 通知提供统一基类。 /// -/// 通知输入数据的类型,必须实现 INotificationInput 接口 -/// 通知执行所需的输入数据 -public abstract class NotificationBase(TInput input) : INotification where TInput : INotificationInput +/// 通知输入类型,必须实现 +/// 通知广播时携带的输入对象。 +/// +/// 该类型继续保留在历史公开命名空间中,以避免调用方因 runtime 程序集拆分而批量修改继承层次。 +/// 具体实现现由 GFramework.Cqrs 程序集承载,并通过 type forward 维持旧程序集兼容性。 +/// +public abstract class NotificationBase(TInput input) : INotification + where TInput : INotificationInput { /// - /// 获取通知的输入数据。 + /// 获取通知广播时携带的输入对象。 /// public TInput Input => input; } diff --git a/GFramework.Cqrs/Query/QueryBase.cs b/GFramework.Cqrs/Query/QueryBase.cs index d0f491b1..9d15e028 100644 --- a/GFramework.Cqrs/Query/QueryBase.cs +++ b/GFramework.Cqrs/Query/QueryBase.cs @@ -13,19 +13,23 @@ using GFramework.Cqrs.Abstractions.Cqrs.Query; -namespace GFramework.Cqrs.Query; +namespace GFramework.Core.Cqrs.Query; /// -/// 表示一个基础查询类,用于处理带有输入和响应的查询模式实现。 -/// 该类实现 IQuery<TResponse> 接口,提供了通用的查询结构。 +/// 为携带输入模型的 CQRS 查询提供统一基类。 /// -/// 查询输入数据的类型,必须实现 IQueryInput 接口 -/// 查询执行后返回结果的类型 -/// 查询执行所需的输入数据 -public abstract class QueryBase(TInput input) : IQuery where TInput : IQueryInput +/// 查询输入类型,必须实现 +/// 查询响应类型。 +/// 查询执行所需的输入对象。 +/// +/// 该类型继续保留在历史公开命名空间中,以避免调用方因 runtime 程序集拆分而批量修改继承层次。 +/// 具体实现现由 GFramework.Cqrs 程序集承载,并通过 type forward 维持旧程序集兼容性。 +/// +public abstract class QueryBase(TInput input) : IQuery + where TInput : IQueryInput { /// - /// 获取查询的输入数据。 + /// 获取查询执行时携带的输入对象。 /// public TInput Input => input; } diff --git a/GFramework.Cqrs/Request/RequestBase.cs b/GFramework.Cqrs/Request/RequestBase.cs index 8c534c86..40d7a44d 100644 --- a/GFramework.Cqrs/Request/RequestBase.cs +++ b/GFramework.Cqrs/Request/RequestBase.cs @@ -14,19 +14,23 @@ using GFramework.Cqrs.Abstractions.Cqrs; using GFramework.Cqrs.Abstractions.Cqrs.Request; -namespace GFramework.Cqrs.Request; +namespace GFramework.Core.Cqrs.Request; /// -/// 表示一个基础请求类,用于处理带有输入和响应的请求模式实现。 -/// 该类实现了 IRequest<TResponse> 接口,提供了通用的请求结构。 +/// 为携带输入模型的通用 CQRS 请求提供统一基类。 /// -/// 请求输入数据的类型,必须实现 IRequestInput 接口 -/// 请求执行后返回结果的类型 -/// 请求执行所需的输入数据 -public abstract class RequestBase(TInput input) : IRequest where TInput : IRequestInput +/// 请求输入类型,必须实现 +/// 请求响应类型。 +/// 请求执行所需的输入对象。 +/// +/// 该类型继续保留在历史公开命名空间中,以避免调用方因 runtime 程序集拆分而批量修改继承层次。 +/// 具体实现现由 GFramework.Cqrs 程序集承载,并通过 type forward 维持旧程序集兼容性。 +/// +public abstract class RequestBase(TInput input) : IRequest + where TInput : IRequestInput { /// - /// 获取请求的输入数据。 + /// 获取请求执行时携带的输入对象。 /// public TInput Input => input; } diff --git a/GFramework.Tests.Common/CqrsTestRuntime.cs b/GFramework.Tests.Common/CqrsTestRuntime.cs index f044bbc8..ad02120e 100644 --- a/GFramework.Tests.Common/CqrsTestRuntime.cs +++ b/GFramework.Tests.Common/CqrsTestRuntime.cs @@ -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 /// /// 目标测试容器。 /// - /// 反射调用底层 CQRS runtime 或注册器构造函数失败时抛出。 /// /// 这使仅使用 的测试环境也能观察与生产路径一致的 runtime 行为, /// 而无需完整启动服务模块管理器。