mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-05-11 12:14:30 +08:00
Merge pull request #225 from GeWuYou/refactor/cqrs-architecture-decoupling-todo-6
feat(cqrs): 添加CQRS运行时模块和兼容性扩展
This commit is contained in:
commit
385893c791
@ -15,7 +15,7 @@ namespace GFramework.Core.Abstractions.Architectures;
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>旧的 <c>GFramework.Core.Abstractions.Command</c> 与 <c>GFramework.Core.Abstractions.Query</c> 契约会继续通过原有 Command/Query Executor 路径执行,以保证存量代码兼容。</para>
|
||||
/// <para>新的 <c>GFramework.Core.Abstractions.Cqrs</c> 契约由内置 CQRS dispatcher 统一处理,支持 request pipeline、notification publish 与 stream request。</para>
|
||||
/// <para>新的 <c>GFramework.Cqrs.Abstractions.Cqrs</c> 契约由内置 CQRS dispatcher 统一处理,支持 request pipeline、notification publish 与 stream request。</para>
|
||||
/// <para>新功能优先使用 <see cref="SendRequestAsync{TResponse}(IRequest{TResponse},CancellationToken)" />、<see cref="SendAsync{TCommand}(TCommand,CancellationToken)" /> 与对应的 CQRS Command/Query 重载;迁移旧代码时可先保留旧入口,再逐步替换为 CQRS 请求模型。</para>
|
||||
/// </remarks>
|
||||
public interface IArchitectureContext
|
||||
@ -175,7 +175,7 @@ public interface IArchitectureContext
|
||||
/// <param name="query">要发送的 CQRS 查询。</param>
|
||||
/// <returns>查询结果。</returns>
|
||||
/// <remarks>
|
||||
/// 这是迁移后的推荐查询入口。新查询应优先实现 <c>GFramework.Core.Abstractions.Cqrs.Query.IQuery<TResponse></c>。
|
||||
/// 这是迁移后的推荐查询入口。新查询应优先实现 <c>GFramework.Cqrs.Abstractions.Cqrs.Query.IQuery<TResponse></c>。
|
||||
/// </remarks>
|
||||
TResponse SendQuery<TResponse>(GFramework.Cqrs.Abstractions.Cqrs.Query.IQuery<TResponse> query);
|
||||
|
||||
|
||||
281
GFramework.Core.Abstractions/Logging/LoggerFactoryResolver.cs
Normal file
281
GFramework.Core.Abstractions/Logging/LoggerFactoryResolver.cs
Normal file
@ -0,0 +1,281 @@
|
||||
namespace GFramework.Core.Abstractions.Logging;
|
||||
|
||||
/// <summary>
|
||||
/// 提供全局日志工厂访问入口。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 该类型位于抽象层,是为了让上层模块可以在不依赖 <c>GFramework.Core</c> 实现程序集的前提下
|
||||
/// 获取日志记录器。默认 provider 会优先通过反射解析 <c>GFramework.Core</c> 中的控制台实现,
|
||||
/// 若宿主未加载该程序集,则退回到静默 provider,避免抽象层形成实现层循环依赖。
|
||||
/// </remarks>
|
||||
public static class LoggerFactoryResolver
|
||||
{
|
||||
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
|
||||
{
|
||||
lock (ProviderLock)
|
||||
{
|
||||
_provider ??= CreateDefaultProvider();
|
||||
return _provider;
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
var provider = value ?? throw new ArgumentNullException(nameof(value));
|
||||
|
||||
lock (ProviderLock)
|
||||
{
|
||||
_provider = provider;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置新创建日志记录器的最小日志级别。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 该属性直接代理到当前 <see cref="Provider" />,确保调用方调整级别后立即影响后续创建的日志器。
|
||||
/// </remarks>
|
||||
public static LogLevel MinLevel
|
||||
{
|
||||
get => Provider.MinLevel;
|
||||
set => Provider.MinLevel = value;
|
||||
}
|
||||
|
||||
private static ILoggerFactoryProvider CreateDefaultProvider()
|
||||
{
|
||||
try
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当宿主未提供默认日志实现时使用的静默 provider。
|
||||
/// </summary>
|
||||
private sealed class SilentLoggerFactoryProvider : ILoggerFactoryProvider
|
||||
{
|
||||
public LogLevel MinLevel { get; set; } = LogLevel.Info;
|
||||
|
||||
public ILogger CreateLogger(string name)
|
||||
{
|
||||
return new SilentLogger(name);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 默认日志实现不可用时的 no-op 日志器。
|
||||
/// </summary>
|
||||
private sealed class SilentLogger(string name) : ILogger
|
||||
{
|
||||
public string Name()
|
||||
{
|
||||
return name;
|
||||
}
|
||||
|
||||
public bool IsTraceEnabled()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool IsDebugEnabled()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool IsInfoEnabled()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool IsWarnEnabled()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool IsErrorEnabled()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool IsFatalEnabled()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool IsEnabledForLevel(LogLevel level)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Trace(string msg)
|
||||
{
|
||||
}
|
||||
|
||||
public void Trace(string format, object arg)
|
||||
{
|
||||
}
|
||||
|
||||
public void Trace(string format, object arg1, object arg2)
|
||||
{
|
||||
}
|
||||
|
||||
public void Trace(string format, params object[] arguments)
|
||||
{
|
||||
}
|
||||
|
||||
public void Trace(string msg, Exception t)
|
||||
{
|
||||
}
|
||||
|
||||
public void Debug(string msg)
|
||||
{
|
||||
}
|
||||
|
||||
public void Debug(string format, object arg)
|
||||
{
|
||||
}
|
||||
|
||||
public void Debug(string format, object arg1, object arg2)
|
||||
{
|
||||
}
|
||||
|
||||
public void Debug(string format, params object[] arguments)
|
||||
{
|
||||
}
|
||||
|
||||
public void Debug(string msg, Exception t)
|
||||
{
|
||||
}
|
||||
|
||||
public void Info(string msg)
|
||||
{
|
||||
}
|
||||
|
||||
public void Info(string format, object arg)
|
||||
{
|
||||
}
|
||||
|
||||
public void Info(string format, object arg1, object arg2)
|
||||
{
|
||||
}
|
||||
|
||||
public void Info(string format, params object[] arguments)
|
||||
{
|
||||
}
|
||||
|
||||
public void Info(string msg, Exception t)
|
||||
{
|
||||
}
|
||||
|
||||
public void Warn(string msg)
|
||||
{
|
||||
}
|
||||
|
||||
public void Warn(string format, object arg)
|
||||
{
|
||||
}
|
||||
|
||||
public void Warn(string format, object arg1, object arg2)
|
||||
{
|
||||
}
|
||||
|
||||
public void Warn(string format, params object[] arguments)
|
||||
{
|
||||
}
|
||||
|
||||
public void Warn(string msg, Exception t)
|
||||
{
|
||||
}
|
||||
|
||||
public void Error(string msg)
|
||||
{
|
||||
}
|
||||
|
||||
public void Error(string format, object arg)
|
||||
{
|
||||
}
|
||||
|
||||
public void Error(string format, object arg1, object arg2)
|
||||
{
|
||||
}
|
||||
|
||||
public void Error(string format, params object[] arguments)
|
||||
{
|
||||
}
|
||||
|
||||
public void Error(string msg, Exception t)
|
||||
{
|
||||
}
|
||||
|
||||
public void Fatal(string msg)
|
||||
{
|
||||
}
|
||||
|
||||
public void Fatal(string format, object arg)
|
||||
{
|
||||
}
|
||||
|
||||
public void Fatal(string format, object arg1, object arg2)
|
||||
{
|
||||
}
|
||||
|
||||
public void Fatal(string format, params object[] arguments)
|
||||
{
|
||||
}
|
||||
|
||||
public void Fatal(string msg, Exception t)
|
||||
{
|
||||
}
|
||||
|
||||
public void Log(LogLevel level, string message)
|
||||
{
|
||||
}
|
||||
|
||||
public void Log(LogLevel level, string format, object arg)
|
||||
{
|
||||
}
|
||||
|
||||
public void Log(LogLevel level, string format, object arg1, object arg2)
|
||||
{
|
||||
}
|
||||
|
||||
public void Log(LogLevel level, string format, params object[] arguments)
|
||||
{
|
||||
}
|
||||
|
||||
public void Log(LogLevel level, string message, Exception exception)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,8 +1,8 @@
|
||||
using System.Reflection;
|
||||
using GFramework.Core.Abstractions.Cqrs;
|
||||
using GFramework.Core.Abstractions.Logging;
|
||||
using GFramework.Core.Architectures;
|
||||
using GFramework.Core.Logging;
|
||||
using GFramework.Cqrs;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs;
|
||||
|
||||
namespace GFramework.Core.Tests.Architectures;
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
using GFramework.Core.Abstractions.Architectures;
|
||||
using GFramework.Core.Abstractions.Enums;
|
||||
using GFramework.Core.Abstractions.Logging;
|
||||
using GFramework.Core.Abstractions.Model;
|
||||
using GFramework.Core.Abstractions.Systems;
|
||||
using GFramework.Core.Abstractions.Utility;
|
||||
using GFramework.Core.Architectures;
|
||||
using GFramework.Core.Logging;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace GFramework.Core.Tests.Architectures;
|
||||
|
||||
@ -714,4 +714,4 @@ public class ArchitectureComponentRegistryBehaviorTests
|
||||
return _context;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,6 +5,7 @@ using GFramework.Core.Abstractions.Cqrs;
|
||||
using GFramework.Core.Abstractions.Enums;
|
||||
using GFramework.Core.Abstractions.Environment;
|
||||
using GFramework.Core.Abstractions.Ioc;
|
||||
using GFramework.Core.Abstractions.Logging;
|
||||
using GFramework.Core.Abstractions.Model;
|
||||
using GFramework.Core.Abstractions.Query;
|
||||
using GFramework.Core.Abstractions.Systems;
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
using GFramework.Core.Abstractions.Enums;
|
||||
using GFramework.Core.Abstractions.Events;
|
||||
using GFramework.Core.Abstractions.Logging;
|
||||
using GFramework.Core.Architectures;
|
||||
using GFramework.Core.Environment;
|
||||
using GFramework.Core.Logging;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace GFramework.Core.Tests.Architectures;
|
||||
|
||||
@ -185,4 +185,4 @@ public class ArchitectureInitializationPipelineTests
|
||||
private sealed class BootstrapMarker
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,12 +2,12 @@ using System.Reflection;
|
||||
using GFramework.Core.Abstractions.Architectures;
|
||||
using GFramework.Core.Abstractions.Enums;
|
||||
using GFramework.Core.Abstractions.Lifecycle;
|
||||
using GFramework.Core.Abstractions.Logging;
|
||||
using GFramework.Core.Abstractions.Model;
|
||||
using GFramework.Core.Abstractions.Systems;
|
||||
using GFramework.Core.Abstractions.Utility;
|
||||
using GFramework.Core.Architectures;
|
||||
using GFramework.Core.Logging;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace GFramework.Core.Tests.Architectures;
|
||||
|
||||
@ -460,4 +460,4 @@ public class ArchitectureLifecycleBehaviorTests
|
||||
return _context;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
using GFramework.Core.Abstractions.Architectures;
|
||||
using GFramework.Core.Abstractions.Logging;
|
||||
using GFramework.Core.Abstractions.Utility;
|
||||
using GFramework.Core.Architectures;
|
||||
using GFramework.Core.Logging;
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
using System.Reflection;
|
||||
using GFramework.Core.Abstractions.Bases;
|
||||
using GFramework.Core.Abstractions.Logging;
|
||||
using GFramework.Core.Abstractions.Model;
|
||||
using GFramework.Core.Abstractions.Systems;
|
||||
using GFramework.Core.Abstractions.Utility;
|
||||
@ -244,4 +245,4 @@ public class PriorityTestUtilityC : IPriorityTestUtility, IPrioritized
|
||||
public int Priority => 30;
|
||||
}
|
||||
|
||||
#endregion
|
||||
#endregion
|
||||
|
||||
@ -0,0 +1,76 @@
|
||||
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;
|
||||
using GFramework.Cqrs.Command;
|
||||
using GFramework.Cqrs.Notification;
|
||||
using GFramework.Cqrs.Query;
|
||||
using GFramework.Cqrs.Request;
|
||||
|
||||
namespace GFramework.Core.Tests.Cqrs;
|
||||
|
||||
/// <summary>
|
||||
/// 锁定 CQRS 基础消息类型在 runtime 拆分后的公开命名空间与程序集兼容性。
|
||||
/// </summary>
|
||||
[TestFixture]
|
||||
public sealed class CqrsPublicNamespaceCompatibilityTests
|
||||
{
|
||||
/// <summary>
|
||||
/// 验证基础消息类型继续暴露在历史公开 CQRS 命名空间(GFramework.Cqrs.*),同时由独立 runtime 程序集承载实现。
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void Base_Message_Types_Should_Live_In_Cqrs_Namespaces_And_Runtime_Assembly()
|
||||
{
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
AssertLegacyType(typeof(CommandBase<TestCommandInput, Unit>), "GFramework.Cqrs.Command");
|
||||
AssertLegacyType(typeof(QueryBase<TestQueryInput, string>), "GFramework.Cqrs.Query");
|
||||
AssertLegacyType(typeof(RequestBase<TestRequestInput, string>), "GFramework.Cqrs.Request");
|
||||
AssertLegacyType(typeof(NotificationBase<TestNotificationInput>), "GFramework.Cqrs.Notification");
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证旧的 GFramework.Core 程序集限定名仍可解析到迁移后的 runtime 实现类型。
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void Type_Forwarding_Should_Resolve_Cqrs_Types_From_Core_Assembly()
|
||||
{
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
AssertForwardedType("GFramework.Cqrs.Command.CommandBase`2, GFramework.Core");
|
||||
AssertForwardedType("GFramework.Cqrs.Query.QueryBase`2, GFramework.Core");
|
||||
AssertForwardedType("GFramework.Cqrs.Request.RequestBase`2, GFramework.Core");
|
||||
AssertForwardedType("GFramework.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: false);
|
||||
|
||||
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,6 +1,7 @@
|
||||
using System.Reflection;
|
||||
using GFramework.Core.Abstractions.Bases;
|
||||
using GFramework.Core.Abstractions.Cqrs;
|
||||
using GFramework.Core.Abstractions.Logging;
|
||||
using GFramework.Core.Ioc;
|
||||
using GFramework.Core.Logging;
|
||||
using GFramework.Core.Tests.Cqrs;
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
using System.Reflection;
|
||||
using GFramework.Core.Abstractions.Enums;
|
||||
using GFramework.Core.Abstractions.Logging;
|
||||
using GFramework.Core.Abstractions.State;
|
||||
using GFramework.Core.Abstractions.Systems;
|
||||
using GFramework.Core.Architectures;
|
||||
@ -373,4 +374,4 @@ public class TestStateV5_2 : IState
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
#endregion
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
using System.ComponentModel;
|
||||
using GFramework.Core.Abstractions.Rule;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs.Command;
|
||||
using GFramework.Cqrs.Extensions;
|
||||
|
||||
namespace GFramework.Core.Extensions;
|
||||
|
||||
@ -11,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>
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
using System.ComponentModel;
|
||||
using GFramework.Core.Abstractions.Rule;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs;
|
||||
using GFramework.Cqrs.Extensions;
|
||||
|
||||
namespace GFramework.Core.Extensions;
|
||||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
using System.ComponentModel;
|
||||
using GFramework.Core.Abstractions.Rule;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs.Query;
|
||||
using GFramework.Cqrs.Extensions;
|
||||
|
||||
namespace GFramework.Core.Extensions;
|
||||
|
||||
@ -11,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>
|
||||
|
||||
@ -10,6 +10,7 @@
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\GFramework.Cqrs.Abstractions\GFramework.Cqrs.Abstractions.csproj"/>
|
||||
<ProjectReference Include="..\GFramework.Cqrs\GFramework.Cqrs.csproj"/>
|
||||
<ProjectReference Include="..\$(AssemblyName).Abstractions\$(AssemblyName).Abstractions.csproj"/>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
||||
@ -1,26 +0,0 @@
|
||||
using GFramework.Core.Abstractions.Logging;
|
||||
|
||||
namespace GFramework.Core.Logging;
|
||||
|
||||
/// <summary>
|
||||
/// 日志工厂提供程序解析器,用于管理和提供日志工厂提供程序实例
|
||||
/// </summary>
|
||||
public static class LoggerFactoryResolver
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取或设置当前的日志工厂提供程序
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// 日志工厂提供程序实例,默认为控制台日志工厂提供程序
|
||||
/// </value>
|
||||
public static ILoggerFactoryProvider Provider { get; set; }
|
||||
= new ConsoleLoggerFactoryProvider();
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置日志记录的最小级别
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// 日志级别枚举值,默认为Info级别
|
||||
/// </value>
|
||||
public static LogLevel MinLevel { get; set; } = LogLevel.Info;
|
||||
}
|
||||
12
GFramework.Core/Properties/TypeForwarders.cs
Normal file
12
GFramework.Core/Properties/TypeForwarders.cs
Normal file
@ -0,0 +1,12 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using GFramework.Core.Abstractions.Logging;
|
||||
using GFramework.Cqrs.Command;
|
||||
using GFramework.Cqrs.Notification;
|
||||
using GFramework.Cqrs.Query;
|
||||
using GFramework.Cqrs.Request;
|
||||
|
||||
[assembly: TypeForwardedTo(typeof(LoggerFactoryResolver))]
|
||||
[assembly: TypeForwardedTo(typeof(CommandBase<,>))]
|
||||
[assembly: TypeForwardedTo(typeof(QueryBase<,>))]
|
||||
[assembly: TypeForwardedTo(typeof(RequestBase<,>))]
|
||||
[assembly: TypeForwardedTo(typeof(NotificationBase<>))]
|
||||
@ -1,8 +1,8 @@
|
||||
using GFramework.Core.Abstractions.Architectures;
|
||||
using GFramework.Core.Abstractions.Cqrs;
|
||||
using GFramework.Core.Abstractions.Ioc;
|
||||
using GFramework.Core.Cqrs.Internal;
|
||||
using GFramework.Core.Logging;
|
||||
using GFramework.Core.Abstractions.Logging;
|
||||
using GFramework.Cqrs;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs;
|
||||
|
||||
namespace GFramework.Core.Services.Modules;
|
||||
@ -37,11 +37,12 @@ public sealed class CqrsRuntimeModule : IServiceModule
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(container);
|
||||
|
||||
var dispatcherLogger = LoggerFactoryResolver.Provider.CreateLogger(nameof(CqrsDispatcher));
|
||||
var registrarLogger = LoggerFactoryResolver.Provider.CreateLogger(nameof(DefaultCqrsHandlerRegistrar));
|
||||
var dispatcherLogger = LoggerFactoryResolver.Provider.CreateLogger("CqrsDispatcher");
|
||||
var registrarLogger = LoggerFactoryResolver.Provider.CreateLogger("DefaultCqrsHandlerRegistrar");
|
||||
|
||||
container.Register<ICqrsRuntime>(new CqrsDispatcher(container, dispatcherLogger));
|
||||
container.Register<ICqrsHandlerRegistrar>(new DefaultCqrsHandlerRegistrar(container, registrarLogger));
|
||||
container.Register<ICqrsRuntime>(CqrsRuntimeFactory.CreateRuntime(container, dispatcherLogger));
|
||||
container.Register<ICqrsHandlerRegistrar>(
|
||||
CqrsRuntimeFactory.CreateHandlerRegistrar(container, registrarLogger));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@ -0,0 +1,74 @@
|
||||
using GFramework.Core.Abstractions.Architectures;
|
||||
using GFramework.Core.Abstractions.Rule;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs.Command;
|
||||
using GFramework.Cqrs.Cqrs.Command;
|
||||
|
||||
namespace GFramework.Cqrs.Tests.Cqrs;
|
||||
|
||||
/// <summary>
|
||||
/// 验证 CQRS handler 基类在脱离 dispatcher 使用时会显式失败,并在注入上下文后保持可观察行为。
|
||||
/// </summary>
|
||||
[TestFixture]
|
||||
internal sealed class AbstractCqrsHandlerContextTests
|
||||
{
|
||||
/// <summary>
|
||||
/// 验证新的轻量 handler 基类不会再偷偷回退到全局 GameContext。
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void GetContext_Should_Throw_When_Handler_Has_Not_Been_Initialized_By_Runtime()
|
||||
{
|
||||
var handler = new TestCommandHandler();
|
||||
|
||||
var exception = Assert.Throws<InvalidOperationException>(() => ((IContextAware)handler).GetContext());
|
||||
|
||||
Assert.That(
|
||||
exception!.Message,
|
||||
Does.Contain("has not been initialized").IgnoreCase);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证 runtime 注入上下文后,派生 handler 可以继续访问 Context 并收到 OnContextReady 回调。
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task Handle_Should_Observe_Injected_Context_And_OnContextReady_Callback()
|
||||
{
|
||||
var handler = new TestCommandHandler();
|
||||
var context = new Mock<IArchitectureContext>(MockBehavior.Strict).Object;
|
||||
|
||||
((IContextAware)handler).SetContext(context);
|
||||
await handler.Handle(new TestCommand(), CancellationToken.None);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(handler.OnContextReadyCallCount, Is.EqualTo(1));
|
||||
Assert.That(handler.LastObservedContext, Is.SameAs(context));
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 用于验证上下文注入行为的最小 CQRS 命令。
|
||||
/// </summary>
|
||||
private sealed record TestCommand : ICommand<Unit>;
|
||||
|
||||
/// <summary>
|
||||
/// 暴露基类上下文访问与初始化回调的测试处理器。
|
||||
/// </summary>
|
||||
private sealed class TestCommandHandler : AbstractCommandHandler<TestCommand>
|
||||
{
|
||||
public int OnContextReadyCallCount { get; private set; }
|
||||
|
||||
public IArchitectureContext? LastObservedContext { get; private set; }
|
||||
|
||||
protected override void OnContextReady()
|
||||
{
|
||||
OnContextReadyCallCount++;
|
||||
}
|
||||
|
||||
public override ValueTask<Unit> Handle(TestCommand command, CancellationToken cancellationToken)
|
||||
{
|
||||
LastObservedContext = Context;
|
||||
return ValueTask.FromResult(Unit.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,3 @@
|
||||
using GFramework.Core.Abstractions.Cqrs;
|
||||
using GFramework.Core.Abstractions.Logging;
|
||||
using GFramework.Core.Architectures;
|
||||
using GFramework.Core.Ioc;
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
using GFramework.Core.Abstractions.Logging;
|
||||
using GFramework.Core.Architectures;
|
||||
using GFramework.Core.Ioc;
|
||||
using GFramework.Core.Logging;
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
using GFramework.Core.Abstractions.Architectures;
|
||||
using GFramework.Core.Abstractions.Logging;
|
||||
using GFramework.Core.Architectures;
|
||||
using GFramework.Core.Command;
|
||||
using GFramework.Core.Ioc;
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
using GFramework.Core.Abstractions.Architectures;
|
||||
using GFramework.Core.Abstractions.Events;
|
||||
using GFramework.Core.Abstractions.Logging;
|
||||
using GFramework.Core.Architectures;
|
||||
using GFramework.Core.Command;
|
||||
using GFramework.Core.Environment;
|
||||
|
||||
@ -13,19 +13,23 @@
|
||||
|
||||
using GFramework.Cqrs.Abstractions.Cqrs.Command;
|
||||
|
||||
namespace GFramework.Core.Cqrs.Command;
|
||||
namespace GFramework.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;
|
||||
}
|
||||
@ -11,19 +11,20 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using System.Diagnostics;
|
||||
using GFramework.Core.Abstractions.Logging;
|
||||
using GFramework.Core.Logging;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs;
|
||||
|
||||
namespace GFramework.Core.Cqrs.Behaviors;
|
||||
namespace GFramework.Cqrs.Cqrs.Behaviors;
|
||||
|
||||
/// <summary>
|
||||
/// 日志记录行为类,用于在CQRS管道中记录请求处理的日志信息
|
||||
/// 实现IPipelineBehavior接口,为请求处理提供日志记录功能
|
||||
/// 在 CQRS 请求管道中记录请求开始、完成、取消与失败日志。
|
||||
/// </summary>
|
||||
/// <typeparam name="TRequest">请求类型,必须实现IRequest接口</typeparam>
|
||||
/// <typeparam name="TResponse">响应类型</typeparam>
|
||||
/// <typeparam name="TRequest">请求类型。</typeparam>
|
||||
/// <typeparam name="TResponse">响应类型。</typeparam>
|
||||
/// <remarks>
|
||||
/// 该行为已迁移到 <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>
|
||||
{
|
||||
@ -31,13 +32,12 @@ public sealed class LoggingBehavior<TRequest, TResponse> : IPipelineBehavior<TRe
|
||||
LoggerFactoryResolver.Provider.CreateLogger(nameof(LoggingBehavior<TRequest, TResponse>));
|
||||
|
||||
/// <summary>
|
||||
/// 处理请求并记录日志
|
||||
/// 在请求处理前后记录调试信息,处理异常时记录错误日志
|
||||
/// 执行日志包装后的下一段请求处理逻辑。
|
||||
/// </summary>
|
||||
/// <param name="message">要处理的请求消息</param>
|
||||
/// <param name="next">下一个处理委托,用于继续管道执行</param>
|
||||
/// <param name="cancellationToken">取消令牌,用于取消操作</param>
|
||||
/// <returns>处理结果的ValueTask</returns>
|
||||
/// <param name="message">当前请求消息。</param>
|
||||
/// <param name="next">后续处理委托。</param>
|
||||
/// <param name="cancellationToken">取消令牌。</param>
|
||||
/// <returns>请求处理结果。</returns>
|
||||
public async ValueTask<TResponse> Handle(
|
||||
TRequest message,
|
||||
MessageHandlerDelegate<TRequest, TResponse> next,
|
||||
@ -11,33 +11,34 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using System.Diagnostics;
|
||||
using GFramework.Core.Abstractions.Logging;
|
||||
using GFramework.Core.Logging;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs;
|
||||
|
||||
namespace GFramework.Core.Cqrs.Behaviors;
|
||||
namespace GFramework.Cqrs.Cqrs.Behaviors;
|
||||
|
||||
/// <summary>
|
||||
/// 性能监控行为类,用于监控CQRS请求的执行时间
|
||||
/// 实现IPipelineBehavior接口,检测并记录执行时间过长的请求
|
||||
/// 在 CQRS 请求管道中监控处理耗时,并对长耗时请求发出告警。
|
||||
/// </summary>
|
||||
/// <typeparam name="TRequest">请求类型,必须实现IRequest接口</typeparam>
|
||||
/// <typeparam name="TResponse">响应类型</typeparam>
|
||||
/// <typeparam name="TRequest">请求类型。</typeparam>
|
||||
/// <typeparam name="TResponse">响应类型。</typeparam>
|
||||
/// <remarks>
|
||||
/// 该行为保留现有公开命名空间以维持消费端兼容性,但实现已迁入 <c>GFramework.Cqrs</c> 程序集。
|
||||
/// </remarks>
|
||||
public sealed class PerformanceBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
|
||||
where TRequest : IRequest<TResponse>
|
||||
{
|
||||
private const double SlowRequestThresholdMilliseconds = 500;
|
||||
|
||||
private readonly ILogger _logger =
|
||||
LoggerFactoryResolver.Provider.CreateLogger(nameof(PerformanceBehavior<TRequest, TResponse>));
|
||||
|
||||
/// <summary>
|
||||
/// 处理请求并监控执行时间
|
||||
/// 使用Stopwatch测量请求处理耗时,超过500ms时记录警告日志
|
||||
/// 统计当前请求处理耗时,并在超阈值时记录警告日志。
|
||||
/// </summary>
|
||||
/// <param name="message">要处理的请求消息</param>
|
||||
/// <param name="next">下一个处理委托,用于继续管道执行</param>
|
||||
/// <param name="cancellationToken">取消令牌,用于取消操作</param>
|
||||
/// <returns>处理结果的ValueTask</returns>
|
||||
/// <param name="message">当前请求消息。</param>
|
||||
/// <param name="next">后续处理委托。</param>
|
||||
/// <param name="cancellationToken">取消令牌。</param>
|
||||
/// <returns>请求处理结果。</returns>
|
||||
public async ValueTask<TResponse> Handle(
|
||||
TRequest message,
|
||||
MessageHandlerDelegate<TRequest, TResponse> next,
|
||||
@ -53,11 +54,10 @@ public sealed class PerformanceBehavior<TRequest, TResponse> : IPipelineBehavior
|
||||
{
|
||||
var elapsed = Stopwatch.GetElapsedTime(start);
|
||||
|
||||
if (elapsed.TotalMilliseconds > 500)
|
||||
if (elapsed.TotalMilliseconds > SlowRequestThresholdMilliseconds)
|
||||
{
|
||||
var requestName = typeof(TRequest).Name;
|
||||
_logger.Warn(
|
||||
$"Long Running Request: {requestName} ({elapsed.TotalMilliseconds:F2} ms)");
|
||||
_logger.Warn($"Long Running Request: {requestName} ({elapsed.TotalMilliseconds:F2} ms)");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -11,19 +11,18 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using GFramework.Core.Rule;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs.Command;
|
||||
|
||||
namespace GFramework.Core.Cqrs.Command;
|
||||
namespace GFramework.Cqrs.Cqrs.Command;
|
||||
|
||||
/// <summary>
|
||||
/// 抽象命令处理器基类
|
||||
/// 继承自 ContextAwareBase 并实现 IRequestHandler 接口,为具体的命令处理器提供基础功能。
|
||||
/// 继承自轻量 CQRS 上下文基类并实现 IRequestHandler 接口,为具体的命令处理器提供基础功能。
|
||||
/// 框架会在每次分发前注入当前架构上下文,因此派生类可以通过 Context 访问架构级服务。
|
||||
/// </summary>
|
||||
/// <typeparam name="TCommand">命令类型</typeparam>
|
||||
public abstract class AbstractCommandHandler<TCommand> : ContextAwareBase, IRequestHandler<TCommand, Unit>
|
||||
public abstract class AbstractCommandHandler<TCommand> : CqrsContextAwareHandlerBase, IRequestHandler<TCommand, Unit>
|
||||
where TCommand : ICommand<Unit>
|
||||
{
|
||||
/// <summary>
|
||||
@ -38,12 +37,13 @@ public abstract class AbstractCommandHandler<TCommand> : ContextAwareBase, IRequ
|
||||
|
||||
/// <summary>
|
||||
/// 抽象命令处理器基类(带返回值版本)
|
||||
/// 继承自 ContextAwareBase 并实现 IRequestHandler 接口,为具体的命令处理器提供基础功能。
|
||||
/// 继承自轻量 CQRS 上下文基类并实现 IRequestHandler 接口,为具体的命令处理器提供基础功能。
|
||||
/// 支持泛型命令和结果类型,框架会在每次分发前注入当前架构上下文。
|
||||
/// </summary>
|
||||
/// <typeparam name="TCommand">命令类型,必须实现ICommand接口</typeparam>
|
||||
/// <typeparam name="TResult">命令执行结果类型</typeparam>
|
||||
public abstract class AbstractCommandHandler<TCommand, TResult> : ContextAwareBase, IRequestHandler<TCommand, TResult>
|
||||
public abstract class AbstractCommandHandler<TCommand, TResult> : CqrsContextAwareHandlerBase,
|
||||
IRequestHandler<TCommand, TResult>
|
||||
where TCommand : ICommand<TResult>
|
||||
{
|
||||
/// <summary>
|
||||
@ -11,15 +11,14 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using GFramework.Core.Rule;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs.Command;
|
||||
|
||||
namespace GFramework.Core.Cqrs.Command;
|
||||
namespace GFramework.Cqrs.Cqrs.Command;
|
||||
|
||||
/// <summary>
|
||||
/// 抽象流式命令处理器基类。
|
||||
/// 继承自 <see cref="ContextAwareBase" /> 并实现 <see cref="IStreamRequestHandler{TRequest,TResponse}" />,
|
||||
/// 继承自轻量 CQRS 上下文基类并实现 <see cref="IStreamRequestHandler{TRequest,TResponse}" />,
|
||||
/// 为具体的流式命令处理器提供基础功能。
|
||||
/// </summary>
|
||||
/// <typeparam name="TCommand">流式命令类型,必须实现 <see cref="IStreamCommand{TResponse}" />。</typeparam>
|
||||
@ -32,7 +31,7 @@ namespace GFramework.Core.Cqrs.Command;
|
||||
/// 传入 <see cref="Handle" /> 的取消令牌同时约束流的创建与后续枚举,
|
||||
/// 派生类应在启动阶段和每次生成响应前尊重取消请求,避免在调用方停止枚举后继续执行后台工作。
|
||||
/// </remarks>
|
||||
public abstract class AbstractStreamCommandHandler<TCommand, TResponse> : ContextAwareBase,
|
||||
public abstract class AbstractStreamCommandHandler<TCommand, TResponse> : CqrsContextAwareHandlerBase,
|
||||
IStreamRequestHandler<TCommand, TResponse>
|
||||
where TCommand : IStreamCommand<TResponse>
|
||||
{
|
||||
59
GFramework.Cqrs/Cqrs/CqrsContextAwareHandlerBase.cs
Normal file
59
GFramework.Cqrs/Cqrs/CqrsContextAwareHandlerBase.cs
Normal file
@ -0,0 +1,59 @@
|
||||
using GFramework.Core.Abstractions.Architectures;
|
||||
using GFramework.Core.Abstractions.Rule;
|
||||
|
||||
namespace GFramework.Cqrs.Cqrs;
|
||||
|
||||
/// <summary>
|
||||
/// 为 CQRS 处理器提供最小化的上下文感知基类实现。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 该基类只承接 CQRS runtime 在分发前注入的 <see cref="IArchitectureContext" />,
|
||||
/// 不再像 <c>ContextAwareBase</c> 那样回退到 <c>GameContext</c> 全局查找。
|
||||
/// 这样可以让 <c>GFramework.Cqrs</c> 保持对 <c>GFramework.Core</c> 运行时实现的零依赖,
|
||||
/// 同时在处理器被错误地脱离 dispatcher 使用时以显式异常快速失败。
|
||||
/// </remarks>
|
||||
public abstract class CqrsContextAwareHandlerBase : IContextAware
|
||||
{
|
||||
private IArchitectureContext? _context;
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前分发周期内已注入的架构上下文。
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidOperationException">
|
||||
/// 当前处理器尚未被 CQRS runtime 注入上下文。
|
||||
/// </exception>
|
||||
protected IArchitectureContext Context => _context ?? throw new InvalidOperationException(
|
||||
"The CQRS handler context has not been initialized. Ensure the handler is invoked through the CQRS runtime.");
|
||||
|
||||
/// <summary>
|
||||
/// 由 runtime 在分发前注入当前架构上下文。
|
||||
/// </summary>
|
||||
/// <param name="context">当前架构上下文。</param>
|
||||
void IContextAware.SetContext(IArchitectureContext context)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(context);
|
||||
|
||||
_context = context;
|
||||
OnContextReady();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前处理器实例已绑定的架构上下文。
|
||||
/// </summary>
|
||||
/// <returns>当前分发周期内的架构上下文。</returns>
|
||||
IArchitectureContext IContextAware.GetContext()
|
||||
{
|
||||
return Context;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当上下文注入完成后执行额外初始化。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 该钩子保留与旧 <c>ContextAwareBase</c> 相近的扩展点,
|
||||
/// 便于处理器在迁移后继续承接分发前的派生类初始化逻辑。
|
||||
/// </remarks>
|
||||
protected virtual void OnContextReady()
|
||||
{
|
||||
}
|
||||
}
|
||||
@ -11,18 +11,18 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using GFramework.Core.Rule;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs;
|
||||
|
||||
namespace GFramework.Core.Cqrs.Notification;
|
||||
namespace GFramework.Cqrs.Cqrs.Notification;
|
||||
|
||||
/// <summary>
|
||||
/// 抽象通知处理器基类
|
||||
/// 继承自ContextAwareBase并实现INotificationHandler接口,为具体的通知处理器提供基础功能
|
||||
/// 继承自轻量 CQRS 上下文基类并实现INotificationHandler接口,为具体的通知处理器提供基础功能
|
||||
/// 用于处理CQRS模式中的通知消息,支持异步处理
|
||||
/// </summary>
|
||||
/// <typeparam name="TNotification">通知类型,必须实现INotification接口</typeparam>
|
||||
public abstract class AbstractNotificationHandler<TNotification> : ContextAwareBase, INotificationHandler<TNotification>
|
||||
public abstract class AbstractNotificationHandler<TNotification> : CqrsContextAwareHandlerBase,
|
||||
INotificationHandler<TNotification>
|
||||
where TNotification : INotification
|
||||
{
|
||||
/// <summary>
|
||||
@ -11,20 +11,20 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using GFramework.Core.Rule;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs.Query;
|
||||
|
||||
namespace GFramework.Core.Cqrs.Query;
|
||||
namespace GFramework.Cqrs.Cqrs.Query;
|
||||
|
||||
/// <summary>
|
||||
/// 抽象查询处理器基类
|
||||
/// 继承自 ContextAwareBase 并实现 IRequestHandler 接口,为具体的查询处理器提供基础功能。
|
||||
/// 继承自轻量 CQRS 上下文基类并实现 IRequestHandler 接口,为具体的查询处理器提供基础功能。
|
||||
/// 框架会在每次分发前注入当前架构上下文,因此派生类可以通过 Context 访问架构级服务。
|
||||
/// </summary>
|
||||
/// <typeparam name="TQuery">查询类型,必须实现IQuery接口</typeparam>
|
||||
/// <typeparam name="TResult">查询结果类型</typeparam>
|
||||
public abstract class AbstractQueryHandler<TQuery, TResult> : ContextAwareBase, IRequestHandler<TQuery, TResult>
|
||||
public abstract class AbstractQueryHandler<TQuery, TResult> : CqrsContextAwareHandlerBase,
|
||||
IRequestHandler<TQuery, TResult>
|
||||
where TQuery : IQuery<TResult>
|
||||
{
|
||||
/// <summary>
|
||||
@ -11,29 +11,30 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using GFramework.Core.Rule;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs.Query;
|
||||
|
||||
namespace GFramework.Core.Cqrs.Query;
|
||||
namespace GFramework.Cqrs.Cqrs.Query;
|
||||
|
||||
/// <summary>
|
||||
/// 抽象流式查询处理器基类
|
||||
/// 继承自ContextAwareBase并实现IStreamQueryHandler接口,为具体的流式查询处理器提供基础功能
|
||||
/// 支持流式处理查询并产生异步可枚举的响应序列,适用于大数据量或实时数据查询场景
|
||||
/// 为流式查询处理器提供共享的 CQRS 上下文访问基类。
|
||||
/// </summary>
|
||||
/// <typeparam name="TQuery">流式查询类型,必须实现IStreamQuery接口</typeparam>
|
||||
/// <typeparam name="TResponse">流式查询响应元素类型</typeparam>
|
||||
public abstract class AbstractStreamQueryHandler<TQuery, TResponse> : ContextAwareBase,
|
||||
/// <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);
|
||||
}
|
||||
@ -11,17 +11,16 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using GFramework.Core.Rule;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs;
|
||||
|
||||
namespace GFramework.Core.Cqrs.Request;
|
||||
namespace GFramework.Cqrs.Cqrs.Request;
|
||||
|
||||
/// <summary>
|
||||
/// 抽象请求处理器基类,用于处理不返回具体响应的请求
|
||||
/// 继承自ContextAwareBase并实现IRequestHandler接口
|
||||
/// 继承自轻量 CQRS 上下文基类并实现IRequestHandler接口
|
||||
/// </summary>
|
||||
/// <typeparam name="TRequest">请求类型,必须实现IRequest[Unit]接口</typeparam>
|
||||
public abstract class AbstractRequestHandler<TRequest> : ContextAwareBase, IRequestHandler<TRequest, Unit>
|
||||
public abstract class AbstractRequestHandler<TRequest> : CqrsContextAwareHandlerBase, IRequestHandler<TRequest, Unit>
|
||||
where TRequest : IRequest<Unit>
|
||||
{
|
||||
/// <summary>
|
||||
@ -35,11 +34,11 @@ public abstract class AbstractRequestHandler<TRequest> : ContextAwareBase, IRequ
|
||||
|
||||
/// <summary>
|
||||
/// 抽象请求处理器基类,用于处理需要返回具体响应的请求
|
||||
/// 继承自ContextAwareBase并实现IRequestHandler接口
|
||||
/// 继承自轻量 CQRS 上下文基类并实现IRequestHandler接口
|
||||
/// </summary>
|
||||
/// <typeparam name="TRequest">请求类型,必须实现IRequest[TResponse]接口</typeparam>
|
||||
/// <typeparam name="TResponse">响应类型</typeparam>
|
||||
public abstract class AbstractRequestHandler<TRequest, TResponse> : ContextAwareBase,
|
||||
public abstract class AbstractRequestHandler<TRequest, TResponse> : CqrsContextAwareHandlerBase,
|
||||
IRequestHandler<TRequest, TResponse> where TRequest : IRequest<TResponse>
|
||||
{
|
||||
/// <summary>
|
||||
@ -11,19 +11,18 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using GFramework.Core.Rule;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs;
|
||||
|
||||
namespace GFramework.Core.Cqrs.Request;
|
||||
namespace GFramework.Cqrs.Cqrs.Request;
|
||||
|
||||
/// <summary>
|
||||
/// 抽象流式请求处理器基类
|
||||
/// 继承自ContextAwareBase并实现IStreamRequestHandler接口,为具体的流式请求处理器提供基础功能
|
||||
/// 继承自轻量 CQRS 上下文基类并实现IStreamRequestHandler接口,为具体的流式请求处理器提供基础功能
|
||||
/// 支持流式处理请求并产生异步可枚举的响应序列,适用于需要逐步返回结果的请求处理场景
|
||||
/// </summary>
|
||||
/// <typeparam name="TRequest">流式请求类型,必须实现IStreamRequest接口</typeparam>
|
||||
/// <typeparam name="TResponse">流式请求响应元素类型</typeparam>
|
||||
public abstract class AbstractStreamRequestHandler<TRequest, TResponse> : ContextAwareBase,
|
||||
public abstract class AbstractStreamRequestHandler<TRequest, TResponse> : CqrsContextAwareHandlerBase,
|
||||
IStreamRequestHandler<TRequest, TResponse>
|
||||
where TRequest : IStreamRequest<TResponse>
|
||||
{
|
||||
@ -1,4 +1,4 @@
|
||||
namespace GFramework.Core.Abstractions.Cqrs;
|
||||
namespace GFramework.Cqrs;
|
||||
|
||||
/// <summary>
|
||||
/// 声明程序集内可供运行时直接调用的 CQRS 处理器注册器类型。
|
||||
51
GFramework.Cqrs/CqrsRuntimeFactory.cs
Normal file
51
GFramework.Cqrs/CqrsRuntimeFactory.cs
Normal file
@ -0,0 +1,51 @@
|
||||
using GFramework.Core.Abstractions.Cqrs;
|
||||
using GFramework.Core.Abstractions.Ioc;
|
||||
using GFramework.Core.Abstractions.Logging;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs;
|
||||
using GFramework.Cqrs.Internal;
|
||||
|
||||
namespace GFramework.Cqrs;
|
||||
|
||||
/// <summary>
|
||||
/// 提供 CQRS runtime 默认实现的跨程序集创建入口。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <see cref="GFramework.Core" /> 需要在不暴露内部实现细节的前提下接入默认 CQRS runtime,
|
||||
/// 因此通过该工厂返回抽象接口,而不是直接公开内部 dispatcher / registrar 类型。
|
||||
/// </remarks>
|
||||
public static class CqrsRuntimeFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// 创建默认 CQRS runtime 分发器。
|
||||
/// </summary>
|
||||
/// <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);
|
||||
ArgumentNullException.ThrowIfNull(logger);
|
||||
|
||||
return new CqrsDispatcher(container, logger);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建默认 CQRS 处理器注册器。
|
||||
/// </summary>
|
||||
/// <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);
|
||||
ArgumentNullException.ThrowIfNull(logger);
|
||||
|
||||
return new DefaultCqrsHandlerRegistrar(container, logger);
|
||||
}
|
||||
}
|
||||
@ -1,7 +1,7 @@
|
||||
using GFramework.Core.Abstractions.Rule;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs.Command;
|
||||
|
||||
namespace GFramework.Core.Extensions;
|
||||
namespace GFramework.Cqrs.Extensions;
|
||||
|
||||
/// <summary>
|
||||
/// 提供对 <see cref="IContextAware" /> 接口的 CQRS 命令扩展方法。
|
||||
@ -1,7 +1,7 @@
|
||||
using GFramework.Core.Abstractions.Rule;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs;
|
||||
|
||||
namespace GFramework.Core.Extensions;
|
||||
namespace GFramework.Cqrs.Extensions;
|
||||
|
||||
/// <summary>
|
||||
/// 提供对 <see cref="IContextAware" /> 接口的 CQRS 统一扩展方法。
|
||||
@ -1,7 +1,7 @@
|
||||
using GFramework.Core.Abstractions.Rule;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs.Query;
|
||||
|
||||
namespace GFramework.Core.Extensions;
|
||||
namespace GFramework.Cqrs.Extensions;
|
||||
|
||||
/// <summary>
|
||||
/// 提供对 <see cref="IContextAware" /> 接口的 CQRS 查询扩展方法。
|
||||
@ -11,6 +11,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\GFramework.Cqrs.Abstractions\GFramework.Cqrs.Abstractions.csproj"/>
|
||||
<ProjectReference Include="..\GFramework.Core.Abstractions\GFramework.Core.Abstractions.csproj"/>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
7
GFramework.Cqrs/GlobalUsings.cs
Normal file
7
GFramework.Cqrs/GlobalUsings.cs
Normal file
@ -0,0 +1,7 @@
|
||||
global using System;
|
||||
global using System.Collections.Generic;
|
||||
global using System.Linq;
|
||||
global using System.Threading;
|
||||
global using System.Threading.Tasks;
|
||||
global using Microsoft.Extensions.DependencyInjection;
|
||||
global using System.Diagnostics;
|
||||
@ -1,6 +1,6 @@
|
||||
using GFramework.Core.Abstractions.Logging;
|
||||
|
||||
namespace GFramework.Core.Abstractions.Cqrs;
|
||||
namespace GFramework.Cqrs;
|
||||
|
||||
/// <summary>
|
||||
/// 定义由源码生成器产出的 CQRS 处理器注册器契约。
|
||||
@ -7,7 +7,7 @@ using GFramework.Core.Abstractions.Logging;
|
||||
using GFramework.Core.Abstractions.Rule;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs;
|
||||
|
||||
namespace GFramework.Core.Cqrs.Internal;
|
||||
namespace GFramework.Cqrs.Internal;
|
||||
|
||||
/// <summary>
|
||||
/// GFramework 自有 CQRS 运行时分发器。
|
||||
@ -1,10 +1,9 @@
|
||||
using System.Reflection;
|
||||
using GFramework.Core.Abstractions.Cqrs;
|
||||
using GFramework.Core.Abstractions.Ioc;
|
||||
using GFramework.Core.Abstractions.Logging;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs;
|
||||
|
||||
namespace GFramework.Core.Cqrs.Internal;
|
||||
namespace GFramework.Cqrs.Internal;
|
||||
|
||||
/// <summary>
|
||||
/// 在架构初始化期间扫描并注册 CQRS 处理器。
|
||||
@ -3,7 +3,7 @@ using GFramework.Core.Abstractions.Ioc;
|
||||
using GFramework.Core.Abstractions.Logging;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs;
|
||||
|
||||
namespace GFramework.Core.Cqrs.Internal;
|
||||
namespace GFramework.Cqrs.Internal;
|
||||
|
||||
/// <summary>
|
||||
/// 默认的 CQRS 处理器注册器实现。
|
||||
@ -14,18 +14,22 @@
|
||||
using GFramework.Cqrs.Abstractions.Cqrs;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs.Notification;
|
||||
|
||||
namespace GFramework.Core.Cqrs.Notification;
|
||||
namespace GFramework.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.Core.Cqrs.Query;
|
||||
namespace GFramework.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.Core.Cqrs.Request;
|
||||
namespace GFramework.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;
|
||||
}
|
||||
@ -1,10 +1,10 @@
|
||||
using GFramework.Core.Abstractions.Rule;
|
||||
using GFramework.Core.Coroutine;
|
||||
using GFramework.Core.Coroutine.Extensions;
|
||||
using GFramework.Core.Extensions;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs.Command;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs.Query;
|
||||
using GFramework.Cqrs.Extensions;
|
||||
|
||||
namespace GFramework.Godot.Coroutine;
|
||||
|
||||
|
||||
@ -15,6 +15,11 @@ public static class PathContests
|
||||
/// </summary>
|
||||
public const string CoreNamespace = $"{BaseNamespace}.Core";
|
||||
|
||||
/// <summary>
|
||||
/// GFramework CQRS runtime 命名空间
|
||||
/// </summary>
|
||||
public const string CqrsNamespace = $"{BaseNamespace}.Cqrs";
|
||||
|
||||
/// <summary>
|
||||
/// GFramework Godot模块命名空间
|
||||
/// </summary>
|
||||
@ -45,4 +50,9 @@ public static class PathContests
|
||||
/// GFramework核心抽象层命名空间
|
||||
/// </summary>
|
||||
public const string CoreAbstractionsNamespace = $"{CoreNamespace}.Abstractions";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// GFramework CQRS 抽象层命名空间
|
||||
/// </summary>
|
||||
public const string CqrsAbstractionsNamespace = $"{CqrsNamespace}.Abstractions";
|
||||
}
|
||||
|
||||
@ -38,7 +38,7 @@ public class CqrsHandlerRegistryGeneratorTests
|
||||
}
|
||||
}
|
||||
|
||||
namespace GFramework.Core.Abstractions.Cqrs
|
||||
namespace GFramework.Cqrs.Abstractions.Cqrs
|
||||
{
|
||||
public interface IRequest<TResponse> { }
|
||||
public interface INotification { }
|
||||
@ -47,7 +47,10 @@ public class CqrsHandlerRegistryGeneratorTests
|
||||
public interface IRequestHandler<in TRequest, TResponse> where TRequest : IRequest<TResponse> { }
|
||||
public interface INotificationHandler<in TNotification> where TNotification : INotification { }
|
||||
public interface IStreamRequestHandler<in TRequest, out TResponse> where TRequest : IStreamRequest<TResponse> { }
|
||||
}
|
||||
|
||||
namespace GFramework.Cqrs
|
||||
{
|
||||
public interface ICqrsHandlerRegistry
|
||||
{
|
||||
void Register(Microsoft.Extensions.DependencyInjection.IServiceCollection services, GFramework.Core.Abstractions.Logging.ILogger logger);
|
||||
@ -62,7 +65,7 @@ public class CqrsHandlerRegistryGeneratorTests
|
||||
|
||||
namespace TestApp
|
||||
{
|
||||
using GFramework.Core.Abstractions.Cqrs;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs;
|
||||
|
||||
public sealed record PingQuery() : IRequest<string>;
|
||||
public sealed record DomainEvent() : INotification;
|
||||
@ -78,11 +81,11 @@ public class CqrsHandlerRegistryGeneratorTests
|
||||
// <auto-generated />
|
||||
#nullable enable
|
||||
|
||||
[assembly: global::GFramework.Core.Abstractions.Cqrs.CqrsHandlerRegistryAttribute(typeof(global::GFramework.Generated.Cqrs.__GFrameworkGeneratedCqrsHandlerRegistry))]
|
||||
[assembly: global::GFramework.Cqrs.CqrsHandlerRegistryAttribute(typeof(global::GFramework.Generated.Cqrs.__GFrameworkGeneratedCqrsHandlerRegistry))]
|
||||
|
||||
namespace GFramework.Generated.Cqrs;
|
||||
|
||||
internal sealed class __GFrameworkGeneratedCqrsHandlerRegistry : global::GFramework.Core.Abstractions.Cqrs.ICqrsHandlerRegistry
|
||||
internal sealed class __GFrameworkGeneratedCqrsHandlerRegistry : global::GFramework.Cqrs.ICqrsHandlerRegistry
|
||||
{
|
||||
public void Register(global::Microsoft.Extensions.DependencyInjection.IServiceCollection services, global::GFramework.Core.Abstractions.Logging.ILogger logger)
|
||||
{
|
||||
@ -93,19 +96,19 @@ public class CqrsHandlerRegistryGeneratorTests
|
||||
|
||||
global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddTransient(
|
||||
services,
|
||||
typeof(global::GFramework.Core.Abstractions.Cqrs.IRequestHandler<global::TestApp.PingQuery, string>),
|
||||
typeof(global::GFramework.Cqrs.Abstractions.Cqrs.IRequestHandler<global::TestApp.PingQuery, string>),
|
||||
typeof(global::TestApp.AlphaQueryHandler));
|
||||
logger.Debug("Registered CQRS handler TestApp.AlphaQueryHandler as GFramework.Core.Abstractions.Cqrs.IRequestHandler<TestApp.PingQuery, string>.");
|
||||
logger.Debug("Registered CQRS handler TestApp.AlphaQueryHandler as GFramework.Cqrs.Abstractions.Cqrs.IRequestHandler<TestApp.PingQuery, string>.");
|
||||
global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddTransient(
|
||||
services,
|
||||
typeof(global::GFramework.Core.Abstractions.Cqrs.IStreamRequestHandler<global::TestApp.NumberStream, int>),
|
||||
typeof(global::GFramework.Cqrs.Abstractions.Cqrs.IStreamRequestHandler<global::TestApp.NumberStream, int>),
|
||||
typeof(global::TestApp.StreamHandler));
|
||||
logger.Debug("Registered CQRS handler TestApp.StreamHandler as GFramework.Core.Abstractions.Cqrs.IStreamRequestHandler<TestApp.NumberStream, int>.");
|
||||
logger.Debug("Registered CQRS handler TestApp.StreamHandler as GFramework.Cqrs.Abstractions.Cqrs.IStreamRequestHandler<TestApp.NumberStream, int>.");
|
||||
global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddTransient(
|
||||
services,
|
||||
typeof(global::GFramework.Core.Abstractions.Cqrs.INotificationHandler<global::TestApp.DomainEvent>),
|
||||
typeof(global::GFramework.Cqrs.Abstractions.Cqrs.INotificationHandler<global::TestApp.DomainEvent>),
|
||||
typeof(global::TestApp.ZetaNotificationHandler));
|
||||
logger.Debug("Registered CQRS handler TestApp.ZetaNotificationHandler as GFramework.Core.Abstractions.Cqrs.INotificationHandler<TestApp.DomainEvent>.");
|
||||
logger.Debug("Registered CQRS handler TestApp.ZetaNotificationHandler as GFramework.Cqrs.Abstractions.Cqrs.INotificationHandler<TestApp.DomainEvent>.");
|
||||
}
|
||||
}
|
||||
|
||||
@ -143,7 +146,7 @@ public class CqrsHandlerRegistryGeneratorTests
|
||||
}
|
||||
}
|
||||
|
||||
namespace GFramework.Core.Abstractions.Cqrs
|
||||
namespace GFramework.Cqrs.Abstractions.Cqrs
|
||||
{
|
||||
public interface IRequest<TResponse> { }
|
||||
public interface INotification { }
|
||||
@ -152,7 +155,10 @@ public class CqrsHandlerRegistryGeneratorTests
|
||||
public interface IRequestHandler<in TRequest, TResponse> where TRequest : IRequest<TResponse> { }
|
||||
public interface INotificationHandler<in TNotification> where TNotification : INotification { }
|
||||
public interface IStreamRequestHandler<in TRequest, out TResponse> where TRequest : IStreamRequest<TResponse> { }
|
||||
}
|
||||
|
||||
namespace GFramework.Cqrs
|
||||
{
|
||||
public interface ICqrsHandlerRegistry
|
||||
{
|
||||
void Register(Microsoft.Extensions.DependencyInjection.IServiceCollection services, GFramework.Core.Abstractions.Logging.ILogger logger);
|
||||
@ -167,7 +173,7 @@ public class CqrsHandlerRegistryGeneratorTests
|
||||
|
||||
namespace TestApp
|
||||
{
|
||||
using GFramework.Core.Abstractions.Cqrs;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs;
|
||||
|
||||
public sealed record VisibleRequest() : IRequest<string>;
|
||||
|
||||
|
||||
@ -8,13 +8,17 @@ namespace GFramework.SourceGenerators.Cqrs;
|
||||
[Generator]
|
||||
public sealed class CqrsHandlerRegistryGenerator : IIncrementalGenerator
|
||||
{
|
||||
private const string CqrsNamespace = $"{PathContests.CoreAbstractionsNamespace}.Cqrs";
|
||||
private const string CqrsContractsNamespace = $"{PathContests.CqrsAbstractionsNamespace}.Cqrs";
|
||||
private const string CqrsRuntimeNamespace = PathContests.CqrsNamespace;
|
||||
private const string LoggingNamespace = $"{PathContests.CoreAbstractionsNamespace}.Logging";
|
||||
private const string IRequestHandlerMetadataName = $"{CqrsNamespace}.IRequestHandler`2";
|
||||
private const string INotificationHandlerMetadataName = $"{CqrsNamespace}.INotificationHandler`1";
|
||||
private const string IStreamRequestHandlerMetadataName = $"{CqrsNamespace}.IStreamRequestHandler`2";
|
||||
private const string ICqrsHandlerRegistryMetadataName = $"{CqrsNamespace}.ICqrsHandlerRegistry";
|
||||
private const string CqrsHandlerRegistryAttributeMetadataName = $"{CqrsNamespace}.CqrsHandlerRegistryAttribute";
|
||||
private const string IRequestHandlerMetadataName = $"{CqrsContractsNamespace}.IRequestHandler`2";
|
||||
private const string INotificationHandlerMetadataName = $"{CqrsContractsNamespace}.INotificationHandler`1";
|
||||
private const string IStreamRequestHandlerMetadataName = $"{CqrsContractsNamespace}.IStreamRequestHandler`2";
|
||||
private const string ICqrsHandlerRegistryMetadataName = $"{CqrsRuntimeNamespace}.ICqrsHandlerRegistry";
|
||||
|
||||
private const string CqrsHandlerRegistryAttributeMetadataName =
|
||||
$"{CqrsRuntimeNamespace}.CqrsHandlerRegistryAttribute";
|
||||
|
||||
private const string ILoggerMetadataName = $"{LoggingNamespace}.ILogger";
|
||||
private const string IServiceCollectionMetadataName = "Microsoft.Extensions.DependencyInjection.IServiceCollection";
|
||||
private const string GeneratedNamespace = "GFramework.Generated.Cqrs";
|
||||
@ -273,7 +277,7 @@ public sealed class CqrsHandlerRegistryGenerator : IIncrementalGenerator
|
||||
builder.AppendLine("#nullable enable");
|
||||
builder.AppendLine();
|
||||
builder.Append("[assembly: global::");
|
||||
builder.Append(CqrsNamespace);
|
||||
builder.Append(CqrsRuntimeNamespace);
|
||||
builder.Append(".CqrsHandlerRegistryAttribute(typeof(global::");
|
||||
builder.Append(GeneratedNamespace);
|
||||
builder.Append('.');
|
||||
@ -287,7 +291,7 @@ public sealed class CqrsHandlerRegistryGenerator : IIncrementalGenerator
|
||||
builder.Append("internal sealed class ");
|
||||
builder.Append(GeneratedTypeName);
|
||||
builder.Append(" : global::");
|
||||
builder.Append(CqrsNamespace);
|
||||
builder.Append(CqrsRuntimeNamespace);
|
||||
builder.AppendLine(".ICqrsHandlerRegistry");
|
||||
builder.AppendLine("{");
|
||||
builder.Append(
|
||||
|
||||
@ -4,10 +4,10 @@ using System.Reflection;
|
||||
using GFramework.Core.Abstractions.Cqrs;
|
||||
using GFramework.Core.Abstractions.Ioc;
|
||||
using GFramework.Core.Abstractions.Logging;
|
||||
using GFramework.Core.Architectures;
|
||||
using GFramework.Core.Ioc;
|
||||
using GFramework.Core.Logging;
|
||||
using GFramework.Cqrs;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs;
|
||||
using GFramework.Cqrs.Command;
|
||||
|
||||
namespace GFramework.Tests.Common;
|
||||
|
||||
@ -20,9 +20,11 @@ namespace GFramework.Tests.Common;
|
||||
/// </remarks>
|
||||
public static class CqrsTestRuntime
|
||||
{
|
||||
private static readonly Type CqrsHandlerRegistrarType = typeof(ArchitectureContext).Assembly
|
||||
private static readonly Assembly CqrsRuntimeAssembly = typeof(CommandBase<,>).Assembly;
|
||||
|
||||
private static readonly Type CqrsHandlerRegistrarType = CqrsRuntimeAssembly
|
||||
.GetType(
|
||||
"GFramework.Core.Cqrs.Internal.CqrsHandlerRegistrar",
|
||||
"GFramework.Cqrs.Internal.CqrsHandlerRegistrar",
|
||||
throwOnError: true)!;
|
||||
|
||||
private static readonly MethodInfo RegisterHandlersMethod = CqrsHandlerRegistrarType
|
||||
@ -40,47 +42,11 @@ public static class CqrsTestRuntime
|
||||
?? throw new InvalidOperationException(
|
||||
"Failed to locate CqrsHandlerRegistrar.RegisterHandlers.");
|
||||
|
||||
private static readonly Type CqrsDispatcherType = typeof(ArchitectureContext).Assembly
|
||||
.GetType(
|
||||
"GFramework.Core.Cqrs.Internal.CqrsDispatcher",
|
||||
throwOnError: true)!;
|
||||
|
||||
private static readonly ConstructorInfo CqrsDispatcherConstructor = CqrsDispatcherType.GetConstructor(
|
||||
BindingFlags.Instance |
|
||||
BindingFlags.Public |
|
||||
BindingFlags.NonPublic,
|
||||
binder: null,
|
||||
[
|
||||
typeof(IIocContainer),
|
||||
typeof(ILogger)
|
||||
],
|
||||
modifiers: null)
|
||||
?? throw new InvalidOperationException(
|
||||
"Failed to locate CqrsDispatcher constructor.");
|
||||
|
||||
private static readonly Type DefaultCqrsHandlerRegistrarType = typeof(ArchitectureContext).Assembly
|
||||
.GetType(
|
||||
"GFramework.Core.Cqrs.Internal.DefaultCqrsHandlerRegistrar",
|
||||
throwOnError: true)!;
|
||||
|
||||
private static readonly ConstructorInfo DefaultCqrsHandlerRegistrarConstructor =
|
||||
DefaultCqrsHandlerRegistrarType.GetConstructor(
|
||||
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic,
|
||||
binder: null,
|
||||
[
|
||||
typeof(IIocContainer),
|
||||
typeof(ILogger)
|
||||
],
|
||||
modifiers: null)
|
||||
?? throw new InvalidOperationException(
|
||||
"Failed to locate DefaultCqrsHandlerRegistrar constructor.");
|
||||
|
||||
/// <summary>
|
||||
/// 为裸测试容器补齐默认 CQRS runtime seam。
|
||||
/// </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 行为,
|
||||
/// 而无需完整启动服务模块管理器。
|
||||
@ -92,16 +58,15 @@ public static class CqrsTestRuntime
|
||||
|
||||
if (container.Get<ICqrsRuntime>() is null)
|
||||
{
|
||||
var runtimeLogger = LoggerFactoryResolver.Provider.CreateLogger(CqrsDispatcherType.Name);
|
||||
var runtime = (ICqrsRuntime)CqrsDispatcherConstructor.Invoke([container, runtimeLogger]);
|
||||
var runtimeLogger = LoggerFactoryResolver.Provider.CreateLogger("CqrsDispatcher");
|
||||
var runtime = CqrsRuntimeFactory.CreateRuntime(container, runtimeLogger);
|
||||
container.Register<ICqrsRuntime>(runtime);
|
||||
}
|
||||
|
||||
if (container.Get<ICqrsHandlerRegistrar>() is null)
|
||||
{
|
||||
var registrarLogger = LoggerFactoryResolver.Provider.CreateLogger(DefaultCqrsHandlerRegistrarType.Name);
|
||||
var registrar =
|
||||
(ICqrsHandlerRegistrar)DefaultCqrsHandlerRegistrarConstructor.Invoke([container, registrarLogger]);
|
||||
var registrarLogger = LoggerFactoryResolver.Provider.CreateLogger("DefaultCqrsHandlerRegistrar");
|
||||
var registrar = CqrsRuntimeFactory.CreateHandlerRegistrar(container, registrarLogger);
|
||||
container.Register<ICqrsHandlerRegistrar>(registrar);
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,6 +10,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\GFramework.Cqrs.Abstractions\GFramework.Cqrs.Abstractions.csproj"/>
|
||||
<ProjectReference Include="..\GFramework.Cqrs\GFramework.Cqrs.csproj"/>
|
||||
<ProjectReference Include="..\GFramework.Core.Abstractions\GFramework.Core.Abstractions.csproj"/>
|
||||
<ProjectReference Include="..\GFramework.Core\GFramework.Core.csproj"/>
|
||||
</ItemGroup>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user