Consejos para diseñar una API REST

Chema Diez del Corral
Secuoyas Experience
10 min readAug 14, 2020

Lo más importante que aprendí sobre como diseñar una API es que no hay una única forma correcta de hacerlo. En esta guia comparto mis conclusiones personales sobre como hacerlo.

Ilustrar.io — made by @Secuoyas

Introducción

Mi único propósito es escribir el post que me hubiera gustado leer cuando empecé a diseñar la API para el proyecto de Secuoyas de Covid-19. Puede que suene prepotente u osado pero esta misiva no es más que un mensaje a mi yo del pasado, solo un poco más ignorante que el yo presente, y lo hago de manera pública porque espero que de alguna manera pueda serte útil.

En los próximos diez puntos se cubren los aspectos más importantes a tener en cuenta en el del diseño de la API, para elaborarlos, me he basado en otros muchos artículos de buenas practicas y en la documentación de importantes servicios API bien diseñados.

1 API First

Esta tenía que ser la primera, no sólo por su propio nombre, sino tambien porque a pesar de ser lógica, es la regla más incumplida y la que más consecuencuas negativas puede traer.

Definir la API antes de empezar a montar toda la estructura es primordial. Es la única forma de ser consistentes sin renunciar a nada y es necesario para estimar el coste de construir el producto que estamos diseñando.

Después de haberos dicho esto, os confesaré que en nuestro proyecto incumplimos esta norma, pues dadas las circunstancias, íbamos a contra reloj: nuestro objetivo era visualizar lo que estaba pasando con la crisis provocada por COVID-19, y la emergencia nos obligó a construir el avión mientras volábamos.

En muchos proyectos se hace imposible diseñar la API antes de comenzar con el desarrollo: tiempos de entrega irreales, proyectos que se construyen sobre proyectos ya existentes o simplemente una mala planificación, suelen ser prácticas bastante habituales.

2 Usa nombres en las rutas y no verbos

Utilizar sustantivos en lugar de verbos en los endpoints, quizás sea la norma más consensuada.

Como concepto, la ruta del endpoint es solo el camino al recurso, similar a como funciona cualquier sistema de ficheros. Esa ruta no está asociada a ninguna acción, es decir, a ningún verbo.

Así que, en lugar de:

/listarProductos/

Usaremos solo nombres autoexplicativos del dato que vamos a devolver:

/productos/

3 Siempre las rutas en plural

Deben estar en plural, pues los endpoints hacen referencia a colecciones de datos, no a un dato en particular.

Este fallo es habitual cuando se crean dos rutas distintas, una en plural para devolver un listado de recursos y otra en singular para devolver solo un recurso concreto. Esto no debe hacerse, porque en realidad se está pidiendo el mismo tipo de recurso. La forma correcta de hacerlo sería usando una subruta o enviar un parámetro que identifique el recurso que queráis identificar.

En lugar de tener dos rutas, una en plural y otra en singular:

# Devuelve un listado de productos
/productos/
# Devuelve el producto con id p123
/producto/p123

En su lugar se puede partir del mismo endpoint y añadir los parámetros en una subruta o parámetros:

# Devuelve un listado de productos
/productos/
# Devuelve el producto con id p123 usando subruta
/productos/p123/
# Devuelve el producto con id p123 usando parámetros
/productos/?id=p123

4 Usa los métodos http correctamente

Los métodos http equivalen al verbo de una sentencia, son los que definen qué acción vamos a realizar sobre los datos.

Los principales métodos:

  • GET Pedimos un dato o colección de datos.
  • POST Enviamos un dato o colección para ser añadidos.
  • PUT / PATCH Modifica un dato o colección existente. La principal diferencia entre ambos es que PUT sobrescribe todo el dato, como si fuera nuevo dato que usa el mismo id, mientras que PATCH solo reescribe parcial o completamente un dato que ya existe con los datos enviados.
  • DELETE Eliminar datos existentes en la API.

Siempre tenemos que usar el método apropiado para cada caso, pues no siempre se usan adecuadamente. Hay servicios que erróneamente te solicitan una petición POST con los datos de autenticación y sesión en el cuerpo de la llamada, y éste es un gran fallo, pues todas las peticiones de consulta deben hacerse con una petición GET y si se necesitan datos de autenticación o sesión, se deben incluir en la cabecera, que está diseñada para ese propósito. POST solo se debe emplear para añadir datos a la API

Sobre el mismo endpoint se pueden ejecutar distintos métodos, y normalmente no todos los endpoints soportan todos los métodos, por lo que es muy importante documentar bien los métodos de cada endpoint y en caso de poder usar métodos que requieren de un body request como POST, PUT y PATCH documentar también como debe ser el payload.

5 Pon limites a la API

