Command Pattern en PHP

El patrón Comando ó Command Pattern es un patrón de comportamiento muy común a tener en cuenta en nuestros desarrollos.

El principio de este patrón es convertir operaciones o requests en objetos.

Encapsular una petición como un objeto, de modo que puedan parametrizarse otros objetos con distintas peticiones o colas de peticiones.

Encapsular una petición como un objeto, de modo que puedan parametrizarse otros objetos con distintas peticiones o colas de peticiones.

Command Pattern

La idea se basa en la necesidad de ejecutar una acción, para la cual creamos un objeto el cual es denominado Command con la información requerida (Propiedades) para ser luego procesada por un handler.

Cada comando al ser despachado, tendrá su propio handler el cual sabra que acción debe realizar con los datos recibidos por el comando.

Los actores que están involucrados en este patrón son:

  • Cliente/Invoker: Es quien inicia la accion a ejecutar, puede ser un controlador o comando de shell.
  • Command: Es la clase que encapsula todos los datos requeridos para la ejecución de la acción. Los Command actúan como simples DTO’s por lo que son clases que tienen que ser fácilmente serializables.
  • CommandHandler/Handler: Es la clase que es invocada en función del comando despachado y la cual contiene toda la lógica de la acción a realizar.

Por convención, todos los handlers deben tener un método en común para ejecutar los comandos. Este método puede llamarse «handle» ó «execute«, o simplemente podemos aprovechar el método mágico de php y llamar al método __invoke.

Adicionalmente, los comandos deberían implementar una misma interfaz Command y todos los handlers una interfaz CommandHandler con un método handle, execute o __invoke por ejemplo.

De esta manera podemos aprovechar el type hinting para que cada handler sepa a qué comando le corresponde atender.

Por razones de comodidad y estandarización, yo particularmente prefiero utilizar el método mágico __invoke de php para los handlers pero es cuestión de cada quien cómo más cómodo con la implementación se sienta.

Pero realmente lo recomiendo usar puesto que se ha convertido en una buena práctica de implementación para este patrón de diseño.

Puedes leer un poco más sobre el método mágico de php __invoker

Lectura recomendada

Ejemplo de implementación

namespace Application\Command\User;

final class RegisterUserCommand
{
    private $email;
    private $password;
    
    public function __construct(string $email, string $password)
    {
        $this->email = $email;
        $this->password = $password;
    }
    
    public function email(): string
    {
        return $this->email;   
    }
    
    
    public function password(): string
    {
        return $this->password;   
    }
}
namespace Application\CommandHandler;

interface CommandHandlerInterface 
{
    public function __invoke();
}
namespace Application\CommandHandler\User;

use Application\Command\User\RegisterUserCommand;
use Application\CommandHandler\CommandHandlerInterface
use Application\Domain\Repository\UserRepository;
use Application\Entity\User;

final class RegisterUserHandler implements CommandHandlerInterface
{
    const MINIMUM_LENGHT = 15;
    
    private $repository;
    
    public function __construct (UserRepository $repository)
    {
        $this->repository = $repository;   
    }
    
    public function __invoke(RegisterUserCommand $command)
    {
        $name = $command->name();
        $email = $command->email();
        $password = $command->password();
        
        $this->checkEmail();
        $this->checkPassword();
        
        $user = new User($name, $email, $password);
        $this->repository->save($user);
    }
    
    private function checkEmail($email)
    {
        if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
            throw new \Exception('Invalid email');
        }
        
        if ($this->repository->userExists($email)) {
            throw new \Exception('User already exist');
        }
    }
    
    private function checkPassword($password)
    {
        if (self::MINIMUM_LENGHT > strlen($password)) {
            throw new \Exception('Password too short');
        }
    }
}
namespace Application\Controller\User;

use Application\Command\User\RegisterUserCommand;
use Application\CommandHandler\User\RegisterUserHandler;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;

final class UserController
{
    public function registerUser(Request $request): Response
    {
        $name = $request->request->get('name');
        $email = $request->request->get('email');
        $password = $request->request->get('password');
        
        $command = new RegisterUserCommand($name, $email, $password);
        
        $userRepository = $this->get('redis.user.respository');
        $handler = new RegisterUserHandler($userRepository);
        
        try {
            $handler($command);
        } catch (\Exception $e) {
            return new Response($e->getMessage(), Response::HTTP_BAD_REQUEST);
        }
        
        return JsonResponse::create('', Response::HTTP_CREATED);
    }
}

Casos de uso

  • En la arquitectura CQRS, se maneja la separación de 2 contextos principales, contextos de lectura y contextos de escritura. Por ende se utiliza este patrón para definir comandos para lectura de datos y comandos para escritura de datos los cuales hacen uso de buses para que de cierta forma podamos ejecutarlos de forma síncrona o asíncrona según se requiera para nuestro caso de uso.
  • Las librerías de CommandBus mas conocidas como Tactician o Broadway hacen uso de este patrón.

Conclusión

Particularmente puedo decirte que este patrón es muy potente y que viene a resolver muchísimas situaciones que de otro modo quizás se complicaría y terminaríamos haciendo un código spaguetti.

En sí este patrón es muy limpio y 100% testable, su implementación es prácticamente fácil y sencilla y en combinación con el uso de un CommandBus como el de la librería Tactician o Broadway harán de este patrón tu mejor aliado

El patrón Command es una herramienta muy potente, que nos permite crear código muy limpio y testable, su implementación básica es muy sencilla, y el hacer uso de un CommandBus como Tactician es muy sencillo también.

En lo personal lo utilizo muchisimo sobre todo con la arquitectura CQRS. He experimentado como utilizar el componente Messenger de Symfony para tratar de reemplazar las librerías mencionadas y la verdad es que va muy bien.

He logrado implementar el componente para hacer uso de su CommandBus e incluso de sus Transports para encolar los comandos con RabbitMQ. Todo sin acoplarme al framework y mantener los principios de la arquitectura hexagonal intactos.

Hasta aquí dejaré este post sobre este maravilloso patrón de diseño.

Recuerda que si tienes alguna sugerencia o pregunta, no dudes en dejar tus comentarios al final del post.

Si te gustó este post, ayúdame a que pueda servirle a muchas más personas, compartiendo mis contenidos en tus redes sociales.

Espero que este post haya sido de gran ayuda para ti, y como siempre, cualquier inquietud o duda que tengas, puedes contactarme por cualquiera de las vías disponibles, o dejando tus comentarios al final de este post. También puedes sugerir que temas o post te gustaría leer a futuro.


Si te gustó el post, regálame tus aplausos!!!