El otro día me tocó integrar la API de una empresa de envío local y la experiencia no fue la mejor, errores que parecen triviales como:
- Respuestas y recursos nombrados en español.
- Siempre respondía con el estatus 200 (No manejaba errores tipo 400, 404, 401, 500, etc).
- El naming de los endpoints no era el correcto.
- No utilizaba paginación, es decir, si consultaba las ciudades de Venezuela me entregaba una respuestas con miles de resultados de golpe.
- No permitía el uso de filtros, es decir, como quería buscar una ciudad en específico tuve que hacer un script en mi código para eso.
- No había consistencia en cómo construyeron la API. Por ejemplo, para crear un envío se usa el endpoint /createShipment y para obtener las ciudades /getCiudades.
Me sorprendió que una empresa tan reconocida en su sector brinde un servicio de tal calidad.
Por eso voy a contarte cuáles son las mejores prácticas que debes tomar en cuenta cuando vas a diseñar una API.
Un repaso rápido: REST son las siglas de Representational State Transfer (Transferencia de estados de representación).
Imagina que cada vez que pides un trozo de pizza, alguien te diera la tarta entera. Innecesario, ¿no?
REST te ayuda a pedir y obtener exactamente lo que necesitas mediante sencillos protocolos HTTP, como si pidieras un trozo en lugar de la pizza entera.
1. Nomenclatura y organización de los recursos
Como te decía más arriba, una API debe mantener la consistencia en cuanto a cómo se nombran los endpoints.
Te puse el ejemplo de:
- /createShipment
- /getCiudades.
Hay varios puntos que voy a destacar aquí:
Primero, NUNCA uses el español para colocarle nombre a las cosas. No sabes quien va a utilizar la API el día de mañana, lo correcto usar inglés para todo y así hacerlo accesible para el mundo.
Luego tenemos el tema de las convenciones. Lo correcto en este caso sería:
- /createShipment => /shipments
- /getCiudades => /cities
Y no hace falta colocar ningún tipo de verbo que indique la tarea que cumple ese endpoint, para eso existen los verbos de HTTP:
- GET: Obtener un recurso
- POST: Crear un recurso
- PATCH: Actualizar PARCIALMENTE un recurso
- PUT: Actualizar POR COMPLETO un recurso
- DELETE: Eliminar un recurso
Entonces si yo estoy leyendo la documentación ya yo entiendo que:
- POST /shipments: Se encarga de crear un envío.
- GET /cities: Puedo saber cuáles son las ciudades.
2. Filtros, Ordenamiento y Paginación
A nadie le gustaría buscar una aguja en un pajar.
El filtrado le permite a los desarrolladores limitar las respuestas de la API a lo que realmente les interesa.
Volviendo al ejemplo de las ciudades, si yo simplemente quiero obtener la ciudad que tenga el nombre de "Caracas", ¿por qué tendría que hacer una lógica extra en mi código que se encargue de hacer esa búsqueda?
Es mejor hacer que nuestra API soporte esta clase de filtrado de la siguiente manera:
https://example.com/api/v1/cities?filter=name:Caracas
En este ejemplo estamos utilizando la URL: "https://example.com/api/v1/cities"
Y además le agregué el parámetro que se llama "filter" para poder enviarle las opciones con el formato de llave:valor y así filtrar desde el backend los recursos que realmente me interesan.
También podemos agregar ordenamiento utilizando algo similar:
https://example.com/api/v1/cities?sort=createdAt_desc
Con esto vamos a poder ordenar los elementos utilizando el campo "createdAt" de forma descendiente.
Y por último, agreguemos la paginación:
https://example.com/api/v1/cities?page=2&pageSize=10
Con estos dos parámetros vamos a soportar paginado en nuestra API.
- "page" nos indica cuál es la página que estamos obteniendo (en este caso sería la página 2).
- "pageSize": Indica la cantidad de elementos que vamos a obtener en ese llamado (en este caso serían 10).
Si agregamos soporte en nuestra API para estas 3 opciones (filtrado, ordenamiento y paginado) vamos a tener un sistema MUUUY flexible donde el desarrollador va a poder ajustarlo a sus necesidades sin mucho esfuerzo.
Así sería el resultado final utilizando todo al mismo tiempo:
https://example.com/api/v1/cities?filter=name:Caracas&sort=createdAt_desc&page=2&pageSize=10
Con estas opciones vamos a obtener todas las ciudades que tengan el nombre de Caracas, vamos a ordenarlas de forma descendiente utilizando la fecha de creación y además vamos a obtener 10 resultados de la página número 2.
3. Gestión de versiones
Si te fijaste, en el ejemplo anterior estaba utilizando la URL:
https://example.com/api/v1/cities
Aquí estoy manejando la versión de la API con el parámetro "v1".
Contexto: Imagina que tu juego favorito recibe una actualización y ahora hay un mapa nuevo, pero:
¿Y si cuando actualices el juego en tu dispositivo ya no vas a poder jugar con tus amigos que aún no han actualizado el suyo?
Esto es un problema.
El versionado de la API resuelve dilemas similares porque le permite a los desarrolladores introducir cambios sin romper las integraciones existentes.
Puedes incluir la versión en la ruta de la URL (/api/v1/cities) o como en una cabecera de la solicitud (headers): Accept-version: v1.
Básicamente el versionado garantiza que todo el mundo sepa a qué versión del "juego" está jugando.
4. Idempotencia: Asegura una comunicación confiable
La idempotencia es como enviar una carta con "acuse de recibido". No importa si el cartero llama una, dos o ninguna vez, sólo vas a recibir una carta.
En el caso de una API es lo mismo, puedes llamarla mil veces sin que el resultado cambie más allá de la solicitud inicial.
Por ejemplo, las solicitudes GET son naturalmente idempotentes; puedes solicitar cien veces el mismo dato y es no es común que vaya a cambiar arbitrariamente en un corto tiempo.
Para las solicitudes POST, garantizar la idempotencia puede implicar otras técnicas como el uso de identificadores de transacción únicos (Ya hablamos sobre eso en otro post).
5. Límite de llamadas
Imagina que miles de personas llaman a tu API sin ningún tipo de control, un caos ¿verdad?
Agregar un límite de llamadas a tu servicio garantiza que la API pueda gestionar una multitud recortando el número de solicitudes que un usuario puede realizar en un determinado periodo de tiempo.
Es como tener un portero en la puerta de tu API, asegurándose de que todo el mundo tiene un turno justo y el servicio siga estando disponibles para los demás.
6. Monitoreo
Con esto me refiero a básicamente instalar cámaras de seguridad y llevar un registro de visitas en la puerta de entrada de tu API.
No se trata de ser entrometido, sino de saber quién ha venido, qué ha hecho y cómo ha ido todo mientras estaba allí.
Esta información es crucial para detectar problemas antes de que se conviertan en tales, comprender cómo se utiliza la API y realizar mejoras en base a esos datos.
Te recomiendo utilizar herramientas como Prometheus o Sentry para la supervisión y el seguimiento de errores. Y ELK (Elasticsearch, Logstash, Kibana) para el registro.
Estas herramientas pueden ayudarte a visualizar los patrones de uso de la API, realizar un seguimiento de las métricas de rendimiento y examinar los registros para encontrar información sobre errores o anomalías.
Para finalizar
El diseño y la implementación de una API pueden marcar la diferencia al momento en el que los desarrolladores comienzan a interactuar con tu API.
Implementar una API siguiendo estos principios básicos asegura que tu servicio sea robusto, confiable y fácil de usar.
Recuerda que el objetivo final es proporcionar una herramienta que potencie las capacidades de quien la utilice, eliminando fricciones y optimizando procesos.
Nos vemos en la edición #29
¿Te gustaría saber más sobre cómo estas prácticas pueden aplicarse en tu proyecto o necesitas asistencia personalizada en el diseño de tu API? No dudes en contactarme.
Recuerda que si quieres hablar de algo en particular puedes sugerir el tema respondiendo este correo.
Hasta pronto 👊🏼
#28 Domina el diseño de APIs: Claves para una Integración Efectiva
Descubre las mejores prácticas en diseño de APIs para optimizar la integración y la experiencia de usuario. Aprende sobre nomenclatura, filtros, versionado y más para llevar tu proyecto al siguiente nivel