Buenas prácticas de diseño de APIs RESTful: Nombrado de recursos

Introducción

En el artículo anterior vimos de manera introductoria lo que es REST y algunos conceptos base relacionados. El objetivo de este artículo es presentar una serie de recomendaciones y buenas practicas, algunas de ellas obtenidas de libros y otras en base a algunas
de las situaciones a las que me he enfrentado definiendo los nombres de los recursos en APIs REST de manera profesional.

Para algunos el nombrado del recurso es una tarea trivial, para mí requiere principalmente del entendimiento correcto del negocio que se pretende modelar y en algunos casos se requiere de experiencia, así que con la práctica aprenderemos a definir los nombres correctos.

Recapitulando un poco del artículo anterior, en la arquitectura REST el recurso lo es casi todo pues la base de como el cliente interactúa con el servidor es a través de recursos REST pero entonces ¿qué es un recurso?.

Un recurso en el contexto de REST es una representación de una entidad que existe en el servidor, el recurso es una estructura de datos que se identifica de manera única mediante su Uniform Resource Identifier (URI). Es algo así como un registro de base de datos el cual identificamos mediante una llave primaria única.

Pero mejor exploremos este concepto con un ejemplo:

http://my-domain.com/my-films-api/v0/films

En el ejemplo de arriba definimos un recurso el cual llamamos ‘films’ el cual representa una colección de películas. Como se puede ver el recurso está representado mediante una URI. Ahora bien aquí entra otro concepto clave, JSON. JSON  (JavaScript Object Notation) es la notación comúnmente usada por su sencillez para representar el estado actual de un recurso, para nuestro ejemplo el servidor podría regresar la representación de una película dada mediante está notación de la siguiente manera:

{
   "data": [
     {
       "filmId": "67490436a8be5153fe7d8bc54344fa1e",
       "name": "The Lord of the Rings: The Return of King",
       "duration": "3h 21m",
       "principalCast": [
         {
           "name": "Elijah",
           "lastName": "Wood",
           "actorId": "60490436a8be5153fe7d8bc54344fa78"
         }
       ]
     }
   ]
 }

Ahora que ya hablamos acerca de que es un recurso a continuación comentaremos de como nombrar el recurso de manera adecuada. Existen algunas recomendaciones base para el nombrado de recursos, en la práctica algunas veces no resulta tan sencillo sobre todo en aquellas situaciones donde cuesta trabajo encontrar el sustantivo adecuado que represente a la funcionalidad a modelar. Hay casos que son fáciles de identificar pero en otros casos no es tan obvio.

Nombrado en sustantivo y en plural

Como primera recomendación es que el recurso REST se define siempre en plural y el nombre que decidamos poner al recurso debería representar un sustantivo dentro del contexto funcional del API, por ejemplo para el caso de un API que gestiona información de películas podríamos tener un recurso llamado ‘films’ y para la gestión de actores uno que se llame ‘actors’, siempre definiendo sustantivos.

Ojo que no siempre la definición de un recurso hace necesariamente match con la definición de las tablas de base de datos del backend, podríamos caer en la trampa de definir los recursos a como se llaman las tablas en la base de datos. Por ejemplo podemos tener un recurso que se llame ‘directors’ en el API pero esto no significa que tengamos una tabla que se llame así en la base de datos de películas.

Un ejercicio recomendado para el nombrado es encontrar los sustantivos adecuados según el negocio que se esté modelando, por ejemplo para un API que maneje reservaciones de vuelos no sería raro encontrar los recursos ‘air-lines’, ‘reservations’, etc. Para un sistema de carrito de compras seguramente encontraremos los recursos ‘carts’, ‘orders’ y ‘items’.

Saliéndonos un poco del ejemplo de películas ¿Cómo nombraríamos a un recurso que gestiona correos electrónicos? por ejemplo si quisiéramos modelar una operación para mandar un correo nuevo a un cliente. Un primer approach sería definirlo como:

POST http://my-domain.com/my-api/v0/emails

Como primer approach está bien llamarlo así pero igual nos valdría más definirlo como un recurso más genérico para cubrir cualquier tipo de notificación y no solo de correo electrónico sino también de SMS o algún otro, en ese caso la definición la cambiaríamos por algo más general:

POST http://my-domain.com/my-api/v0/notifications

Los métodos HTTP

Los métodos HTTP nos dicen el significado de la acción a realizar hacia el recurso REST a continuación una lista para darnos una idea del uso más común de cada método.

  • GET es comúnmente usado para hacer consultas de un recurso ya sea para obtener una lista de recursos de un mismo tipo o para obtener el detalle de un recurso mediante su identificador único.
  • PUT es comúnmente usado para crear o actualizar un recurso.
  • PATCH es comúnmente usado para actualizar un recurso de manera parcial.
  • HEAD es comúnmente para verificar si un recurso existe o no en el servidor.
  • DELETE es comúnmente usado para eliminar un recurso de manera física o incluso lógica.
  • POST es comúnmente usado para crear un recurso nuevo dónde el ID del recurso es generado de manera automática por el servidor. También se usa el método POST para ejecutar una acción usando el patrón ‘Controller’ (el cual describiremos a continuación).