Hay dos principales motivos para limitar el acceso a la API: Seguridad y consumo de recursos.

Seguridad

No todo el mundo puede acceder a todos los endpoints ni escribir o sobrescribir datos. Debemos definir qué roles tienen acceso a qué endpoints, y qué métodos http pueden usar en cada endpoint.

Este proceso puede ser muy complejo y debemos simplificar, el uso de roles es un buen acercamiento, paquetizando los permisos en un número determinado de roles. Si el número de casuísticas que detectamos es muy elevado, quizás debamos intentar clusterizar los endpoints y asignar permisos de escritura o lectura por cluster. Por ejemplo, un usuario que pueda editar los endpoints relacionados con productos y tiendas, pero solo puede leer la colección de empleados.

Consumo de recursos

Nunca sabemos las aplicaciones que un cliente puede construir sobre nuestra API y puede ser que una aplicación que consume nuestra API tenga millones de usuarios sin que nosotros lo tuviéramos contemplado, esto puede acarrear problemas para dar nuestro servicio correctamente y grandes gastos en servidores.

Debemos establecer unos límites de consumo de la API, generalmente un número de peticiones determinado en un espacio de tiempo concreto. En muchos casos, el acceso a la API no es gratuito y existen diferentes planes de precios en función del número de peticiones que se realizan.

6 JSON es tu único amigo

JSON es el formato idóneo tanto para el payload como para el output, y en todos los endpoints. Nunca se debe responder en texto plano, siempre se puede convertir de manera sencilla en un JSON.

En el caso de que necesitemos que la API nos devuelva la salida en otro formato, siempre será algo añadido que no sustituirá a la respuesta por defecto en JSON.

En nuestro caso, necesitábamos usar CSV tanto para alimentar los gráficos como para facilitar la accesibilidad de los datos a un perfil menos técnico, que estaba más familiarizado con hojas de cálculo. La solución fue añadir un parámetro para poder pedir los datos en CSV, aunque por defecto la respuesta siempre era JSON.

7 Se consistente

La consistencia simplifica mucho el uso, facilita el escalado de la API y agiliza el consumo. Es un fallo habitual en servicios API que se han ido construyendo a lo largo del tiempo o que unen diferentes servicios en uno. Es un fallo común en las de la administración pública, o empresas con sistemas muy antiguos y dificiles de actualizar.

Hay muchos lugares en los que puede fallar la consistencia:

  • Mismo formato de fichero: Que todos los endpoints devuelvan siempre el mismo formato de archivo y debería ser JSON. Hay casos que dependiendo del endpoint nos devuelve un CSV o XLS, TXT o cualquier otro formato.
  • Misma estructura: Todos los endpoints deben responder a la misma estructura base. Hay muchas formas de devolver los datos, y hay una parte de cómo se ordenan que responde a una decisión de diseño. Esos criterios deben aplicarse a todos los endpoints.
  • Misma terminología: Si en un endpoint llamamos “productos” a lo que vendemos en nuestra tienda en otro endpoint no se pueden llamar “ítems” o “artículos”, siempre que nos refiramos al mismo término debemos hacerlo con la misma nomenclatura. De hecho en algunos casos es necesario un glosario para entender ciertas palabras
  • Mismos metadatos: Todos los endpoints deben responder a la misma estructura base. Hay muchas formas de devolver los datos, y hay una parte de cómo se ordenan que responde a una decisión de diseño. Esos criterios deben aplicarse a todos los endpoints.
  • Mismo name case: Es decir, usar de manera consistente el estilo de escritura. Si usamos JSON, como ya hemos dicho que debería ser, escribiremos los nombres en camelCase; y si por alguna razón usamos otro name casing, deberemos hacerlo siempre.
  • Mismas unidades: Siempre utilizar la misma unidad de medida, y esto no puede mantenerse, como por ejemplo, una API del tiempo, en la que en cada país necesite usar unas unidades distintas para la temperatura o velocidad del viento, podemos: o bien devolver siempre en todos los archivos todos los valores para las distintas unidades, o si preferimos devolver diferentes ficheros para cada unidad, debemos tener siempre un campo de metadatos que nos clarifique la unidad que se usa.

En resumidas cuentas, intentar al máximo que el cotenedor cambie lo mínimo posible y que solo cambien los datos que están contenidos

8 Versiona y vencerás

Con el paso del tiempo llegan nuevas funcionalidades, y éstas afectan al servicio en general, y nos encontramos con que la estructura definida no se puede adaptar lo suficiente a las nuevas necesidades. En ese momento, necesitamos hacer una nueva versión de la API con un nuevo diseño capaz de albergar todos los nuevos requerimientos.

