mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-05-11 20:38:58 +08:00
feat(cqrs): 添加 CQRS 命令协程扩展功能
- 实现 CqrsCoroutineExtensions 扩展类,提供协程方式发送 CQRS 命令的功能 - 添加 SendCommandCoroutine 方法支持命令异步执行与异常处理 - 实现取消操作的特殊处理逻辑,区分取消、失败和成功状态 - 添加 ContextAwareCqrsCommandExtensions 扩展类,提供同步和异步命令发送方法 - 增加对 TaskCanceledException 的专门处理机制 - 完善相关单元测试,验证取消操作的异常处理行为
This commit is contained in:
parent
088f02d586
commit
5a2981a557
@ -92,6 +92,53 @@ public class CqrsCoroutineExtensionsTests
|
||||
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>
|
||||
@ -102,13 +149,24 @@ public class CqrsCoroutineExtensionsTests
|
||||
/// </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,3 +1,4 @@
|
||||
using System.Runtime.ExceptionServices;
|
||||
using GFramework.Core.Abstractions.Coroutine;
|
||||
using GFramework.Core.Abstractions.Cqrs;
|
||||
using GFramework.Core.Abstractions.Rule;
|
||||
@ -22,9 +23,12 @@ public static class CqrsCoroutineExtensions
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// 当 <paramref name="contextAware" /> 或 <paramref name="command" /> 为 <see langword="null" /> 时抛出。
|
||||
/// </exception>
|
||||
/// <exception cref="TaskCanceledException">
|
||||
/// 当底层命令调度被取消且未提供 <paramref name="onError" /> 时抛出。
|
||||
/// </exception>
|
||||
/// <remarks>
|
||||
/// 当底层命令调度失败时,该扩展会把底层异常解包后传给 <paramref name="onError" />,
|
||||
/// 或在未提供回调时重新抛出同一个异常实例,避免两条失败路径暴露不同的异常类型。
|
||||
/// 在取消时则统一暴露 <see cref="TaskCanceledException" />,避免成功、失败与取消三种完成状态被混淆。
|
||||
/// </remarks>
|
||||
public static IEnumerator<IYieldInstruction> SendCommandCoroutine<TCommand>(
|
||||
this IContextAware contextAware,
|
||||
@ -39,6 +43,18 @@ public static class CqrsCoroutineExtensions
|
||||
|
||||
yield return task.AsCoroutineInstruction();
|
||||
|
||||
if (task.IsCanceled)
|
||||
{
|
||||
var canceledException = new TaskCanceledException(task);
|
||||
if (onError != null)
|
||||
{
|
||||
onError.Invoke(canceledException);
|
||||
yield break;
|
||||
}
|
||||
|
||||
ExceptionDispatchInfo.Capture(canceledException).Throw();
|
||||
}
|
||||
|
||||
if (!task.IsFaulted)
|
||||
yield break;
|
||||
|
||||
@ -46,6 +62,6 @@ public static class CqrsCoroutineExtensions
|
||||
if (onError != null)
|
||||
onError.Invoke(exception);
|
||||
else
|
||||
throw exception;
|
||||
ExceptionDispatchInfo.Capture(exception).Throw();
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,6 +6,9 @@ namespace GFramework.Core.Cqrs.Extensions;
|
||||
/// <summary>
|
||||
/// 提供对 <see cref="IContextAware" /> 接口的 CQRS 命令扩展方法。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 该扩展类将命令分发统一路由到架构上下文中的 CQRS 运行时。
|
||||
/// </remarks>
|
||||
public static class ContextAwareCqrsCommandExtensions
|
||||
{
|
||||
/// <summary>
|
||||
@ -18,6 +21,9 @@ public static class ContextAwareCqrsCommandExtensions
|
||||
/// <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);
|
||||
@ -37,6 +43,9 @@ public static class ContextAwareCqrsCommandExtensions
|
||||
/// <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,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user