Categorías de recursos

Algunos autores de libros sobre REST han identificado patrones o clasificaciones de recursos, por ejemplo en el libro RESTful API Design: Best Practices in API Design with REST se destacan los siguientes.

Son de tres categorías:

  • Collection
  • Instance/Document
  • Controller

* Existe una cuarta categoría llamada Store el cual no abordaremos en este momento.

Veamos a continuación el detalle de cada uno de ellos.

Collection

Es un directorio de recursos gestionado por el backend. Los clientes generalmente consultan el directorio y solicitan al backend que se agregue un nuevo recurso al directorio.

Métodos HTTP dónde se usa el patrón: GET y POST.

El endpoint que devuelve una lista con todas las películas de un cliente se considera como un recurso de tipo de Collection.

Ejemplo de URI para obtener una lista de películas:

GET http://my-domain.com/my-films-api/v0/films

Obtener los directores asociados a una película:

GET http://my-domain.com/my-films-api/v0/films/123434/directors

Crear una nueva película:

POST http://my-domain.com/my-films-api/v0/films

Instance/Document

Representa una instancia de un recurso de negocio, una instancia es identificada por un ID único. Por ejemplo:

Obtener una película en base a su ID:

GET http://my-domain.com/my-films-api/v0/films/{film-id}

Actualizar todos los atributos de una película:

PUT http://my-domain.com/my-films-api/v0/films/{film-id}

Actualizar uno o algunos de los atributos de una película:

PATCH http://my-domain.com/my-films-api/v0/films/{film-id} 

Eliminar una película:

DELETE http://my-domain.com/my-films-api/v0/films/{film-id}

Controller

Se utiliza para representar acciones que no necesariamente en todos los casos modifican el estado de un recurso en el servidor.

En RESTful modelar la URI como recurso a modo de sustantivo es la manera adecuada para representar las funcionalidades del API y las acciones se identifican por el método de HTTP (GET, PUT, PATCH, HEAD, POST, DELETE) pero algunas veces es complicado modelar una funcionalidad de esta manera algunos ejemplos de su posible uso:

  • Si queremos modelar la ejecución de un proceso en batch
  • Si queremos realizar alguna validación del lado del servidor por ejemplo validar si un correo electrónico de un cliente está vigente o no.
  • Si queremos resolver alguna tarea que resulte difícil modelar mediante una entidad de negocio, comúnmente aplicado para resolver tareas técnicas por ejemplo migrar registros de una base de datos a otra.

En este caso la funcionalidad se podría modelar como un patrón ‘Controller’ además para este caso se recomienda usar siempre el método POST y al nombrar al recurso siempre se hará como acción o verbo.

Algunos ejemplos:

Simular el otorgamiento de un crédito bancario. Lo haremos mediante la definición de un recurso que nos represente préstamos el cual llamaremos ‘loans’ y del cual cuelga una acción llamada (como verbo) ‘simulate’.

POST http://my-domain.com/my-api/v0/loans/simulate

Otro ejemplo: ejecutar un proceso batch que elimina clientes duplicados.

POST http://my-domain.com/my-api/v0/customers/remove-duplicates

Como nota final de este patrón he de decir que hay que ocuparlo con reserva y solo en caso de que la funcionalidad deseada no pueda ser modelada con alguno de los otros patrones.

Granularidad de un servicio

La granularidad de un servicio REST se puede medir en varias dimensiones, una de esas dimensiones es la responsabilidad funcional que tiene el servicio y esa granularidad puede influenciar al nombre del recurso.

La responsabilidad funcional que tiene cada operación del servicio puede ser de granularidad gruesa o delgada y lo voy a poner más claro con un ejemplo.

Si tenemos una operación que da de alta películas se podría modelar de diversas maneras (dependiendo de la responsabilidad funcional). Si quiero cubrir todo tipo de películas (grano grueso) entonces lo que más conviene es modelar la operación así:

POST http://my-domain.com/my-films-api/v0/films

Pero podríamos modelar esa misma operación de manera más específica dividiéndola en endpoints más específicos (granularidad delgada), bien podría definir un endpoint particular según el tipo de película como a continuación se ilustra:

POST http://my-domain.com/my-films-api/v0/ci-fi-films
POST http://my-domain.com/my-films-api/v0/action-films
POST http://my-domain.com/my-films-api/v0/adventure-films

¿Qué nos conviene más entonces? ¿Tener un solo recurso que de alta todo tipo de películas o tener diversificados los recursos por tipo de película?.

Para esto se tendrá que hacer una evaluación entre granularidad fina y gruesa y asociarlo a nuestra necesidad, en un siguiente artículo hablaré de la granularidad de un servicio REST así como pros y contras de granularidad fina vs granularidad gruesa para así poder llegar a entender cuál de los dos modelos se adaptan mejor a nuestra necesidad particular.

