Utiliza el Patrón de Diseño Chain Of Responsibility en LARAVEL

Spread the love

Chain of Responsibility es un patrón de diseño que te permite pasar un mensaje a lo largo de una cadena de manejadores. Al recibir una solicitud, cada manejador decide si procesa o pasa al siguiente manejador de la cadena el mensaje.

Problema

Vamos a suponer que tenemos un controlador que nos proporciona una lista de usuario con dos respuestas distintas dependiendo del cliente que hace la solicitud.

class UserController extends Controller
{
    public function index()
    {
        $users = User::all();

        if (request()->wantsJson()) {
            return $users;
        }

        return view('users.index', [
            'users' => $users
        ]);
    }

    //...
}

Hasta aquí ya salvaste el día, pero que pasa si pasado un tiempo te piden que ahora se pueda obtener la lista de usuario mediante xml.

class UserController extends Controller
{
    public function index()
    {
        $users = User::all();

        if (request()->wantsJson()) {
            return $users;
        }

        if (request()->wantsXml()) {
            return request()->xml($users);
        }

        return view('users.index', [
            'users' => $users
        ]);
    }

    //...
}

Pero la cosa no acaba aquí, pocos días después te dicen que se va a crear un api para exponer algunos puntos de la aplicación y quieren que pueda responder con json y xml.

¿Qué hacemos?

Obviamente no se puede mover la condición a cada End point de la aplicación y mucho menos vamos a estar manteniendo controladores por separado.

Seria genial que hubiera una forma en la que mediante el request, la aplicación pudiera decidir que forma entregar sin importar lo que se haga con la request y sin tener que hacerlo directamente en el controlador.

Se escucha difícil, pero tenemos una solución usando el patrón cadena de responsabilidad. Que precisamente nos permite mover la lógica que entrega cada formato en clases que respondan de acuerdo a lo que se solicite en el request.

Pero antes de pasar a la solución veamos de que va este patrón.

Repositorio

Si quieres ver todos los detalles de la implementación Seba Carrasco Poblete a realizado un gran trabajo creando un repositorio que recrea este ejemplo basándose en esta explicación 👍🏼

¿Qué es el Patrón de Diseño Chain of Responsibility o Cadena de Responsabilidad?

El Patrón Chain of Responsibility (Cadena de Responsabilidad) Evita el acoplamiento entre el emisor y el receptor de una solicitud, dando a más de un objeto la posibilidad de gestionar la solicitud. Encadena los objetos receptores y pasa la solicitud a lo largo de la cadena hasta que un objeto la atienda.

GoFel

El patrón permite pasar una solicitud a lo largo de una cadena a varios objetos diferentes que tienen la oportunidad de manejar la solicitud. El emisor no necesita saber qué objeto maneja la solicitud, y el objeto manejador no necesita saber quién envió la solicitud, de esta forma no existe acoplamiento entre ambos ya que el cliente y el manejador solo conocen la solicitud.

Puedes imaginar este patrón como una lista enlazada.

💡 ¿Cuándo se aplica el Patrón Chain Of Responsibility?

Más de un objeto puede gestionar una solicitud, y el gestor no se conoce a
a priori. El gestor debe determinarse automáticamente.

✅ Si desea realizar una solicitud a uno de los objetos sin especificar
el receptor explícitamente.

✅ El conjunto de objetos que pueden gestionar una solicitud debe especificarse dinámicamente.

📕 Estructura del Patrón Chain of Responsibility

Diagrama de clases patrón chain of responsability
Diagrama Uml

🎯 Participantes en el Modelo

  • Client: Usuario o sistema que inicia la ejecución del patrón.
  • AbstractHandler(Handler1… HandlerN): Clase base utilizada para definir la estructura de todos los Handler. Esta clase es una agregación de sí misma, lo que le permite contener otro Handler que continuará con la cadena de ejecución.
  • ConcreteHandler(Handler): Representan las implementaciones concretas de AbstractHandler las cuales se utilizarán para procesar las solicitudes.

🛠 Cómo Funciona

Diagrama de interacción patrón chain of responsability
Diagrama de interacción
  1. El cliente solicita el procesamiento de una solicitud a una cadena de responsabilidad.
  2. El primer Handler intenta procesar el mensaje, si no puede manejarlo lo pasara al siguiente Handler en la secuencia.
  3. El HandlerN (Algún handler de la secuencia) procesa el mensaje exitosamente y regresa una respuesta (opcional) rompiendo la secuencia de llamadas.

Una implantación mínima de este patrón seria algo similar a esto:

$chain = new Handler1();
$chain->setNext(new HandlerN());
$result = $chain->handle(new Message());

