Controladores livianos y buenas prácticas.

Gracias a los frameworks modernos de desarrollo web, crear aplicaciones se vuelve una tarea muy sencilla y basta con entender un poco el funcionamiento del protocolo HTTP y un poco de MVC (o variantes) para darnos cuenta de que todo se reduce a:

Request -> Router -> Controller -> Response

En dónde el cliente envía una petición al servidor, esta es procesada por un sistema de rutas (router), quien lo envía al controlador correspondiente y el controlador se encarga de ejecutar una o varias acciones y construir una respuesta en el formato necesario (por ejemplo un archivo html o una respuesta xml o json) y la envía como respuesta al cliente.

Y siguiendo ese patrón nosotros podemos construir todo tipo de aplicaciones, sin embargo, el mal manejo de controladores puede resultar en una aplicación imposible de mantener y por ende, un software que estará destinado al fracaso.

"El mayor costo en el desarrollo de software no es para crearlo, sino para mantenerlo. "

Una frase del libro Clean Code de Robert C. Martin, el cual hace énfasis en las buenas prácticas de la programación orientada a objetos.

Una de las mejores maneras de crear código mantenible es dejar que los controladores de nuestra aplicación solo se encarguen únicamente de acciones CRUD y utilizar otros patrones de diseño o técnicas para delegar actividades secundarias.

CRUD es un acrónimo de: Create, Read, Update & Delete (Crear, Leer, Actualizar y eliminar).

Un controlador CRUD debería verse similar a:


class Controller
{
 // lista un recurso
 public function index() {}

 // muestra formulario de creación (opcional en API's)
 public function create() {}

 // crea un nuevo registro en BD
 public function store()

 // muestra detalles de un registro
 public function show()

 // muestra formulario de edición (opcional en API's)
 public function edit() {}

 // actualiza un registro en BD
 public function update() {}

 // elimina un registro en DB
 public function delete() {}
}

Puede llegar a tener variantes dependiendo del lenguaje y el framework utilizado.

Existen un par de escenarios en los que te puede causar ruido el uso de los controladores CRUD, estos son:

  1. Cuando necesitamos manejar recursos relacionados.
  2. Cuando nuestro controlador contiene un sólo método.


1. Manejado recursos relacionados


Si tu controlador requiere trabajar con recursos relacionados, por ejemplo, si tienes un blog, en dónde un artículo puede tener comentarios y para administrar los comentarios necesitas una instancia de artículo, en vez de agregar métodos adicionales al controlador de artículos, crea un controlador que se encargue de manejar esas relaciones, ejemplo:



class ArticleCommentsController
{
 // lista los comentarios de un artículo
 public function index(int $idArticle) {}

 // crea un nuevo comentario para un artículo
 public function store(int $idArticle)

 // muestra detalles de un comentario relacionado a un artículo
 public function show(int $idArticle)

 // actualiza un comentario relacionado al artículo
 public function update(int $idArticle) {}

 // elimina un comentario relacionado al artículo
 public function delete(int $idArticle) {}
}


En dónde cada método recibe el id del recurso principal (en este caso articulo) y pero las acciones afectan al recurso comentario.


2. Manejando controladores de un solo método


Cuando tu controlador tiene una función distinta a la de acciones CRUD, por ejemplo, si tenemos una página de inicio para una landing page o un dashboard si estámos trabajando con usuarios administradores. En éstos casos se puede utilizar el método index como la única fuente o si tu framework te lo permite, también puedes utilizar controladores invocables, esto quiere decir que se sobreescribe el método invoke de la clase, ejemplo:


class HomeController

{
  public function __invoke(/* parameters */)
  {
   // Lógica aquí
  }
}


De ésta manera nos aseguramos que nuestro controlador solo ejecutará una acción.


Quitando carga a los controladores


Todo método o funcionalidad fuera de lo métodos CRUD puede ser extraído a su propia clase, en seguida te comparto algunas herramientas que puedes utilizar para lograr mantener tu controlador libre de código adicional:

Es muy probable que el framework de tu preferencia no cuente algunos de los elementos citados.


Middleware


