11 Nov 24

La importancia de refactorizar endpoints para un código reluciente

Los invisibles de la CDN

Refactorizar y simplificar código es imprescindible para que tu aplicación escale bien, sea fácil de mantener y, en general, funcione sin darnos grandes quebraderos de cabeza. En la publicación de hoy abordamos asuntos cruciales del backend a través de un caso al que me he enfrentado recientemente. 

Al refactorizar el código conseguimos mejorar considerablemente la comunicación entre el frontend y el backend, y además redujimos bastante la cantidad de requests a la API. Estoy 100% seguro de que nuestros servidores agradecen en gran medida todas estas mejoras.

El problema: demasiados endpoints, muchas requests

En el proyecto que tenía entre manos, había cuatro endpoints diferentes para manejar toda la información de consumo y facturación. Cada uno hacía lo suyo:

  • Consumo por cliente
  • Consumo por producto
  • Compromiso (commitment) de clientes
  • Compromiso de productos

Hasta aquí todo bien, ¿no? Bueno, para peticiones sencillas, por supuesto. Pero imagina la siguiente situación: el usuario necesita saber el consumo para 100 clientes, sus relaciones con 20 productos diferentes (productos, servicios, como los quieras llamar), y cuánto han gastado en un determinado periodo de tiempo, todo esto dándole a un solo botón, obviamente. 

Imagino que estás pensando: “vaya cantidad de endpoints hay que llamar para un único usuario, el enmarañado de requests a resolver y el tiempo que va a tardar en procesar todo eso”. Sí, era una locura. El servidor lloraba, el frontend también, y el usuario final ni te cuento. Tal vez solo precisaba unos segundos más, pero hoy en día unos segundos es una eternidad: lo queremos todo al momento.

La solución: agrupar endpoints

Ante este panorama me planteé si habría alguna manera de simplificar todo en un solo endpoint.  El objetivo estaba claro: una sola solicitud que devolviera toda la información de golpe, ¿pero cómo?

Y aquí es donde les doy las gracias a mis compañeros por elegir Django como nuestro framework backend. También tengo que enaltecer a los endpoints que ya existían, porque a partir de ellos pude visualizar el problema y la solución. 

Django tiene un ORM (Object-Relational Mapping) muy poderoso y flexible. En pocas palabras, el ORM permite interactuar con la base de datos utilizando modelos de Python, en lugar de escribir consultas SQL manuales. Esto no solo hace que el código sea más fácil de leer y mantener, sino que también ofrece formas más eficientes de manejar los datos.

El principal desafío era cómo unir todas esas consultas en un solo endpoint sin comprometer el rendimiento y mantener al mismo tiempo la flexibilidad para aplicar filtros específicos por cliente, producto y fecha. No me entiendan mal, los endpoints anteriores cumplieron su propósito en un primer momento, en el que las solicitudes estaban separadas, no eran tan complejas y la velocidad no era prioridad (hablo de segundos eh, no es como si tardaran semanas). Para poner un ejemplo, observemos estas dos solicitudes, donde pedimos las líneas de consumo de las facturas seleccionadas:

billing_lines =
BillingLine.objects.filter(billing__in=billing_list).select_related("billing","item)

#En esta, filtramos por un producto en específico
billing_lines =BillingLine.objects.filter(billing__in=billing_list, product=product).select_related("billing","item")

Cada `billing_lines` estaba presente en un endpoint, pero son prácticamente la misma solicitud con más o menos filtros. Al ver esto, fue cuando decidí crear un nuevo endpoint que tuviera una consulta más simplificada pero genérica, y pudiese, a partir de ella, obtener todos los datos necesarios de una sola vez. Para no alargarme más, uniendo unos `select_related()` por aquí y unos `annotate()` por allá, conseguí tener los datos en mano, y a partir de ahí, solo restaba tratarlos y devolver la respuesta en formato JSON.

¿Por qué es esto importante?

Primero, porque se realiza una menor cantidad de requests. Esta fue la mayor ventaja. Pasamos de hacer cientos de solicitudes separadas a una sola. Imagínate la diferencia en rendimiento. Para el usuario final, la diferencia en velocidad le ahorró cuestión de pocos segundos; pero, para el servidor, el cambio fue muy positivo en consumo y eficiencia.

Segundo (y lo más importante en mi opinión), el mantenimiento. El código se volvió mucho más fácil de mantener. Ya no es necesario andar haciendo cambios en varios lugares. Si algo cambia, lo hacemos en un solo sitio y listo. Menos riesgos de errores y menos código duplicado. El desarrollador del futuro lo agradecerá 💪

Mantener el código simple (y poderoso)

Creo que aquí la lección más importante es que no basta con que el código funcione, también tiene que ser legible y fácil de mantener. En este caso, el código servía a lo que estaba propuesto, pero el mantenimiento era un poco más costoso. Mi objetivo en todo momento fue facilitar la lectura del código a cualquier desarrollador que llegara a tocarlo, porque, bueno, no quiero que me llamen por dudas técnicas cuando esté disfrutando de mis vacaciones en Cancún 🤪.

Con un solo endpoint, le hicimos la vida más fácil al equipo de frontend y a nosotros mismos. La aplicación ahora puede crecer sin tener que preocuparnos por problemas de rendimiento a medida que se agregan más clientes y productos.

En resumen, refactorizar siempre es un desafío, pero los beneficios que trae realmente valen la pena. Así que, cuando se trate de endpoints, recuerda que menos, es más. Un buen código limpio y eficiente es clave para cualquier proyecto backend que quiera durar a largo plazo.