-
Notifications
You must be signed in to change notification settings - Fork 12
Command Module
The Command Module provides the infrastructure for implementing the "Command" side of Command Query Separation (CQS). Commands are messages that represent an intention to change the system's state.
- Intent to Change State: Commands encapsulate all the information needed to perform an action, such as creating, updating, or deleting data.
- Single Handler: Each command must be handled by exactly one handler. LiteBus will throw an exception if zero or multiple handlers are found for a command.
-
Naming Convention: Commands should be named with imperative verbs in the present tense (e.g.,
CreateProductCommand
,UpdateUserAddress
).
LiteBus provides two interfaces for defining commands.
Use ICommand
for operations that do not need to return a value to the caller. These are "fire-and-forget" style commands where the caller only needs to know if the operation succeeded or failed (via an exception).
/// <summary>
/// A command to update the stock level of a specific product.
/// </summary>
public sealed class UpdateStockLevelCommand : ICommand
{
public required Guid ProductId { get; init; }
public required int NewQuantity { get │...
}
Use ICommand<TCommandResult>
for commands that must return a value after execution, such as the ID of a newly created entity.
/// <summary>
/// A command to create a new product that returns the new product's DTO.
/// </summary>
public sealed class CreateProductCommand : ICommand<ProductDto>
{
public required string Name { get; init; }
public required decimal Price { get; init; }
}
Command handlers contain the business logic to process a command.
-
ICommandHandler<TCommand>
: For commands implementingICommand
. -
ICommandHandler<TCommand, TCommandResult>
: For commands implementingICommand<TCommandResult>
.
// Handler for a command without a result
public sealed class UpdateStockLevelCommandHandler : ICommandHandler<UpdateStockLevelCommand>
{
public async Task HandleAsync(UpdateStockLevelCommand command, CancellationToken cancellationToken = default)
{
// Business logic to update stock level...
}
}
// Handler for a command with a result
public sealed class CreateProductCommandHandler : ICommandHandler<CreateProductCommand, ProductDto>
{
public async Task<ProductDto> HandleAsync(CreateProductCommand command, CancellationToken cancellationToken = default)
{
// Business logic to create the product...
var newProduct = new ProductDto { Id = Guid.NewGuid(), Name = command.Name };
return newProduct;
}
}
The ICommandMediator
is used to send commands into the pipeline for processing.
public interface ICommandMediator
{
Task SendAsync(ICommand command, CommandMediationSettings? settings = null, CancellationToken cancellationToken = default);
Task<TCommandResult> SendAsync<TCommandResult>(ICommand<TCommandResult> command, CommandMediationSettings? settings = null, CancellationToken cancellationToken = default);
}
// In a controller or service
public class ProductsController : ControllerBase
{
private readonly ICommandMediator _commandMediator;
public ProductsController(ICommandMediator commandMediator)
{
_commandMediator = commandMediator;
}
[HttpPost]
public async Task<ActionResult<ProductDto>> Create(CreateProductCommand command)
{
// Send a command that returns a result
var result = await _commandMediator.SendAsync(command);
return CreatedAtAction(nameof(GetById), new { id = result.Id }, result);
}
[HttpPut("{id}/stock")]
public async Task<IActionResult> UpdateStock(Guid id, UpdateStockLevelCommand command)
{
// Send a command that does not return a result
await _commandMediator.SendAsync(command);
return NoContent();
}
}
A major feature introduced in v4.0 is the durable command inbox, which provides guaranteed, fault-tolerant, and deferred command execution.
- Purpose: To ensure critical commands are eventually processed, even if the application restarts or fails.
- Mechanism: Commands are first persisted to a durable store (e.g., a database table) and then processed asynchronously by a background service.
Mark any command with the [StoreInInbox]
attribute to enable this behavior.
using LiteBus.Commands.Abstractions;
/// <summary>
/// This command is critical and must be processed. It will be stored
/// in the inbox for guaranteed execution.
/// </summary>
[StoreInInbox]
public sealed class ProcessPaymentCommand : ICommand
{
public required Guid OrderId { get; init; }
public required decimal Amount { get; init; }
}
- When
SendAsync
is called for an inbox-enabled command, LiteBus stores it via the registeredICommandInbox
implementation and returns immediately. - If the command has a result type (e.g.,
ICommand<TResult>
),SendAsync
will returnTask.FromResult(default(TResult))
. The caller should not expect an immediate result. This pattern is suitable for API endpoints that return anHTTP 202 Accepted
response.
To use the inbox, you must:
- Implement
ICommandInbox
andICommandInboxProcessor
. - Register your implementations in the DI container.
- Register the
CommandInboxProcessorHostedService
to run the background processor.
The Command Module utilizes several advanced features that are shared across all LiteBus modules. For detailed explanations, see the dedicated pages:
-
Handler Priority: Control the execution order of pre-handlers and post-handlers using the
[HandlerPriority]
attribute. - Handler Filtering: Selectively execute handlers based on context using tags or predicates.
- Execution Context: Share data between handlers and control the execution flow within a single command pipeline.
- Polymorphic Dispatch: Create handlers for base command types that can process derived commands.
- Generic Messages & Handlers: Build reusable, generic commands and handlers.
-
Immutability: Design commands as immutable records or classes with
init
-only properties. -
Validation: Use pre-handlers or the specialized
ICommandValidator
interface to validate commands before execution. - Idempotency: For critical operations, design handlers to be idempotent so they can be safely retried.
- Focus: A command should represent a single, atomic business operation.