Un middleware es una capa de software que puede interceptar la petición del cliente o la respuesta generada por nuestra aplicación y regularmente se utilizan para hacer revisar de los datos de sesión, encabezados del navegador, validar datos de la petición del cliente y/o agregar encabezados adicionales a las respuestas.


Eventos


Un evento es una forma de decirle a nuestra aplicación que algo ha ocurrido, dando paso a que la aplicación pueda actuar en consecuencia con una o más acciones (estas acciones también son conocidas como escuchadores o suscriptores).

Uno de los ejemplos más concurridos es disparar un evento cuando un usuario se registra en nuestra aplicación, dejando que el controlador se encargue de persistir al usuario y delegando la lógica de enviar un correo de bienvenida a un escuchador o a algún suscriptor.

Una ventaja de utilizar eventos, es que se pueden ejecutar muchas acciones en consecuencia, cada una con su propia lógica.


Peticiones personalizadas


Gran parte de la lógica de los controladores se basa en validar que los datos ingresados por el usuario sean correctos y que estos no vayan a provocar inconsistencias en nuestra base de datos.

Por fortuna, muchos frameworks cuentan con peticiones personalizadas, estas son clases que “decoran” una petición HTTP común y agregan esa capa de validación necesaria.

En caso de que el framework que utilices no cuente con esta opción, puedes utilizar middlewares para este fin.


Servicios


Un servicio es una clase encarga de hacer algo.

No muy claro, ¿cierto?. Esto se debe a la versatilidad que puede tener un servicio.

Un servicio es un componente que cumple con una función relacionada a regas de negocio de la aplicación. regularmente son fragmentos pequeños de código de responsabilidad única.

Estas clases suelen delegarse al contenedor de la aplicación utilizando inyección de dependencias para instanciarse de manera automática y brindando la posibilidad de que de manera interna puedan utilizar otros servicios en caso de que sea lógica muy compleja.

Los servicios suelen utilizarse cuando un controlador requiere mucha lógica en sus métodos CRUD y/o cuando tenemos fragmentos de código que pueden ser reutilizados en nuestra aplicación.


Repositorios


Un repositorio es una fuente de datos y se traduce a clases encargadas de brindar información de diversas fuentes, por ejemplo, traer un arreglo de una base de datos local o proporcionar datos de una API externa, entre ortro.

Una buena práctica es definir contratos o interfaces que definan los métodos que necesitemos en nuestra aplicación y hacer tantas implementaciones como se requieran. De esta manera, si en algún futuro se llega a cambiar la fuente de datos, entonces, la aplicación no tendrá que modificar todo el código, solo las implementaciones de los contratos.

Ejemplo:


# src/Repositories/UserRepository.php

interface UserRepository
{

 public function findById(): User;
 public function all(): User[];
}

# src/Repositories/Implementations/DbUserRepository.php

class DbUserRepository {
  /* @var DBAL */
  private $connection;

  // ...

  public function findById(): User {}
  public function all(): User[] {}
}


Utilerías


En ocasiones tenemos código que no está relacionado a las reglas de negocio, pero que puede utilizado en muchas partes de la aplicación, por ejemplo, si necesitamos dar formato a un decimal para mostrarlo en formato de moneda, o si necesitamos hacer conversiones de fechas, en éstos casos se puede crear una clase de utilerías que contenga estas funciones.

Es muy importante que no intentes crear "navajas suizas" en las clases de utilerías, para evitarlo, intenta agrupar las funciones por su tipo, por ejemplo, crea una utilería para manejar strings (class StringUtils), y otra para manejar fechas (class DateUtils).

Conclusión


Utilizar estas técnicas o patrones nos van a ayudar a tener controladores y componentes muy fáciles de mantener a largo plazo. Y aunque en éste post solamente se mencionan de manera muy superficial, es importante que conozcas los conceptos para que puedas comenzar a implementarlos en tus proyectos.

Te invito a que comiences por utilizar lo aprendido en éste post en tus proyectos y si tienes algún consejo adicional o comentario, siempre será bienvenido en los comentarios, ¡gracias por tu tiempo!

¡Hasta la próxima!