Implementar La Solución

Lo primero que tenemos que hacer es determinar como representar nuestro problema usando este patrón

Para este caso nuestro cliente es el controlador y cada clase manejadora será cada uno de los formatos que necesitamos y finalmente el request será el mensaje que nos permita determinar que se tiene que entregar en cada petición.

Ahora si veamos como será ese código.

Crear la clase Abstracta para las clases que van a manejar el Request.

Esta clase representa cada manejador y si no puede manejar la petición debe de regresar el siguiente manejador disponible:

namespace App\Services\Handlers;


use Illuminate\Http\Request;

/**
 * Class AbstractHandler
 * @package App\Services\Handlers
 */
abstract class AbstractHandler
{
    /** @var AbstractHandler|null */
    private ?AbstractHandler $nextHandler = null;

    /**
     * @param AbstractHandler $handler
     * @return AbstractHandler
     */
    public function setNextHandler(AbstractHandler $handler): AbstractHandler
    {
        return $this->hasNext() ?
            $this->nextHandler = $handler :
            $this->nextHandler->setNextHandler($handler);
    }

    /**
     * @return AbstractHandler|null
     */
    public function nextHandler(): ?AbstractHandler
    {
        return $this->nextHandler;
    }

    /**
     * @return bool
     */
    protected function hasNext(): bool
    {
        return $this->nextHandler == null;
    }

    /**
     * @param Request $request
     * @return mixed
     */
    abstract public function handle(Request $request);

    /**
     * @param Request $request
     * @return bool|mixed
     */
    protected function next(Request $request)
    {
        return $this->hasNext() ?: $this->nextHandler()->handle($request);
    }
}

Crear Subclases Manejadoras

Para crear cada manejador solo tenemos que implementar el método handler de la Clase AbstractaHandler y mover la lógica de cada condición a una subclase.

<?php


namespace App\Services\Handlers;


use App\Models\User;
use Illuminate\Http\Request;

class JsonResponseHandler extends AbstractHandler
{
    /**
     * @param Request $request
     * @return mixed
     */
    public function handle(Request $request)
    {
        if ($request->wantsJson()) {
            return User::all();
        }

        return $this->next($request);
    }

}
<?php


namespace App\Services\Handlers;


use App\Models\User;
use Illuminate\Http\Request;

class XmlResponseHandler extends AbstractHandler
{
    /**
     * @param Request $request
     * @return mixed
     */
    public function handle(Request $request)
    {
        if ($request->wantsXml()) {
            return request()->xml(User::all());
        }

        return $this->next($request);
    }

}
<?php


namespace App\Services\Handlers;


use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\View;

class ViewResponseHandler extends AbstractHandler
{

    /**
     * @param Request $request
     * @return mixed
     */
    public function handle(Request $request)
    {
        return View::make('users.index', [
            'users' => User::all(),
        ]);

    }
}

Actualizar código

Finalmente tenemos que actualizar el controlador creador

class UserController extends Controller
{
    public function index()
    {
        $response = $this->getResponseHandler();

        return $response->handle(request());
    }

    /**
     * @return JsonResponseHandler
     */
    protected function getResponseHandler(): JsonResponseHandler
    {
        $response = new JsonResponseHandler();
        $response->setNextHandler(new XmlResponseHandler());
        $response->setNextHandler(new ViewResponseHandler());
        return $response;
    }
    //...
}

Ahora si, cada vez que se ejecuta el index se entregara el formato correcto, además para el asunto del api solo tienes que conectar los manejadores o crear nuevos sin importar el controlador o el modelo que se use.

Si observas cada manejador debe de conectarse con el siguiente y obviamente con varios manejadores no es muy cómodo, tener que crear muchas instancias a mano y todo en un método, así que la mejor opción es crear una clase que pueda configurar los manejadores desde el contenedor de dependencias de Laravel.

Pero para este ejemplo lo vamos dejar así, ya que solo queremos ilustrar como funciona el patrón.

Conclusión

Este patrón es muy similar al Pipeline, y de hecho diría que es una forma general del patrón chain of responsability. pero con mejoras, ya que con el pipeline los manejadores no conocen al manejador que les sigue y puedes manejar cualquier cosa en el mensaje sin tener que definirlo en el pipeline.

Para finalizar recuerda usar este patrón cada vez que quieras:

  • Desacoplar el emisor del receptor.
  • Utilizar reglas dinámicas de procesamiento o validación de datos.
  • Procesar las solicitudes en un orden específico.

El diseño de este patrón además nos permite cumplir con el principio SRP y OCP; dos de los principios SOLID.

Si tienes alguna duda, deja tu comentario 👍🏼