Rigor Talks – PHP – #20 – Application Services and Command Handlers (Spanish)

Hola Amigos del Rigor! Vamos con una entrega más. En esta ocasión, vamos a ver la diferencia entre Application Services y Command Handlers, así como algún truco sobre Command Handlers para decorarlos y facilitar tareas como el Logging o la gestión de Transacciones.

He creado una lista de reproducción pública con los videos que vaya publicando. La podéis encontrar aquí. Si los videos os gustan, no olvidéis suscribiros a mi canal. Espero que os guste!

#MayTheRigorBeWithYou

  • Me ha parecido muy interesante la introducción de los decorators, porque yo al menos no es algo que acostumbre a usar mucho y se le puede sacar mucho jugo combinando los que necesites :)

    • Gracias! En el siguiente vídeo hablaré de un decorator especial para gestionar eventos, ya verás! :)

    • Carlos Buenosvinos Zamora

      Gracias sdecandelario! Los decoradores dan mucho juego y veremos algunos trucos en los próximos videos.

  • Fran Iglesias

    Hola Carlos. Me maravilla como puedes llevar de un concepto a otro con tanta sencillez.

    Quería hacerte una pregunta que me tiene bastante ocupado. Tengo diversos Command y CommandHandlers, así como Listeners. Mi problema es a la hora de hacer tests. Dado que ni los CommandHandlers ni los Events devuelven resultado (salvo enviar otros events) tengo dos modelos de test. Uno que llamo Command Scenario en el que testeo que se lanzan los eventos adecuados o se disparan otros comandos.

    El otro es el test de los Handlers o Listener, al final me quedan muy acoplados a la implementación porque realmente lo que hacen es testar la interacción de los colaboradores del Handler (p.j un repositorio y un mailer) a través de sus mocks. Me preguntaba si hay algún otro enfoque menos dependiente.

    Gracias.

    • Carlos Buenosvinos Zamora

      Gracias por el comentario, Fran. Estás en lo cierto, en los casos de no retorno, has de usar Spies, que garanticen que la invocación al método clave se ha hecho. Depende del caso, puede haber algún atajo. A ver si puedo meter estos conceptos en algún video.

  • Manuel Pijierro Sa

    Hola Carlos,

    como siempre en primer lugar agradecerte todo lo que aportas con tus vídeos, charlas, blog…etc.

    Te escribo porque me gustaría saber tu opinión respecto a un tema que tiene que ver con el tratado en este vídeo. A ver si logro explicarlo bien.

    Como digo, en el vídeo expones el caso de un Application Service o Command Handler sobre un caso de uso que publica un post. La clase se llama Publish. Hasta aquí bien, todo correcto y entendible.

    Pero, para llegar hasta aquí, normalmente, el usuario habrá introducido el contenido del post en algún formulario. Dicho formulario pertenecerá normalmente a una vista que puede tener cierta configuración por ejemplo en el contenido de desplegables,..información que deba aparecer en el formulario por el motivo que sea…etc. Imagina un formulario complejo, el que quieras.

    Ahora las preguntas:
    ¿Consideras un caso de uso ese ‘Mostrar formulario del post’?, si el formulario necesitara cierta lógica de negocio para crearse….¿Crearías de igual manera un Application Service o Command Hander para generar dicha configuración?…de hacerlo…¿cómo llamarías a este Application Service?, ¿podríamos llegar a tener un AS/CH llamado ConfigPublish y luego el Publish?…o..¿cómo lo llamarías?..

    …y de modo general, ¿sería correcto gestionar cada vista del usuario como un caso de uso?.

    Eso es todo.

    Gracias nuevamente.
    Un saludo.

    • Carlos Buenosvinos Zamora

      Hola Manuel! Gracias a ti por el comentario. Vamos con las respuestas:

      1. ¿Consideras un caso de uso ese ‘Mostrar formulario del post’?, si el formulario necesitara cierta lógica de negocio para crearse….

      No. Necesitas construir un Form (Symfony) o HTML Form (a mano) dado un User. Lo que sí puede que hagas es un caso de uso (este tipo es más una Query) que retorne el usuario (+la información que necesites) para montar tu formulario.

      Los casos de uso tienen que tener sentido de negocio independientemente de la infraestructura. En este caso, si no fuera Web sino API, seguiría teniendo sentido? o si fuera un command line? no. Así que obtendrás el usuario y montarás el form. Como variante, podrías pasarle el tipo de output que quieres al caso de uso de obtener el User y que te diera el form ya. Está explicado en el DDD in PHP como DataTransformers.

      2. …y de modo general, ¿sería correcto gestionar cada vista del usuario como un caso de uso?.

      Puedes hacerlo cambiando el formato de salida (DataTransformers) de un único caso de uso (llamados Query en estos casos). Pero sí, otra opción es tener una Query por representación.

      Un abrazo!

      • Hola Carlos,

        solo agradecerte enormemente todas tus respuestas. Has sido muy revelador.

        Un saludo.

        • Carlos Buenosvinos Zamora

          De nada Manuel. No dudes en preguntarme si tienes más temas.

  • Manuel Pijierro Sa

    Hola Carlos,

    me han surgido un par de dudas más pensando y ‘re-visualizando’ este vídeo.

    1) En el caso de que tanto el userId como el postId del PublishCommand necesitaran algún tipo de validación, ¿qué lugar es el correcto para hacer dicha validación?. ¿Dentro del mismo PublishCommand o dentro del método execute(..) de la clase Publish?…¿u otro sitio :p?

    2) ¿Qué diferencia hay entre pasar el PublishCommand al método execute(..) o inyectarlo directamente en el constructor de la clase Publish?. ¿Por qué uno y no el otro?, ¿podría convenir en algún caso hacerlo en el constructor de la clase Publish?

    Eso es todo. Gracias.

    Un saludo.

    • Manuel Pijierro Sa

      Pensándolo bien, entiendo y se me ocurre que en el punto (2) inyectando el Command en el método execute(..) con una sola instancia de Publish podríamos ejecutar muchos comandos…mientras que si se inyecta en el constructor habría que instanciar tantos Publish como Command queramos lanzar.

      • Carlos Buenosvinos Zamora

        Los Command Handlers son stateless por naturaleza. El primer approach mantiene esa propiedad un poco mejor. Además, es más sencillo pasar el CH como dependencia a un tercero si no lleva el Command en la constructora.

    • Carlos Buenosvinos Zamora

      Hola Manuel! Seguimos con las respuestas:

      1. Yo intento validar las cosas lo más abajo posible (por orden: Value Objects, Entities, Repositories, Domain Services, Application Services).

      Suelo hacer pocas validaciones en los Commands. Si las hago, son superficiales (strings, campos requeridos, etc.) pero no lógicas de negocio. Te dejo un post que lo explica bastante bien: http://danielwhittaker.me/2016/04/20/how-to-validate-commands-in-a-cqrs-application/

  • Jorge

    Hola Carlos,

    Antes de nada felicitaciones por los videos ya son super interesantes.

    Viendo el video me ha salido una duda que no sabría como hacerlo.

    Imagínate que tenemos un modelo producto y su repositorio, por ejemplo:

    class Product
    {
    private $id;
    private $name;
    private $description;
    private $price;
    }

    interface ProductRepository
    {
    public function all($locale);
    public function findById($locale, $id)
    public function create(Product $product)
    }

    Y tenemos dos servicios, uno para obtener el listado de productos y otro para crear un product. La pregunta viene cuando queramos usar localización, es decir, que los productos puedan tener el nombre y la descripción en diferentes idiomas. Para obtener el listado de productos solo bastaría con pasar al repositorio el locale para que devuelva el nombre y descripción en el idioma correcto. Pero para crear como lo podríamos hacer considerando que en el request nos vienen las traducciones del controlador? Como plantearíamos el modelo producto?

    Ejemplo de los servicios:

    class GetProductsService
    {
    public function execute(GetProductsRequest $request)
    {
    return $this->productRepository->all($request->locale())
    }
    }

    class CreateProductService
    {
    public function execute(CreateProductRequest $request)
    {
    $product = new Product(
    new ProductId(),
    $request->name(),
    $request->description(),
    $request->price()
    );
    $product->validate()

    $this->productRepository->create($product);
    }
    }

    Mil gracias.