Command, Command Handler and Command Executor¶
Command¶
The state of the data is altered via commands. A command handler is responsible for managing commands in CQRS. The following code snippet creates the command:
public class PlaceOrderCommand : ICommand<CommandExecutionResult<PlaceOrderResponse>>
{
public string OrderCode { get; set; }
}
In the above code snippet, the PlaceOrderCommand
implements the ICommand<T>
interface, with T
representing the generic type that signifies the command’s result. In the Crystal Sharp framework, the standard result type for CQRS-based commands is CommandExecutionResult<TResult>
, where TResult
can be of any value or reference type.
IMPORTANT
It is necessary for every CQRS-based command to implement the “ICommand
CommandExecutionResult<TResult>
has the following instance properties and static methods:
public bool Success { get; set; } | This property will be set to true if the command executes successfully and without any issues, and to false otherwise. |
public IEnumerable<Error> Errors { get; set; } | Errors will be listed here if the command is not successful; otherwise, null. |
public TResult Data { get; set; } | This property contains the actual result, which could be a value or reference type. |
public static CommandExecutionResult<TResult> WithError(IEnumerable<Error> errors) | Static method, returns the CommandExecutionResult<TResult> object with errors and sets the Success property to false. |
Command Handler¶
To handle the command, a handler is required. A command handler is where a command is actually executed. An illustration of a command handler is provided in the following code snippet:
public class PlaceOrderCommandHandler : CommandHandler<PlaceOrderCommand, PlaceOrderResponse>
{
public override async Task<CommandExecutionResult<PlaceOrderResponse>> Handle(PlaceOrderCommand request, CancellationToken cancellationToken = default)
{
PlaceOrderResponse response = new() { Id = Guid.NewGuid(), OrderCode = request.OrderCode };
return await Ok(response);
}
}
public class PlaceOrderResponse
{
public Guid Id { get; set; }
public string OrderCode { get; set; }
}
The code snippet above exhibits how the PlaceOrderCommandHandler
class manages the PlaceOrderCommand
. It is necessary for the command handler class to inherit from the CommandHandler<TRequest, TResponse>
class and override the method Handle(TRequest request, CancellationToken cancellationToken = default)
. The TRequest
parameter in the Handle
method is the command that is being handled.
The method Ok
is from the base class and expects any value or reference type, which is the actual result. The Ok
method returns the CommandExecutionResult<TResult>
object with its Success
property set to true
and assigns the result to the Data
property.
The CommandHandler<TRequest, TResponse>
base class has a method Fail(params string[] errorMessages)
, which could be utilized if there is any validation error or failure.
Following code snippet calls the Fail
method if the request object is null
:
public class PlaceOrderCommandHandler : CommandHandler<PlaceOrderCommand, PlaceOrderResponse>
{
public override async Task<CommandExecutionResult<PlaceOrderResponse>> Handle(PlaceOrderCommand request, CancellationToken cancellationToken = default)
{
if (request == null)
{
return await Fail("Invalid command.");
}
PlaceOrderResponse response = new() { Id = Guid.NewGuid(), OrderCode = request.OrderCode };
return await Ok(response);
}
}
public class PlaceOrderResponse
{
public Guid Id { get; set; }
public string OrderCode { get; set; }
}
In the above code snippet, if the request object is null
, the Fail
method will be invoked from the base class. This will cause the CommandExecutionResult<TResult>
to be returned with its Success
property set to false
and the arguments of the Fail
method will be assigned to the Errors
property of the CommandExecutionResult<TResult>
object.
Command Executor¶
In order to execute the command, a command executor is required, which dispatches the command to its appropriate command handler. The Crystal Sharp framework provides an interface called ICommandExecutor
to execute commands. The following code snippet represents an example of a command executor:
public class OrderController : ControllerBase
{
private readonly ICommandExecutor _commandExecutor;
public OrderController(ICommandExecutor commandExecutor)
{
_commandExecutor = commandExecutor;
}
[HttpPost]
public async Task<ActionResult<CommandExecutionResult<PlaceOrderResponse>>> Post([FromBody] PlaceOrderRequest request)
{
PlaceOrderCommand command = new() { OrderCode = request.OrderCode };
return await _commandExecutor.Execute(command, CancellationToken.None).ConfigureAwait(false);
}
}
The code snippet above uses constructor injection to inject the ICommandExecutor
interface into OrderController
. In the Post
method of OrderController
, a new command is created and executed by the command executor.
ICommandExecutor
interface has the following method:
public Task<CommandExecutionResult<TResult>> Execute<TResult>(ICommand<CommandExecutionResult<TResult>> command, CancellationToken cancellationToken = default) | Executes the command and returns the CommandExecutionResult<TResult> object. The parameter command could be any class that implements the interface ICommand<CommandExecutionResult<TResult>>. The parameter cancellationToken is optional. |