Identificar el recurso dependiendo del objetivo del servicio

Otra forma para darle un buen nombre a un recurso es hacernos varias preguntas acerca de la funcionalidad que estamos modelando, preguntas como:

  • ¿Quién usará el servicio?
  • ¿Cuál es el objetivo del servicio?
  • ¿Qué datos de entrada se esperan recibir y que datos de salida se espera regresar?
  • ¿Internamente el servicio qué hace? ¿guarda, consulta, actualiza, calcula?

Estas preguntas nos van a permitir identificar acciones y recursos dentro de nuestro contexto de negocio, algunos ejemplos:

  • Buscar productos en el catálogo.
  • Actualizar una tarjeta de crédito en la lista negra.
  • Actualizar el precio de un producto.
  • Procesar una solicitud de crédito.

Para nuestra API de películas ¿Cómo podríamos modelar el recurso que nos permita asociar una imagen a una película?.

Para empezar la acción a realizar es un alta por lo que lo mas adecuado sería utilizar un método POST (aunque aquí ojo el método PUT también puede ser usado para crear un recurso nuevo solo que tiene diferencias sutiles con POST).

En la frase “Asociar una imagen a una película” encontramos dos posibles sustantivos ‘films’ e ‘images’. Por lo que un posible diseño de recurso para este caso podría ser:

POST http://my-domain.com/my-films-api/v0/films/t5t5ggtt5/images

En nuestro ejemplo tenemos un recurso llamado ‘films’ el cual tiene un identificador único ‘t5t5ggtt5’ el cual tiene un subrecurso asociado llamado ‘images’. Finalmente la acción la representamos con el método http POST que en este caso nos representaría en una creación de un recurso ‘images’ nuevo.

Ahora que ya sabemos como nombrar un recurso en base a su contexto funcional hagamos un ejercicio más:

Diseñar el recurso de la operación que crea una solicitud de crédito para auto. ¿Cuál de las siguientes opciones es la más correcta para tal fin?

  1. POST http://my-domain.com/my-api/v0/CarCreditLine/create
  2. GET http://my-domain.com/my-api/v0/car/credit
  3. POST http://my-domain.com/my-api/v0/car-credit-applications
  4. POST http://my-domain.com/my-api/v0/create-car-credit

Para este ejemplo la opción más a adecuada según las buenas prácticas y lo más apegado a RESTful es usar la opción 3.

Recursos y Anti-patrones

A continuación mostraré una lista de ejemplos que nos describen algunas malos ejemplos en el nombrado de recursos:

GET http://api.example.com/services?op=update_customer&id=12345&format=json

En el ejemplo anterior el recurso REST se nombra como ‘services’ un nombre demasiado genérico y la acción a realizar se envía como query param ‘op=update_customer’. Las acciones a realizar en REST se representan por los métodos de http GET, PUT, POST, etc. y no deben representarse mediante el uso de query params.

Veamos otro ejemplo:

GET http://api.example.com/update_customer/12345

En este caso si la acción a realizar es actualizar un cliente lo mas adecuado sería usar un método de http PUT o PATCH y no GET por lo que resultaría más adecuado definir el endpoint como:

PUT http://api.example.com/customers/12345

Otro ejemplo más:

GET http://api.example.com/customers/12345/update

En el ejemplo anterior estamos definiendo una acción la cual llamamos ‘update’ pero la estamos invocando con un método GET lo cual es incorrecto.

Y un ejemplo más:

GET http://www.example.com/customer/33245

En este último ejemplo el recurso se llama ‘customer’ en singular, cuando la recomendación es que el nombrado se haga en plural como ‘customers’.

Recomendaciones finales

  • No se recomienda usar caracteres mayúsculas y minúsculas juntas en el nombre de recursos por ejemplo del estilo camelCase : ‘CarInvoice’, ‘RootClient’.
  • Para separar palabras se recomienda el uso de guiones medios en lugar de guiones bajos. Ejemplo: ‘car-invoice’ o ‘root-client’.
  • El nombre del recurso debe ser independiente del formato, no debe contener extensiones de archivos por ejemplo: ‘car-invoice.pdf’
  • No usar espacios en blanco para el nombre. Ejemplo: ‘car invoice’
  • Los nombres de recursos no deben implicar una acción, por lo tanto debe evitarse usar verbos en ellos a menos que estemos usando el patrón del tipo ‘Controller’.

Para finalizar recomiendo un libro que a mi me sirvió para profundizar acerca del diseño de APIs Web con REST y donde se explican a mas detalle algunas de las recomendaciones aquí expuestas:

También comparto la referencia al API interactiva de MARVEL la cual tiene buenas prácticas en relación a los nombres de los recursos. Los invito a explorarla para que se den una idea de cómo definieron sus recursos relacionados al universo de los comics:

En mi siguiente artículo hablaré de la granularidad de un servicio, ¿Qué es? y cómo podemos usarla para aplicarlas a nuestras APIs REST.

Hasta la próxima