DEV Community

Cover image for Controladores livianos y buenas prácticas.
Javier Ledezma
Javier Ledezma

Posted on • Originally published at blog.javierledezma.com

Controladores livianos y buenas prácticas.

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() {}
}
Enter fullscreen mode Exit fullscreen mode

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) {}
}
Enter fullscreen mode Exit fullscreen mode

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í
  } 
}
Enter fullscreen mode Exit fullscreen mode

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[] {}
}
Enter fullscreen mode Exit fullscreen mode

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!

Top comments (0)