Patrón Command
El patrón Command encapsula toda la información necesaria para realizar una acción o desencadenar un evento en una aplicación. Se trata de un objeto que contiene datos y comportamientos que representan una instrucción para actuar sobre entidades o dominios específicos. Este patrón está formado por dos clases principales: el Command y el CommandHandler (o Query y QueryHandler).
La clase Command o Query es un simple DTO que contiene los parámetros necesarios para realizar una acción. Es un objeto inmutable que se utiliza para transportar datos entre componentes y no contiene lógica de negocio. Por otro lado, la clase CommandHandler o QueryHandler es una clase callable (que contiene el método público __invoke()) que recibe como argumento un objeto Command o Query y ejecuta la acción correspondiente. Es aquí donde se encapsula la lógica de negocio y se toman las decisiones sobre cómo y cuándo se ejecutan las acciones.
Este patrón se usa ampliamente en aplicaciones donde se desea separar las solicitudes de ejecución de la implementación real de las acciones. Facilita la operación desacoplada, la repetición de comandos, y puede simplificar la operación transaccional al tratar las acciones como un todo atómico.
Es especialmente útil en escenarios donde se necesita mantener un alto nivel de trazabilidad y registro de acciones, como sistemas financieros, donde cada acción puede tener consecuencias significativas.
Ejemplo
A continuación se muestran dos ejemplos de casos de uso básicos, uno para la creación de un producto, que ejecuta una acción en el sistema (Command), y otro para obtener un producto por su SKU, que devuelve información (Query).
<?php
#[Immutable]final readonly class CreateProductCommand { public function __construct( public string $name, public float $price ) { }}?><?php
final readonly class CreateProductCommandHandler{
public function __construct(private ProductRepository $repository) { }
public function __invoke(CreateProductCommand $command): void { $product = new Product( name: $command->name, price: $command->price );
$this->repository->save($product); }}?><?php#[Immutable]final readonly class GetProductBySkuQuery { public function __construct( public string $sku) {}}?><?php
final readonly class GetProductBySkuHandler{
public function __construct(private ProductRepository $repository) { }
public function __invoke(GetProductBySkuQuery $query): ProductDTO { $product = $this->repository->findBySku($query->sku);
$this-assertThatProductExists($product);
return new ProductDTO( sku: $product->getSku(), name: $product->getName(), price: $product->getPrice() ); }
private function assertThat productExists(Product $product): void { if ($product === null) { throw new ProductNotFoundException(); } }}?>