mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-05-08 01:24:31 +08:00
Merge pull request #224 from GeWuYou/refactor/cqrs-architecture-decoupling-todo-5
Refactor/cqrs architecture decoupling todo 5
This commit is contained in:
commit
f7b4ae9995
6
.github/workflows/ci.yml
vendored
6
.github/workflows/ci.yml
vendored
@ -166,6 +166,12 @@ jobs:
|
||||
--logger "trx;LogFileName=sg-$RANDOM.trx" \
|
||||
--results-directory TestResults &
|
||||
|
||||
dotnet test GFramework.Cqrs.Tests \
|
||||
-c Release \
|
||||
--no-build \
|
||||
--logger "trx;LogFileName=cqrs-$RANDOM.trx" \
|
||||
--results-directory TestResults &
|
||||
|
||||
dotnet test GFramework.Ecs.Arch.Tests \
|
||||
-c Release \
|
||||
--no-build \
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
using GFramework.Core.Abstractions.Command;
|
||||
using GFramework.Core.Abstractions.Cqrs;
|
||||
using GFramework.Core.Abstractions.Environment;
|
||||
using GFramework.Core.Abstractions.Events;
|
||||
using GFramework.Core.Abstractions.Model;
|
||||
using GFramework.Core.Abstractions.Query;
|
||||
using GFramework.Core.Abstractions.Systems;
|
||||
using GFramework.Core.Abstractions.Utility;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs;
|
||||
using ICommand = GFramework.Core.Abstractions.Command.ICommand;
|
||||
|
||||
namespace GFramework.Core.Abstractions.Architectures;
|
||||
@ -131,7 +131,7 @@ public interface IArchitectureContext
|
||||
/// <remarks>
|
||||
/// 这是迁移后的推荐命令入口。无返回值命令应实现 <c>IRequest<Unit></c>,并优先通过 <see cref="SendAsync{TCommand}(TCommand,CancellationToken)" /> 调用。
|
||||
/// </remarks>
|
||||
TResponse SendCommand<TResponse>(Cqrs.Command.ICommand<TResponse> command);
|
||||
TResponse SendCommand<TResponse>(GFramework.Cqrs.Abstractions.Cqrs.Command.ICommand<TResponse> command);
|
||||
|
||||
|
||||
/// <summary>
|
||||
@ -147,7 +147,8 @@ public interface IArchitectureContext
|
||||
/// <param name="command">要发送的 CQRS 命令。</param>
|
||||
/// <param name="cancellationToken">取消令牌。</param>
|
||||
/// <returns>包含命令执行结果的值任务。</returns>
|
||||
ValueTask<TResponse> SendCommandAsync<TResponse>(Cqrs.Command.ICommand<TResponse> command,
|
||||
ValueTask<TResponse> SendCommandAsync<TResponse>(
|
||||
GFramework.Cqrs.Abstractions.Cqrs.Command.ICommand<TResponse> command,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
|
||||
@ -176,7 +177,7 @@ public interface IArchitectureContext
|
||||
/// <remarks>
|
||||
/// 这是迁移后的推荐查询入口。新查询应优先实现 <c>GFramework.Core.Abstractions.Cqrs.Query.IQuery<TResponse></c>。
|
||||
/// </remarks>
|
||||
TResponse SendQuery<TResponse>(Cqrs.Query.IQuery<TResponse> query);
|
||||
TResponse SendQuery<TResponse>(GFramework.Cqrs.Abstractions.Cqrs.Query.IQuery<TResponse> query);
|
||||
|
||||
/// <summary>
|
||||
/// 异步发送一个旧版查询请求。
|
||||
@ -193,7 +194,7 @@ public interface IArchitectureContext
|
||||
/// <param name="query">要发送的 CQRS 查询。</param>
|
||||
/// <param name="cancellationToken">取消令牌。</param>
|
||||
/// <returns>包含查询结果的值任务。</returns>
|
||||
ValueTask<TResponse> SendQueryAsync<TResponse>(Cqrs.Query.IQuery<TResponse> query,
|
||||
ValueTask<TResponse> SendQueryAsync<TResponse>(GFramework.Cqrs.Abstractions.Cqrs.Query.IQuery<TResponse> query,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
|
||||
@ -1,25 +0,0 @@
|
||||
namespace GFramework.Core.Abstractions.Cqrs.Command;
|
||||
|
||||
/// <summary>
|
||||
/// 表示一个 CQRS 命令。
|
||||
/// 命令通常用于修改系统状态。
|
||||
/// </summary>
|
||||
/// <typeparam name="TResponse">命令响应类型。</typeparam>
|
||||
public interface ICommand<out TResponse> : IRequest<TResponse>
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 表示一个无显式返回值的 CQRS 命令。
|
||||
/// </summary>
|
||||
public interface ICommand : ICommand<Unit>
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 表示一个流式 CQRS 命令。
|
||||
/// </summary>
|
||||
/// <typeparam name="TResponse">流式响应元素类型。</typeparam>
|
||||
public interface IStreamCommand<out TResponse> : IStreamRequest<TResponse>
|
||||
{
|
||||
}
|
||||
52
GFramework.Core.Abstractions/Cqrs/ICqrsRuntime.cs
Normal file
52
GFramework.Core.Abstractions/Cqrs/ICqrsRuntime.cs
Normal file
@ -0,0 +1,52 @@
|
||||
using GFramework.Core.Abstractions.Architectures;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs;
|
||||
|
||||
namespace GFramework.Core.Abstractions.Cqrs;
|
||||
|
||||
/// <summary>
|
||||
/// 定义架构上下文使用的 CQRS runtime seam。
|
||||
/// 该抽象把请求分发、通知发布与流式处理从具体实现中解耦,
|
||||
/// 使 <see cref="IArchitectureContext" /> 不再直接依赖某个固定的 runtime 类型。
|
||||
/// </summary>
|
||||
public interface ICqrsRuntime
|
||||
{
|
||||
/// <summary>
|
||||
/// 发送请求并返回响应。
|
||||
/// </summary>
|
||||
/// <typeparam name="TResponse">响应类型。</typeparam>
|
||||
/// <param name="context">当前架构上下文,用于上下文感知处理器注入与嵌套请求访问。</param>
|
||||
/// <param name="request">要分发的请求。</param>
|
||||
/// <param name="cancellationToken">取消令牌。</param>
|
||||
/// <returns>请求响应。</returns>
|
||||
ValueTask<TResponse> SendAsync<TResponse>(
|
||||
IArchitectureContext context,
|
||||
IRequest<TResponse> request,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 发布通知到所有已注册处理器。
|
||||
/// </summary>
|
||||
/// <typeparam name="TNotification">通知类型。</typeparam>
|
||||
/// <param name="context">当前架构上下文,用于上下文感知处理器注入。</param>
|
||||
/// <param name="notification">要发布的通知。</param>
|
||||
/// <param name="cancellationToken">取消令牌。</param>
|
||||
/// <returns>表示通知分发完成的值任务。</returns>
|
||||
ValueTask PublishAsync<TNotification>(
|
||||
IArchitectureContext context,
|
||||
TNotification notification,
|
||||
CancellationToken cancellationToken = default)
|
||||
where TNotification : INotification;
|
||||
|
||||
/// <summary>
|
||||
/// 创建流式请求的异步响应序列。
|
||||
/// </summary>
|
||||
/// <typeparam name="TResponse">流元素类型。</typeparam>
|
||||
/// <param name="context">当前架构上下文,用于上下文感知处理器注入。</param>
|
||||
/// <param name="request">流式请求。</param>
|
||||
/// <param name="cancellationToken">取消令牌。</param>
|
||||
/// <returns>按需生成的异步响应序列。</returns>
|
||||
IAsyncEnumerable<TResponse> CreateStream<TResponse>(
|
||||
IArchitectureContext context,
|
||||
IStreamRequest<TResponse> request,
|
||||
CancellationToken cancellationToken = default);
|
||||
}
|
||||
@ -1,18 +0,0 @@
|
||||
namespace GFramework.Core.Abstractions.Cqrs.Query;
|
||||
|
||||
/// <summary>
|
||||
/// 表示一个 CQRS 查询。
|
||||
/// 查询用于读取数据,不应产生副作用。
|
||||
/// </summary>
|
||||
/// <typeparam name="TResponse">查询响应类型。</typeparam>
|
||||
public interface IQuery<out TResponse> : IRequest<TResponse>
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 表示一个流式 CQRS 查询。
|
||||
/// </summary>
|
||||
/// <typeparam name="TResponse">流式响应元素类型。</typeparam>
|
||||
public interface IStreamQuery<out TResponse> : IStreamRequest<TResponse>
|
||||
{
|
||||
}
|
||||
@ -17,6 +17,9 @@
|
||||
<ItemGroup>
|
||||
<Using Include="GFramework.Core.Abstractions"/>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\GFramework.Cqrs.Abstractions\GFramework.Cqrs.Abstractions.csproj"/>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Update="Meziantou.Analyzer" Version="3.0.46">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
|
||||
@ -3,6 +3,7 @@ using GFramework.Core.Abstractions.Cqrs;
|
||||
using GFramework.Core.Abstractions.Logging;
|
||||
using GFramework.Core.Architectures;
|
||||
using GFramework.Core.Logging;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs;
|
||||
|
||||
namespace GFramework.Core.Tests.Architectures;
|
||||
|
||||
@ -12,8 +13,6 @@ namespace GFramework.Core.Tests.Architectures;
|
||||
[TestFixture]
|
||||
public sealed class ArchitectureAdditionalCqrsHandlersTests
|
||||
{
|
||||
private ILoggerFactoryProvider? _previousLoggerFactoryProvider;
|
||||
|
||||
/// <summary>
|
||||
/// 初始化日志工厂和共享测试状态。
|
||||
/// </summary>
|
||||
@ -39,6 +38,8 @@ public sealed class ArchitectureAdditionalCqrsHandlersTests
|
||||
"LoggerFactoryResolver.Provider should be captured during setup.");
|
||||
}
|
||||
|
||||
private ILoggerFactoryProvider? _previousLoggerFactoryProvider;
|
||||
|
||||
/// <summary>
|
||||
/// 验证显式声明的额外程序集会在初始化阶段接入当前架构容器。
|
||||
/// </summary>
|
||||
@ -197,4 +198,4 @@ internal sealed class AdditionalAssemblyNotificationHandlerRegistry : ICqrsHandl
|
||||
});
|
||||
return handler.Object;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
using System.Reflection;
|
||||
using GFramework.Core.Abstractions.Architectures;
|
||||
using GFramework.Core.Abstractions.Command;
|
||||
using GFramework.Core.Abstractions.Cqrs;
|
||||
using GFramework.Core.Abstractions.Enums;
|
||||
using GFramework.Core.Abstractions.Environment;
|
||||
using GFramework.Core.Abstractions.Ioc;
|
||||
using GFramework.Core.Abstractions.Model;
|
||||
using GFramework.Core.Abstractions.Query;
|
||||
using GFramework.Core.Abstractions.Systems;
|
||||
@ -14,6 +16,7 @@ using GFramework.Core.Events;
|
||||
using GFramework.Core.Ioc;
|
||||
using GFramework.Core.Logging;
|
||||
using GFramework.Core.Query;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs;
|
||||
|
||||
namespace GFramework.Core.Tests.Architectures;
|
||||
|
||||
@ -73,13 +76,14 @@ public class ArchitectureContextTests
|
||||
_context = new ArchitectureContext(_container);
|
||||
}
|
||||
|
||||
private ArchitectureContext? _context;
|
||||
private MicrosoftDiContainer? _container;
|
||||
private EventBus? _eventBus;
|
||||
private CommandExecutor? _commandBus;
|
||||
private QueryExecutor? _queryBus;
|
||||
private AsyncQueryExecutor? _asyncQueryBus;
|
||||
private CommandExecutor? _commandBus;
|
||||
private MicrosoftDiContainer? _container;
|
||||
|
||||
private ArchitectureContext? _context;
|
||||
private DefaultEnvironment? _environment;
|
||||
private EventBus? _eventBus;
|
||||
private QueryExecutor? _queryBus;
|
||||
|
||||
/// <summary>
|
||||
/// 测试构造函数在所有参数都有效时不应抛出异常
|
||||
@ -298,6 +302,76 @@ public class ArchitectureContextTests
|
||||
Assert.That(environment, Is.Not.Null);
|
||||
Assert.That(environment, Is.InstanceOf<IEnvironment>());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试 CQRS runtime 在并发首次访问时只会从容器解析一次。
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task SendRequestAsync_Should_ResolveCqrsRuntime_OnlyOnce_When_AccessedConcurrently()
|
||||
{
|
||||
const int workerCount = 8;
|
||||
var workerStartupTimeout = TimeSpan.FromSeconds(5);
|
||||
var firstResolutionTimeout = TimeSpan.FromSeconds(5);
|
||||
using var startGate = new ManualResetEventSlim(false);
|
||||
using var allowResolutionToComplete = new ManualResetEventSlim(false);
|
||||
using var workersReady = new CountdownEvent(workerCount);
|
||||
var resolutionCallCount = 0;
|
||||
var runtime = new Mock<ICqrsRuntime>(MockBehavior.Strict);
|
||||
var container = new Mock<IIocContainer>(MockBehavior.Strict);
|
||||
|
||||
runtime.Setup(mockRuntime => mockRuntime.SendAsync(
|
||||
It.IsAny<IArchitectureContext>(),
|
||||
It.IsAny<IRequest<int>>(),
|
||||
It.IsAny<CancellationToken>()))
|
||||
.Returns(new ValueTask<int>(42));
|
||||
|
||||
container.Setup(mockContainer => mockContainer.Get<ICqrsRuntime>())
|
||||
.Returns(() =>
|
||||
{
|
||||
Interlocked.Increment(ref resolutionCallCount);
|
||||
allowResolutionToComplete.Wait();
|
||||
return runtime.Object;
|
||||
});
|
||||
|
||||
var context = new ArchitectureContext(container.Object);
|
||||
var requests = Enumerable.Range(0, workerCount)
|
||||
.Select(_ => Task.Run(async () =>
|
||||
{
|
||||
workersReady.Signal();
|
||||
startGate.Wait();
|
||||
return await context.SendRequestAsync(new TestCqrsRequest());
|
||||
}))
|
||||
.ToArray();
|
||||
|
||||
Assert.That(
|
||||
workersReady.Wait(workerStartupTimeout),
|
||||
Is.True,
|
||||
"Expected all workers to be ready before releasing start gate.");
|
||||
startGate.Set();
|
||||
|
||||
Assert.That(
|
||||
SpinWait.SpinUntil(() => Volatile.Read(ref resolutionCallCount) > 0, firstResolutionTimeout),
|
||||
Is.True,
|
||||
"Expected at least one CQRS runtime resolution attempt.");
|
||||
|
||||
allowResolutionToComplete.Set();
|
||||
|
||||
var responses = await Task.WhenAll(requests);
|
||||
|
||||
Assert.That(responses, Has.All.EqualTo(42));
|
||||
Assert.That(resolutionCallCount, Is.EqualTo(1));
|
||||
container.Verify(mockContainer => mockContainer.Get<ICqrsRuntime>(), Times.Once);
|
||||
runtime.Verify(
|
||||
mockRuntime => mockRuntime.SendAsync(
|
||||
It.IsAny<IArchitectureContext>(),
|
||||
It.IsAny<IRequest<int>>(),
|
||||
It.IsAny<CancellationToken>()),
|
||||
Times.Exactly(requests.Length));
|
||||
}
|
||||
|
||||
private sealed class TestCqrsRequest : IRequest<int>
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
#region Test Classes
|
||||
@ -442,4 +516,4 @@ public class TestEventV2
|
||||
public int Data { get; init; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
#endregion
|
||||
|
||||
@ -2,7 +2,7 @@ using GFramework.Core.Abstractions.Architectures;
|
||||
using GFramework.Core.Abstractions.Utility;
|
||||
using GFramework.Core.Architectures;
|
||||
using GFramework.Core.Logging;
|
||||
using GfCqrs = GFramework.Core.Abstractions.Cqrs;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs;
|
||||
|
||||
namespace GFramework.Core.Tests.Architectures;
|
||||
|
||||
@ -151,14 +151,14 @@ public class ArchitectureModulesBehaviorTests
|
||||
/// <summary>
|
||||
/// 用于验证管道行为注册是否生效的测试请求。
|
||||
/// </summary>
|
||||
public sealed class ModuleBehaviorRequest : GfCqrs.IRequest<string>
|
||||
public sealed class ModuleBehaviorRequest : IRequest<string>
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理测试请求的处理器。
|
||||
/// </summary>
|
||||
public sealed class ModuleBehaviorRequestHandler : GfCqrs.IRequestHandler<ModuleBehaviorRequest, string>
|
||||
public sealed class ModuleBehaviorRequestHandler : IRequestHandler<ModuleBehaviorRequest, string>
|
||||
{
|
||||
/// <summary>
|
||||
/// 返回固定结果,便于聚焦验证管道行为是否执行。
|
||||
@ -177,8 +177,8 @@ public sealed class ModuleBehaviorRequestHandler : GfCqrs.IRequestHandler<Module
|
||||
/// </summary>
|
||||
/// <typeparam name="TRequest">请求类型。</typeparam>
|
||||
/// <typeparam name="TResponse">响应类型。</typeparam>
|
||||
public sealed class TrackingPipelineBehavior<TRequest, TResponse> : GfCqrs.IPipelineBehavior<TRequest, TResponse>
|
||||
where TRequest : GfCqrs.IRequest<TResponse>
|
||||
public sealed class TrackingPipelineBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
|
||||
where TRequest : IRequest<TResponse>
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取当前测试进程中该请求类型对应的行为触发次数。
|
||||
@ -193,7 +193,7 @@ public sealed class TrackingPipelineBehavior<TRequest, TResponse> : GfCqrs.IPipe
|
||||
/// <param name="cancellationToken">取消令牌。</param>
|
||||
/// <returns>下游处理器的响应结果。</returns>
|
||||
public async ValueTask<TResponse> Handle(
|
||||
TRequest message, GfCqrs.MessageHandlerDelegate<TRequest, TResponse> next,
|
||||
TRequest message, MessageHandlerDelegate<TRequest, TResponse> next,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
InvocationCount++;
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
using GFramework.Core.Abstractions.Architectures;
|
||||
using GFramework.Core.Abstractions.Command;
|
||||
using GFramework.Core.Abstractions.Cqrs;
|
||||
using GFramework.Core.Abstractions.Environment;
|
||||
using GFramework.Core.Abstractions.Events;
|
||||
using GFramework.Core.Abstractions.Ioc;
|
||||
@ -14,6 +13,7 @@ using GFramework.Core.Environment;
|
||||
using GFramework.Core.Events;
|
||||
using GFramework.Core.Ioc;
|
||||
using GFramework.Core.Query;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs;
|
||||
using ICommand = GFramework.Core.Abstractions.Command.ICommand;
|
||||
|
||||
namespace GFramework.Core.Tests.Architectures;
|
||||
@ -34,10 +34,6 @@ namespace GFramework.Core.Tests.Architectures;
|
||||
[TestFixture]
|
||||
public class ArchitectureServicesTests
|
||||
{
|
||||
private TestArchitectureContextV3? _context;
|
||||
|
||||
private ArchitectureServices? _services;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
@ -45,6 +41,10 @@ public class ArchitectureServicesTests
|
||||
_context = new TestArchitectureContextV3();
|
||||
}
|
||||
|
||||
private TestArchitectureContextV3? _context;
|
||||
|
||||
private ArchitectureServices? _services;
|
||||
|
||||
private void RegisterBuiltInServices()
|
||||
{
|
||||
_services!.ModuleManager.RegisterBuiltInModules(_services.Container);
|
||||
@ -359,24 +359,56 @@ public class TestArchitectureContextV3 : IArchitectureContext
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public ValueTask<TResponse> SendCommandAsync<TResponse>(Abstractions.Cqrs.Command.ICommand<TResponse> command,
|
||||
/// <summary>
|
||||
/// 测试桩:异步发送 CQRS 命令并返回响应。
|
||||
/// </summary>
|
||||
/// <typeparam name="TResponse">命令响应类型。</typeparam>
|
||||
/// <param name="command">要发送的命令。</param>
|
||||
/// <param name="cancellationToken">取消令牌。</param>
|
||||
/// <returns>命令响应任务。</returns>
|
||||
/// <exception cref="NotImplementedException">该测试桩未实现此成员。</exception>
|
||||
public ValueTask<TResponse> SendCommandAsync<TResponse>(
|
||||
GFramework.Cqrs.Abstractions.Cqrs.Command.ICommand<TResponse> command,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public TResponse SendCommand<TResponse>(Abstractions.Cqrs.Command.ICommand<TResponse> command)
|
||||
/// <summary>
|
||||
/// 测试桩:同步发送 CQRS 命令并返回响应。
|
||||
/// </summary>
|
||||
/// <typeparam name="TResponse">命令响应类型。</typeparam>
|
||||
/// <param name="command">要发送的命令。</param>
|
||||
/// <returns>命令响应。</returns>
|
||||
/// <exception cref="NotImplementedException">该测试桩未实现此成员。</exception>
|
||||
public TResponse SendCommand<TResponse>(GFramework.Cqrs.Abstractions.Cqrs.Command.ICommand<TResponse> command)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public ValueTask<TResponse> SendQueryAsync<TResponse>(Abstractions.Cqrs.Query.IQuery<TResponse> query,
|
||||
/// <summary>
|
||||
/// 测试桩:异步发送 CQRS 查询并返回结果。
|
||||
/// </summary>
|
||||
/// <typeparam name="TResponse">查询结果类型。</typeparam>
|
||||
/// <param name="query">要发送的查询。</param>
|
||||
/// <param name="cancellationToken">取消令牌。</param>
|
||||
/// <returns>查询结果任务。</returns>
|
||||
/// <exception cref="NotImplementedException">该测试桩未实现此成员。</exception>
|
||||
public ValueTask<TResponse> SendQueryAsync<TResponse>(
|
||||
GFramework.Cqrs.Abstractions.Cqrs.Query.IQuery<TResponse> query,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public TResponse SendQuery<TResponse>(Abstractions.Cqrs.Query.IQuery<TResponse> query)
|
||||
/// <summary>
|
||||
/// 测试桩:同步发送 CQRS 查询并返回结果。
|
||||
/// </summary>
|
||||
/// <typeparam name="TResponse">查询结果类型。</typeparam>
|
||||
/// <param name="query">要发送的查询。</param>
|
||||
/// <returns>查询结果。</returns>
|
||||
/// <exception cref="NotImplementedException">该测试桩未实现此成员。</exception>
|
||||
public TResponse SendQuery<TResponse>(GFramework.Cqrs.Abstractions.Cqrs.Query.IQuery<TResponse> query)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
using GFramework.Core.Abstractions.Architectures;
|
||||
using GFramework.Core.Abstractions.Command;
|
||||
using GFramework.Core.Abstractions.Cqrs;
|
||||
using GFramework.Core.Abstractions.Environment;
|
||||
using GFramework.Core.Abstractions.Events;
|
||||
using GFramework.Core.Abstractions.Ioc;
|
||||
@ -14,6 +13,7 @@ using GFramework.Core.Environment;
|
||||
using GFramework.Core.Events;
|
||||
using GFramework.Core.Ioc;
|
||||
using GFramework.Core.Query;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs;
|
||||
using ICommand = GFramework.Core.Abstractions.Command.ICommand;
|
||||
|
||||
namespace GFramework.Core.Tests.Architectures;
|
||||
@ -428,7 +428,8 @@ public class TestArchitectureContext : IArchitectureContext
|
||||
/// <param name="cancellationToken">取消令牌。</param>
|
||||
/// <returns>命令响应任务。</returns>
|
||||
/// <exception cref="NotImplementedException">该测试桩未实现此成员。</exception>
|
||||
public ValueTask<TResponse> SendCommandAsync<TResponse>(Abstractions.Cqrs.Command.ICommand<TResponse> command,
|
||||
public ValueTask<TResponse> SendCommandAsync<TResponse>(
|
||||
GFramework.Cqrs.Abstractions.Cqrs.Command.ICommand<TResponse> command,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
@ -441,7 +442,7 @@ public class TestArchitectureContext : IArchitectureContext
|
||||
/// <param name="command">要发送的命令。</param>
|
||||
/// <returns>命令响应。</returns>
|
||||
/// <exception cref="NotImplementedException">该测试桩未实现此成员。</exception>
|
||||
public TResponse SendCommand<TResponse>(Abstractions.Cqrs.Command.ICommand<TResponse> command)
|
||||
public TResponse SendCommand<TResponse>(GFramework.Cqrs.Abstractions.Cqrs.Command.ICommand<TResponse> command)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
@ -454,7 +455,8 @@ public class TestArchitectureContext : IArchitectureContext
|
||||
/// <param name="cancellationToken">取消令牌。</param>
|
||||
/// <returns>查询结果任务。</returns>
|
||||
/// <exception cref="NotImplementedException">该测试桩未实现此成员。</exception>
|
||||
public ValueTask<TResponse> SendQueryAsync<TResponse>(Abstractions.Cqrs.Query.IQuery<TResponse> query,
|
||||
public ValueTask<TResponse> SendQueryAsync<TResponse>(
|
||||
GFramework.Cqrs.Abstractions.Cqrs.Query.IQuery<TResponse> query,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
@ -467,7 +469,7 @@ public class TestArchitectureContext : IArchitectureContext
|
||||
/// <param name="query">要发送的查询。</param>
|
||||
/// <returns>查询结果。</returns>
|
||||
/// <exception cref="NotImplementedException">该测试桩未实现此成员。</exception>
|
||||
public TResponse SendQuery<TResponse>(Abstractions.Cqrs.Query.IQuery<TResponse> query)
|
||||
public TResponse SendQuery<TResponse>(GFramework.Cqrs.Abstractions.Cqrs.Query.IQuery<TResponse> query)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
using GFramework.Core.Abstractions.Command;
|
||||
using GFramework.Core.Abstractions.Cqrs.Command;
|
||||
using GFramework.Core.Abstractions.Rule;
|
||||
using GFramework.Core.Architectures;
|
||||
using GFramework.Core.Command;
|
||||
@ -7,6 +6,7 @@ using GFramework.Core.Environment;
|
||||
using GFramework.Core.Events;
|
||||
using GFramework.Core.Ioc;
|
||||
using GFramework.Core.Query;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs.Command;
|
||||
|
||||
namespace GFramework.Core.Tests.Command;
|
||||
|
||||
@ -396,4 +396,4 @@ public sealed class TestAsyncCommandWithResultChildV3 : AbstractAsyncCommand<Tes
|
||||
Executed = true;
|
||||
return Task.FromResult(input.Value * 3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
using GFramework.Core.Abstractions.Cqrs.Command;
|
||||
using GFramework.Core.Command;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs.Command;
|
||||
|
||||
namespace GFramework.Core.Tests.Command;
|
||||
|
||||
@ -261,4 +261,4 @@ public sealed class TestAsyncCommandWithResult : AbstractAsyncCommand<TestComman
|
||||
Executed = true;
|
||||
return Task.FromResult(input.Value * 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
38
GFramework.Core.Tests/Cqrs/ContainerRegistrationFixtures.cs
Normal file
38
GFramework.Core.Tests/Cqrs/ContainerRegistrationFixtures.cs
Normal file
@ -0,0 +1,38 @@
|
||||
// Copyright (c) 2026 GeWuYou
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using GFramework.Cqrs.Abstractions.Cqrs;
|
||||
|
||||
namespace GFramework.Core.Tests.Cqrs;
|
||||
|
||||
/// <summary>
|
||||
/// 为容器层测试提供可扫描的最小通知夹具。
|
||||
/// </summary>
|
||||
internal sealed record DeterministicOrderNotification : INotification;
|
||||
|
||||
/// <summary>
|
||||
/// 供容器注册测试验证程序集扫描结果的通知处理器。
|
||||
/// </summary>
|
||||
internal sealed class DeterministicOrderNotificationHandler : INotificationHandler<DeterministicOrderNotification>
|
||||
{
|
||||
/// <summary>
|
||||
/// 无副作用地消费通知。
|
||||
/// </summary>
|
||||
/// <param name="notification">通知实例。</param>
|
||||
/// <param name="cancellationToken">取消令牌。</param>
|
||||
/// <returns>已完成任务。</returns>
|
||||
public ValueTask Handle(DeterministicOrderNotification notification, CancellationToken cancellationToken)
|
||||
{
|
||||
return ValueTask.CompletedTask;
|
||||
}
|
||||
}
|
||||
@ -35,16 +35,16 @@ public class MediatorCompatibilityDeprecationTests
|
||||
{
|
||||
AssertLegacyType(
|
||||
typeof(ContextAwareMediatorExtensions),
|
||||
"Use GFramework.Core.Cqrs.Extensions.ContextAwareCqrsExtensions instead.");
|
||||
"Use GFramework.Core.Extensions.ContextAwareCqrsExtensions instead.");
|
||||
AssertLegacyType(
|
||||
typeof(ContextAwareMediatorCommandExtensions),
|
||||
"Use GFramework.Core.Cqrs.Extensions.ContextAwareCqrsCommandExtensions instead.");
|
||||
"Use GFramework.Core.Extensions.ContextAwareCqrsCommandExtensions instead.");
|
||||
AssertLegacyType(
|
||||
typeof(ContextAwareMediatorQueryExtensions),
|
||||
"Use GFramework.Core.Cqrs.Extensions.ContextAwareCqrsQueryExtensions instead.");
|
||||
"Use GFramework.Core.Extensions.ContextAwareCqrsQueryExtensions instead.");
|
||||
AssertLegacyType(
|
||||
typeof(MediatorCoroutineExtensions),
|
||||
"Use GFramework.Core.Cqrs.Extensions.CqrsCoroutineExtensions instead.");
|
||||
"Use GFramework.Core.Coroutine.Extensions.CqrsCoroutineExtensions instead.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@ -1,56 +0,0 @@
|
||||
using System.Reflection;
|
||||
using GFramework.Core.Abstractions.Ioc;
|
||||
using GFramework.Core.Abstractions.Logging;
|
||||
using GFramework.Core.Architectures;
|
||||
using GFramework.Core.Ioc;
|
||||
using GFramework.Core.Logging;
|
||||
|
||||
namespace GFramework.Core.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// 为测试项目提供对 CQRS 处理器真实注册入口的受控访问。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 测试应通过该入口驱动注册流程,而不是直接反射调用注册器的私有辅助方法,
|
||||
/// 这样可以覆盖生产启动路径中的程序集去重、日志记录与容错恢复行为。
|
||||
/// </remarks>
|
||||
internal static class CqrsTestRuntime
|
||||
{
|
||||
private static readonly Type CqrsHandlerRegistrarType = typeof(ArchitectureContext).Assembly
|
||||
.GetType(
|
||||
"GFramework.Core.Cqrs.Internal.CqrsHandlerRegistrar",
|
||||
throwOnError: true)!
|
||||
?? throw new InvalidOperationException(
|
||||
"Failed to locate CqrsHandlerRegistrar type.");
|
||||
|
||||
private static readonly MethodInfo RegisterHandlersMethod = CqrsHandlerRegistrarType
|
||||
.GetMethod(
|
||||
"RegisterHandlers",
|
||||
BindingFlags.Public | BindingFlags.NonPublic |
|
||||
BindingFlags.Static,
|
||||
binder: null,
|
||||
[
|
||||
typeof(IIocContainer),
|
||||
typeof(IEnumerable<Assembly>),
|
||||
typeof(ILogger)
|
||||
],
|
||||
modifiers: null)
|
||||
?? throw new InvalidOperationException(
|
||||
"Failed to locate CqrsHandlerRegistrar.RegisterHandlers.");
|
||||
|
||||
/// <summary>
|
||||
/// 通过与生产代码一致的注册入口扫描并注册指定程序集中的 CQRS 处理器。
|
||||
/// </summary>
|
||||
/// <param name="container">承载处理器映射的测试容器。</param>
|
||||
/// <param name="assemblies">要扫描的程序集集合。</param>
|
||||
internal static void RegisterHandlers(MicrosoftDiContainer container, params Assembly[] assemblies)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(container);
|
||||
ArgumentNullException.ThrowIfNull(assemblies);
|
||||
|
||||
var logger = LoggerFactoryResolver.Provider.CreateLogger(nameof(CqrsTestRuntime));
|
||||
RegisterHandlersMethod.Invoke(
|
||||
null,
|
||||
[container, assemblies.Where(static assembly => assembly is not null).Distinct().ToArray(), logger]);
|
||||
}
|
||||
}
|
||||
@ -18,6 +18,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Scriban" Version="7.1.0" />
|
||||
<ProjectReference Include="..\GFramework.Tests.Common\GFramework.Tests.Common.csproj"/>
|
||||
<ProjectReference Include="..\GFramework.Core.Abstractions\GFramework.Core.Abstractions.csproj"/>
|
||||
<ProjectReference Include="..\GFramework.Core\GFramework.Core.csproj"/>
|
||||
<ProjectReference Include="..\GFramework.SourceGenerators.Abstractions\GFramework.SourceGenerators.Abstractions.csproj"/>
|
||||
|
||||
@ -16,6 +16,7 @@ global using System.Collections.Generic;
|
||||
global using System.Linq;
|
||||
global using System.Threading;
|
||||
global using System.Threading.Tasks;
|
||||
global using GFramework.Tests.Common;
|
||||
global using NUnit.Framework;
|
||||
global using NUnit.Compatibility;
|
||||
global using GFramework.Core.Systems;
|
||||
|
||||
@ -5,6 +5,7 @@ using GFramework.Core.Ioc;
|
||||
using GFramework.Core.Logging;
|
||||
using GFramework.Core.Tests.Cqrs;
|
||||
using GFramework.Core.Tests.Systems;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs;
|
||||
|
||||
namespace GFramework.Core.Tests.Ioc;
|
||||
|
||||
@ -29,6 +30,8 @@ public class MicrosoftDiContainerTests
|
||||
BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
loggerField?.SetValue(_container,
|
||||
LoggerFactoryResolver.Provider.CreateLogger(nameof(MicrosoftDiContainer)));
|
||||
|
||||
CqrsTestRuntime.RegisterInfrastructure(_container);
|
||||
}
|
||||
|
||||
private MicrosoftDiContainer _container = null!;
|
||||
@ -149,6 +152,21 @@ public class MicrosoftDiContainerTests
|
||||
Assert.That(result, Is.SameAs(instance));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试当 CQRS 基础设施已手动接线后,再调用处理器注册入口不会重复注册 runtime seam。
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void RegisterHandlers_Should_Not_Duplicate_Cqrs_Infrastructure_When_It_Is_Already_Registered()
|
||||
{
|
||||
Assert.That(_container.GetAll<ICqrsRuntime>(), Has.Count.EqualTo(1));
|
||||
Assert.That(_container.GetAll<ICqrsHandlerRegistrar>(), Has.Count.EqualTo(1));
|
||||
|
||||
CqrsTestRuntime.RegisterHandlers(_container);
|
||||
|
||||
Assert.That(_container.GetAll<ICqrsRuntime>(), Has.Count.EqualTo(1));
|
||||
Assert.That(_container.GetAll<ICqrsHandlerRegistrar>(), Has.Count.EqualTo(1));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试当没有实例时获取应返回 null 的功能
|
||||
/// </summary>
|
||||
@ -314,7 +332,7 @@ public class MicrosoftDiContainerTests
|
||||
[Test]
|
||||
public void Clear_Should_Reset_Cqrs_Assembly_Deduplication_State()
|
||||
{
|
||||
var assembly = typeof(CqrsHandlerRegistrarTests).Assembly;
|
||||
var assembly = typeof(DeterministicOrderNotification).Assembly;
|
||||
|
||||
_container.RegisterCqrsHandlersFromAssembly(assembly);
|
||||
Assert.That(
|
||||
@ -328,6 +346,8 @@ public class MicrosoftDiContainerTests
|
||||
descriptor.ServiceType == typeof(INotificationHandler<DeterministicOrderNotification>)),
|
||||
Is.False);
|
||||
|
||||
// Clear 会移除测试手工补齐的 CQRS seam,需要先恢复基础设施再验证程序集去重状态是否已重置。
|
||||
CqrsTestRuntime.RegisterInfrastructure(_container);
|
||||
_container.RegisterCqrsHandlersFromAssembly(assembly);
|
||||
|
||||
Assert.That(
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
using GFramework.Core.Abstractions.Cqrs.Query;
|
||||
using GFramework.Core.Abstractions.Query;
|
||||
using GFramework.Core.Abstractions.Rule;
|
||||
using GFramework.Core.Architectures;
|
||||
@ -7,6 +6,7 @@ using GFramework.Core.Environment;
|
||||
using GFramework.Core.Events;
|
||||
using GFramework.Core.Ioc;
|
||||
using GFramework.Core.Query;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs.Query;
|
||||
|
||||
namespace GFramework.Core.Tests.Query;
|
||||
|
||||
@ -411,4 +411,4 @@ public sealed class TestAsyncQueryResultV2
|
||||
/// 获取或设置双倍值
|
||||
/// </summary>
|
||||
public int DoubleValue { get; init; }
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
using GFramework.Core.Abstractions.Cqrs.Query;
|
||||
using GFramework.Core.Query;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs.Query;
|
||||
|
||||
namespace GFramework.Core.Tests.Query;
|
||||
|
||||
@ -292,4 +292,4 @@ public sealed class TestAsyncQueryResult
|
||||
/// 获取或设置双倍值
|
||||
/// </summary>
|
||||
public int DoubleValue { get; init; }
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
using GFramework.Core.Abstractions.Cqrs.Query;
|
||||
using GFramework.Core.Query;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs.Query;
|
||||
|
||||
namespace GFramework.Core.Tests.Query;
|
||||
|
||||
@ -121,4 +121,4 @@ public sealed class TestStringQuery : AbstractQuery<TestQueryInput, string>
|
||||
{
|
||||
return $"Result: {input.Value * 2}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,18 +2,14 @@ using System.Collections.Concurrent;
|
||||
using GFramework.Core.Abstractions.Architectures;
|
||||
using GFramework.Core.Abstractions.Command;
|
||||
using GFramework.Core.Abstractions.Cqrs;
|
||||
using GFramework.Core.Abstractions.Cqrs.Command;
|
||||
using GFramework.Core.Abstractions.Cqrs.Query;
|
||||
using GFramework.Core.Abstractions.Environment;
|
||||
using GFramework.Core.Abstractions.Events;
|
||||
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;
|
||||
using GFramework.Core.Abstractions.Utility;
|
||||
using GFramework.Core.Cqrs.Internal;
|
||||
using GFramework.Core.Logging;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs;
|
||||
using ICommand = GFramework.Core.Abstractions.Command.ICommand;
|
||||
|
||||
namespace GFramework.Core.Architectures;
|
||||
@ -21,19 +17,48 @@ namespace GFramework.Core.Architectures;
|
||||
/// <summary>
|
||||
/// 架构上下文类,提供对系统、模型、工具等组件的访问以及命令、查询、事件的执行管理
|
||||
/// </summary>
|
||||
public class ArchitectureContext(IIocContainer container) : IArchitectureContext
|
||||
public class ArchitectureContext : IArchitectureContext
|
||||
{
|
||||
private readonly IIocContainer _container = container ?? throw new ArgumentNullException(nameof(container));
|
||||
private readonly IIocContainer _container;
|
||||
private readonly Lazy<ICqrsRuntime> _cqrsRuntime;
|
||||
private readonly ConcurrentDictionary<Type, object> _serviceCache = new();
|
||||
private readonly ILogger _logger = LoggerFactoryResolver.Provider.CreateLogger(nameof(ArchitectureContext));
|
||||
private CqrsDispatcher? _cqrsDispatcher;
|
||||
|
||||
/// <summary>
|
||||
/// 初始化新的架构上下文,并绑定其依赖容器。
|
||||
/// </summary>
|
||||
/// <param name="container">
|
||||
/// 当前架构使用的 IOC 容器。
|
||||
/// CQRS runtime 与其他框架服务会通过该容器延迟解析,以避免在上下文构造阶段强制拉起整条运行时链路。
|
||||
/// </param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="container" /> 为 <see langword="null" />。</exception>
|
||||
public ArchitectureContext(IIocContainer container)
|
||||
{
|
||||
_container = container ?? throw new ArgumentNullException(nameof(container));
|
||||
_cqrsRuntime = new Lazy<ICqrsRuntime>(
|
||||
ResolveCqrsRuntime,
|
||||
LazyThreadSafetyMode.ExecutionAndPublication);
|
||||
}
|
||||
|
||||
#region CQRS Integration
|
||||
|
||||
/// <summary>
|
||||
/// 获取 CQRS 运行时分发器(延迟初始化)。
|
||||
/// 获取 CQRS runtime seam。
|
||||
/// </summary>
|
||||
private CqrsDispatcher CqrsDispatcher => _cqrsDispatcher ??= new CqrsDispatcher(_container, this, _logger);
|
||||
/// <remarks>
|
||||
/// 该实例会在首次访问时从容器解析,并通过 <see cref="Lazy{T}" /> 保证并发场景下只执行一次初始化,
|
||||
/// 避免多个请求线程重复触发同一个 runtime 的容器解析。
|
||||
/// </remarks>
|
||||
private ICqrsRuntime CqrsRuntime => _cqrsRuntime.Value;
|
||||
|
||||
/// <summary>
|
||||
/// 从容器解析当前架构上下文依赖的 CQRS runtime。
|
||||
/// </summary>
|
||||
/// <returns>已注册的 CQRS runtime 实例。</returns>
|
||||
/// <exception cref="InvalidOperationException">容器中未注册 <see cref="ICqrsRuntime" />。</exception>
|
||||
private ICqrsRuntime ResolveCqrsRuntime()
|
||||
{
|
||||
return _container.Get<ICqrsRuntime>() ?? throw new InvalidOperationException("ICqrsRuntime not registered");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定类型的服务实例,如果缓存中存在则直接返回,否则从容器中获取并缓存
|
||||
@ -73,7 +98,7 @@ public class ArchitectureContext(IIocContainer container) : IArchitectureContext
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(request);
|
||||
return await CqrsDispatcher.SendAsync(request, cancellationToken);
|
||||
return await CqrsRuntime.SendAsync(this, request, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -100,7 +125,7 @@ public class ArchitectureContext(IIocContainer container) : IArchitectureContext
|
||||
where TNotification : INotification
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(notification);
|
||||
await CqrsDispatcher.PublishAsync(notification, cancellationToken);
|
||||
await CqrsRuntime.PublishAsync(this, notification, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -115,7 +140,7 @@ public class ArchitectureContext(IIocContainer container) : IArchitectureContext
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(request);
|
||||
return CqrsDispatcher.CreateStream(request, cancellationToken);
|
||||
return CqrsRuntime.CreateStream(this, request, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -151,7 +176,7 @@ public class ArchitectureContext(IIocContainer container) : IArchitectureContext
|
||||
/// <typeparam name="TResult">查询结果类型</typeparam>
|
||||
/// <param name="query">要发送的查询</param>
|
||||
/// <returns>查询结果</returns>
|
||||
public TResult SendQuery<TResult>(Abstractions.Query.IQuery<TResult> query)
|
||||
public TResult SendQuery<TResult>(IQuery<TResult> query)
|
||||
{
|
||||
if (query == null) throw new ArgumentNullException(nameof(query));
|
||||
var queryBus = GetOrCache<IQueryExecutor>();
|
||||
@ -165,7 +190,7 @@ public class ArchitectureContext(IIocContainer container) : IArchitectureContext
|
||||
/// <typeparam name="TResponse">查询响应类型</typeparam>
|
||||
/// <param name="query">要发送的查询对象</param>
|
||||
/// <returns>查询结果</returns>
|
||||
public TResponse SendQuery<TResponse>(GFramework.Core.Abstractions.Cqrs.Query.IQuery<TResponse> query)
|
||||
public TResponse SendQuery<TResponse>(GFramework.Cqrs.Abstractions.Cqrs.Query.IQuery<TResponse> query)
|
||||
{
|
||||
return SendQueryAsync(query).AsTask().GetAwaiter().GetResult();
|
||||
}
|
||||
@ -191,7 +216,8 @@ public class ArchitectureContext(IIocContainer container) : IArchitectureContext
|
||||
/// <param name="query">要发送的查询对象</param>
|
||||
/// <param name="cancellationToken">取消令牌,用于取消操作</param>
|
||||
/// <returns>包含查询结果的ValueTask</returns>
|
||||
public async ValueTask<TResponse> SendQueryAsync<TResponse>(GFramework.Core.Abstractions.Cqrs.Query.IQuery<TResponse> query,
|
||||
public async ValueTask<TResponse> SendQueryAsync<TResponse>(
|
||||
GFramework.Cqrs.Abstractions.Cqrs.Query.IQuery<TResponse> query,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(query);
|
||||
@ -327,7 +353,8 @@ public class ArchitectureContext(IIocContainer container) : IArchitectureContext
|
||||
/// <param name="command">要发送的命令对象</param>
|
||||
/// <param name="cancellationToken">取消令牌,用于取消操作</param>
|
||||
/// <returns>包含命令执行结果的ValueTask</returns>
|
||||
public async ValueTask<TResponse> SendCommandAsync<TResponse>(GFramework.Core.Abstractions.Cqrs.Command.ICommand<TResponse> command,
|
||||
public async ValueTask<TResponse> SendCommandAsync<TResponse>(
|
||||
GFramework.Cqrs.Abstractions.Cqrs.Command.ICommand<TResponse> command,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(command);
|
||||
@ -366,7 +393,7 @@ public class ArchitectureContext(IIocContainer container) : IArchitectureContext
|
||||
/// <typeparam name="TResponse">命令响应类型</typeparam>
|
||||
/// <param name="command">要发送的命令对象</param>
|
||||
/// <returns>命令执行结果</returns>
|
||||
public TResponse SendCommand<TResponse>(GFramework.Core.Abstractions.Cqrs.Command.ICommand<TResponse> command)
|
||||
public TResponse SendCommand<TResponse>(GFramework.Cqrs.Abstractions.Cqrs.Command.ICommand<TResponse> command)
|
||||
{
|
||||
return SendCommandAsync(command).AsTask().GetAwaiter().GetResult();
|
||||
}
|
||||
@ -388,7 +415,7 @@ public class ArchitectureContext(IIocContainer container) : IArchitectureContext
|
||||
/// <typeparam name="TResult">命令执行结果类型</typeparam>
|
||||
/// <param name="command">要发送的命令</param>
|
||||
/// <returns>命令执行结果</returns>
|
||||
public TResult SendCommand<TResult>(Abstractions.Command.ICommand<TResult> command)
|
||||
public TResult SendCommand<TResult>(ICommand<TResult> command)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(command);
|
||||
var commandBus = GetOrCache<ICommandExecutor>();
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
using GFramework.Core.Abstractions.Command;
|
||||
using GFramework.Core.Abstractions.Cqrs.Command;
|
||||
using GFramework.Core.Rule;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs.Command;
|
||||
|
||||
namespace GFramework.Core.Command;
|
||||
|
||||
@ -26,4 +26,4 @@ public abstract class AbstractAsyncCommand<TInput>(TInput input) : ContextAwareB
|
||||
/// <param name="input">命令输入参数</param>
|
||||
/// <returns>表示异步操作的任务</returns>
|
||||
protected abstract Task OnExecuteAsync(TInput input);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
using GFramework.Core.Abstractions.Command;
|
||||
using GFramework.Core.Abstractions.Cqrs.Command;
|
||||
using GFramework.Core.Rule;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs.Command;
|
||||
|
||||
namespace GFramework.Core.Command;
|
||||
|
||||
@ -27,4 +27,4 @@ public abstract class AbstractAsyncCommand<TInput, TResult>(TInput input) : Cont
|
||||
/// <param name="input">命令输入参数</param>
|
||||
/// <returns>表示异步操作且包含结果的任务</returns>
|
||||
protected abstract Task<TResult> OnExecuteAsync(TInput input);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
using GFramework.Core.Abstractions.Command;
|
||||
using GFramework.Core.Abstractions.Cqrs.Command;
|
||||
using GFramework.Core.Rule;
|
||||
using GFramework.Core.Rule;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs.Command;
|
||||
using ICommand = GFramework.Core.Abstractions.Command.ICommand;
|
||||
|
||||
namespace GFramework.Core.Command;
|
||||
|
||||
@ -9,13 +9,13 @@ namespace GFramework.Core.Command;
|
||||
/// </summary>
|
||||
/// <typeparam name="TInput">命令输入参数类型,必须实现 ICommandInput 接口</typeparam>
|
||||
/// <param name="input">命令执行所需的输入参数</param>
|
||||
public abstract class AbstractCommand<TInput>(TInput input) : ContextAwareBase, GFramework.Core.Abstractions.Command.ICommand
|
||||
public abstract class AbstractCommand<TInput>(TInput input) : ContextAwareBase, ICommand
|
||||
where TInput : ICommandInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 执行命令的入口方法,实现 ICommand 接口的 Execute 方法
|
||||
/// </summary>
|
||||
void GFramework.Core.Abstractions.Command.ICommand.Execute()
|
||||
void ICommand.Execute()
|
||||
{
|
||||
OnExecute(input);
|
||||
}
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
using GFramework.Core.Abstractions.Command;
|
||||
using GFramework.Core.Abstractions.Cqrs.Command;
|
||||
using GFramework.Core.Rule;
|
||||
using GFramework.Core.Rule;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs.Command;
|
||||
|
||||
namespace GFramework.Core.Command;
|
||||
|
||||
@ -10,14 +9,15 @@ namespace GFramework.Core.Command;
|
||||
/// <typeparam name="TInput">命令输入参数类型,必须实现 ICommandInput 接口</typeparam>
|
||||
/// <typeparam name="TResult">命令执行后返回的结果类型</typeparam>
|
||||
/// <param name="input">命令执行所需的输入参数</param>
|
||||
public abstract class AbstractCommand<TInput, TResult>(TInput input) : ContextAwareBase, GFramework.Core.Abstractions.Command.ICommand<TResult>
|
||||
public abstract class AbstractCommand<TInput, TResult>(TInput input)
|
||||
: ContextAwareBase, Abstractions.Command.ICommand<TResult>
|
||||
where TInput : ICommandInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 执行命令的入口方法,实现 ICommand{TResult} 接口的 Execute 方法
|
||||
/// </summary>
|
||||
/// <returns>命令执行后的结果</returns>
|
||||
TResult GFramework.Core.Abstractions.Command.ICommand<TResult>.Execute()
|
||||
TResult Abstractions.Command.ICommand<TResult>.Execute()
|
||||
{
|
||||
return OnExecute(input);
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
using GFramework.Core.Abstractions.Cqrs.Command;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs.Command;
|
||||
|
||||
namespace GFramework.Core.Command;
|
||||
|
||||
@ -9,4 +9,4 @@ namespace GFramework.Core.Command;
|
||||
/// 该类实现了ICommandInput接口,作为命令模式中的输入参数载体
|
||||
/// 通常用于不需要额外输入参数的简单命令操作
|
||||
/// </remarks>
|
||||
public sealed class EmptyCommandInput : ICommandInput;
|
||||
public sealed class EmptyCommandInput : ICommandInput;
|
||||
|
||||
@ -1,10 +1,9 @@
|
||||
using System.Runtime.ExceptionServices;
|
||||
using GFramework.Core.Abstractions.Coroutine;
|
||||
using GFramework.Core.Abstractions.Cqrs;
|
||||
using GFramework.Core.Abstractions.Rule;
|
||||
using GFramework.Core.Coroutine.Extensions;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs;
|
||||
|
||||
namespace GFramework.Core.Cqrs.Extensions;
|
||||
namespace GFramework.Core.Coroutine.Extensions;
|
||||
|
||||
/// <summary>
|
||||
/// 提供 CQRS 命令与协程集成的扩展方法。
|
||||
|
||||
@ -13,20 +13,19 @@
|
||||
|
||||
using System.ComponentModel;
|
||||
using GFramework.Core.Abstractions.Coroutine;
|
||||
using GFramework.Core.Abstractions.Cqrs;
|
||||
using GFramework.Core.Abstractions.Rule;
|
||||
using GFramework.Core.Cqrs.Extensions;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs;
|
||||
|
||||
namespace GFramework.Core.Coroutine.Extensions;
|
||||
|
||||
/// <summary>
|
||||
/// 提供 CQRS 命令与协程集成的扩展方法。
|
||||
/// 该类型保留旧名称以兼容历史调用点;新代码应改用 <see cref="GFramework.Core.Cqrs.Extensions.CqrsCoroutineExtensions" />。
|
||||
/// 该类型保留旧名称以兼容历史调用点;新代码应改用 <see cref="CqrsCoroutineExtensions" />。
|
||||
/// 兼容层计划在未来的 major 版本中移除,因此不会继续承载新能力。
|
||||
/// </summary>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
[Obsolete(
|
||||
"Use GFramework.Core.Cqrs.Extensions.CqrsCoroutineExtensions instead. This compatibility alias will be removed in a future major version.")]
|
||||
"Use GFramework.Core.Coroutine.Extensions.CqrsCoroutineExtensions instead. This compatibility alias will be removed in a future major version.")]
|
||||
public static class MediatorCoroutineExtensions
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@ -12,9 +12,9 @@
|
||||
// limitations under the License.
|
||||
|
||||
using System.Diagnostics;
|
||||
using GFramework.Core.Abstractions.Cqrs;
|
||||
using GFramework.Core.Abstractions.Logging;
|
||||
using GFramework.Core.Logging;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs;
|
||||
|
||||
namespace GFramework.Core.Cqrs.Behaviors;
|
||||
|
||||
|
||||
@ -12,9 +12,9 @@
|
||||
// limitations under the License.
|
||||
|
||||
using System.Diagnostics;
|
||||
using GFramework.Core.Abstractions.Cqrs;
|
||||
using GFramework.Core.Abstractions.Logging;
|
||||
using GFramework.Core.Logging;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs;
|
||||
|
||||
namespace GFramework.Core.Cqrs.Behaviors;
|
||||
|
||||
|
||||
@ -11,9 +11,9 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using GFramework.Core.Abstractions.Cqrs;
|
||||
using GFramework.Core.Abstractions.Cqrs.Command;
|
||||
using GFramework.Core.Rule;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs.Command;
|
||||
|
||||
namespace GFramework.Core.Cqrs.Command;
|
||||
|
||||
|
||||
@ -11,9 +11,9 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using GFramework.Core.Abstractions.Cqrs;
|
||||
using GFramework.Core.Abstractions.Cqrs.Command;
|
||||
using GFramework.Core.Rule;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs.Command;
|
||||
|
||||
namespace GFramework.Core.Cqrs.Command;
|
||||
|
||||
|
||||
@ -11,7 +11,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using GFramework.Core.Abstractions.Cqrs.Command;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs.Command;
|
||||
|
||||
namespace GFramework.Core.Cqrs.Command;
|
||||
|
||||
|
||||
@ -5,6 +5,7 @@ using GFramework.Core.Abstractions.Cqrs;
|
||||
using GFramework.Core.Abstractions.Ioc;
|
||||
using GFramework.Core.Abstractions.Logging;
|
||||
using GFramework.Core.Abstractions.Rule;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs;
|
||||
|
||||
namespace GFramework.Core.Cqrs.Internal;
|
||||
|
||||
@ -14,34 +15,76 @@ namespace GFramework.Core.Cqrs.Internal;
|
||||
/// </summary>
|
||||
internal sealed class CqrsDispatcher(
|
||||
IIocContainer container,
|
||||
IArchitectureContext context,
|
||||
ILogger logger)
|
||||
ILogger logger) : ICqrsRuntime
|
||||
{
|
||||
private delegate ValueTask<object?> RequestInvoker(object handler, object request, CancellationToken cancellationToken);
|
||||
private delegate ValueTask<object?> RequestPipelineInvoker(
|
||||
object handler,
|
||||
IReadOnlyList<object> behaviors,
|
||||
object request,
|
||||
CancellationToken cancellationToken);
|
||||
private delegate ValueTask NotificationInvoker(object handler, object notification, CancellationToken cancellationToken);
|
||||
private delegate object StreamInvoker(object handler, object request, CancellationToken cancellationToken);
|
||||
// 进程级缓存:按请求/响应类型缓存直接处理器调用委托,避免热路径重复反射。
|
||||
// 线程安全依赖 ConcurrentDictionary;缓存与进程同寿命,默认假设请求类型集合有限且稳定。
|
||||
private static readonly ConcurrentDictionary<(Type RequestType, Type ResponseType), RequestInvoker>
|
||||
RequestInvokers = new();
|
||||
|
||||
private static readonly ConcurrentDictionary<(Type RequestType, Type ResponseType), RequestInvoker> RequestInvokers = new();
|
||||
private static readonly ConcurrentDictionary<(Type RequestType, Type ResponseType), RequestPipelineInvoker> RequestPipelineInvokers = new();
|
||||
// 进程级缓存:缓存带 pipeline 的请求调用委托,减少每次分发时的反射与表达式重建开销。
|
||||
// 若后续引入动态生成请求类型,需要重新评估该缓存的增长边界。
|
||||
private static readonly ConcurrentDictionary<(Type RequestType, Type ResponseType), RequestPipelineInvoker>
|
||||
RequestPipelineInvokers = new();
|
||||
|
||||
// 进程级缓存:缓存通知调用委托,复用并发安全字典以支撑多线程发布路径。
|
||||
private static readonly ConcurrentDictionary<Type, NotificationInvoker> NotificationInvokers = new();
|
||||
private static readonly ConcurrentDictionary<(Type RequestType, Type ResponseType), StreamInvoker> StreamInvokers = new();
|
||||
|
||||
// 进程级缓存:缓存流式请求调用委托,避免每次创建流时重复解析反射签名。
|
||||
private static readonly ConcurrentDictionary<(Type RequestType, Type ResponseType), StreamInvoker> StreamInvokers =
|
||||
new();
|
||||
|
||||
/// <summary>
|
||||
/// 发布通知到所有已注册处理器。
|
||||
/// </summary>
|
||||
/// <typeparam name="TNotification">通知类型。</typeparam>
|
||||
/// <param name="context">当前架构上下文,用于上下文感知处理器注入。</param>
|
||||
/// <param name="notification">通知对象。</param>
|
||||
/// <param name="cancellationToken">取消令牌。</param>
|
||||
public async ValueTask PublishAsync<TNotification>(
|
||||
IArchitectureContext context,
|
||||
TNotification notification,
|
||||
CancellationToken cancellationToken = default)
|
||||
where TNotification : INotification
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(context);
|
||||
ArgumentNullException.ThrowIfNull(notification);
|
||||
|
||||
var notificationType = notification.GetType();
|
||||
var handlerType = typeof(INotificationHandler<>).MakeGenericType(notificationType);
|
||||
var handlers = container.GetAll(handlerType);
|
||||
|
||||
if (handlers.Count == 0)
|
||||
{
|
||||
logger.Debug($"No CQRS notification handler registered for {notificationType.FullName}.");
|
||||
return;
|
||||
}
|
||||
|
||||
var invoker = NotificationInvokers.GetOrAdd(
|
||||
notificationType,
|
||||
CreateNotificationInvoker);
|
||||
|
||||
foreach (var handler in handlers)
|
||||
{
|
||||
PrepareHandler(handler, context);
|
||||
await invoker(handler, notification, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 发送请求并返回结果。
|
||||
/// </summary>
|
||||
/// <typeparam name="TResponse">响应类型。</typeparam>
|
||||
/// <param name="context">当前架构上下文,用于上下文感知处理器注入。</param>
|
||||
/// <param name="request">请求对象。</param>
|
||||
/// <param name="cancellationToken">取消令牌。</param>
|
||||
/// <returns>请求响应。</returns>
|
||||
public async ValueTask<TResponse> SendAsync<TResponse>(
|
||||
IArchitectureContext context,
|
||||
IRequest<TResponse> request,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(context);
|
||||
ArgumentNullException.ThrowIfNull(request);
|
||||
|
||||
var requestType = request.GetType();
|
||||
@ -50,12 +93,12 @@ internal sealed class CqrsDispatcher(
|
||||
?? throw new InvalidOperationException(
|
||||
$"No CQRS request handler registered for {requestType.FullName}.");
|
||||
|
||||
PrepareHandler(handler);
|
||||
PrepareHandler(handler, context);
|
||||
var behaviorType = typeof(IPipelineBehavior<,>).MakeGenericType(requestType, typeof(TResponse));
|
||||
var behaviors = container.GetAll(behaviorType);
|
||||
|
||||
foreach (var behavior in behaviors)
|
||||
PrepareHandler(behavior);
|
||||
PrepareHandler(behavior, context);
|
||||
|
||||
if (behaviors.Count == 0)
|
||||
{
|
||||
@ -75,51 +118,20 @@ internal sealed class CqrsDispatcher(
|
||||
return pipelineResult is null ? default! : (TResponse)pipelineResult;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 发布通知到所有已注册处理器。
|
||||
/// </summary>
|
||||
/// <typeparam name="TNotification">通知类型。</typeparam>
|
||||
/// <param name="notification">通知对象。</param>
|
||||
/// <param name="cancellationToken">取消令牌。</param>
|
||||
public async ValueTask PublishAsync<TNotification>(
|
||||
TNotification notification,
|
||||
CancellationToken cancellationToken = default)
|
||||
where TNotification : INotification
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(notification);
|
||||
|
||||
var notificationType = notification.GetType();
|
||||
var handlerType = typeof(INotificationHandler<>).MakeGenericType(notificationType);
|
||||
var handlers = container.GetAll(handlerType);
|
||||
|
||||
if (handlers.Count == 0)
|
||||
{
|
||||
logger.Debug($"No CQRS notification handler registered for {notificationType.FullName}.");
|
||||
return;
|
||||
}
|
||||
|
||||
var invoker = NotificationInvokers.GetOrAdd(
|
||||
notificationType,
|
||||
CreateNotificationInvoker);
|
||||
|
||||
foreach (var handler in handlers)
|
||||
{
|
||||
PrepareHandler(handler);
|
||||
await invoker(handler, notification, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建流式请求并返回异步响应序列。
|
||||
/// </summary>
|
||||
/// <typeparam name="TResponse">响应元素类型。</typeparam>
|
||||
/// <param name="context">当前架构上下文,用于上下文感知处理器注入。</param>
|
||||
/// <param name="request">流式请求对象。</param>
|
||||
/// <param name="cancellationToken">取消令牌。</param>
|
||||
/// <returns>异步响应序列。</returns>
|
||||
public IAsyncEnumerable<TResponse> CreateStream<TResponse>(
|
||||
IArchitectureContext context,
|
||||
IStreamRequest<TResponse> request,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(context);
|
||||
ArgumentNullException.ThrowIfNull(request);
|
||||
|
||||
var requestType = request.GetType();
|
||||
@ -128,7 +140,7 @@ internal sealed class CqrsDispatcher(
|
||||
?? throw new InvalidOperationException(
|
||||
$"No CQRS stream handler registered for {requestType.FullName}.");
|
||||
|
||||
PrepareHandler(handler);
|
||||
PrepareHandler(handler, context);
|
||||
|
||||
var invoker = StreamInvokers.GetOrAdd(
|
||||
(requestType, typeof(TResponse)),
|
||||
@ -141,7 +153,8 @@ internal sealed class CqrsDispatcher(
|
||||
/// 为上下文感知处理器注入当前架构上下文。
|
||||
/// </summary>
|
||||
/// <param name="handler">处理器实例。</param>
|
||||
private void PrepareHandler(object handler)
|
||||
/// <param name="context">当前架构上下文。</param>
|
||||
private static void PrepareHandler(object handler, IArchitectureContext context)
|
||||
{
|
||||
if (handler is IContextAware contextAware)
|
||||
contextAware.SetContext(context);
|
||||
@ -260,4 +273,18 @@ internal sealed class CqrsDispatcher(
|
||||
var typedRequest = (TRequest)request;
|
||||
return typedHandler.Handle(typedRequest, cancellationToken);
|
||||
}
|
||||
|
||||
private delegate ValueTask<object?> RequestInvoker(object handler, object request,
|
||||
CancellationToken cancellationToken);
|
||||
|
||||
private delegate ValueTask<object?> RequestPipelineInvoker(
|
||||
object handler,
|
||||
IReadOnlyList<object> behaviors,
|
||||
object request,
|
||||
CancellationToken cancellationToken);
|
||||
|
||||
private delegate ValueTask NotificationInvoker(object handler, object notification,
|
||||
CancellationToken cancellationToken);
|
||||
|
||||
private delegate object StreamInvoker(object handler, object request, CancellationToken cancellationToken);
|
||||
}
|
||||
|
||||
@ -2,6 +2,7 @@ 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;
|
||||
|
||||
|
||||
27
GFramework.Core/Cqrs/Internal/DefaultCqrsHandlerRegistrar.cs
Normal file
27
GFramework.Core/Cqrs/Internal/DefaultCqrsHandlerRegistrar.cs
Normal file
@ -0,0 +1,27 @@
|
||||
using System.Reflection;
|
||||
using GFramework.Core.Abstractions.Ioc;
|
||||
using GFramework.Core.Abstractions.Logging;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs;
|
||||
|
||||
namespace GFramework.Core.Cqrs.Internal;
|
||||
|
||||
/// <summary>
|
||||
/// 默认的 CQRS 处理器注册器实现。
|
||||
/// 该适配器把容器公开的 handler 接入入口转发到现有的注册流水线,
|
||||
/// 使容器主路径只依赖 <see cref="ICqrsHandlerRegistrar" /> 抽象。
|
||||
/// </summary>
|
||||
internal sealed class DefaultCqrsHandlerRegistrar(IIocContainer container, ILogger logger) : ICqrsHandlerRegistrar
|
||||
{
|
||||
private readonly IIocContainer _container = container ?? throw new ArgumentNullException(nameof(container));
|
||||
private readonly ILogger _logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
|
||||
/// <summary>
|
||||
/// 按当前 runtime 约定扫描并注册处理器程序集。
|
||||
/// </summary>
|
||||
/// <param name="assemblies">要接入的程序集集合。</param>
|
||||
public void RegisterHandlers(IEnumerable<Assembly> assemblies)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(assemblies);
|
||||
CqrsHandlerRegistrar.RegisterHandlers(_container, assemblies, _logger);
|
||||
}
|
||||
}
|
||||
@ -12,7 +12,7 @@
|
||||
// limitations under the License.
|
||||
|
||||
using GFramework.Core.Rule;
|
||||
using GFramework.Core.Abstractions.Cqrs;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs;
|
||||
|
||||
namespace GFramework.Core.Cqrs.Notification;
|
||||
|
||||
|
||||
@ -11,8 +11,8 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using GFramework.Core.Abstractions.Cqrs.Notification;
|
||||
using GFramework.Core.Abstractions.Cqrs;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs.Notification;
|
||||
|
||||
namespace GFramework.Core.Cqrs.Notification;
|
||||
|
||||
|
||||
@ -11,9 +11,9 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using GFramework.Core.Abstractions.Cqrs;
|
||||
using GFramework.Core.Abstractions.Cqrs.Query;
|
||||
using GFramework.Core.Rule;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs.Query;
|
||||
|
||||
namespace GFramework.Core.Cqrs.Query;
|
||||
|
||||
|
||||
@ -12,8 +12,8 @@
|
||||
// limitations under the License.
|
||||
|
||||
using GFramework.Core.Rule;
|
||||
using GFramework.Core.Abstractions.Cqrs;
|
||||
using GFramework.Core.Abstractions.Cqrs.Query;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs.Query;
|
||||
|
||||
namespace GFramework.Core.Cqrs.Query;
|
||||
|
||||
|
||||
@ -11,7 +11,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using GFramework.Core.Abstractions.Cqrs.Query;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs.Query;
|
||||
|
||||
namespace GFramework.Core.Cqrs.Query;
|
||||
|
||||
|
||||
@ -12,7 +12,7 @@
|
||||
// limitations under the License.
|
||||
|
||||
using GFramework.Core.Rule;
|
||||
using GFramework.Core.Abstractions.Cqrs;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs;
|
||||
|
||||
namespace GFramework.Core.Cqrs.Request;
|
||||
|
||||
|
||||
@ -12,7 +12,7 @@
|
||||
// limitations under the License.
|
||||
|
||||
using GFramework.Core.Rule;
|
||||
using GFramework.Core.Abstractions.Cqrs;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs;
|
||||
|
||||
namespace GFramework.Core.Cqrs.Request;
|
||||
|
||||
|
||||
@ -11,8 +11,8 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using GFramework.Core.Abstractions.Cqrs.Request;
|
||||
using GFramework.Core.Abstractions.Cqrs;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs.Request;
|
||||
|
||||
namespace GFramework.Core.Cqrs.Request;
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
using GFramework.Core.Abstractions.Cqrs.Command;
|
||||
using GFramework.Core.Abstractions.Rule;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs.Command;
|
||||
|
||||
namespace GFramework.Core.Cqrs.Extensions;
|
||||
namespace GFramework.Core.Extensions;
|
||||
|
||||
/// <summary>
|
||||
/// 提供对 <see cref="IContextAware" /> 接口的 CQRS 命令扩展方法。
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
using GFramework.Core.Abstractions.Cqrs;
|
||||
using GFramework.Core.Abstractions.Rule;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs;
|
||||
|
||||
namespace GFramework.Core.Cqrs.Extensions;
|
||||
namespace GFramework.Core.Extensions;
|
||||
|
||||
/// <summary>
|
||||
/// 提供对 <see cref="IContextAware" /> 接口的 CQRS 统一扩展方法。
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
using GFramework.Core.Abstractions.Cqrs.Query;
|
||||
using GFramework.Core.Abstractions.Rule;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs.Query;
|
||||
|
||||
namespace GFramework.Core.Cqrs.Extensions;
|
||||
namespace GFramework.Core.Extensions;
|
||||
|
||||
/// <summary>
|
||||
/// 提供对 <see cref="IContextAware" /> 接口的 CQRS 查询扩展方法。
|
||||
|
||||
@ -1,18 +1,17 @@
|
||||
using System.ComponentModel;
|
||||
using GFramework.Core.Abstractions.Cqrs.Command;
|
||||
using GFramework.Core.Abstractions.Rule;
|
||||
using GFramework.Core.Cqrs.Extensions;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs.Command;
|
||||
|
||||
namespace GFramework.Core.Extensions;
|
||||
|
||||
/// <summary>
|
||||
/// 提供对 <see cref="IContextAware" /> 接口的 CQRS 命令扩展方法。
|
||||
/// 该类型保留旧名称以兼容历史调用点;新代码应改用 <see cref="GFramework.Core.Cqrs.Extensions.ContextAwareCqrsCommandExtensions" />。
|
||||
/// 该类型保留旧名称以兼容历史调用点;新代码应改用 <see cref="ContextAwareCqrsCommandExtensions" />。
|
||||
/// 兼容层计划在未来的 major 版本中移除,因此不会继续承载新能力。
|
||||
/// </summary>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
[Obsolete(
|
||||
"Use GFramework.Core.Cqrs.Extensions.ContextAwareCqrsCommandExtensions instead. This compatibility alias will be removed in a future major version.")]
|
||||
"Use GFramework.Core.Extensions.ContextAwareCqrsCommandExtensions instead. This compatibility alias will be removed in a future major version.")]
|
||||
public static class ContextAwareMediatorCommandExtensions
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@ -1,18 +1,17 @@
|
||||
using System.ComponentModel;
|
||||
using GFramework.Core.Abstractions.Cqrs;
|
||||
using GFramework.Core.Abstractions.Rule;
|
||||
using GFramework.Core.Cqrs.Extensions;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs;
|
||||
|
||||
namespace GFramework.Core.Extensions;
|
||||
|
||||
/// <summary>
|
||||
/// 提供对 <see cref="IContextAware" /> 接口的 CQRS 统一接口扩展方法。
|
||||
/// 该类型保留旧名称以兼容历史调用点;新代码应改用 <see cref="GFramework.Core.Cqrs.Extensions.ContextAwareCqrsExtensions" />。
|
||||
/// 该类型保留旧名称以兼容历史调用点;新代码应改用 <see cref="ContextAwareCqrsExtensions" />。
|
||||
/// 兼容层计划在未来的 major 版本中移除,因此不会继续承载新能力。
|
||||
/// </summary>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
[Obsolete(
|
||||
"Use GFramework.Core.Cqrs.Extensions.ContextAwareCqrsExtensions instead. This compatibility alias will be removed in a future major version.")]
|
||||
"Use GFramework.Core.Extensions.ContextAwareCqrsExtensions instead. This compatibility alias will be removed in a future major version.")]
|
||||
public static class ContextAwareMediatorExtensions
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@ -1,18 +1,17 @@
|
||||
using System.ComponentModel;
|
||||
using GFramework.Core.Abstractions.Cqrs.Query;
|
||||
using GFramework.Core.Abstractions.Rule;
|
||||
using GFramework.Core.Cqrs.Extensions;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs.Query;
|
||||
|
||||
namespace GFramework.Core.Extensions;
|
||||
|
||||
/// <summary>
|
||||
/// 提供对 <see cref="IContextAware" /> 接口的 CQRS 查询扩展方法。
|
||||
/// 该类型保留旧名称以兼容历史调用点;新代码应改用 <see cref="GFramework.Core.Cqrs.Extensions.ContextAwareCqrsQueryExtensions" />。
|
||||
/// 该类型保留旧名称以兼容历史调用点;新代码应改用 <see cref="ContextAwareCqrsQueryExtensions" />。
|
||||
/// 兼容层计划在未来的 major 版本中移除,因此不会继续承载新能力。
|
||||
/// </summary>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
[Obsolete(
|
||||
"Use GFramework.Core.Cqrs.Extensions.ContextAwareCqrsQueryExtensions instead. This compatibility alias will be removed in a future major version.")]
|
||||
"Use GFramework.Core.Extensions.ContextAwareCqrsQueryExtensions instead. This compatibility alias will be removed in a future major version.")]
|
||||
public static class ContextAwareMediatorQueryExtensions
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@ -9,6 +9,7 @@
|
||||
<EnableGFrameworkPackageTransitiveGlobalUsings>true</EnableGFrameworkPackageTransitiveGlobalUsings>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\GFramework.Cqrs.Abstractions\GFramework.Cqrs.Abstractions.csproj"/>
|
||||
<ProjectReference Include="..\$(AssemblyName).Abstractions\$(AssemblyName).Abstractions.csproj"/>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
||||
@ -1,13 +1,12 @@
|
||||
using System.ComponentModel;
|
||||
using System.Reflection;
|
||||
using GFramework.Core.Abstractions.Bases;
|
||||
using GFramework.Core.Abstractions.Cqrs;
|
||||
using GFramework.Core.Abstractions.Ioc;
|
||||
using GFramework.Core.Abstractions.Logging;
|
||||
using GFramework.Core.Abstractions.Systems;
|
||||
using GFramework.Core.Cqrs.Internal;
|
||||
using GFramework.Core.Logging;
|
||||
using GFramework.Core.Rule;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs;
|
||||
|
||||
namespace GFramework.Core.Ioc;
|
||||
|
||||
@ -424,7 +423,7 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
|
||||
continue;
|
||||
}
|
||||
|
||||
CqrsHandlerRegistrar.RegisterHandlers(this, [assembly], _logger);
|
||||
ResolveCqrsHandlerRegistrar().RegisterHandlers([assembly]);
|
||||
_registeredCqrsHandlerAssemblyKeys.Add(assemblyKey);
|
||||
}
|
||||
}
|
||||
@ -456,6 +455,27 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
|
||||
|
||||
#region Get
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前容器中已注册的 CQRS 处理器注册器。
|
||||
/// 该方法仅供容器内部在注册阶段使用,因此直接读取服务描述符中的实例绑定,
|
||||
/// 避免在容器未冻结前依赖完整的服务提供者构建流程。
|
||||
/// </summary>
|
||||
/// <returns>已注册的 CQRS 处理器注册器实例。</returns>
|
||||
/// <exception cref="InvalidOperationException">未找到可用的 CQRS 处理器注册器实例时抛出。</exception>
|
||||
private ICqrsHandlerRegistrar ResolveCqrsHandlerRegistrar()
|
||||
{
|
||||
var descriptor = GetServicesUnsafe.LastOrDefault(static service =>
|
||||
service.ServiceType == typeof(ICqrsHandlerRegistrar));
|
||||
|
||||
if (descriptor?.ImplementationInstance is ICqrsHandlerRegistrar registrar)
|
||||
return registrar;
|
||||
|
||||
const string errorMessage =
|
||||
"ICqrsHandlerRegistrar not registered. Ensure the CQRS runtime module has been installed before registering handlers.";
|
||||
_logger.Error(errorMessage);
|
||||
throw new InvalidOperationException(errorMessage);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定泛型类型的服务实例
|
||||
/// 返回第一个匹配的注册实例,如果不存在则返回null
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
using GFramework.Core.Abstractions.Cqrs.Query;
|
||||
using GFramework.Core.Abstractions.Query;
|
||||
using GFramework.Core.Abstractions.Query;
|
||||
using GFramework.Core.Rule;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs.Query;
|
||||
|
||||
namespace GFramework.Core.Query;
|
||||
|
||||
@ -30,4 +30,4 @@ public abstract class AbstractAsyncQuery<TInput, TResult>(
|
||||
/// <param name="input">查询输入参数</param>
|
||||
/// <returns>返回查询结果的异步任务</returns>
|
||||
protected abstract Task<TResult> OnDoAsync(TInput input);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
using GFramework.Core.Abstractions.Cqrs.Query;
|
||||
using GFramework.Core.Abstractions.Query;
|
||||
using GFramework.Core.Rule;
|
||||
using GFramework.Core.Rule;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs.Query;
|
||||
|
||||
namespace GFramework.Core.Query;
|
||||
|
||||
@ -9,7 +8,8 @@ namespace GFramework.Core.Query;
|
||||
/// </summary>
|
||||
/// <typeparam name="TInput">查询输入参数的类型,必须实现IQueryInput接口</typeparam>
|
||||
/// <typeparam name="TResult">查询结果的类型</typeparam>
|
||||
public abstract class AbstractQuery<TInput, TResult>(TInput input) : ContextAwareBase, GFramework.Core.Abstractions.Query.IQuery<TResult>
|
||||
public abstract class AbstractQuery<TInput, TResult>(TInput input)
|
||||
: ContextAwareBase, Abstractions.Query.IQuery<TResult>
|
||||
where TInput : IQueryInput
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
using GFramework.Core.Abstractions.Cqrs.Query;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs.Query;
|
||||
|
||||
namespace GFramework.Core.Query;
|
||||
|
||||
@ -8,4 +8,4 @@ namespace GFramework.Core.Query;
|
||||
/// <remarks>
|
||||
/// 该类实现了IQueryInput接口,作为占位符使用,适用于那些不需要额外输入参数的查询场景
|
||||
/// </remarks>
|
||||
public sealed class EmptyQueryInput : IQueryInput;
|
||||
public sealed class EmptyQueryInput : IQueryInput;
|
||||
|
||||
62
GFramework.Core/Services/Modules/CqrsRuntimeModule.cs
Normal file
62
GFramework.Core/Services/Modules/CqrsRuntimeModule.cs
Normal file
@ -0,0 +1,62 @@
|
||||
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.Cqrs.Abstractions.Cqrs;
|
||||
|
||||
namespace GFramework.Core.Services.Modules;
|
||||
|
||||
/// <summary>
|
||||
/// CQRS runtime 模块,用于把默认请求分发器与处理器注册器接入架构容器。
|
||||
/// 该模块在架构初始化早期完成注册,保证用户初始化阶段即可使用 CQRS 入口与 handler 自动接入能力。
|
||||
/// </summary>
|
||||
public sealed class CqrsRuntimeModule : IServiceModule
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取模块名称。
|
||||
/// </summary>
|
||||
public string ModuleName => nameof(CqrsRuntimeModule);
|
||||
|
||||
/// <summary>
|
||||
/// 获取模块优先级。
|
||||
/// CQRS runtime 需要先于架构默认 handler 扫描路径可用,因此放在基础总线模块之后、用户初始化之前注册。
|
||||
/// </summary>
|
||||
public int Priority => 15;
|
||||
|
||||
/// <summary>
|
||||
/// 获取模块启用状态,默认启用。
|
||||
/// </summary>
|
||||
public bool IsEnabled => true;
|
||||
|
||||
/// <summary>
|
||||
/// 注册默认 CQRS runtime seam 实现。
|
||||
/// </summary>
|
||||
/// <param name="container">目标依赖注入容器。</param>
|
||||
public void Register(IIocContainer container)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(container);
|
||||
|
||||
var dispatcherLogger = LoggerFactoryResolver.Provider.CreateLogger(nameof(CqrsDispatcher));
|
||||
var registrarLogger = LoggerFactoryResolver.Provider.CreateLogger(nameof(DefaultCqrsHandlerRegistrar));
|
||||
|
||||
container.Register<ICqrsRuntime>(new CqrsDispatcher(container, dispatcherLogger));
|
||||
container.Register<ICqrsHandlerRegistrar>(new DefaultCqrsHandlerRegistrar(container, registrarLogger));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 初始化模块。
|
||||
/// </summary>
|
||||
public void Initialize()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步销毁模块。
|
||||
/// </summary>
|
||||
/// <returns>已完成的值任务。</returns>
|
||||
public ValueTask DestroyAsync()
|
||||
{
|
||||
return ValueTask.CompletedTask;
|
||||
}
|
||||
}
|
||||
@ -42,7 +42,7 @@ public sealed class ServiceModuleManager : IServiceModuleManager
|
||||
|
||||
/// <summary>
|
||||
/// 注册内置服务模块,并根据优先级排序后完成服务注册。
|
||||
/// 内置模块包括事件总线、命令执行器、查询执行器等核心模块。
|
||||
/// 内置模块包括事件总线、命令执行器、CQRS runtime、查询执行器等核心模块。
|
||||
/// 同时注册通过 ArchitectureModuleRegistry 自动注册的外部模块。
|
||||
/// </summary>
|
||||
/// <param name="container">IoC容器实例,用于模块服务注册。</param>
|
||||
@ -57,6 +57,7 @@ public sealed class ServiceModuleManager : IServiceModuleManager
|
||||
// 注册内置模块
|
||||
RegisterModule(new EventBusModule());
|
||||
RegisterModule(new CommandExecutorModule());
|
||||
RegisterModule(new CqrsRuntimeModule());
|
||||
RegisterModule(new QueryExecutorModule());
|
||||
RegisterModule(new AsyncQueryExecutorModule());
|
||||
|
||||
@ -148,4 +149,4 @@ public sealed class ServiceModuleManager : IServiceModuleManager
|
||||
_builtInModulesRegistered = false;
|
||||
_logger.Info("All service modules destroyed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
13
GFramework.Cqrs.Abstractions/Cqrs/Command/ICommand.cs
Normal file
13
GFramework.Cqrs.Abstractions/Cqrs/Command/ICommand.cs
Normal file
@ -0,0 +1,13 @@
|
||||
namespace GFramework.Cqrs.Abstractions.Cqrs.Command;
|
||||
|
||||
/// <summary>
|
||||
/// 表示一个 CQRS 命令。
|
||||
/// 命令通常用于修改系统状态。
|
||||
/// </summary>
|
||||
/// <typeparam name="TResponse">命令响应类型。</typeparam>
|
||||
public interface ICommand<out TResponse> : IRequest<TResponse>;
|
||||
|
||||
/// <summary>
|
||||
/// 表示一个无显式返回值的 CQRS 命令。
|
||||
/// </summary>
|
||||
public interface ICommand : ICommand<Unit>;
|
||||
@ -1,7 +1,7 @@
|
||||
namespace GFramework.Core.Abstractions.Cqrs.Command;
|
||||
namespace GFramework.Cqrs.Abstractions.Cqrs.Command;
|
||||
|
||||
/// <summary>
|
||||
/// 命令输入接口,定义命令模式中输入数据的契约
|
||||
/// 该接口作为标记接口使用,不包含任何成员定义
|
||||
/// </summary>
|
||||
public interface ICommandInput : IInput;
|
||||
public interface ICommandInput : IInput;
|
||||
20
GFramework.Cqrs.Abstractions/Cqrs/Command/IStreamCommand.cs
Normal file
20
GFramework.Cqrs.Abstractions/Cqrs/Command/IStreamCommand.cs
Normal file
@ -0,0 +1,20 @@
|
||||
// Copyright (c) 2026 GeWuYou
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
namespace GFramework.Cqrs.Abstractions.Cqrs.Command;
|
||||
|
||||
/// <summary>
|
||||
/// 表示一个流式 CQRS 命令。
|
||||
/// </summary>
|
||||
/// <typeparam name="TResponse">流式响应元素类型。</typeparam>
|
||||
public interface IStreamCommand<out TResponse> : IStreamRequest<TResponse>;
|
||||
17
GFramework.Cqrs.Abstractions/Cqrs/ICqrsHandlerRegistrar.cs
Normal file
17
GFramework.Cqrs.Abstractions/Cqrs/ICqrsHandlerRegistrar.cs
Normal file
@ -0,0 +1,17 @@
|
||||
using System.Reflection;
|
||||
|
||||
namespace GFramework.Cqrs.Abstractions.Cqrs;
|
||||
|
||||
/// <summary>
|
||||
/// 定义 CQRS 处理器程序集接入的 runtime seam。
|
||||
/// 该抽象负责承接“生成注册器优先、反射扫描回退”的处理器注册流程,
|
||||
/// 让容器与架构启动链不再直接依赖固定的注册实现类型。
|
||||
/// </summary>
|
||||
public interface ICqrsHandlerRegistrar
|
||||
{
|
||||
/// <summary>
|
||||
/// 扫描并注册指定程序集集合中的 CQRS 处理器。
|
||||
/// </summary>
|
||||
/// <param name="assemblies">要接入的程序集集合。</param>
|
||||
void RegisterHandlers(IEnumerable<Assembly> assemblies);
|
||||
}
|
||||
@ -11,10 +11,10 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
namespace GFramework.Core.Abstractions.Cqrs;
|
||||
namespace GFramework.Cqrs.Abstractions.Cqrs;
|
||||
|
||||
/// <summary>
|
||||
/// 表示输入数据的标记接口。
|
||||
/// 该接口用于标识各类CQRS模式中的输入参数类型。
|
||||
/// </summary>
|
||||
public interface IInput;
|
||||
public interface IInput;
|
||||
@ -1,9 +1,7 @@
|
||||
namespace GFramework.Core.Abstractions.Cqrs;
|
||||
namespace GFramework.Cqrs.Abstractions.Cqrs;
|
||||
|
||||
/// <summary>
|
||||
/// 表示一个一对多发布的通知消息。
|
||||
/// 通知不要求返回值,允许被零个或多个处理器消费。
|
||||
/// </summary>
|
||||
public interface INotification
|
||||
{
|
||||
}
|
||||
public interface INotification;
|
||||
@ -1,4 +1,4 @@
|
||||
namespace GFramework.Core.Abstractions.Cqrs;
|
||||
namespace GFramework.Cqrs.Abstractions.Cqrs;
|
||||
|
||||
/// <summary>
|
||||
/// 表示处理通知消息的处理器契约。
|
||||
@ -1,4 +1,4 @@
|
||||
namespace GFramework.Core.Abstractions.Cqrs;
|
||||
namespace GFramework.Cqrs.Abstractions.Cqrs;
|
||||
|
||||
/// <summary>
|
||||
/// 定义 CQRS 请求处理前后的管道行为。
|
||||
@ -1,10 +1,8 @@
|
||||
namespace GFramework.Core.Abstractions.Cqrs;
|
||||
namespace GFramework.Cqrs.Abstractions.Cqrs;
|
||||
|
||||
/// <summary>
|
||||
/// 表示一个有响应的 CQRS 请求。
|
||||
/// 该接口是命令、查询以及其他请求语义的统一基接口。
|
||||
/// </summary>
|
||||
/// <typeparam name="TResponse">请求响应类型。</typeparam>
|
||||
public interface IRequest<out TResponse>
|
||||
{
|
||||
}
|
||||
public interface IRequest<out TResponse>;
|
||||
@ -1,4 +1,4 @@
|
||||
namespace GFramework.Core.Abstractions.Cqrs;
|
||||
namespace GFramework.Cqrs.Abstractions.Cqrs;
|
||||
|
||||
/// <summary>
|
||||
/// 表示处理单个 CQRS 请求的处理器契约。
|
||||
@ -1,10 +1,8 @@
|
||||
namespace GFramework.Core.Abstractions.Cqrs;
|
||||
namespace GFramework.Cqrs.Abstractions.Cqrs;
|
||||
|
||||
/// <summary>
|
||||
/// 表示一个流式 CQRS 请求。
|
||||
/// 请求处理器可以逐步产生响应序列,而不是一次性返回完整结果。
|
||||
/// </summary>
|
||||
/// <typeparam name="TResponse">流式响应元素类型。</typeparam>
|
||||
public interface IStreamRequest<out TResponse>
|
||||
{
|
||||
}
|
||||
public interface IStreamRequest<out TResponse>;
|
||||
@ -1,4 +1,4 @@
|
||||
namespace GFramework.Core.Abstractions.Cqrs;
|
||||
namespace GFramework.Cqrs.Abstractions.Cqrs;
|
||||
|
||||
/// <summary>
|
||||
/// 表示处理流式 CQRS 请求的处理器契约。
|
||||
@ -1,4 +1,4 @@
|
||||
namespace GFramework.Core.Abstractions.Cqrs;
|
||||
namespace GFramework.Cqrs.Abstractions.Cqrs;
|
||||
|
||||
/// <summary>
|
||||
/// 表示 CQRS 请求在管道中继续向下执行的处理委托。
|
||||
@ -11,10 +11,10 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
namespace GFramework.Core.Abstractions.Cqrs.Notification;
|
||||
namespace GFramework.Cqrs.Abstractions.Cqrs.Notification;
|
||||
|
||||
/// <summary>
|
||||
/// 表示通知输入数据的标记接口。
|
||||
/// 该接口继承自 IInput,用于标识CQRS模式中通知类型的输入参数。
|
||||
/// </summary>
|
||||
public interface INotificationInput : IInput;
|
||||
public interface INotificationInput : IInput;
|
||||
8
GFramework.Cqrs.Abstractions/Cqrs/Query/IQuery.cs
Normal file
8
GFramework.Cqrs.Abstractions/Cqrs/Query/IQuery.cs
Normal file
@ -0,0 +1,8 @@
|
||||
namespace GFramework.Cqrs.Abstractions.Cqrs.Query;
|
||||
|
||||
/// <summary>
|
||||
/// 表示一个 CQRS 查询。
|
||||
/// 查询用于读取数据,不应产生副作用。
|
||||
/// </summary>
|
||||
/// <typeparam name="TResponse">查询响应类型。</typeparam>
|
||||
public interface IQuery<out TResponse> : IRequest<TResponse>;
|
||||
@ -1,6 +1,6 @@
|
||||
namespace GFramework.Core.Abstractions.Cqrs.Query;
|
||||
namespace GFramework.Cqrs.Abstractions.Cqrs.Query;
|
||||
|
||||
/// <summary>
|
||||
/// 查询输入接口,定义了查询操作的输入规范
|
||||
/// </summary>
|
||||
public interface IQueryInput : IInput;
|
||||
public interface IQueryInput : IInput;
|
||||
20
GFramework.Cqrs.Abstractions/Cqrs/Query/IStreamQuery.cs
Normal file
20
GFramework.Cqrs.Abstractions/Cqrs/Query/IStreamQuery.cs
Normal file
@ -0,0 +1,20 @@
|
||||
// Copyright (c) 2026 GeWuYou
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
namespace GFramework.Cqrs.Abstractions.Cqrs.Query;
|
||||
|
||||
/// <summary>
|
||||
/// 表示一个流式 CQRS 查询。
|
||||
/// </summary>
|
||||
/// <typeparam name="TResponse">流式响应元素类型。</typeparam>
|
||||
public interface IStreamQuery<out TResponse> : IStreamRequest<TResponse>;
|
||||
@ -11,10 +11,10 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
namespace GFramework.Core.Abstractions.Cqrs.Request;
|
||||
namespace GFramework.Cqrs.Abstractions.Cqrs.Request;
|
||||
|
||||
/// <summary>
|
||||
/// 表示请求输入数据的标记接口。
|
||||
/// 该接口继承自 IInput,用于标识CQRS模式中请求类型的输入参数。
|
||||
/// </summary>
|
||||
public interface IRequestInput : IInput;
|
||||
public interface IRequestInput : IInput;
|
||||
@ -1,4 +1,4 @@
|
||||
namespace GFramework.Core.Abstractions.Cqrs;
|
||||
namespace GFramework.Cqrs.Abstractions.Cqrs;
|
||||
|
||||
/// <summary>
|
||||
/// 表示没有实际返回值的 CQRS 响应类型。
|
||||
18
GFramework.Cqrs.Abstractions/Directory.Build.props
Normal file
18
GFramework.Cqrs.Abstractions/Directory.Build.props
Normal file
@ -0,0 +1,18 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.1</TargetFramework>
|
||||
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
|
||||
<EmbedUntrackedSources>true</EmbedUntrackedSources>
|
||||
<LangVersion>preview</LangVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Meziantou.Analyzer" Version="3.0.46">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Meziantou.Polyfill" Version="1.0.109">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@ -0,0 +1,11 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<PackageId>GeWuYou.$(AssemblyName)</PackageId>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<MeziantouPolyfill_IncludedPolyfills>T:System.Diagnostics.CodeAnalysis.NotNullWhenAttribute</MeziantouPolyfill_IncludedPolyfills>
|
||||
<Nullable>enable</Nullable>
|
||||
<EnableGFrameworkPackageTransitiveGlobalUsings>true</EnableGFrameworkPackageTransitiveGlobalUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
3
GFramework.Cqrs.Abstractions/GlobalUsings.cs
Normal file
3
GFramework.Cqrs.Abstractions/GlobalUsings.cs
Normal file
@ -0,0 +1,3 @@
|
||||
global using System.Collections.Generic;
|
||||
global using System.Threading;
|
||||
global using System.Threading.Tasks;
|
||||
@ -13,11 +13,11 @@
|
||||
|
||||
using GFramework.Core.Abstractions.Architectures;
|
||||
using GFramework.Core.Abstractions.Coroutine;
|
||||
using GFramework.Core.Abstractions.Cqrs;
|
||||
using GFramework.Core.Abstractions.Rule;
|
||||
using GFramework.Core.Cqrs.Extensions;
|
||||
using GFramework.Core.Coroutine.Extensions;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs;
|
||||
|
||||
namespace GFramework.Core.Tests.Coroutine;
|
||||
namespace GFramework.Cqrs.Tests.Coroutine;
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="CqrsCoroutineExtensions" /> 的单元测试类。
|
||||
@ -1,12 +1,12 @@
|
||||
using System.Reflection;
|
||||
using GFramework.Core.Abstractions.Cqrs;
|
||||
using GFramework.Core.Abstractions.Logging;
|
||||
using GFramework.Core.Architectures;
|
||||
using GFramework.Core.Ioc;
|
||||
using GFramework.Core.Logging;
|
||||
using GFramework.Core.Tests.Logging;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs;
|
||||
using GFramework.Cqrs.Tests.Logging;
|
||||
|
||||
namespace GFramework.Core.Tests.Cqrs;
|
||||
namespace GFramework.Cqrs.Tests.Cqrs;
|
||||
|
||||
/// <summary>
|
||||
/// 验证 CQRS 处理器自动注册在顺序与容错层面的可观察行为。
|
||||
@ -14,9 +14,6 @@ namespace GFramework.Core.Tests.Cqrs;
|
||||
[TestFixture]
|
||||
internal sealed class CqrsHandlerRegistrarTests
|
||||
{
|
||||
private MicrosoftDiContainer? _container;
|
||||
private ArchitectureContext? _context;
|
||||
|
||||
/// <summary>
|
||||
/// 初始化测试容器并重置共享状态。
|
||||
/// </summary>
|
||||
@ -46,6 +43,9 @@ internal sealed class CqrsHandlerRegistrarTests
|
||||
DeterministicNotificationHandlerState.Reset();
|
||||
}
|
||||
|
||||
private MicrosoftDiContainer? _container;
|
||||
private ArchitectureContext? _context;
|
||||
|
||||
/// <summary>
|
||||
/// 验证自动扫描到的通知处理器会按稳定名称顺序执行,而不是依赖反射枚举顺序。
|
||||
/// </summary>
|
||||
26
GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj
Normal file
26
GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj
Normal file
@ -0,0 +1,26 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TestTargetFrameworks Condition="'$(TestTargetFrameworks)' == ''">net10.0</TestTargetFrameworks>
|
||||
<TargetFrameworks>$(TestTargetFrameworks)</TargetFrameworks>
|
||||
<ImplicitUsings>disable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.4.0"/>
|
||||
<PackageReference Include="Moq" Version="4.20.72"/>
|
||||
<PackageReference Include="NUnit" Version="4.5.1"/>
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="6.2.0"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\GFramework.Tests.Common\GFramework.Tests.Common.csproj"/>
|
||||
<ProjectReference Include="..\GFramework.Cqrs.Abstractions\GFramework.Cqrs.Abstractions.csproj"/>
|
||||
<ProjectReference Include="..\GFramework.Core.Abstractions\GFramework.Core.Abstractions.csproj"/>
|
||||
<ProjectReference Include="..\GFramework.Core\GFramework.Core.csproj"/>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
27
GFramework.Cqrs.Tests/GlobalUsings.cs
Normal file
27
GFramework.Cqrs.Tests/GlobalUsings.cs
Normal file
@ -0,0 +1,27 @@
|
||||
// Copyright (c) 2026 GeWuYou
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
global using System;
|
||||
global using System.Collections;
|
||||
global using System.Collections.Generic;
|
||||
global using System.Diagnostics;
|
||||
global using System.Linq;
|
||||
global using System.Reflection;
|
||||
global using System.Runtime.CompilerServices;
|
||||
global using System.Threading;
|
||||
global using System.Threading.Tasks;
|
||||
global using GFramework.Tests.Common;
|
||||
global using Microsoft.Extensions.DependencyInjection;
|
||||
global using Moq;
|
||||
global using NUnit.Compatibility;
|
||||
global using NUnit.Framework;
|
||||
56
GFramework.Cqrs.Tests/Logging/TestLogger.cs
Normal file
56
GFramework.Cqrs.Tests/Logging/TestLogger.cs
Normal file
@ -0,0 +1,56 @@
|
||||
// Copyright (c) 2026 GeWuYou
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using GFramework.Core.Abstractions.Logging;
|
||||
using GFramework.Core.Logging;
|
||||
|
||||
namespace GFramework.Cqrs.Tests.Logging;
|
||||
|
||||
/// <summary>
|
||||
/// 供 CQRS 测试项目复用的最小日志记录器实现。
|
||||
/// </summary>
|
||||
public sealed class TestLogger : AbstractLogger
|
||||
{
|
||||
/// <summary>
|
||||
/// 初始化测试日志记录器。
|
||||
/// </summary>
|
||||
/// <param name="name">日志名称。</param>
|
||||
/// <param name="minLevel">最小日志级别。</param>
|
||||
public TestLogger(string? name = null, LogLevel minLevel = LogLevel.Info) : base(name, minLevel)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前测试期间捕获到的日志条目。
|
||||
/// </summary>
|
||||
public List<LogEntry> Logs { get; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// 将日志写入内存,供断言使用。
|
||||
/// </summary>
|
||||
/// <param name="level">日志级别。</param>
|
||||
/// <param name="message">日志消息。</param>
|
||||
/// <param name="exception">关联异常。</param>
|
||||
protected override void Write(LogLevel level, string message, Exception? exception)
|
||||
{
|
||||
Logs.Add(new LogEntry(level, message, exception));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 表示单条测试日志记录。
|
||||
/// </summary>
|
||||
/// <param name="Level">日志级别。</param>
|
||||
/// <param name="Message">日志消息。</param>
|
||||
/// <param name="Exception">关联异常。</param>
|
||||
public sealed record LogEntry(LogLevel Level, string Message, Exception? Exception);
|
||||
}
|
||||
@ -1,11 +1,9 @@
|
||||
using System.Diagnostics;
|
||||
using System.Reflection;
|
||||
using GFramework.Core.Abstractions.Cqrs;
|
||||
using GFramework.Core.Architectures;
|
||||
using GFramework.Core.Ioc;
|
||||
using GFramework.Core.Logging;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs;
|
||||
|
||||
namespace GFramework.Core.Tests.Mediator;
|
||||
namespace GFramework.Cqrs.Tests.Mediator;
|
||||
|
||||
/// <summary>
|
||||
/// Mediator高级特性专项测试
|
||||
@ -14,10 +12,6 @@ namespace GFramework.Core.Tests.Mediator;
|
||||
[TestFixture]
|
||||
public class MediatorAdvancedFeaturesTests
|
||||
{
|
||||
private MicrosoftDiContainer? _container;
|
||||
|
||||
private ArchitectureContext? _context;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
@ -46,6 +40,10 @@ public class MediatorAdvancedFeaturesTests
|
||||
_container = null;
|
||||
}
|
||||
|
||||
private MicrosoftDiContainer? _container;
|
||||
|
||||
private ArchitectureContext? _context;
|
||||
|
||||
|
||||
[Test]
|
||||
public async Task Request_With_Validation_Behavior_Should_Validate_Input()
|
||||
@ -1,15 +1,13 @@
|
||||
using System.Diagnostics;
|
||||
using System.Reflection;
|
||||
using GFramework.Core.Abstractions.Architectures;
|
||||
using GFramework.Core.Abstractions.Cqrs;
|
||||
using GFramework.Core.Architectures;
|
||||
using GFramework.Core.Command;
|
||||
using GFramework.Core.Ioc;
|
||||
using GFramework.Core.Logging;
|
||||
using GFramework.Core.Rule;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs;
|
||||
using ICommand = GFramework.Core.Abstractions.Command.ICommand;
|
||||
|
||||
namespace GFramework.Core.Tests.Mediator;
|
||||
namespace GFramework.Cqrs.Tests.Mediator;
|
||||
|
||||
/// <summary>
|
||||
/// Mediator与架构上下文集成测试
|
||||
@ -18,11 +16,6 @@ namespace GFramework.Core.Tests.Mediator;
|
||||
[TestFixture]
|
||||
public class MediatorArchitectureIntegrationTests
|
||||
{
|
||||
private CommandExecutor? _commandBus;
|
||||
private MicrosoftDiContainer? _container;
|
||||
|
||||
private ArchitectureContext? _context;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
@ -56,6 +49,11 @@ public class MediatorArchitectureIntegrationTests
|
||||
_commandBus = null;
|
||||
}
|
||||
|
||||
private CommandExecutor? _commandBus;
|
||||
private MicrosoftDiContainer? _container;
|
||||
|
||||
private ArchitectureContext? _context;
|
||||
|
||||
[Test]
|
||||
public async Task Handler_Can_Access_Architecture_Context()
|
||||
{
|
||||
@ -1,8 +1,4 @@
|
||||
using System.Diagnostics;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using GFramework.Core.Abstractions.Architectures;
|
||||
using GFramework.Core.Abstractions.Cqrs;
|
||||
using GFramework.Core.Abstractions.Events;
|
||||
using GFramework.Core.Architectures;
|
||||
using GFramework.Core.Command;
|
||||
@ -11,23 +7,14 @@ using GFramework.Core.Events;
|
||||
using GFramework.Core.Ioc;
|
||||
using GFramework.Core.Logging;
|
||||
using GFramework.Core.Query;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs;
|
||||
using ICommand = GFramework.Core.Abstractions.Command.ICommand;
|
||||
using Unit = GFramework.Core.Abstractions.Cqrs.Unit;
|
||||
|
||||
namespace GFramework.Core.Tests.Mediator;
|
||||
namespace GFramework.Cqrs.Tests.Mediator;
|
||||
|
||||
[TestFixture]
|
||||
public class MediatorComprehensiveTests
|
||||
{
|
||||
private AsyncQueryExecutor? _asyncQueryBus;
|
||||
private CommandExecutor? _commandBus;
|
||||
private MicrosoftDiContainer? _container;
|
||||
|
||||
private ArchitectureContext? _context;
|
||||
private DefaultEnvironment? _environment;
|
||||
private EventBus? _eventBus;
|
||||
private QueryExecutor? _queryBus;
|
||||
|
||||
/// <summary>
|
||||
/// 测试初始化方法,在每个测试方法执行前运行。
|
||||
/// 负责初始化日志工厂、依赖注入容器、自有 CQRS 处理器以及各种总线服务。
|
||||
@ -82,6 +69,15 @@ public class MediatorComprehensiveTests
|
||||
_environment = null;
|
||||
}
|
||||
|
||||
private AsyncQueryExecutor? _asyncQueryBus;
|
||||
private CommandExecutor? _commandBus;
|
||||
private MicrosoftDiContainer? _container;
|
||||
|
||||
private ArchitectureContext? _context;
|
||||
private DefaultEnvironment? _environment;
|
||||
private EventBus? _eventBus;
|
||||
private QueryExecutor? _queryBus;
|
||||
|
||||
/// <summary>
|
||||
/// 测试SendRequestAsync方法在请求有效时返回结果
|
||||
/// </summary>
|
||||
16
GFramework.Cqrs/GFramework.Cqrs.csproj
Normal file
16
GFramework.Cqrs/GFramework.Cqrs.csproj
Normal file
@ -0,0 +1,16 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<PackageId>GeWuYou.$(AssemblyName)</PackageId>
|
||||
<TargetFrameworks>net8.0;net9.0;net10.0</TargetFrameworks>
|
||||
<ImplicitUsings>disable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<EnableGFrameworkPackageTransitiveGlobalUsings>true</EnableGFrameworkPackageTransitiveGlobalUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\GFramework.Cqrs.Abstractions\GFramework.Cqrs.Abstractions.csproj"/>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@ -1,4 +1,4 @@
|
||||
namespace GFramework.Godot.SourceGenerators.Abstractions;
|
||||
namespace GFramework.Godot.SourceGenerators.Abstractions.UI;
|
||||
|
||||
/// <summary>
|
||||
/// 标记类型允许为带映射特性的导出集合生成批量注册代码。
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
namespace GFramework.Godot.SourceGenerators.Abstractions;
|
||||
namespace GFramework.Godot.SourceGenerators.Abstractions.UI;
|
||||
|
||||
/// <summary>
|
||||
/// 标记场景根节点类型,Source Generator 会生成场景行为样板代码。
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
namespace GFramework.Godot.SourceGenerators.Abstractions;
|
||||
namespace GFramework.Godot.SourceGenerators.Abstractions.UI;
|
||||
|
||||
/// <summary>
|
||||
/// 标记 UI 页面类型,Source Generator 会生成页面行为样板代码。
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
namespace GFramework.Godot.SourceGenerators.Abstractions;
|
||||
namespace GFramework.Godot.SourceGenerators.Abstractions.UI;
|
||||
|
||||
/// <summary>
|
||||
/// 声明导出集合应当转发到哪个注册器成员及其方法。
|
||||
|
||||
@ -11,10 +11,10 @@ public class AutoSceneGeneratorTests
|
||||
{
|
||||
const string source = """
|
||||
using System;
|
||||
using GFramework.Godot.SourceGenerators.Abstractions;
|
||||
using GFramework.Godot.SourceGenerators.Abstractions.UI;
|
||||
using Godot;
|
||||
|
||||
namespace GFramework.Godot.SourceGenerators.Abstractions
|
||||
namespace GFramework.Godot.SourceGenerators.Abstractions.UI
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
|
||||
public sealed class AutoSceneAttribute : Attribute
|
||||
@ -88,10 +88,10 @@ public class AutoSceneGeneratorTests
|
||||
{
|
||||
const string source = """
|
||||
using System;
|
||||
using GFramework.Godot.SourceGenerators.Abstractions;
|
||||
using GFramework.Godot.SourceGenerators.Abstractions.UI;
|
||||
using Godot;
|
||||
|
||||
namespace GFramework.Godot.SourceGenerators.Abstractions
|
||||
namespace GFramework.Godot.SourceGenerators.Abstractions.UI
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
|
||||
public sealed class AutoSceneAttribute : Attribute
|
||||
@ -137,10 +137,10 @@ public class AutoSceneGeneratorTests
|
||||
const string source = """
|
||||
#nullable enable
|
||||
using System;
|
||||
using GFramework.Godot.SourceGenerators.Abstractions;
|
||||
using GFramework.Godot.SourceGenerators.Abstractions.UI;
|
||||
using Godot;
|
||||
|
||||
namespace GFramework.Godot.SourceGenerators.Abstractions
|
||||
namespace GFramework.Godot.SourceGenerators.Abstractions.UI
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
|
||||
public sealed class AutoSceneAttribute : Attribute
|
||||
@ -225,10 +225,10 @@ public class AutoSceneGeneratorTests
|
||||
{
|
||||
const string source = """
|
||||
using System;
|
||||
using GFramework.Godot.SourceGenerators.Abstractions;
|
||||
using GFramework.Godot.SourceGenerators.Abstractions.UI;
|
||||
using Godot;
|
||||
|
||||
namespace GFramework.Godot.SourceGenerators.Abstractions
|
||||
namespace GFramework.Godot.SourceGenerators.Abstractions.UI
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
|
||||
public sealed class AutoSceneAttribute : Attribute
|
||||
@ -279,10 +279,10 @@ public class AutoSceneGeneratorTests
|
||||
const string source = """
|
||||
using System;
|
||||
using GFramework.Game.Abstractions.Scene;
|
||||
using GFramework.Godot.SourceGenerators.Abstractions;
|
||||
using GFramework.Godot.SourceGenerators.Abstractions.UI;
|
||||
using Godot;
|
||||
|
||||
namespace GFramework.Godot.SourceGenerators.Abstractions
|
||||
namespace GFramework.Godot.SourceGenerators.Abstractions.UI
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
|
||||
public sealed class AutoSceneAttribute : Attribute
|
||||
|
||||
@ -11,10 +11,10 @@ public class AutoUiPageGeneratorTests
|
||||
{
|
||||
const string source = """
|
||||
using System;
|
||||
using GFramework.Godot.SourceGenerators.Abstractions;
|
||||
using GFramework.Godot.SourceGenerators.Abstractions.UI;
|
||||
using Godot;
|
||||
|
||||
namespace GFramework.Godot.SourceGenerators.Abstractions
|
||||
namespace GFramework.Godot.SourceGenerators.Abstractions.UI
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
|
||||
public sealed class AutoUiPageAttribute : Attribute
|
||||
@ -100,10 +100,10 @@ public class AutoUiPageGeneratorTests
|
||||
{
|
||||
const string source = """
|
||||
using System;
|
||||
using GFramework.Godot.SourceGenerators.Abstractions;
|
||||
using GFramework.Godot.SourceGenerators.Abstractions.UI;
|
||||
using Godot;
|
||||
|
||||
namespace GFramework.Godot.SourceGenerators.Abstractions
|
||||
namespace GFramework.Godot.SourceGenerators.Abstractions.UI
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
|
||||
public sealed class AutoUiPageAttribute : Attribute
|
||||
@ -183,10 +183,10 @@ public class AutoUiPageGeneratorTests
|
||||
const string source = """
|
||||
#nullable enable
|
||||
using System;
|
||||
using GFramework.Godot.SourceGenerators.Abstractions;
|
||||
using GFramework.Godot.SourceGenerators.Abstractions.UI;
|
||||
using Godot;
|
||||
|
||||
namespace GFramework.Godot.SourceGenerators.Abstractions
|
||||
namespace GFramework.Godot.SourceGenerators.Abstractions.UI
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
|
||||
public sealed class AutoUiPageAttribute : Attribute
|
||||
|
||||
@ -13,9 +13,9 @@ public class AutoRegisterExportedCollectionsGeneratorTests
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using GFramework.Godot.SourceGenerators.Abstractions;
|
||||
using GFramework.Godot.SourceGenerators.Abstractions.UI;
|
||||
|
||||
namespace GFramework.Godot.SourceGenerators.Abstractions
|
||||
namespace GFramework.Godot.SourceGenerators.Abstractions.UI
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
|
||||
public sealed class AutoRegisterExportedCollectionsAttribute : Attribute { }
|
||||
@ -86,9 +86,9 @@ public class AutoRegisterExportedCollectionsGeneratorTests
|
||||
const string source = """
|
||||
using System;
|
||||
using System.Collections;
|
||||
using GFramework.Godot.SourceGenerators.Abstractions;
|
||||
using GFramework.Godot.SourceGenerators.Abstractions.UI;
|
||||
|
||||
namespace GFramework.Godot.SourceGenerators.Abstractions
|
||||
namespace GFramework.Godot.SourceGenerators.Abstractions.UI
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
|
||||
public sealed class AutoRegisterExportedCollectionsAttribute : Attribute { }
|
||||
@ -141,9 +141,9 @@ public class AutoRegisterExportedCollectionsGeneratorTests
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using GFramework.Godot.SourceGenerators.Abstractions;
|
||||
using GFramework.Godot.SourceGenerators.Abstractions.UI;
|
||||
|
||||
namespace GFramework.Godot.SourceGenerators.Abstractions
|
||||
namespace GFramework.Godot.SourceGenerators.Abstractions.UI
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
|
||||
public sealed class AutoRegisterExportedCollectionsAttribute : Attribute { }
|
||||
@ -207,9 +207,9 @@ public class AutoRegisterExportedCollectionsGeneratorTests
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using GFramework.Godot.SourceGenerators.Abstractions;
|
||||
using GFramework.Godot.SourceGenerators.Abstractions.UI;
|
||||
|
||||
namespace GFramework.Godot.SourceGenerators.Abstractions
|
||||
namespace GFramework.Godot.SourceGenerators.Abstractions.UI
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
|
||||
public sealed class AutoRegisterExportedCollectionsAttribute : Attribute { }
|
||||
@ -284,9 +284,9 @@ public class AutoRegisterExportedCollectionsGeneratorTests
|
||||
const string source = """
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using GFramework.Godot.SourceGenerators.Abstractions;
|
||||
using GFramework.Godot.SourceGenerators.Abstractions.UI;
|
||||
|
||||
namespace GFramework.Godot.SourceGenerators.Abstractions
|
||||
namespace GFramework.Godot.SourceGenerators.Abstractions.UI
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
|
||||
public sealed class AutoRegisterExportedCollectionsAttribute : Attribute { }
|
||||
@ -344,9 +344,9 @@ public class AutoRegisterExportedCollectionsGeneratorTests
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using GFramework.Godot.SourceGenerators.Abstractions;
|
||||
using GFramework.Godot.SourceGenerators.Abstractions.UI;
|
||||
|
||||
namespace GFramework.Godot.SourceGenerators.Abstractions
|
||||
namespace GFramework.Godot.SourceGenerators.Abstractions.UI
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
|
||||
public sealed class AutoRegisterExportedCollectionsAttribute : Attribute { }
|
||||
@ -414,9 +414,9 @@ public class AutoRegisterExportedCollectionsGeneratorTests
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using GFramework.Godot.SourceGenerators.Abstractions;
|
||||
using GFramework.Godot.SourceGenerators.Abstractions.UI;
|
||||
|
||||
namespace GFramework.Godot.SourceGenerators.Abstractions
|
||||
namespace GFramework.Godot.SourceGenerators.Abstractions.UI
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
|
||||
public sealed class AutoRegisterExportedCollectionsAttribute : Attribute { }
|
||||
@ -482,9 +482,9 @@ public class AutoRegisterExportedCollectionsGeneratorTests
|
||||
const string source = """
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using GFramework.Godot.SourceGenerators.Abstractions;
|
||||
using GFramework.Godot.SourceGenerators.Abstractions.UI;
|
||||
|
||||
namespace GFramework.Godot.SourceGenerators.Abstractions
|
||||
namespace GFramework.Godot.SourceGenerators.Abstractions.UI
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
|
||||
public sealed class AutoRegisterExportedCollectionsAttribute : Attribute { }
|
||||
@ -549,9 +549,9 @@ public class AutoRegisterExportedCollectionsGeneratorTests
|
||||
const string source = """
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using GFramework.Godot.SourceGenerators.Abstractions;
|
||||
using GFramework.Godot.SourceGenerators.Abstractions.UI;
|
||||
|
||||
namespace GFramework.Godot.SourceGenerators.Abstractions
|
||||
namespace GFramework.Godot.SourceGenerators.Abstractions.UI
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
|
||||
public sealed class AutoRegisterExportedCollectionsAttribute : Attribute { }
|
||||
@ -604,9 +604,9 @@ public class AutoRegisterExportedCollectionsGeneratorTests
|
||||
const string source = """
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using GFramework.Godot.SourceGenerators.Abstractions;
|
||||
using GFramework.Godot.SourceGenerators.Abstractions.UI;
|
||||
|
||||
namespace GFramework.Godot.SourceGenerators.Abstractions
|
||||
namespace GFramework.Godot.SourceGenerators.Abstractions.UI
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
|
||||
public sealed class AutoRegisterExportedCollectionsAttribute : Attribute { }
|
||||
@ -659,9 +659,9 @@ public class AutoRegisterExportedCollectionsGeneratorTests
|
||||
const string source = """
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using GFramework.Godot.SourceGenerators.Abstractions;
|
||||
using GFramework.Godot.SourceGenerators.Abstractions.UI;
|
||||
|
||||
namespace GFramework.Godot.SourceGenerators.Abstractions
|
||||
namespace GFramework.Godot.SourceGenerators.Abstractions.UI
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
|
||||
public sealed class AutoRegisterExportedCollectionsAttribute : Attribute { }
|
||||
@ -715,9 +715,9 @@ public class AutoRegisterExportedCollectionsGeneratorTests
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using GFramework.Godot.SourceGenerators.Abstractions;
|
||||
using GFramework.Godot.SourceGenerators.Abstractions.UI;
|
||||
|
||||
namespace GFramework.Godot.SourceGenerators.Abstractions
|
||||
namespace GFramework.Godot.SourceGenerators.Abstractions.UI
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = true)]
|
||||
public sealed class AutoRegisterExportedCollectionsAttribute : Attribute { }
|
||||
|
||||
@ -18,7 +18,8 @@ namespace GFramework.Godot.SourceGenerators.Behavior;
|
||||
public sealed class AutoSceneGenerator : IIncrementalGenerator
|
||||
{
|
||||
private const string AutoSceneAttributeMetadataName =
|
||||
$"{PathContests.GodotSourceGeneratorsAbstractionsPath}.AutoSceneAttribute";
|
||||
$"{PathContests.GodotSourceGeneratorsAbstractionsPath}.UI.AutoSceneAttribute";
|
||||
|
||||
private static readonly string[] GeneratedMemberNames =
|
||||
[
|
||||
"SceneKeyStr",
|
||||
|
||||
@ -12,7 +12,7 @@ namespace GFramework.Godot.SourceGenerators.Behavior;
|
||||
public sealed class AutoUiPageGenerator : IIncrementalGenerator
|
||||
{
|
||||
private const string AutoUiPageAttributeMetadataName =
|
||||
$"{PathContests.GodotSourceGeneratorsAbstractionsPath}.AutoUiPageAttribute";
|
||||
$"{PathContests.GodotSourceGeneratorsAbstractionsPath}.UI.AutoUiPageAttribute";
|
||||
|
||||
public void Initialize(IncrementalGeneratorInitializationContext context)
|
||||
{
|
||||
|
||||
@ -18,10 +18,10 @@ namespace GFramework.Godot.SourceGenerators.Registration;
|
||||
public sealed class AutoRegisterExportedCollectionsGenerator : IIncrementalGenerator
|
||||
{
|
||||
private const string AutoRegisterExportedCollectionsAttributeMetadataName =
|
||||
$"{PathContests.GodotSourceGeneratorsAbstractionsPath}.AutoRegisterExportedCollectionsAttribute";
|
||||
$"{PathContests.GodotSourceGeneratorsAbstractionsPath}.UI.AutoRegisterExportedCollectionsAttribute";
|
||||
|
||||
private const string RegisterExportedCollectionAttributeMetadataName =
|
||||
$"{PathContests.GodotSourceGeneratorsAbstractionsPath}.RegisterExportedCollectionAttribute";
|
||||
$"{PathContests.GodotSourceGeneratorsAbstractionsPath}.UI.RegisterExportedCollectionAttribute";
|
||||
|
||||
private const string GeneratedMethodName = "__RegisterExportedCollections_Generated";
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user