mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-05-13 06:04:30 +08:00
Merge pull request #221 from GeWuYou/refactor/cqrs-architecture-decoupling-todo-2
Replace Mediator pattern with CQRS pipeline behavior registration
This commit is contained in:
commit
779c521a20
@ -73,7 +73,8 @@ Architecture 负责统一生命周期编排,核心阶段包括:
|
|||||||
|
|
||||||
### CQRS
|
### CQRS
|
||||||
|
|
||||||
命令与查询分离,支持同步与异步执行。Mediator 模式通过源码生成器集成,以减少模板代码并保持调用路径清晰。
|
命令与查询分离,支持同步与异步执行。当前版本内建自有 CQRS runtime、行为管道和 handler 自动注册;公开 API 里仍保留少量历史
|
||||||
|
`Mediator` 命名以兼容旧调用点。
|
||||||
|
|
||||||
### EventBus
|
### EventBus
|
||||||
|
|
||||||
|
|||||||
@ -73,12 +73,22 @@ public interface IArchitecture : IAsyncInitializable, IAsyncDestroyable, IInitia
|
|||||||
void RegisterUtility<T>(Action<T>? onCreated = null) where T : class, IUtility;
|
void RegisterUtility<T>(Action<T>? onCreated = null) where T : class, IUtility;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 注册中介行为管道
|
/// 注册 CQRS 请求管道行为。
|
||||||
/// 用于配置Mediator框架的行为拦截和处理逻辑。
|
|
||||||
/// 既支持实现 <c>IPipelineBehavior<,></c> 的开放泛型行为类型,
|
/// 既支持实现 <c>IPipelineBehavior<,></c> 的开放泛型行为类型,
|
||||||
/// 也支持绑定到单一请求/响应对的封闭行为类型。
|
/// 也支持绑定到单一请求/响应对的封闭行为类型。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="TBehavior">行为类型,必须是引用类型</typeparam>
|
/// <typeparam name="TBehavior">行为类型,必须是引用类型</typeparam>
|
||||||
|
void RegisterCqrsPipelineBehavior<TBehavior>()
|
||||||
|
where TBehavior : class;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 注册 CQRS 请求管道行为。
|
||||||
|
/// 该成员保留旧名称以兼容历史调用点,内部行为与 <see cref="RegisterCqrsPipelineBehavior{TBehavior}" /> 一致。
|
||||||
|
/// 既支持实现 <c>IPipelineBehavior<,></c> 的开放泛型行为类型,
|
||||||
|
/// 也支持绑定到单一请求/响应对的封闭行为类型。
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TBehavior">行为类型,必须是引用类型</typeparam>
|
||||||
|
[Obsolete("Use RegisterCqrsPipelineBehavior<TBehavior>() instead.")]
|
||||||
void RegisterMediatorBehavior<TBehavior>()
|
void RegisterMediatorBehavior<TBehavior>()
|
||||||
where TBehavior : class;
|
where TBehavior : class;
|
||||||
|
|
||||||
@ -101,4 +111,4 @@ public interface IArchitecture : IAsyncInitializable, IAsyncDestroyable, IInitia
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>表示异步等待操作的任务</returns>
|
/// <returns>表示异步等待操作的任务</returns>
|
||||||
Task WaitUntilReadyAsync();
|
Task WaitUntilReadyAsync();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -26,6 +26,6 @@
|
|||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Mediator.Abstractions" Version="3.0.2"/>
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.5"/>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@ -90,10 +90,18 @@ public interface IIocContainer : IContextAware
|
|||||||
void RegisterFactory<TService>(Func<IServiceProvider, TService> factory) where TService : class;
|
void RegisterFactory<TService>(Func<IServiceProvider, TService> factory) where TService : class;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 注册中介行为管道
|
/// 注册 CQRS 请求管道行为。
|
||||||
/// 用于配置Mediator框架的行为拦截和处理逻辑
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="TBehavior">行为类型,必须是引用类型</typeparam>
|
/// <typeparam name="TBehavior">行为类型,必须是引用类型</typeparam>
|
||||||
|
void RegisterCqrsPipelineBehavior<TBehavior>()
|
||||||
|
where TBehavior : class;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 注册 CQRS 请求管道行为。
|
||||||
|
/// 该成员保留旧名称以兼容历史调用点,内部行为与 <see cref="RegisterCqrsPipelineBehavior{TBehavior}" /> 一致。
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TBehavior">行为类型,必须是引用类型</typeparam>
|
||||||
|
[Obsolete("Use RegisterCqrsPipelineBehavior<TBehavior>() instead.")]
|
||||||
void RegisterMediatorBehavior<TBehavior>()
|
void RegisterMediatorBehavior<TBehavior>()
|
||||||
where TBehavior : class;
|
where TBehavior : class;
|
||||||
|
|
||||||
@ -227,4 +235,4 @@ public interface IIocContainer : IContextAware
|
|||||||
IServiceScope CreateScope();
|
IServiceScope CreateScope();
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
|
|||||||
@ -59,6 +59,28 @@ public class ArchitectureModulesBehaviorTests
|
|||||||
/// 验证注册的 CQRS 行为会参与请求管道执行。
|
/// 验证注册的 CQRS 行为会参与请求管道执行。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Test]
|
[Test]
|
||||||
|
public async Task RegisterCqrsPipelineBehavior_Should_Apply_Pipeline_Behavior_To_Request()
|
||||||
|
{
|
||||||
|
var architecture = new ModuleTestArchitecture(target =>
|
||||||
|
target.RegisterCqrsPipelineBehavior<TrackingPipelineBehavior<ModuleBehaviorRequest, string>>());
|
||||||
|
|
||||||
|
await architecture.InitializeAsync();
|
||||||
|
|
||||||
|
var response = await architecture.Context.SendRequestAsync(new ModuleBehaviorRequest());
|
||||||
|
|
||||||
|
Assert.Multiple(() =>
|
||||||
|
{
|
||||||
|
Assert.That(response, Is.EqualTo("handled"));
|
||||||
|
Assert.That(TrackingPipelineBehavior<ModuleBehaviorRequest, string>.InvocationCount, Is.EqualTo(1));
|
||||||
|
});
|
||||||
|
|
||||||
|
await architecture.DestroyAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 验证兼容别名 <c>RegisterMediatorBehavior</c> 仍会把 CQRS 行为接入请求管道。
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
public async Task RegisterMediatorBehavior_Should_Apply_Pipeline_Behavior_To_Request()
|
public async Task RegisterMediatorBehavior_Should_Apply_Pipeline_Behavior_To_Request()
|
||||||
{
|
{
|
||||||
var architecture = new ModuleTestArchitecture(target =>
|
var architecture = new ModuleTestArchitecture(target =>
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
using GFramework.Core.Abstractions.Architectures;
|
using GFramework.Core.Abstractions.Architectures;
|
||||||
using GFramework.Core.Abstractions.Command;
|
using GFramework.Core.Abstractions.Command;
|
||||||
|
using GFramework.Core.Abstractions.Cqrs;
|
||||||
using GFramework.Core.Abstractions.Environment;
|
using GFramework.Core.Abstractions.Environment;
|
||||||
using GFramework.Core.Abstractions.Events;
|
using GFramework.Core.Abstractions.Events;
|
||||||
using GFramework.Core.Abstractions.Ioc;
|
using GFramework.Core.Abstractions.Ioc;
|
||||||
@ -13,7 +14,6 @@ using GFramework.Core.Environment;
|
|||||||
using GFramework.Core.Events;
|
using GFramework.Core.Events;
|
||||||
using GFramework.Core.Ioc;
|
using GFramework.Core.Ioc;
|
||||||
using GFramework.Core.Query;
|
using GFramework.Core.Query;
|
||||||
using Mediator;
|
|
||||||
using ICommand = GFramework.Core.Abstractions.Command.ICommand;
|
using ICommand = GFramework.Core.Abstractions.Command.ICommand;
|
||||||
|
|
||||||
namespace GFramework.Core.Tests.Architectures;
|
namespace GFramework.Core.Tests.Architectures;
|
||||||
@ -34,6 +34,10 @@ namespace GFramework.Core.Tests.Architectures;
|
|||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class ArchitectureServicesTests
|
public class ArchitectureServicesTests
|
||||||
{
|
{
|
||||||
|
private TestArchitectureContextV3? _context;
|
||||||
|
|
||||||
|
private ArchitectureServices? _services;
|
||||||
|
|
||||||
[SetUp]
|
[SetUp]
|
||||||
public void SetUp()
|
public void SetUp()
|
||||||
{
|
{
|
||||||
@ -41,9 +45,6 @@ public class ArchitectureServicesTests
|
|||||||
_context = new TestArchitectureContextV3();
|
_context = new TestArchitectureContextV3();
|
||||||
}
|
}
|
||||||
|
|
||||||
private ArchitectureServices? _services;
|
|
||||||
private TestArchitectureContextV3? _context;
|
|
||||||
|
|
||||||
private void RegisterBuiltInServices()
|
private void RegisterBuiltInServices()
|
||||||
{
|
{
|
||||||
_services!.ModuleManager.RegisterBuiltInModules(_services.Container);
|
_services!.ModuleManager.RegisterBuiltInModules(_services.Container);
|
||||||
@ -347,61 +348,59 @@ public class TestArchitectureContextV3 : IArchitectureContext
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public ValueTask<TResponse> SendRequestAsync<TResponse>(global::GFramework.Core.Abstractions.Cqrs.IRequest<TResponse> request,
|
public ValueTask<TResponse> SendRequestAsync<TResponse>(IRequest<TResponse> request,
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public TResponse SendRequest<TResponse>(global::GFramework.Core.Abstractions.Cqrs.IRequest<TResponse> request)
|
public TResponse SendRequest<TResponse>(IRequest<TResponse> request)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ValueTask<TResponse> SendCommandAsync<TResponse>(
|
public ValueTask<TResponse> SendCommandAsync<TResponse>(Abstractions.Cqrs.Command.ICommand<TResponse> command,
|
||||||
global::GFramework.Core.Abstractions.Cqrs.Command.ICommand<TResponse> command,
|
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public TResponse SendCommand<TResponse>(global::GFramework.Core.Abstractions.Cqrs.Command.ICommand<TResponse> command)
|
public TResponse SendCommand<TResponse>(Abstractions.Cqrs.Command.ICommand<TResponse> command)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ValueTask<TResponse> SendQueryAsync<TResponse>(
|
public ValueTask<TResponse> SendQueryAsync<TResponse>(Abstractions.Cqrs.Query.IQuery<TResponse> query,
|
||||||
global::GFramework.Core.Abstractions.Cqrs.Query.IQuery<TResponse> query,
|
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public TResponse SendQuery<TResponse>(global::GFramework.Core.Abstractions.Cqrs.Query.IQuery<TResponse> query)
|
public TResponse SendQuery<TResponse>(Abstractions.Cqrs.Query.IQuery<TResponse> query)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ValueTask PublishAsync<TNotification>(TNotification notification,
|
public ValueTask PublishAsync<TNotification>(TNotification notification,
|
||||||
CancellationToken cancellationToken = default) where TNotification : global::GFramework.Core.Abstractions.Cqrs.INotification
|
CancellationToken cancellationToken = default) where TNotification : INotification
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public IAsyncEnumerable<TResponse> CreateStream<TResponse>(
|
public IAsyncEnumerable<TResponse> CreateStream<TResponse>(
|
||||||
global::GFramework.Core.Abstractions.Cqrs.IStreamRequest<TResponse> request,
|
IStreamRequest<TResponse> request,
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ValueTask SendAsync<TCommand>(TCommand command, CancellationToken cancellationToken = default)
|
public ValueTask SendAsync<TCommand>(TCommand command, CancellationToken cancellationToken = default)
|
||||||
where TCommand : global::GFramework.Core.Abstractions.Cqrs.IRequest<global::GFramework.Core.Abstractions.Cqrs.Unit>
|
where TCommand : IRequest<Unit>
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ValueTask<TResponse> SendAsync<TResponse>(global::GFramework.Core.Abstractions.Cqrs.IRequest<TResponse> command,
|
public ValueTask<TResponse> SendAsync<TResponse>(IRequest<TResponse> command,
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
using GFramework.Core.Abstractions.Architectures;
|
using GFramework.Core.Abstractions.Architectures;
|
||||||
using GFramework.Core.Abstractions.Command;
|
using GFramework.Core.Abstractions.Command;
|
||||||
|
using GFramework.Core.Abstractions.Cqrs;
|
||||||
using GFramework.Core.Abstractions.Environment;
|
using GFramework.Core.Abstractions.Environment;
|
||||||
using GFramework.Core.Abstractions.Events;
|
using GFramework.Core.Abstractions.Events;
|
||||||
using GFramework.Core.Abstractions.Ioc;
|
using GFramework.Core.Abstractions.Ioc;
|
||||||
@ -13,7 +14,6 @@ using GFramework.Core.Environment;
|
|||||||
using GFramework.Core.Events;
|
using GFramework.Core.Events;
|
||||||
using GFramework.Core.Ioc;
|
using GFramework.Core.Ioc;
|
||||||
using GFramework.Core.Query;
|
using GFramework.Core.Query;
|
||||||
using Mediator;
|
|
||||||
using ICommand = GFramework.Core.Abstractions.Command.ICommand;
|
using ICommand = GFramework.Core.Abstractions.Command.ICommand;
|
||||||
|
|
||||||
namespace GFramework.Core.Tests.Architectures;
|
namespace GFramework.Core.Tests.Architectures;
|
||||||
@ -394,61 +394,136 @@ public class TestArchitectureContext : IArchitectureContext
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public ValueTask<TResponse> SendRequestAsync<TResponse>(global::GFramework.Core.Abstractions.Cqrs.IRequest<TResponse> request,
|
/// <summary>
|
||||||
|
/// 测试桩:异步发送统一 CQRS 请求。
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TResponse">响应类型。</typeparam>
|
||||||
|
/// <param name="request">要发送的请求。</param>
|
||||||
|
/// <param name="cancellationToken">取消令牌。</param>
|
||||||
|
/// <returns>请求响应任务。</returns>
|
||||||
|
/// <exception cref="NotImplementedException">该测试桩未实现此成员。</exception>
|
||||||
|
public ValueTask<TResponse> SendRequestAsync<TResponse>(IRequest<TResponse> request,
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public TResponse SendRequest<TResponse>(global::GFramework.Core.Abstractions.Cqrs.IRequest<TResponse> request)
|
/// <summary>
|
||||||
|
/// 测试桩:同步发送统一 CQRS 请求。
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TResponse">响应类型。</typeparam>
|
||||||
|
/// <param name="request">要发送的请求。</param>
|
||||||
|
/// <returns>请求响应。</returns>
|
||||||
|
/// <exception cref="NotImplementedException">该测试桩未实现此成员。</exception>
|
||||||
|
public TResponse SendRequest<TResponse>(IRequest<TResponse> request)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ValueTask<TResponse> SendCommandAsync<TResponse>(
|
/// <summary>
|
||||||
global::GFramework.Core.Abstractions.Cqrs.Command.ICommand<TResponse> command,
|
/// 测试桩:异步发送 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>(Abstractions.Cqrs.Command.ICommand<TResponse> command,
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public TResponse SendCommand<TResponse>(global::GFramework.Core.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>(Abstractions.Cqrs.Command.ICommand<TResponse> command)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ValueTask<TResponse> SendQueryAsync<TResponse>(
|
/// <summary>
|
||||||
global::GFramework.Core.Abstractions.Cqrs.Query.IQuery<TResponse> query,
|
/// 测试桩:异步发送 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>(Abstractions.Cqrs.Query.IQuery<TResponse> query,
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public TResponse SendQuery<TResponse>(global::GFramework.Core.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>(Abstractions.Cqrs.Query.IQuery<TResponse> query)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 测试桩:异步发布 CQRS 通知。
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TNotification">通知类型。</typeparam>
|
||||||
|
/// <param name="notification">要发布的通知。</param>
|
||||||
|
/// <param name="cancellationToken">取消令牌。</param>
|
||||||
|
/// <returns>通知发布任务。</returns>
|
||||||
|
/// <exception cref="NotImplementedException">该测试桩未实现此成员。</exception>
|
||||||
public ValueTask PublishAsync<TNotification>(TNotification notification,
|
public ValueTask PublishAsync<TNotification>(TNotification notification,
|
||||||
CancellationToken cancellationToken = default) where TNotification : global::GFramework.Core.Abstractions.Cqrs.INotification
|
CancellationToken cancellationToken = default) where TNotification : INotification
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 测试桩:创建 CQRS 流式请求响应序列。
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TResponse">流式响应元素类型。</typeparam>
|
||||||
|
/// <param name="request">流式请求。</param>
|
||||||
|
/// <param name="cancellationToken">取消令牌。</param>
|
||||||
|
/// <returns>异步响应流。</returns>
|
||||||
|
/// <exception cref="NotImplementedException">该测试桩未实现此成员。</exception>
|
||||||
public IAsyncEnumerable<TResponse> CreateStream<TResponse>(
|
public IAsyncEnumerable<TResponse> CreateStream<TResponse>(
|
||||||
global::GFramework.Core.Abstractions.Cqrs.IStreamRequest<TResponse> request,
|
IStreamRequest<TResponse> request,
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 测试桩:异步发送无返回值 CQRS 命令。
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TCommand">命令类型。</typeparam>
|
||||||
|
/// <param name="command">要发送的命令。</param>
|
||||||
|
/// <param name="cancellationToken">取消令牌。</param>
|
||||||
|
/// <returns>命令发送任务。</returns>
|
||||||
|
/// <exception cref="NotImplementedException">该测试桩未实现此成员。</exception>
|
||||||
public ValueTask SendAsync<TCommand>(TCommand command, CancellationToken cancellationToken = default)
|
public ValueTask SendAsync<TCommand>(TCommand command, CancellationToken cancellationToken = default)
|
||||||
where TCommand : global::GFramework.Core.Abstractions.Cqrs.IRequest<global::GFramework.Core.Abstractions.Cqrs.Unit>
|
where TCommand : IRequest<Unit>
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ValueTask<TResponse> SendAsync<TResponse>(global::GFramework.Core.Abstractions.Cqrs.IRequest<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> SendAsync<TResponse>(IRequest<TResponse> command,
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
@ -468,7 +543,7 @@ public class TestArchitectureContext : IArchitectureContext
|
|||||||
/// <typeparam name="TResult">返回值类型</typeparam>
|
/// <typeparam name="TResult">返回值类型</typeparam>
|
||||||
/// <param name="command">命令对象</param>
|
/// <param name="command">命令对象</param>
|
||||||
/// <returns>命令执行结果</returns>
|
/// <returns>命令执行结果</returns>
|
||||||
public TResult SendCommand<TResult>(Abstractions.Command.ICommand<TResult> command)
|
public TResult SendCommand<TResult>(ICommand<TResult> command)
|
||||||
{
|
{
|
||||||
return default!;
|
return default!;
|
||||||
}
|
}
|
||||||
@ -489,7 +564,7 @@ public class TestArchitectureContext : IArchitectureContext
|
|||||||
/// <typeparam name="TResult">查询结果类型</typeparam>
|
/// <typeparam name="TResult">查询结果类型</typeparam>
|
||||||
/// <param name="query">查询对象</param>
|
/// <param name="query">查询对象</param>
|
||||||
/// <returns>查询结果</returns>
|
/// <returns>查询结果</returns>
|
||||||
public TResult SendQuery<TResult>(Abstractions.Query.IQuery<TResult> query)
|
public TResult SendQuery<TResult>(IQuery<TResult> query)
|
||||||
{
|
{
|
||||||
return default!;
|
return default!;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,7 +5,6 @@ using GFramework.Core.Abstractions.Model;
|
|||||||
using GFramework.Core.Abstractions.Systems;
|
using GFramework.Core.Abstractions.Systems;
|
||||||
using GFramework.Core.Abstractions.Utility;
|
using GFramework.Core.Abstractions.Utility;
|
||||||
using GFramework.Core.Architectures;
|
using GFramework.Core.Architectures;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
|
|
||||||
namespace GFramework.Core.Tests.Architectures;
|
namespace GFramework.Core.Tests.Architectures;
|
||||||
|
|
||||||
@ -181,11 +180,17 @@ public class TestArchitectureWithRegistry : IArchitecture
|
|||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RegisterMediatorBehavior<TBehavior>() where TBehavior : class
|
public void RegisterCqrsPipelineBehavior<TBehavior>() where TBehavior : class
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Obsolete("Use RegisterCqrsPipelineBehavior<TBehavior>() instead.")]
|
||||||
|
public void RegisterMediatorBehavior<TBehavior>() where TBehavior : class
|
||||||
|
{
|
||||||
|
RegisterCqrsPipelineBehavior<TBehavior>();
|
||||||
|
}
|
||||||
|
|
||||||
public IArchitectureModule InstallModule(IArchitectureModule module)
|
public IArchitectureModule InstallModule(IArchitectureModule module)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
@ -306,11 +311,17 @@ public class TestArchitectureWithoutRegistry : IArchitecture
|
|||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RegisterMediatorBehavior<TBehavior>() where TBehavior : class
|
public void RegisterCqrsPipelineBehavior<TBehavior>() where TBehavior : class
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Obsolete("Use RegisterCqrsPipelineBehavior<TBehavior>() instead.")]
|
||||||
|
public void RegisterMediatorBehavior<TBehavior>() where TBehavior : class
|
||||||
|
{
|
||||||
|
RegisterCqrsPipelineBehavior<TBehavior>();
|
||||||
|
}
|
||||||
|
|
||||||
public IArchitectureModule InstallModule(IArchitectureModule module)
|
public IArchitectureModule InstallModule(IArchitectureModule module)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
@ -363,4 +374,4 @@ public class TestArchitectureWithoutRegistry : IArchitecture
|
|||||||
public void RegisterLifecycleHook(IArchitectureLifecycleHook hook)
|
public void RegisterLifecycleHook(IArchitectureLifecycleHook hook)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
174
GFramework.Core.Tests/Coroutine/CqrsCoroutineExtensionsTests.cs
Normal file
174
GFramework.Core.Tests/Coroutine/CqrsCoroutineExtensionsTests.cs
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
// 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.Architectures;
|
||||||
|
using GFramework.Core.Abstractions.Coroutine;
|
||||||
|
using GFramework.Core.Abstractions.Cqrs;
|
||||||
|
using GFramework.Core.Abstractions.Rule;
|
||||||
|
using GFramework.Core.Cqrs.Extensions;
|
||||||
|
|
||||||
|
namespace GFramework.Core.Tests.Coroutine;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <see cref="CqrsCoroutineExtensions" /> 的单元测试类。
|
||||||
|
/// 验证新的 CQRS 协程扩展直接走框架内建 CQRS runtime,
|
||||||
|
/// 并确保协程对命令调度异常的传播行为保持稳定。
|
||||||
|
/// </summary>
|
||||||
|
[TestFixture]
|
||||||
|
public class CqrsCoroutineExtensionsTests
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 验证SendCommandCoroutine应该返回IEnumerator<IYieldInstruction>
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void SendCommandCoroutine_Should_Return_IEnumerator_Of_YieldInstruction()
|
||||||
|
{
|
||||||
|
var command = new TestCommand("Test");
|
||||||
|
var contextAware = new TestContextAware();
|
||||||
|
|
||||||
|
contextAware.MockContext
|
||||||
|
.Setup(ctx => ctx.SendAsync(command, It.IsAny<CancellationToken>()))
|
||||||
|
.Returns(ValueTask.CompletedTask);
|
||||||
|
|
||||||
|
var coroutine = CqrsCoroutineExtensions.SendCommandCoroutine(contextAware, command);
|
||||||
|
|
||||||
|
Assert.That(coroutine, Is.InstanceOf<IEnumerator<IYieldInstruction>>());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 验证 SendCommandCoroutine 在底层命令调度失败时会重新抛出原始异常。
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void SendCommandCoroutine_Should_Rethrow_Inner_Exception_When_Command_Fails()
|
||||||
|
{
|
||||||
|
var command = new TestCommand("Test");
|
||||||
|
var contextAware = new TestContextAware();
|
||||||
|
var expectedException = new InvalidOperationException("Command failed.");
|
||||||
|
|
||||||
|
contextAware.MockContext
|
||||||
|
.Setup(ctx => ctx.SendAsync(command, It.IsAny<CancellationToken>()))
|
||||||
|
.Returns(new ValueTask(Task.FromException(expectedException)));
|
||||||
|
|
||||||
|
var coroutine = CqrsCoroutineExtensions.SendCommandCoroutine(contextAware, command);
|
||||||
|
|
||||||
|
Assert.That(coroutine.MoveNext(), Is.True);
|
||||||
|
var exception = Assert.Throws<InvalidOperationException>(() => coroutine.MoveNext());
|
||||||
|
Assert.That(exception, Is.SameAs(expectedException));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 验证 SendCommandCoroutine 在提供错误回调时也会传递解包后的原始异常,
|
||||||
|
/// 避免回调路径暴露 <see cref="AggregateException" />。
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void SendCommandCoroutine_Should_Forward_Inner_Exception_To_Error_Handler()
|
||||||
|
{
|
||||||
|
var command = new TestCommand("Test");
|
||||||
|
var contextAware = new TestContextAware();
|
||||||
|
var expectedException = new InvalidOperationException("Command failed.");
|
||||||
|
Exception? capturedException = null;
|
||||||
|
|
||||||
|
contextAware.MockContext
|
||||||
|
.Setup(ctx => ctx.SendAsync(command, It.IsAny<CancellationToken>()))
|
||||||
|
.Returns(new ValueTask(Task.FromException(expectedException)));
|
||||||
|
|
||||||
|
var coroutine = CqrsCoroutineExtensions.SendCommandCoroutine(
|
||||||
|
contextAware,
|
||||||
|
command,
|
||||||
|
exception => capturedException = exception);
|
||||||
|
|
||||||
|
Assert.That(coroutine.MoveNext(), Is.True);
|
||||||
|
Assert.That(coroutine.MoveNext(), Is.False);
|
||||||
|
Assert.That(capturedException, Is.SameAs(expectedException));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 验证 SendCommandCoroutine 在底层命令被取消且未提供错误回调时会抛出取消异常。
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void SendCommandCoroutine_Should_Throw_TaskCanceledException_When_Command_Is_Canceled()
|
||||||
|
{
|
||||||
|
var command = new TestCommand("Test");
|
||||||
|
var contextAware = new TestContextAware();
|
||||||
|
using var cancellationTokenSource = new CancellationTokenSource();
|
||||||
|
|
||||||
|
cancellationTokenSource.Cancel();
|
||||||
|
contextAware.MockContext
|
||||||
|
.Setup(ctx => ctx.SendAsync(command, It.IsAny<CancellationToken>()))
|
||||||
|
.Returns(new ValueTask(Task.FromCanceled(cancellationTokenSource.Token)));
|
||||||
|
|
||||||
|
var coroutine = CqrsCoroutineExtensions.SendCommandCoroutine(contextAware, command);
|
||||||
|
|
||||||
|
Assert.That(coroutine.MoveNext(), Is.True);
|
||||||
|
Assert.Throws<TaskCanceledException>(() => coroutine.MoveNext());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 验证 SendCommandCoroutine 在底层命令被取消且提供错误回调时会把取消异常转发给回调。
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void SendCommandCoroutine_Should_Forward_TaskCanceledException_To_Error_Handler_When_Command_Is_Canceled()
|
||||||
|
{
|
||||||
|
var command = new TestCommand("Test");
|
||||||
|
var contextAware = new TestContextAware();
|
||||||
|
using var cancellationTokenSource = new CancellationTokenSource();
|
||||||
|
Exception? capturedException = null;
|
||||||
|
|
||||||
|
cancellationTokenSource.Cancel();
|
||||||
|
contextAware.MockContext
|
||||||
|
.Setup(ctx => ctx.SendAsync(command, It.IsAny<CancellationToken>()))
|
||||||
|
.Returns(new ValueTask(Task.FromCanceled(cancellationTokenSource.Token)));
|
||||||
|
|
||||||
|
var coroutine = CqrsCoroutineExtensions.SendCommandCoroutine(
|
||||||
|
contextAware,
|
||||||
|
command,
|
||||||
|
exception => capturedException = exception);
|
||||||
|
|
||||||
|
Assert.That(coroutine.MoveNext(), Is.True);
|
||||||
|
Assert.That(coroutine.MoveNext(), Is.False);
|
||||||
|
Assert.That(capturedException, Is.TypeOf<TaskCanceledException>());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 测试用的简单命令类
|
||||||
|
/// </summary>
|
||||||
|
private sealed record TestCommand(string Data) : IRequest<Unit>;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 上下文感知基类的模拟实现
|
||||||
|
/// </summary>
|
||||||
|
private sealed class TestContextAware : IContextAware
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 提供可配置的架构上下文 Mock。
|
||||||
|
/// </summary>
|
||||||
|
public Mock<IArchitectureContext> MockContext { get; } = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取当前架构上下文。
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>用于 CQRS 调用的架构上下文实例。</returns>
|
||||||
|
public IArchitectureContext GetContext()
|
||||||
|
{
|
||||||
|
return MockContext.Object;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 设置架构上下文。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context">要设置的架构上下文。</param>
|
||||||
|
public void SetContext(IArchitectureContext context)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,104 +0,0 @@
|
|||||||
// 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.Architectures;
|
|
||||||
using GFramework.Core.Abstractions.Coroutine;
|
|
||||||
using GFramework.Core.Abstractions.Rule;
|
|
||||||
using GFramework.Core.Coroutine.Extensions;
|
|
||||||
using Mediator;
|
|
||||||
using Moq;
|
|
||||||
|
|
||||||
namespace GFramework.Core.Tests.Coroutine;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// MediatorCoroutineExtensions的单元测试类
|
|
||||||
/// 测试Mediator模式与协程集成的扩展方法
|
|
||||||
/// 注意:由于 Mediator 使用源生成器,本测试类主要验证接口和参数验证
|
|
||||||
/// </summary>
|
|
||||||
[TestFixture]
|
|
||||||
public class MediatorCoroutineExtensionsTests
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 测试用的简单命令类
|
|
||||||
/// </summary>
|
|
||||||
private class TestCommand : IRequest<Unit>
|
|
||||||
{
|
|
||||||
public string Data { get; set; } = string.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 测试用的简单事件类
|
|
||||||
/// </summary>
|
|
||||||
private class TestEvent
|
|
||||||
{
|
|
||||||
public string Data { get; set; } = string.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 上下文感知基类的模拟实现
|
|
||||||
/// </summary>
|
|
||||||
private class TestContextAware : IContextAware
|
|
||||||
{
|
|
||||||
public readonly Mock<IArchitectureContext> _mockContext = new();
|
|
||||||
|
|
||||||
public IArchitectureContext GetContext()
|
|
||||||
{
|
|
||||||
return _mockContext.Object;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetContext(IArchitectureContext context)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 验证SendCommandCoroutine应该返回IEnumerator<IYieldInstruction>
|
|
||||||
/// </summary>
|
|
||||||
[Test]
|
|
||||||
public void SendCommandCoroutine_Should_Return_IEnumerator_Of_YieldInstruction()
|
|
||||||
{
|
|
||||||
var command = new TestCommand { Data = "Test" };
|
|
||||||
var contextAware = new TestContextAware();
|
|
||||||
|
|
||||||
// 创建 mediator 模拟
|
|
||||||
var mediatorMock = new Mock<IMediator>();
|
|
||||||
contextAware._mockContext
|
|
||||||
.Setup(ctx => ctx.GetService<IMediator>())
|
|
||||||
.Returns(mediatorMock.Object);
|
|
||||||
|
|
||||||
var coroutine = MediatorCoroutineExtensions.SendCommandCoroutine(contextAware, command);
|
|
||||||
|
|
||||||
Assert.That(coroutine, Is.InstanceOf<IEnumerator<IYieldInstruction>>());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 验证SendCommandCoroutine应该在mediator为null时抛出NullReferenceException
|
|
||||||
/// </summary>
|
|
||||||
[Test]
|
|
||||||
public void SendCommandCoroutine_Should_Throw_When_Mediator_Null()
|
|
||||||
{
|
|
||||||
var command = new TestCommand { Data = "Test" };
|
|
||||||
var contextAware = new TestContextAware();
|
|
||||||
|
|
||||||
// 设置上下文服务以返回null mediator
|
|
||||||
contextAware._mockContext
|
|
||||||
.Setup(ctx => ctx.GetService<IMediator>())
|
|
||||||
.Returns((IMediator?)null);
|
|
||||||
|
|
||||||
// 创建协程
|
|
||||||
var coroutine = MediatorCoroutineExtensions.SendCommandCoroutine(contextAware, command);
|
|
||||||
|
|
||||||
// 调用 MoveNext 时应该抛出 NullReferenceException
|
|
||||||
Assert.Throws<NullReferenceException>(() => coroutine.MoveNext());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -10,11 +10,6 @@
|
|||||||
<WarningLevel>0</WarningLevel>
|
<WarningLevel>0</WarningLevel>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Mediator.Abstractions" Version="3.0.2"/>
|
|
||||||
<PackageReference Include="Mediator.SourceGenerator" Version="3.0.2">
|
|
||||||
<PrivateAssets>all</PrivateAssets>
|
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
|
||||||
</PackageReference>
|
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.4.0"/>
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.4.0"/>
|
||||||
<PackageReference Include="Moq" Version="4.20.72"/>
|
<PackageReference Include="Moq" Version="4.20.72"/>
|
||||||
<PackageReference Include="NUnit" Version="4.5.1"/>
|
<PackageReference Include="NUnit" Version="4.5.1"/>
|
||||||
|
|||||||
@ -23,4 +23,6 @@ global using GFramework.Core.Abstractions.StateManagement;
|
|||||||
global using GFramework.Core.Extensions;
|
global using GFramework.Core.Extensions;
|
||||||
global using GFramework.Core.Property;
|
global using GFramework.Core.Property;
|
||||||
global using GFramework.Core.StateManagement;
|
global using GFramework.Core.StateManagement;
|
||||||
global using GFramework.Core.Abstractions.Property;
|
global using GFramework.Core.Abstractions.Property;
|
||||||
|
global using Microsoft.Extensions.DependencyInjection;
|
||||||
|
global using Moq;
|
||||||
|
|||||||
@ -7,7 +7,6 @@ using GFramework.Core.Abstractions.Systems;
|
|||||||
using GFramework.Core.Abstractions.Utility;
|
using GFramework.Core.Abstractions.Utility;
|
||||||
using GFramework.Core.Environment;
|
using GFramework.Core.Environment;
|
||||||
using GFramework.Core.Logging;
|
using GFramework.Core.Logging;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
|
|
||||||
namespace GFramework.Core.Architectures;
|
namespace GFramework.Core.Architectures;
|
||||||
|
|
||||||
@ -146,14 +145,24 @@ public abstract class Architecture : IArchitecture
|
|||||||
#region Module Management
|
#region Module Management
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 注册中介行为管道
|
/// 注册 CQRS 请求管道行为。
|
||||||
/// 用于配置Mediator框架的行为拦截和处理逻辑。
|
|
||||||
/// 可以传入开放泛型行为类型,也可以传入绑定到特定请求的封闭行为类型。
|
/// 可以传入开放泛型行为类型,也可以传入绑定到特定请求的封闭行为类型。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="TBehavior">行为类型,必须是引用类型</typeparam>
|
/// <typeparam name="TBehavior">行为类型,必须是引用类型</typeparam>
|
||||||
|
public void RegisterCqrsPipelineBehavior<TBehavior>() where TBehavior : class
|
||||||
|
{
|
||||||
|
_modules.RegisterCqrsPipelineBehavior<TBehavior>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 注册 CQRS 请求管道行为。
|
||||||
|
/// 该成员保留旧名称以兼容历史调用点,内部行为与 <see cref="RegisterCqrsPipelineBehavior{TBehavior}" /> 一致。
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TBehavior">行为类型,必须是引用类型</typeparam>
|
||||||
|
[Obsolete("Use RegisterCqrsPipelineBehavior<TBehavior>() instead.")]
|
||||||
public void RegisterMediatorBehavior<TBehavior>() where TBehavior : class
|
public void RegisterMediatorBehavior<TBehavior>() where TBehavior : class
|
||||||
{
|
{
|
||||||
_modules.RegisterMediatorBehavior<TBehavior>();
|
RegisterCqrsPipelineBehavior<TBehavior>();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -328,4 +337,4 @@ public abstract class Architecture : IArchitecture
|
|||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,7 +2,6 @@ using GFramework.Core.Abstractions.Architectures;
|
|||||||
using GFramework.Core.Abstractions.Environment;
|
using GFramework.Core.Abstractions.Environment;
|
||||||
using GFramework.Core.Abstractions.Logging;
|
using GFramework.Core.Abstractions.Logging;
|
||||||
using GFramework.Core.Cqrs.Internal;
|
using GFramework.Core.Cqrs.Internal;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
|
|
||||||
namespace GFramework.Core.Architectures;
|
namespace GFramework.Core.Architectures;
|
||||||
|
|
||||||
@ -23,7 +22,7 @@ internal sealed class ArchitectureBootstrapper(
|
|||||||
/// 因为用户初始化逻辑通常会立即访问事件总线、查询执行器或环境对象。
|
/// 因为用户初始化逻辑通常会立即访问事件总线、查询执行器或环境对象。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="existingContext">调用方已经提供的上下文;如果为空则创建默认上下文。</param>
|
/// <param name="existingContext">调用方已经提供的上下文;如果为空则创建默认上下文。</param>
|
||||||
/// <param name="configurator">可选的容器配置委托,用于接入 Mediator 等扩展服务。</param>
|
/// <param name="configurator">可选的容器配置委托,用于接入额外服务或覆盖默认依赖绑定。</param>
|
||||||
/// <param name="asyncMode">是否以异步模式初始化服务模块。</param>
|
/// <param name="asyncMode">是否以异步模式初始化服务模块。</param>
|
||||||
/// <returns>已绑定到当前架构类型的架构上下文。</returns>
|
/// <returns>已绑定到当前架构类型的架构上下文。</returns>
|
||||||
public async Task<IArchitectureContext> PrepareForInitializationAsync(
|
public async Task<IArchitectureContext> PrepareForInitializationAsync(
|
||||||
|
|||||||
@ -5,7 +5,7 @@ namespace GFramework.Core.Architectures;
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 架构模块管理器
|
/// 架构模块管理器
|
||||||
/// 负责管理架构模块的安装和中介行为注册
|
/// 负责管理架构模块的安装和 CQRS 行为注册
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal sealed class ArchitectureModules(
|
internal sealed class ArchitectureModules(
|
||||||
IArchitecture architecture,
|
IArchitecture architecture,
|
||||||
@ -13,15 +13,25 @@ internal sealed class ArchitectureModules(
|
|||||||
ILogger logger)
|
ILogger logger)
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 注册中介行为管道
|
/// 注册 CQRS 请求管道行为。
|
||||||
/// 用于配置Mediator框架的行为拦截和处理逻辑。
|
|
||||||
/// 支持开放泛型行为类型和针对单一请求的封闭行为类型。
|
/// 支持开放泛型行为类型和针对单一请求的封闭行为类型。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="TBehavior">行为类型,必须是引用类型</typeparam>
|
/// <typeparam name="TBehavior">行为类型,必须是引用类型</typeparam>
|
||||||
|
public void RegisterCqrsPipelineBehavior<TBehavior>() where TBehavior : class
|
||||||
|
{
|
||||||
|
logger.Debug($"Registering CQRS pipeline behavior: {typeof(TBehavior).Name}");
|
||||||
|
services.Container.RegisterCqrsPipelineBehavior<TBehavior>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 注册 CQRS 请求管道行为。
|
||||||
|
/// 该成员保留旧名称以兼容历史调用点,内部行为与 <see cref="RegisterCqrsPipelineBehavior{TBehavior}" /> 一致。
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TBehavior">行为类型,必须是引用类型</typeparam>
|
||||||
|
[Obsolete("Use RegisterCqrsPipelineBehavior<TBehavior>() instead.")]
|
||||||
public void RegisterMediatorBehavior<TBehavior>() where TBehavior : class
|
public void RegisterMediatorBehavior<TBehavior>() where TBehavior : class
|
||||||
{
|
{
|
||||||
logger.Debug($"Registering mediator behavior: {typeof(TBehavior).Name}");
|
RegisterCqrsPipelineBehavior<TBehavior>();
|
||||||
services.Container.RegisterMediatorBehavior<TBehavior>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -37,4 +47,4 @@ internal sealed class ArchitectureModules(
|
|||||||
logger.Info($"Module installed: {name}");
|
logger.Info($"Module installed: {name}");
|
||||||
return module;
|
return module;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,73 @@
|
|||||||
|
using System.Runtime.ExceptionServices;
|
||||||
|
using GFramework.Core.Abstractions.Coroutine;
|
||||||
|
using GFramework.Core.Abstractions.Cqrs;
|
||||||
|
using GFramework.Core.Abstractions.Rule;
|
||||||
|
using GFramework.Core.Coroutine.Extensions;
|
||||||
|
|
||||||
|
namespace GFramework.Core.Cqrs.Extensions;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 提供 CQRS 命令与协程集成的扩展方法。
|
||||||
|
/// 这些扩展直接走架构上下文的内建 CQRS runtime,不依赖外部 Mediator 服务。
|
||||||
|
/// </summary>
|
||||||
|
public static class CqrsCoroutineExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 以协程方式发送无返回值 CQRS 命令并处理可能的异常。
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TCommand">命令类型。</typeparam>
|
||||||
|
/// <param name="contextAware">上下文感知对象,用于获取架构上下文。</param>
|
||||||
|
/// <param name="command">要发送的命令对象。</param>
|
||||||
|
/// <param name="onError">发生异常时的回调处理函数。</param>
|
||||||
|
/// <returns>协程枚举器,用于协程执行。</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">
|
||||||
|
/// 当 <paramref name="contextAware" /> 或 <paramref name="command" /> 为 <see langword="null" /> 时抛出。
|
||||||
|
/// </exception>
|
||||||
|
/// <exception cref="TaskCanceledException">
|
||||||
|
/// 当底层命令调度被取消且未提供 <paramref name="onError" /> 时抛出。
|
||||||
|
/// </exception>
|
||||||
|
/// <exception cref="Exception">
|
||||||
|
/// 当底层命令调度失败且未提供 <paramref name="onError" /> 时,抛出底层原始异常。
|
||||||
|
/// </exception>
|
||||||
|
/// <remarks>
|
||||||
|
/// 当底层命令调度失败时,该扩展会把底层异常解包后传给 <paramref name="onError" />,
|
||||||
|
/// 在取消时则统一暴露 <see cref="TaskCanceledException" />,避免成功、失败与取消三种完成状态被混淆。
|
||||||
|
/// </remarks>
|
||||||
|
public static IEnumerator<IYieldInstruction> SendCommandCoroutine<TCommand>(
|
||||||
|
this IContextAware contextAware,
|
||||||
|
TCommand command,
|
||||||
|
Action<Exception>? onError = null)
|
||||||
|
where TCommand : IRequest<Unit>
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(contextAware);
|
||||||
|
ArgumentNullException.ThrowIfNull(command);
|
||||||
|
|
||||||
|
var task = contextAware.GetContext().SendAsync(command).AsTask();
|
||||||
|
|
||||||
|
yield return task.AsCoroutineInstruction();
|
||||||
|
|
||||||
|
if (task.IsCanceled)
|
||||||
|
{
|
||||||
|
// 取消态与成功态区分:协程层统一映射为 TaskCanceledException。
|
||||||
|
var canceledException = new TaskCanceledException(task);
|
||||||
|
if (onError != null)
|
||||||
|
{
|
||||||
|
onError.Invoke(canceledException);
|
||||||
|
yield break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保留原始抛出栈,避免调试时丢失异常来源。
|
||||||
|
ExceptionDispatchInfo.Capture(canceledException).Throw();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!task.IsFaulted)
|
||||||
|
yield break;
|
||||||
|
// 优先解包业务异常,避免直接暴露 AggregateException。
|
||||||
|
var exception = task.Exception!.InnerException ?? task.Exception;
|
||||||
|
if (onError != null)
|
||||||
|
onError.Invoke(exception);
|
||||||
|
else
|
||||||
|
// 继续保留原始栈信息。
|
||||||
|
ExceptionDispatchInfo.Capture(exception).Throw();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -12,21 +12,23 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
using GFramework.Core.Abstractions.Coroutine;
|
using GFramework.Core.Abstractions.Coroutine;
|
||||||
|
using GFramework.Core.Abstractions.Cqrs;
|
||||||
using GFramework.Core.Abstractions.Rule;
|
using GFramework.Core.Abstractions.Rule;
|
||||||
using Mediator;
|
using GFramework.Core.Cqrs.Extensions;
|
||||||
|
|
||||||
namespace GFramework.Core.Coroutine.Extensions;
|
namespace GFramework.Core.Coroutine.Extensions;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 提供Mediator模式与协程集成的扩展方法。
|
/// 提供 CQRS 命令与协程集成的扩展方法。
|
||||||
/// 包含发送命令和等待事件的协程实现。
|
/// 该类型保留旧名称以兼容历史调用点;新代码应改用 <see cref="GFramework.Core.Cqrs.Extensions.CqrsCoroutineExtensions" />。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Obsolete("Use GFramework.Core.Cqrs.Extensions.CqrsCoroutineExtensions instead.")]
|
||||||
public static class MediatorCoroutineExtensions
|
public static class MediatorCoroutineExtensions
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 以协程方式发送命令并处理可能的异常。
|
/// 以协程方式发送无返回值 CQRS 命令并处理可能的异常。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="TCommand">命令的类型</typeparam>
|
/// <typeparam name="TCommand">命令的类型。</typeparam>
|
||||||
/// <param name="contextAware">上下文感知对象,用于获取服务</param>
|
/// <param name="contextAware">上下文感知对象,用于获取服务</param>
|
||||||
/// <param name="command">要发送的命令对象</param>
|
/// <param name="command">要发送的命令对象</param>
|
||||||
/// <param name="onError">发生异常时的回调处理函数</param>
|
/// <param name="onError">发生异常时的回调处理函数</param>
|
||||||
@ -35,20 +37,8 @@ public static class MediatorCoroutineExtensions
|
|||||||
this IContextAware contextAware,
|
this IContextAware contextAware,
|
||||||
TCommand command,
|
TCommand command,
|
||||||
Action<Exception>? onError = null)
|
Action<Exception>? onError = null)
|
||||||
where TCommand : notnull
|
where TCommand : IRequest<Unit>
|
||||||
{
|
{
|
||||||
var mediator = contextAware
|
return CqrsCoroutineExtensions.SendCommandCoroutine(contextAware, command, onError);
|
||||||
.GetContext()
|
|
||||||
.GetService<IMediator>()!;
|
|
||||||
|
|
||||||
var task = mediator.Send(command).AsTask();
|
|
||||||
|
|
||||||
yield return task.AsCoroutineInstruction();
|
|
||||||
|
|
||||||
if (!task.IsFaulted) yield break;
|
|
||||||
if (onError != null)
|
|
||||||
onError.Invoke(task.Exception!);
|
|
||||||
else
|
|
||||||
throw task.Exception!.InnerException ?? task.Exception;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,7 +7,7 @@ namespace GFramework.Core.Cqrs.Internal;
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 在架构初始化期间扫描并注册 CQRS 处理器。
|
/// 在架构初始化期间扫描并注册 CQRS 处理器。
|
||||||
/// 首批实现采用运行时反射扫描,优先满足“无需 AddMediator 即可工作”的迁移目标。
|
/// 首批实现采用运行时反射扫描,优先满足“无需额外注册步骤即可工作”的迁移目标。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static class CqrsHandlerRegistrar
|
internal static class CqrsHandlerRegistrar
|
||||||
{
|
{
|
||||||
|
|||||||
@ -0,0 +1,59 @@
|
|||||||
|
using GFramework.Core.Abstractions.Cqrs.Command;
|
||||||
|
using GFramework.Core.Abstractions.Rule;
|
||||||
|
|
||||||
|
namespace GFramework.Core.Cqrs.Extensions;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 提供对 <see cref="IContextAware" /> 接口的 CQRS 命令扩展方法。
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// 该扩展类将命令分发统一路由到架构上下文中的 CQRS 运行时。
|
||||||
|
/// </remarks>
|
||||||
|
public static class ContextAwareCqrsCommandExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 发送命令的同步版本(不推荐,仅用于兼容同步调用链)。
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TResponse">命令响应类型。</typeparam>
|
||||||
|
/// <param name="contextAware">实现 <see cref="IContextAware" /> 接口的对象。</param>
|
||||||
|
/// <param name="command">要发送的命令对象。</param>
|
||||||
|
/// <returns>命令执行结果。</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">
|
||||||
|
/// 当 <paramref name="contextAware" /> 或 <paramref name="command" /> 为 <see langword="null" /> 时抛出。
|
||||||
|
/// </exception>
|
||||||
|
/// <remarks>
|
||||||
|
/// 同步方法仅用于兼容同步调用链;新代码建议优先使用异步版本。
|
||||||
|
/// </remarks>
|
||||||
|
public static TResponse SendCommand<TResponse>(this IContextAware contextAware, ICommand<TResponse> command)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(contextAware);
|
||||||
|
ArgumentNullException.ThrowIfNull(command);
|
||||||
|
|
||||||
|
return contextAware.GetContext().SendCommand(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 异步发送命令并返回结果。
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TResponse">命令响应类型。</typeparam>
|
||||||
|
/// <param name="contextAware">实现 <see cref="IContextAware" /> 接口的对象。</param>
|
||||||
|
/// <param name="command">要发送的命令对象。</param>
|
||||||
|
/// <param name="cancellationToken">取消令牌,用于取消操作。</param>
|
||||||
|
/// <returns>包含命令执行结果的 <see cref="ValueTask{TResult}" />。</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">
|
||||||
|
/// 当 <paramref name="contextAware" /> 或 <paramref name="command" /> 为 <see langword="null" /> 时抛出。
|
||||||
|
/// </exception>
|
||||||
|
/// <remarks>
|
||||||
|
/// 该方法直接返回底层 <see cref="ValueTask{TResult}" />,避免额外的 async 状态机分配。
|
||||||
|
/// </remarks>
|
||||||
|
public static ValueTask<TResponse> SendCommandAsync<TResponse>(
|
||||||
|
this IContextAware contextAware,
|
||||||
|
ICommand<TResponse> command,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(contextAware);
|
||||||
|
ArgumentNullException.ThrowIfNull(command);
|
||||||
|
|
||||||
|
return contextAware.GetContext().SendCommandAsync(command, cancellationToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
141
GFramework.Core/Extensions/ContextAwareCqrsExtensions.cs
Normal file
141
GFramework.Core/Extensions/ContextAwareCqrsExtensions.cs
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
using GFramework.Core.Abstractions.Cqrs;
|
||||||
|
using GFramework.Core.Abstractions.Rule;
|
||||||
|
|
||||||
|
namespace GFramework.Core.Cqrs.Extensions;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 提供对 <see cref="IContextAware" /> 接口的 CQRS 统一扩展方法。
|
||||||
|
/// 这些扩展直接委托给架构上下文的内建 CQRS runtime,作为新的中性命名入口。
|
||||||
|
/// </summary>
|
||||||
|
public static class ContextAwareCqrsExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 发送请求(统一处理 Command/Query)。
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TResponse">响应类型。</typeparam>
|
||||||
|
/// <param name="contextAware">实现 <see cref="IContextAware" /> 接口的对象。</param>
|
||||||
|
/// <param name="request">要发送的请求。</param>
|
||||||
|
/// <param name="cancellationToken">取消令牌。</param>
|
||||||
|
/// <returns>请求结果。</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">
|
||||||
|
/// 当 <paramref name="contextAware" /> 或 <paramref name="request" /> 为 <see langword="null" /> 时抛出。
|
||||||
|
/// </exception>
|
||||||
|
public static ValueTask<TResponse> SendRequestAsync<TResponse>(
|
||||||
|
this IContextAware contextAware,
|
||||||
|
IRequest<TResponse> request,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(contextAware);
|
||||||
|
ArgumentNullException.ThrowIfNull(request);
|
||||||
|
|
||||||
|
return contextAware.GetContext().SendRequestAsync(request, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 发送请求(同步版本,不推荐)。
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TResponse">响应类型。</typeparam>
|
||||||
|
/// <param name="contextAware">实现 <see cref="IContextAware" /> 接口的对象。</param>
|
||||||
|
/// <param name="request">要发送的请求。</param>
|
||||||
|
/// <returns>请求结果。</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">
|
||||||
|
/// 当 <paramref name="contextAware" /> 或 <paramref name="request" /> 为 <see langword="null" /> 时抛出。
|
||||||
|
/// </exception>
|
||||||
|
public static TResponse SendRequest<TResponse>(this IContextAware contextAware, IRequest<TResponse> request)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(contextAware);
|
||||||
|
ArgumentNullException.ThrowIfNull(request);
|
||||||
|
|
||||||
|
return contextAware.GetContext().SendRequest(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 发布通知(一对多事件)。
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TNotification">通知类型。</typeparam>
|
||||||
|
/// <param name="contextAware">实现 <see cref="IContextAware" /> 接口的对象。</param>
|
||||||
|
/// <param name="notification">要发布的通知。</param>
|
||||||
|
/// <param name="cancellationToken">取消令牌。</param>
|
||||||
|
/// <returns>异步任务。</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">
|
||||||
|
/// 当 <paramref name="contextAware" /> 或 <paramref name="notification" /> 为 <see langword="null" /> 时抛出。
|
||||||
|
/// </exception>
|
||||||
|
public static ValueTask PublishAsync<TNotification>(
|
||||||
|
this IContextAware contextAware,
|
||||||
|
TNotification notification,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
where TNotification : INotification
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(contextAware);
|
||||||
|
ArgumentNullException.ThrowIfNull(notification);
|
||||||
|
|
||||||
|
return contextAware.GetContext().PublishAsync(notification, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建流式请求。
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TResponse">响应类型。</typeparam>
|
||||||
|
/// <param name="contextAware">实现 <see cref="IContextAware" /> 接口的对象。</param>
|
||||||
|
/// <param name="request">流式请求。</param>
|
||||||
|
/// <param name="cancellationToken">取消令牌。</param>
|
||||||
|
/// <returns>异步响应流。</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">
|
||||||
|
/// 当 <paramref name="contextAware" /> 或 <paramref name="request" /> 为 <see langword="null" /> 时抛出。
|
||||||
|
/// </exception>
|
||||||
|
public static IAsyncEnumerable<TResponse> CreateStream<TResponse>(
|
||||||
|
this IContextAware contextAware,
|
||||||
|
IStreamRequest<TResponse> request,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(contextAware);
|
||||||
|
ArgumentNullException.ThrowIfNull(request);
|
||||||
|
|
||||||
|
return contextAware.GetContext().CreateStream(request, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 发送无返回值命令。
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TCommand">命令类型。</typeparam>
|
||||||
|
/// <param name="contextAware">实现 <see cref="IContextAware" /> 接口的对象。</param>
|
||||||
|
/// <param name="command">要发送的命令。</param>
|
||||||
|
/// <param name="cancellationToken">取消令牌。</param>
|
||||||
|
/// <returns>异步任务。</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">
|
||||||
|
/// 当 <paramref name="contextAware" /> 或 <paramref name="command" /> 为 <see langword="null" /> 时抛出。
|
||||||
|
/// </exception>
|
||||||
|
public static ValueTask SendAsync<TCommand>(
|
||||||
|
this IContextAware contextAware,
|
||||||
|
TCommand command,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
where TCommand : IRequest<Unit>
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(contextAware);
|
||||||
|
ArgumentNullException.ThrowIfNull(command);
|
||||||
|
|
||||||
|
return contextAware.GetContext().SendAsync(command, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 发送带返回值命令。
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TResponse">响应类型。</typeparam>
|
||||||
|
/// <param name="contextAware">实现 <see cref="IContextAware" /> 接口的对象。</param>
|
||||||
|
/// <param name="command">要发送的命令。</param>
|
||||||
|
/// <param name="cancellationToken">取消令牌。</param>
|
||||||
|
/// <returns>命令执行结果。</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">
|
||||||
|
/// 当 <paramref name="contextAware" /> 或 <paramref name="command" /> 为 <see langword="null" /> 时抛出。
|
||||||
|
/// </exception>
|
||||||
|
public static ValueTask<TResponse> SendAsync<TResponse>(
|
||||||
|
this IContextAware contextAware,
|
||||||
|
IRequest<TResponse> command,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(contextAware);
|
||||||
|
ArgumentNullException.ThrowIfNull(command);
|
||||||
|
|
||||||
|
return contextAware.GetContext().SendAsync(command, cancellationToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,50 @@
|
|||||||
|
using GFramework.Core.Abstractions.Cqrs.Query;
|
||||||
|
using GFramework.Core.Abstractions.Rule;
|
||||||
|
|
||||||
|
namespace GFramework.Core.Cqrs.Extensions;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 提供对 <see cref="IContextAware" /> 接口的 CQRS 查询扩展方法。
|
||||||
|
/// </summary>
|
||||||
|
public static class ContextAwareCqrsQueryExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 发送查询的同步版本(不推荐,仅用于兼容同步调用链)。
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TResponse">查询响应类型。</typeparam>
|
||||||
|
/// <param name="contextAware">实现 <see cref="IContextAware" /> 接口的对象。</param>
|
||||||
|
/// <param name="query">要发送的查询对象。</param>
|
||||||
|
/// <returns>查询结果。</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">
|
||||||
|
/// 当 <paramref name="contextAware" /> 或 <paramref name="query" /> 为 <see langword="null" /> 时抛出。
|
||||||
|
/// </exception>
|
||||||
|
public static TResponse SendQuery<TResponse>(this IContextAware contextAware, IQuery<TResponse> query)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(contextAware);
|
||||||
|
ArgumentNullException.ThrowIfNull(query);
|
||||||
|
|
||||||
|
return contextAware.GetContext().SendQuery(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 异步发送查询并返回结果。
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TResponse">查询响应类型。</typeparam>
|
||||||
|
/// <param name="contextAware">实现 <see cref="IContextAware" /> 接口的对象。</param>
|
||||||
|
/// <param name="query">要发送的查询对象。</param>
|
||||||
|
/// <param name="cancellationToken">取消令牌,用于取消操作。</param>
|
||||||
|
/// <returns>包含查询结果的 <see cref="ValueTask{TResult}" />。</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">
|
||||||
|
/// 当 <paramref name="contextAware" /> 或 <paramref name="query" /> 为 <see langword="null" /> 时抛出。
|
||||||
|
/// </exception>
|
||||||
|
public static ValueTask<TResponse> SendQueryAsync<TResponse>(
|
||||||
|
this IContextAware contextAware,
|
||||||
|
IQuery<TResponse> query,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(contextAware);
|
||||||
|
ArgumentNullException.ThrowIfNull(query);
|
||||||
|
|
||||||
|
return contextAware.GetContext().SendQueryAsync(query, cancellationToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,11 +1,14 @@
|
|||||||
using GFramework.Core.Abstractions.Rule;
|
|
||||||
using GFramework.Core.Abstractions.Cqrs.Command;
|
using GFramework.Core.Abstractions.Cqrs.Command;
|
||||||
|
using GFramework.Core.Abstractions.Rule;
|
||||||
|
using GFramework.Core.Cqrs.Extensions;
|
||||||
|
|
||||||
namespace GFramework.Core.Extensions;
|
namespace GFramework.Core.Extensions;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 提供对 IContextAware 接口的 CQRS 命令扩展方法。
|
/// 提供对 <see cref="IContextAware" /> 接口的 CQRS 命令扩展方法。
|
||||||
|
/// 该类型保留旧名称以兼容历史调用点;新代码应改用 <see cref="GFramework.Core.Cqrs.Extensions.ContextAwareCqrsCommandExtensions" />。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Obsolete("Use GFramework.Core.Cqrs.Extensions.ContextAwareCqrsCommandExtensions instead.")]
|
||||||
public static class ContextAwareMediatorCommandExtensions
|
public static class ContextAwareMediatorCommandExtensions
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -19,11 +22,7 @@ public static class ContextAwareMediatorCommandExtensions
|
|||||||
public static TResponse SendCommand<TResponse>(this IContextAware contextAware,
|
public static TResponse SendCommand<TResponse>(this IContextAware contextAware,
|
||||||
ICommand<TResponse> command)
|
ICommand<TResponse> command)
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(contextAware);
|
return ContextAwareCqrsCommandExtensions.SendCommand(contextAware, command);
|
||||||
ArgumentNullException.ThrowIfNull(command);
|
|
||||||
|
|
||||||
var context = contextAware.GetContext();
|
|
||||||
return context.SendCommand(command);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -38,10 +37,9 @@ public static class ContextAwareMediatorCommandExtensions
|
|||||||
public static ValueTask<TResponse> SendCommandAsync<TResponse>(this IContextAware contextAware,
|
public static ValueTask<TResponse> SendCommandAsync<TResponse>(this IContextAware contextAware,
|
||||||
ICommand<TResponse> command, CancellationToken cancellationToken = default)
|
ICommand<TResponse> command, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(contextAware);
|
return ContextAwareCqrsCommandExtensions.SendCommandAsync(
|
||||||
ArgumentNullException.ThrowIfNull(command);
|
contextAware,
|
||||||
|
command,
|
||||||
var context = contextAware.GetContext();
|
cancellationToken);
|
||||||
return context.SendCommandAsync(command, cancellationToken);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,11 +1,14 @@
|
|||||||
using GFramework.Core.Abstractions.Rule;
|
|
||||||
using GFramework.Core.Abstractions.Cqrs;
|
using GFramework.Core.Abstractions.Cqrs;
|
||||||
|
using GFramework.Core.Abstractions.Rule;
|
||||||
|
using GFramework.Core.Cqrs.Extensions;
|
||||||
|
|
||||||
namespace GFramework.Core.Extensions;
|
namespace GFramework.Core.Extensions;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 提供对 IContextAware 接口的 CQRS 统一接口扩展方法。
|
/// 提供对 <see cref="IContextAware" /> 接口的 CQRS 统一接口扩展方法。
|
||||||
|
/// 该类型保留旧名称以兼容历史调用点;新代码应改用 <see cref="GFramework.Core.Cqrs.Extensions.ContextAwareCqrsExtensions" />。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Obsolete("Use GFramework.Core.Cqrs.Extensions.ContextAwareCqrsExtensions instead.")]
|
||||||
public static class ContextAwareMediatorExtensions
|
public static class ContextAwareMediatorExtensions
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -20,11 +23,10 @@ public static class ContextAwareMediatorExtensions
|
|||||||
public static ValueTask<TResponse> SendRequestAsync<TResponse>(this IContextAware contextAware,
|
public static ValueTask<TResponse> SendRequestAsync<TResponse>(this IContextAware contextAware,
|
||||||
IRequest<TResponse> request, CancellationToken cancellationToken = default)
|
IRequest<TResponse> request, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(contextAware);
|
return ContextAwareCqrsExtensions.SendRequestAsync(
|
||||||
ArgumentNullException.ThrowIfNull(request);
|
contextAware,
|
||||||
|
request,
|
||||||
var context = contextAware.GetContext();
|
cancellationToken);
|
||||||
return context.SendRequestAsync(request, cancellationToken);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -38,11 +40,7 @@ public static class ContextAwareMediatorExtensions
|
|||||||
public static TResponse SendRequest<TResponse>(this IContextAware contextAware,
|
public static TResponse SendRequest<TResponse>(this IContextAware contextAware,
|
||||||
IRequest<TResponse> request)
|
IRequest<TResponse> request)
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(contextAware);
|
return ContextAwareCqrsExtensions.SendRequest(contextAware, request);
|
||||||
ArgumentNullException.ThrowIfNull(request);
|
|
||||||
|
|
||||||
var context = contextAware.GetContext();
|
|
||||||
return context.SendRequest(request);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -58,11 +56,10 @@ public static class ContextAwareMediatorExtensions
|
|||||||
TNotification notification, CancellationToken cancellationToken = default)
|
TNotification notification, CancellationToken cancellationToken = default)
|
||||||
where TNotification : INotification
|
where TNotification : INotification
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(contextAware);
|
return ContextAwareCqrsExtensions.PublishAsync(
|
||||||
ArgumentNullException.ThrowIfNull(notification);
|
contextAware,
|
||||||
|
notification,
|
||||||
var context = contextAware.GetContext();
|
cancellationToken);
|
||||||
return context.PublishAsync(notification, cancellationToken);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -77,11 +74,10 @@ public static class ContextAwareMediatorExtensions
|
|||||||
public static IAsyncEnumerable<TResponse> CreateStream<TResponse>(this IContextAware contextAware,
|
public static IAsyncEnumerable<TResponse> CreateStream<TResponse>(this IContextAware contextAware,
|
||||||
IStreamRequest<TResponse> request, CancellationToken cancellationToken = default)
|
IStreamRequest<TResponse> request, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(contextAware);
|
return ContextAwareCqrsExtensions.CreateStream(
|
||||||
ArgumentNullException.ThrowIfNull(request);
|
contextAware,
|
||||||
|
request,
|
||||||
var context = contextAware.GetContext();
|
cancellationToken);
|
||||||
return context.CreateStream(request, cancellationToken);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -97,11 +93,10 @@ public static class ContextAwareMediatorExtensions
|
|||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
where TCommand : IRequest<Unit>
|
where TCommand : IRequest<Unit>
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(contextAware);
|
return ContextAwareCqrsExtensions.SendAsync(
|
||||||
ArgumentNullException.ThrowIfNull(command);
|
contextAware,
|
||||||
|
command,
|
||||||
var context = contextAware.GetContext();
|
cancellationToken);
|
||||||
return context.SendAsync(command, cancellationToken);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -116,10 +111,9 @@ public static class ContextAwareMediatorExtensions
|
|||||||
public static ValueTask<TResponse> SendAsync<TResponse>(this IContextAware contextAware,
|
public static ValueTask<TResponse> SendAsync<TResponse>(this IContextAware contextAware,
|
||||||
IRequest<TResponse> command, CancellationToken cancellationToken = default)
|
IRequest<TResponse> command, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(contextAware);
|
return ContextAwareCqrsExtensions.SendAsync(
|
||||||
ArgumentNullException.ThrowIfNull(command);
|
contextAware,
|
||||||
|
command,
|
||||||
var context = contextAware.GetContext();
|
cancellationToken);
|
||||||
return context.SendAsync(command, cancellationToken);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,11 +1,14 @@
|
|||||||
using GFramework.Core.Abstractions.Rule;
|
|
||||||
using GFramework.Core.Abstractions.Cqrs.Query;
|
using GFramework.Core.Abstractions.Cqrs.Query;
|
||||||
|
using GFramework.Core.Abstractions.Rule;
|
||||||
|
using GFramework.Core.Cqrs.Extensions;
|
||||||
|
|
||||||
namespace GFramework.Core.Extensions;
|
namespace GFramework.Core.Extensions;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 提供对 IContextAware 接口的 CQRS 查询扩展方法。
|
/// 提供对 <see cref="IContextAware" /> 接口的 CQRS 查询扩展方法。
|
||||||
|
/// 该类型保留旧名称以兼容历史调用点;新代码应改用 <see cref="GFramework.Core.Cqrs.Extensions.ContextAwareCqrsQueryExtensions" />。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Obsolete("Use GFramework.Core.Cqrs.Extensions.ContextAwareCqrsQueryExtensions instead.")]
|
||||||
public static class ContextAwareMediatorQueryExtensions
|
public static class ContextAwareMediatorQueryExtensions
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -18,11 +21,7 @@ public static class ContextAwareMediatorQueryExtensions
|
|||||||
/// <exception cref="ArgumentNullException">当 contextAware 或 query 为 null 时抛出</exception>
|
/// <exception cref="ArgumentNullException">当 contextAware 或 query 为 null 时抛出</exception>
|
||||||
public static TResponse SendQuery<TResponse>(this IContextAware contextAware, IQuery<TResponse> query)
|
public static TResponse SendQuery<TResponse>(this IContextAware contextAware, IQuery<TResponse> query)
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(contextAware);
|
return ContextAwareCqrsQueryExtensions.SendQuery(contextAware, query);
|
||||||
ArgumentNullException.ThrowIfNull(query);
|
|
||||||
|
|
||||||
var context = contextAware.GetContext();
|
|
||||||
return context.SendQuery(query);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -37,10 +36,9 @@ public static class ContextAwareMediatorQueryExtensions
|
|||||||
public static ValueTask<TResponse> SendQueryAsync<TResponse>(this IContextAware contextAware,
|
public static ValueTask<TResponse> SendQueryAsync<TResponse>(this IContextAware contextAware,
|
||||||
IQuery<TResponse> query, CancellationToken cancellationToken = default)
|
IQuery<TResponse> query, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(contextAware);
|
return ContextAwareCqrsQueryExtensions.SendQueryAsync(
|
||||||
ArgumentNullException.ThrowIfNull(query);
|
contextAware,
|
||||||
|
query,
|
||||||
var context = contextAware.GetContext();
|
cancellationToken);
|
||||||
return context.SendQueryAsync(query, cancellationToken);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,4 +16,5 @@ global using System.Collections.Generic;
|
|||||||
global using System.Linq;
|
global using System.Linq;
|
||||||
global using System.Threading;
|
global using System.Threading;
|
||||||
global using System.Threading.Tasks;
|
global using System.Threading.Tasks;
|
||||||
global using System.Threading.Channels;
|
global using System.Threading.Channels;
|
||||||
|
global using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|||||||
@ -5,7 +5,6 @@ using GFramework.Core.Abstractions.Logging;
|
|||||||
using GFramework.Core.Abstractions.Systems;
|
using GFramework.Core.Abstractions.Systems;
|
||||||
using GFramework.Core.Logging;
|
using GFramework.Core.Logging;
|
||||||
using GFramework.Core.Rule;
|
using GFramework.Core.Rule;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
|
|
||||||
namespace GFramework.Core.Ioc;
|
namespace GFramework.Core.Ioc;
|
||||||
|
|
||||||
@ -310,13 +309,12 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
|
|||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 注册中介行为管道
|
/// 注册 CQRS 请求管道行为。
|
||||||
/// 用于配置Mediator框架的行为拦截和处理逻辑。
|
|
||||||
/// 同时支持开放泛型行为类型和已闭合的具体行为类型,
|
/// 同时支持开放泛型行为类型和已闭合的具体行为类型,
|
||||||
/// 以兼容通用行为和针对单一请求的专用行为两种注册方式。
|
/// 以兼容通用行为和针对单一请求的专用行为两种注册方式。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="TBehavior">行为类型,必须是引用类型</typeparam>
|
/// <typeparam name="TBehavior">行为类型,必须是引用类型</typeparam>
|
||||||
public void RegisterMediatorBehavior<TBehavior>() where TBehavior : class
|
public void RegisterCqrsPipelineBehavior<TBehavior>() where TBehavior : class
|
||||||
{
|
{
|
||||||
_lock.EnterWriteLock();
|
_lock.EnterWriteLock();
|
||||||
try
|
try
|
||||||
@ -351,7 +349,7 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.Debug($"Mediator behavior registered: {behaviorType.Name}");
|
_logger.Debug($"CQRS pipeline behavior registered: {behaviorType.Name}");
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@ -359,6 +357,17 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 注册 CQRS 请求管道行为。
|
||||||
|
/// 该成员保留旧名称以兼容历史调用点,内部行为与 <see cref="RegisterCqrsPipelineBehavior{TBehavior}" /> 一致。
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TBehavior">行为类型,必须是引用类型</typeparam>
|
||||||
|
[Obsolete("Use RegisterCqrsPipelineBehavior<TBehavior>() instead.")]
|
||||||
|
public void RegisterMediatorBehavior<TBehavior>() where TBehavior : class
|
||||||
|
{
|
||||||
|
RegisterCqrsPipelineBehavior<TBehavior>();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 配置服务
|
/// 配置服务
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@ -4,7 +4,7 @@ using GFramework.Core.Abstractions.Cqrs.Query;
|
|||||||
using GFramework.Core.Abstractions.Rule;
|
using GFramework.Core.Abstractions.Rule;
|
||||||
using GFramework.Core.Coroutine;
|
using GFramework.Core.Coroutine;
|
||||||
using GFramework.Core.Coroutine.Extensions;
|
using GFramework.Core.Coroutine.Extensions;
|
||||||
using GFramework.Core.Extensions;
|
using GFramework.Core.Cqrs.Extensions;
|
||||||
|
|
||||||
namespace GFramework.Godot.Coroutine;
|
namespace GFramework.Godot.Coroutine;
|
||||||
|
|
||||||
@ -29,8 +29,8 @@ public static class ContextAwareCoroutineExtensions
|
|||||||
string? tag = null,
|
string? tag = null,
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
return contextAware
|
return ContextAwareCqrsCommandExtensions
|
||||||
.SendCommandAsync(command, cancellationToken)
|
.SendCommandAsync(contextAware, command, cancellationToken)
|
||||||
.AsTask()
|
.AsTask()
|
||||||
.ToCoroutineEnumerator()
|
.ToCoroutineEnumerator()
|
||||||
.RunCoroutine(segment, tag);
|
.RunCoroutine(segment, tag);
|
||||||
@ -53,8 +53,8 @@ public static class ContextAwareCoroutineExtensions
|
|||||||
string? tag = null,
|
string? tag = null,
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
return contextAware
|
return ContextAwareCqrsCommandExtensions
|
||||||
.SendCommandAsync(command, cancellationToken)
|
.SendCommandAsync(contextAware, command, cancellationToken)
|
||||||
.AsTask()
|
.AsTask()
|
||||||
.ToCoroutineEnumerator()
|
.ToCoroutineEnumerator()
|
||||||
.RunCoroutine(segment, tag);
|
.RunCoroutine(segment, tag);
|
||||||
@ -77,8 +77,8 @@ public static class ContextAwareCoroutineExtensions
|
|||||||
string? tag = null,
|
string? tag = null,
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
return contextAware
|
return ContextAwareCqrsQueryExtensions
|
||||||
.SendQueryAsync(query, cancellationToken)
|
.SendQueryAsync(contextAware, query, cancellationToken)
|
||||||
.AsTask()
|
.AsTask()
|
||||||
.ToCoroutineEnumerator()
|
.ToCoroutineEnumerator()
|
||||||
.RunCoroutine(segment, tag);
|
.RunCoroutine(segment, tag);
|
||||||
@ -100,8 +100,8 @@ public static class ContextAwareCoroutineExtensions
|
|||||||
string? tag = null,
|
string? tag = null,
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
return contextAware
|
return ContextAwareCqrsExtensions
|
||||||
.PublishAsync(notification, cancellationToken)
|
.PublishAsync(contextAware, notification, cancellationToken)
|
||||||
.AsTask()
|
.AsTask()
|
||||||
.ToCoroutineEnumerator()
|
.ToCoroutineEnumerator()
|
||||||
.RunCoroutine(segment, tag);
|
.RunCoroutine(segment, tag);
|
||||||
|
|||||||
@ -1,21 +1,21 @@
|
|||||||
---
|
---
|
||||||
title: CQRS 与 Mediator
|
title: CQRS
|
||||||
description: CQRS 模式通过 Mediator 实现命令查询职责分离,提供清晰的业务逻辑组织方式。
|
description: GFramework 内建 CQRS runtime,用统一请求分发、通知发布和流式处理组织业务逻辑。
|
||||||
---
|
---
|
||||||
|
|
||||||
# CQRS 与 Mediator
|
# CQRS
|
||||||
|
|
||||||
## 概述
|
## 概述
|
||||||
|
|
||||||
CQRS(Command Query Responsibility Segregation,命令查询职责分离)是一种架构模式,将数据的读取(Query)和修改(Command)操作分离。GFramework
|
CQRS(Command Query Responsibility Segregation,命令查询职责分离)是一种架构模式,将数据的读取(Query)和修改(Command)操作分离。GFramework
|
||||||
通过集成 Mediator 库实现了 CQRS 模式,提供了类型安全、解耦的业务逻辑处理方式。
|
当前内建自有 CQRS runtime,通过统一的请求分发器、通知发布和流式请求管道提供类型安全、解耦的业务逻辑处理方式。
|
||||||
|
|
||||||
通过 CQRS,你可以将复杂的业务逻辑拆分为独立的命令和查询处理器,每个处理器只负责单一职责,使代码更易于测试和维护。
|
通过 CQRS,你可以将复杂的业务逻辑拆分为独立的命令和查询处理器,每个处理器只负责单一职责,使代码更易于测试和维护。
|
||||||
|
|
||||||
**主要特性**:
|
**主要特性**:
|
||||||
|
|
||||||
- 命令查询职责分离
|
- 命令查询职责分离
|
||||||
- 基于 Mediator 模式的解耦设计
|
- 内建请求分发与解耦设计
|
||||||
- 支持管道行为(Behaviors)
|
- 支持管道行为(Behaviors)
|
||||||
- 异步处理支持
|
- 异步处理支持
|
||||||
- 与架构系统深度集成
|
- 与架构系统深度集成
|
||||||
@ -72,7 +72,6 @@ public class GetPlayerQuery : QueryBase<GetPlayerInput, PlayerData>
|
|||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
using GFramework.Core.CQRS.Command;
|
using GFramework.Core.CQRS.Command;
|
||||||
using Mediator;
|
|
||||||
|
|
||||||
// 命令处理器
|
// 命令处理器
|
||||||
public class CreatePlayerCommandHandler : AbstractCommandHandler<CreatePlayerCommand, int>
|
public class CreatePlayerCommandHandler : AbstractCommandHandler<CreatePlayerCommand, int>
|
||||||
@ -92,19 +91,19 @@ public class CreatePlayerCommandHandler : AbstractCommandHandler<CreatePlayerCom
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Mediator(中介者)
|
### Dispatcher(请求分发器)
|
||||||
|
|
||||||
Mediator 负责将命令/查询路由到对应的处理器:
|
架构上下文会负责将命令、查询和通知路由到对应的处理器:
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
// 通过 Mediator 发送命令
|
// 通过架构上下文发送命令
|
||||||
var command = new CreatePlayerCommand(new CreatePlayerInput
|
var command = new CreatePlayerCommand(new CreatePlayerInput
|
||||||
{
|
{
|
||||||
Name = "Player1",
|
Name = "Player1",
|
||||||
Level = 1
|
Level = 1
|
||||||
});
|
});
|
||||||
|
|
||||||
var playerId = await mediator.Send(command);
|
var playerId = await this.SendAsync(command);
|
||||||
```
|
```
|
||||||
|
|
||||||
## 基本用法
|
## 基本用法
|
||||||
@ -148,15 +147,13 @@ public class SaveGameCommandHandler : AbstractCommandHandler<SaveGameCommand>
|
|||||||
// 4. 发送命令
|
// 4. 发送命令
|
||||||
public async Task SaveGame()
|
public async Task SaveGame()
|
||||||
{
|
{
|
||||||
var mediator = this.GetService<IMediator>();
|
|
||||||
|
|
||||||
var command = new SaveGameCommand(new SaveGameInput
|
var command = new SaveGameCommand(new SaveGameInput
|
||||||
{
|
{
|
||||||
SlotId = 1,
|
SlotId = 1,
|
||||||
Data = currentGameData
|
Data = currentGameData
|
||||||
});
|
});
|
||||||
|
|
||||||
await mediator.Send(command);
|
await this.SendAsync(command);
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -195,37 +192,38 @@ public class GetHighScoresQueryHandler : AbstractQueryHandler<GetHighScoresQuery
|
|||||||
// 4. 发送查询
|
// 4. 发送查询
|
||||||
public async Task<List<ScoreData>> GetHighScores()
|
public async Task<List<ScoreData>> GetHighScores()
|
||||||
{
|
{
|
||||||
var mediator = this.GetService<IMediator>();
|
|
||||||
|
|
||||||
var query = new GetHighScoresQuery(new GetHighScoresInput
|
var query = new GetHighScoresQuery(new GetHighScoresInput
|
||||||
{
|
{
|
||||||
Count = 10
|
Count = 10
|
||||||
});
|
});
|
||||||
|
|
||||||
var scores = await mediator.Send(query);
|
var scores = await this.SendQueryAsync(query);
|
||||||
return scores;
|
return scores;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### 注册处理器
|
### 注册处理器
|
||||||
|
|
||||||
在架构中注册 Mediator 和处理器:
|
在架构中注册 CQRS 行为;默认会自动扫描当前架构所在程序集和 `GFramework.Core` 程序集中的处理器:
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
public class GameArchitecture : Architecture
|
public class GameArchitecture : Architecture
|
||||||
{
|
{
|
||||||
protected override void Init()
|
protected override void OnInitialize()
|
||||||
{
|
{
|
||||||
// 注册通用开放泛型行为
|
// 注册通用开放泛型行为
|
||||||
RegisterMediatorBehavior<LoggingBehavior<,>>();
|
RegisterCqrsPipelineBehavior<LoggingBehavior<,>>();
|
||||||
RegisterMediatorBehavior<PerformanceBehavior<,>>();
|
RegisterCqrsPipelineBehavior<PerformanceBehavior<,>>();
|
||||||
|
|
||||||
// 处理器会自动通过依赖注入注册
|
// 默认只自动扫描当前架构程序集和 GFramework.Core 程序集中的处理器
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
`RegisterMediatorBehavior<TBehavior>()` 同时支持两种形式:
|
如果处理器位于其他模块或扩展程序集中,需要额外接入对应程序集的处理器注册,而不是依赖默认扫描。
|
||||||
|
|
||||||
|
`RegisterCqrsPipelineBehavior<TBehavior>()` 是推荐入口;旧的 `RegisterMediatorBehavior<TBehavior>()`
|
||||||
|
仅作为兼容名称保留。当前接口支持两种形式:
|
||||||
|
|
||||||
- 开放泛型行为,例如 `LoggingBehavior<,>`,用于匹配所有请求
|
- 开放泛型行为,例如 `LoggingBehavior<,>`,用于匹配所有请求
|
||||||
- 封闭行为类型,例如某个只服务于单一请求的 `SpecialBehavior`
|
- 封闭行为类型,例如某个只服务于单一请求的 `SpecialBehavior`
|
||||||
@ -326,7 +324,7 @@ var notification = new PlayerLevelUpNotification(new PlayerLevelUpInput
|
|||||||
NewLevel = 10
|
NewLevel = 10
|
||||||
});
|
});
|
||||||
|
|
||||||
await mediator.Publish(notification);
|
await this.PublishAsync(notification);
|
||||||
```
|
```
|
||||||
|
|
||||||
### Pipeline Behaviors(管道行为)
|
### Pipeline Behaviors(管道行为)
|
||||||
@ -334,16 +332,16 @@ await mediator.Publish(notification);
|
|||||||
Behaviors 可以在处理器执行前后添加横切关注点:
|
Behaviors 可以在处理器执行前后添加横切关注点:
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
using Mediator;
|
using GFramework.Core.Abstractions.Cqrs;
|
||||||
|
|
||||||
// 日志行为
|
// 日志行为
|
||||||
public class LoggingBehavior<TMessage, TResponse> : IPipelineBehavior<TMessage, TResponse>
|
public class LoggingBehavior<TMessage, TResponse> : IPipelineBehavior<TMessage, TResponse>
|
||||||
where TMessage : IMessage
|
where TMessage : IRequest<TResponse>
|
||||||
{
|
{
|
||||||
public async ValueTask<TResponse> Handle(
|
public async ValueTask<TResponse> Handle(
|
||||||
TMessage message,
|
TMessage message,
|
||||||
CancellationToken cancellationToken,
|
MessageHandlerDelegate<TMessage, TResponse> next,
|
||||||
MessageHandlerDelegate<TMessage, TResponse> next)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var messageName = message.GetType().Name;
|
var messageName = message.GetType().Name;
|
||||||
Console.WriteLine($"[开始] {messageName}");
|
Console.WriteLine($"[开始] {messageName}");
|
||||||
@ -358,12 +356,12 @@ public class LoggingBehavior<TMessage, TResponse> : IPipelineBehavior<TMessage,
|
|||||||
|
|
||||||
// 性能监控行为
|
// 性能监控行为
|
||||||
public class PerformanceBehavior<TMessage, TResponse> : IPipelineBehavior<TMessage, TResponse>
|
public class PerformanceBehavior<TMessage, TResponse> : IPipelineBehavior<TMessage, TResponse>
|
||||||
where TMessage : IMessage
|
where TMessage : IRequest<TResponse>
|
||||||
{
|
{
|
||||||
public async ValueTask<TResponse> Handle(
|
public async ValueTask<TResponse> Handle(
|
||||||
TMessage message,
|
TMessage message,
|
||||||
CancellationToken cancellationToken,
|
MessageHandlerDelegate<TMessage, TResponse> next,
|
||||||
MessageHandlerDelegate<TMessage, TResponse> next)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var stopwatch = Stopwatch.StartNew();
|
var stopwatch = Stopwatch.StartNew();
|
||||||
|
|
||||||
@ -382,20 +380,20 @@ public class PerformanceBehavior<TMessage, TResponse> : IPipelineBehavior<TMessa
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 注册行为
|
// 注册行为
|
||||||
RegisterMediatorBehavior<LoggingBehavior<,>>();
|
RegisterCqrsPipelineBehavior<LoggingBehavior<,>>();
|
||||||
RegisterMediatorBehavior<PerformanceBehavior<,>>();
|
RegisterCqrsPipelineBehavior<PerformanceBehavior<,>>();
|
||||||
```
|
```
|
||||||
|
|
||||||
### 验证行为
|
### 验证行为
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
public class ValidationBehavior<TMessage, TResponse> : IPipelineBehavior<TMessage, TResponse>
|
public class ValidationBehavior<TMessage, TResponse> : IPipelineBehavior<TMessage, TResponse>
|
||||||
where TMessage : IMessage
|
where TMessage : IRequest<TResponse>
|
||||||
{
|
{
|
||||||
public async ValueTask<TResponse> Handle(
|
public async ValueTask<TResponse> Handle(
|
||||||
TMessage message,
|
TMessage message,
|
||||||
CancellationToken cancellationToken,
|
MessageHandlerDelegate<TMessage, TResponse> next,
|
||||||
MessageHandlerDelegate<TMessage, TResponse> next)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
// 验证输入
|
// 验证输入
|
||||||
if (message is IValidatable validatable)
|
if (message is IValidatable validatable)
|
||||||
@ -441,7 +439,7 @@ public class GetAllPlayersStreamQueryHandler : AbstractStreamQueryHandler<GetAll
|
|||||||
|
|
||||||
// 使用流式查询
|
// 使用流式查询
|
||||||
var query = new GetAllPlayersStreamQuery();
|
var query = new GetAllPlayersStreamQuery();
|
||||||
var stream = await mediator.CreateStream(query);
|
var stream = this.CreateStream(query);
|
||||||
|
|
||||||
await foreach (var player in stream)
|
await foreach (var player in stream)
|
||||||
{
|
{
|
||||||
@ -476,8 +474,8 @@ await foreach (var player in stream)
|
|||||||
|
|
||||||
4. **使用 Behaviors 处理横切关注点**:日志、性能、验证等
|
4. **使用 Behaviors 处理横切关注点**:日志、性能、验证等
|
||||||
```csharp
|
```csharp
|
||||||
RegisterMediatorBehavior<LoggingBehavior<,>>();
|
RegisterCqrsPipelineBehavior<LoggingBehavior<,>>();
|
||||||
RegisterMediatorBehavior<ValidationBehavior<,>>();
|
RegisterCqrsPipelineBehavior<ValidationBehavior<,>>();
|
||||||
```
|
```
|
||||||
|
|
||||||
5. **保持处理器简单**:一个处理器只做一件事
|
5. **保持处理器简单**:一个处理器只做一件事
|
||||||
@ -530,12 +528,12 @@ CalculateDamageRequest
|
|||||||
|
|
||||||
**解答**:
|
**解答**:
|
||||||
|
|
||||||
- **Notification**:通过 Mediator 发送,处理器在同一请求上下文中执行
|
- **Notification**:通过内建 CQRS runtime 发送,处理器在同一请求上下文中执行
|
||||||
- **Event**:通过 EventBus 发送,监听器异步执行
|
- **Event**:通过 EventBus 发送,监听器异步执行
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
// Notification: 同步处理
|
// Notification: 同步处理
|
||||||
await mediator.Publish(notification); // 等待所有处理器完成
|
await this.PublishAsync(notification); // 等待所有处理器完成
|
||||||
|
|
||||||
// Event: 异步处理
|
// Event: 异步处理
|
||||||
this.SendEvent(event); // 立即返回,监听器异步执行
|
this.SendEvent(event); // 立即返回,监听器异步执行
|
||||||
@ -569,15 +567,13 @@ public override async ValueTask<Result> Handle(...)
|
|||||||
### 问题:处理器可以调用其他处理器吗?
|
### 问题:处理器可以调用其他处理器吗?
|
||||||
|
|
||||||
**解答**:
|
**解答**:
|
||||||
可以,通过 Mediator 发送新的命令或查询:
|
可以,通过架构上下文继续发送新的命令或查询:
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
public override async ValueTask<Unit> Handle(...)
|
public override async ValueTask<Unit> Handle(...)
|
||||||
{
|
{
|
||||||
var mediator = this.GetService<IMediator>();
|
|
||||||
|
|
||||||
// 调用其他命令
|
// 调用其他命令
|
||||||
await mediator.Send(new AnotherCommand(...));
|
await this.SendAsync(new AnotherCommand(...));
|
||||||
|
|
||||||
return Unit.Value;
|
return Unit.Value;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -391,17 +391,17 @@ public class PlayerController : IController
|
|||||||
|
|
||||||
#### 5. ArchitectureModules (模块管理器)
|
#### 5. ArchitectureModules (模块管理器)
|
||||||
|
|
||||||
**职责**: 管理架构模块和中介行为
|
**职责**: 管理架构模块和 CQRS 管道行为
|
||||||
|
|
||||||
**核心功能**:
|
**核心功能**:
|
||||||
|
|
||||||
- 模块安装 (IArchitectureModule)
|
- 模块安装 (IArchitectureModule)
|
||||||
- 中介行为注册 (Mediator Behaviors)
|
- CQRS 管道行为注册(推荐 API 为 `RegisterCqrsPipelineBehavior`)
|
||||||
|
|
||||||
**关键方法**:
|
**关键方法**:
|
||||||
|
|
||||||
- `InstallModule()` - 安装模块
|
- `InstallModule()` - 安装模块
|
||||||
- `RegisterMediatorBehavior<T>()` - 注册中介行为
|
- `RegisterCqrsPipelineBehavior<T>()` - 注册 CQRS 管道行为
|
||||||
|
|
||||||
#### 设计优势
|
#### 设计优势
|
||||||
|
|
||||||
@ -672,4 +672,3 @@ public interface IController :
|
|||||||
- 添加 `PhaseChanged` 事件,支持阶段监听
|
- 添加 `PhaseChanged` 事件,支持阶段监听
|
||||||
|
|
||||||
**向后兼容**: 所有公共 API 保持不变,现有代码无需修改。
|
**向后兼容**: 所有公共 API 保持不变,现有代码无需修改。
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user