En la ruta de la API es necesario especificar la versión para hacer el proceso transparente y permitir que convivan las dos versiones sin conflictos. Para ello, se suele nombrar usando /v1/ o similares, dependiendo del número de la versión al principio del endpoint.

https://rutademiservicio.com/v1/endpoint

Hacer una nueva versión es algo arduo, que cuesta mucho trabajo y supone dar soporte a múltiples versiones durante un tiempo razonable, para que se adapten nuestros clientes. Así que, es una acción que solo realizaremos cuando sea realmente necesario y estemos en un momento en el que podamos asumir ese trabajo.

9 La magia está en los parámetros

La función de una API es devolvernos la información solicitada. Dicha solicitud puede ser más o menos detallada y está formada por tres partes: la ruta, los parámetros y la cabecera de la llamada. Normalmente no nos va a servir la respuesta por defecto del endpoint, y necesitaremos hacer uso de parámetros para acceder a una información más concreta. Cuanto mayor sea la cantidad de datos que ofrece la API, la capacidad de filtrado y de selección, mayor será la importancia de los parámetros.

Los parámetros se pueden especificar de distintas maneras:

En la cabecera de la petición

Debemos usarlo solo para parámetros operativos, como para tokens de autenticación, datos sobre la sesión y el estado de la aplicación. Son datos que no varían en la misma sesión, independientemente del recurso al que llame.

Path variable, parámetros como parte de la ruta

Aparentemente es igual que los endpoints, y generalmente se usan para acceder a recursos concretos, es decir, llamar a un dato determinado dentro de la colección de datos.

Por ejemplo, para ver el producto de mi catálogo con id 1234, usaría:

/productos/1234

Query String, variables como cadena de texto después de la ruta

Su uso más extendido es para filtrar, ordenar o limitar el número de objetos a devolver, aunque también se emplea para pedir que se devuelvan los datos de otra manera, como puede ser en otro idioma o formato.

Si necesitamos que nos devuelva un listado de máximo 30 productos ordenados por precio ascendente, sería:

/productos/?value=price&sort=asc&limit=30

No mandéis parámetros en el body de una petición

El body de la petición debe limitarse a su cometido, enviar los datos que queremos escribir o reescribir en la API.

Es un error típico hacer un Post en lugar de un GET para poder mandar los datos de autenticación o de la sesión en el body request de la petición POST. Para pedir datos siempre usar el GET.

10 Estructura de las rutas

Hemos llegado al quid de la cuestión, existen diferentes formas correctas de diseñar las rutas, y su uso dependerá en gran parte de la morfología de nuestros datos.

Si queremos acceder a un producto concreto podemos usar:

/productos/p123

Ahora bien, podemos entender que elegir un elemento concreto es en realidad un filtrado, por lo que podemos aplicar un query strings que quedaría de la siguiente manera:

/productos/?id=p123

Esta diferenciación es importante porque definirá bastante la jerarquía de nuestra API. Por ejemplo, en el caso de tener una colección accesorios que depende de productos, los parámetros en la url quedarían de esta manera:

/productos/p123/accesorios/

Mientras que usando los query params quedarían así:

/accesorios/?producto=p123

Mientras que de una forma la url es más legible, de la segunda manera podemos tener todos los endpoints aislados. Según la magnitud y morfología de los datos, una o otra manera pueden tener ventajas.

Hay muchas otras variables a tener en cuenta en el diseño de las rutas, si has leído hasta aquí ya te habrás encontrado con muchas: Ser consistentes, usar nombres en lugar de verbos y siempre en plural, definir claramente qué métodos se pueden emplear en cada endpoint y quién tiene permiso para emplearlos, y otras muchas particularidades de cada API que hacen que este proceso no sea una ciencia exacta.

Conclusión

El diseño y construcción de la API es un trabajo complejo, y las decisiones que se toman tiene un alto impacto en todos los aspectos del producto y agentes que participan en su construcción.

Hasta ahora era una tarea que caía primordialmente en desarrolladores backend, y secundariamente en frontend, por la barrera técnica que suponía. Pero en la actualidad, las especificaciones como openAPI o RAML, y herramientas como Swagger, permiten que se pueda abordar el diseño de la API desde el primer día antes de programarla y sin tocar el código.

Se necesita trabajar unidos los equipos de backend, frontend, diseño, usabilidad e incluso negocio, ya que sus áreas se ven afectadas por las decisiones que se tomen en el diseño de la API, y no tener en cuenta todas estas necesidades puede complicar el desarrollo del producto e incluso obligar a volver atrás sobre nuestros pasos.

Tener una API bien construida y estructurada, que responda a las necesidades del producto, es el core para hacer que un producto digital pueda escalar correctamente, incluir nuevas funcionalidades y soportar bien el paso del tiempo.

--

--