使用CQRSLite时,如何从POST操作返回结果?
Hey there! I’ve worked with CQRSLite quite a bit, so let me walk you through practical ways to return results from your POST operations when using this library.
1. Synchronous Command Handling (Direct Result)
While CQRS leans toward fire-and-forget commands, you can still get immediate results by passing state back through the command object itself (since commands are reference types). This works great for simple scenarios where you need a value like a newly created entity ID.
First, define your command with a property to hold the result:
public class CreateOrderCommand : ICommand { public Guid Id { get; set; } public string CustomerName { get; set; } // Property to store the generated order ID after processing public Guid GeneratedOrderId { get; set; } }
Then update your command handler to populate this property:
public class CreateOrderCommandHandler : ICommandHandler<CreateOrderCommand> { private readonly ISession _session; public CreateOrderCommandHandler(ISession session) { _session = session; } public async Task Handle(CreateOrderCommand command) { var newOrder = new Order(command.CustomerName); command.GeneratedOrderId = newOrder.Id; // Pass the ID back via the command await _session.Add(newOrder); await _session.Commit(); } }
Finally, access the result in your POST controller:
[HttpPost("orders")] public async Task<IActionResult> CreateOrder([FromBody] CreateOrderCommand command) { await _commandDispatcher.Dispatch(command); return Ok(new { OrderId = command.GeneratedOrderId }); }
2. Asynchronous Result via Event Tracking
For more complex, async workflows (like when commands are processed in a background queue), you can use a TaskCompletionSource to wait for the corresponding domain event and return the result once it fires.
First, add a TaskCompletionSource to your command:
public class CreateOrderCommand : ICommand { public Guid Id { get; set; } public string CustomerName { get; set; } public TaskCompletionSource<Guid> OrderIdCompletionSource { get; set; } = new TaskCompletionSource<Guid>(); }
Next, use an event handler to resolve the completion source when the order is created:
// In-memory store to track active commands (use distributed cache for multi-instance apps) private static readonly Dictionary<Guid, CreateOrderCommand> _activeCommands = new(); public class OrderCreatedEventHandler : IEventHandler<OrderCreatedEvent> { public Task Handle(OrderCreatedEvent @event) { if (_activeCommands.TryGetValue(@event.CommandId, out var command)) { command.OrderIdCompletionSource.SetResult(@event.AggregateId); _activeCommands.Remove(@event.CommandId); } return Task.CompletedTask; } }
Then update your controller to wait for the event:
[HttpPost("orders")] public async Task<IActionResult> CreateOrder([FromBody] CreateOrderCommand command) { command.Id = Guid.NewGuid(); _activeCommands.Add(command.Id, command); await _commandDispatcher.Dispatch(command); var orderId = await command.OrderIdCompletionSource.Task; return Ok(new { OrderId = orderId }); }
3. Return Command ID for Subsequent Queries
This is the most CQRS-aligned approach: return a command ID immediately, then let clients query a separate endpoint to check status or retrieve the final result.
Controller code:
[HttpPost("orders")] public IActionResult CreateOrder([FromBody] CreateOrderCommand command) { command.Id = Guid.NewGuid(); // Dispatch asynchronously (e.g., via a message queue) _commandDispatcher.DispatchAsync(command); // Return command ID and a link to check status return AcceptedAtAction( nameof(GetCommandStatus), new { commandId = command.Id }, new { CommandId = command.Id, Message = "Order creation in progress" } ); } [HttpGet("commands/{commandId}")] public async Task<IActionResult> GetCommandStatus(Guid commandId) { var commandStatus = await _commandStatusRepo.GetById(commandId); if (commandStatus == null) return NotFound(); if (commandStatus.IsCompleted) { var order = await _orderRepo.GetById(commandStatus.AggregateId); return Ok(new { Status = "Completed", Order = order }); } return Ok(new { Status = "Processing" }); }
You’ll need a CommandStatus entity to track whether the command succeeded/failed and link to the created aggregate.
内容的提问来源于stack exchange,提问作者KDS

