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
deRobert 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:
- Cuando necesitamos manejar recursos relacionados.
- 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 utilizandoinyecció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!
Top comments (0)