Introducción al diseño de una API HTTP (Parte 4 de 4): Estándares

Este cuarto y último artículo hace un breve resumen sobre los distintos estándares aplicados en el diseño de una API HTTP para el intercambio de información.

Simple Response

Uno de los objetivos de nuestra API es responder con información que entienda el consumidor. Muchas veces, solo nos bastará con responder con solo el recurso solicitado o una matriz de recursos solicitados. Sin embargo, también hay metadatos que queremos proporcionar a nuestros consumidores. Una alternativa es  usar los HTTP Headers, los cuales son usados para proporcionar metadatos, pero por lo general no es lo suficientemente potente como para transmitir todo lo que el consumidor necesita saber del recurso.

Unos de los casos que se dan muy a menudo, son los mensajes de error que debemos proporcionar al consumidor de nuestra API. Si bien, podemos suministrar un código de estado 4XX o 5XX cuando falla una solicitud, pero ¿Cómo podemos ser más específicos? ¿Cómo hacemos para que nuestro cliente sepa si una solicitud es un error y cómo obtener mayor información del error?.

Ante este escenario, podemos responder con un objeto principal a los datos reales (en del HTTP Response). Nos referimos a responder con un objeto JSON estandarizado como un “sobre” (respuesta simple), ya que envuelve los datos importantes. Por ejemplo:

En el ejemplo, se observa dos propiedades de error. El primero, “error“, es un código de error analizable por una aplicación (consumidor). Hay APIs que optan por usar valores numéricos en vez de un valor de cadena, pero ¿por qué usar un formato numérico ilegible cuando lo tratan como una cadena? También queremos una cadena separada legible por humanos, como por ejemplo la propiedad “error_description“. Esta cadena podría traducirse teóricamente para coincidir con el Accept-Language del Request y mostrarle a un usuario final.

Si la respuesta (HTTP Response) fuera exitosa, podemos agregar propiedades adicionales como estas:

En este caso, todavía tenemos la propiedad de “error” y “error_description“, pero dado que no hay un error, lo configuramos en nulo (null). La propiedad “data” contiene el contenido solicitado por el consumidor, en este caso, el consumidor solicita una recopilación de datos (por ejemplo: estudiantes), por lo que proporcionamos una serie de recursos en esa colección (id y name). Finalmente tenemos dos propiedades de metadatos adicionales, “offset” y “per_page“, que le informa al consumidor sobre la respuesta. En este caso, el cliente ha solicitado la segunda página de resultados con 10 entradas por página, por lo que básicamente respondemos con esos datos para el contexto.

JSON API

API JSON es un estándar para el intercambio de información entre aplicaciones, que recomienda buenas prácticas para eliminar redundancias de datos, a la hora de devolver información a nuestros consumidores. Por citar un ejemplo, tenemos una API que representa a una colección de libros de una reconocida cadena de librerías. Cada libro tendrá un contenido único, es decir, tendrá un título, un código (identificador) y un precio. Sin embargo, cada libro tendrá información potencialmente redundante, como la información del autor.

Por lo general, ante estas situaciones, una API devuelve la información del autor de forma redundante por cada libro. Es probable que existan muchos libros cuyo autor es el mismo, por lo tanto, estaríamos enviando un montón de contenido al consumidor. El estándar API JSON nos permite, en cambio, definir relaciones entre diferentes tipos de recursos, eliminando así las redundancias. Mira el siguiente ejemplo:

GraphQL

GraphQL es un estándar API desarrollado por Facebook. Incluye un formato personalizado para consultar datos. Normalmente, las respuestas se envían en formato JSON. El formato de consulta requiere que el consumidor especifique todos los atributos que desea en la respuesta. Esto nació de la necesidad de que las aplicaciones clientes móviles obtengan solo datos importantes, desperdiciando menos bytes en la transferencia de información.

Otra característica de GraphQL es que los atributos solicitados en la respuesta pueden correlacionarse con datos de diferentes colecciones. Esto hace que GraphQL sea particularmente atractivo cuando se construyen componentes estructurales (facades), servicios que consumen datos de otros servicios. GraphQL puede realizar las agregaciones necesarias en una única solicitud (HTTP Request), evitando que el cliente tenga que realizar múltiples solicitudes a diferentes colecciones.

Las solicitudes generalmente usan un endpoint HTTP único, con el cuerpo recibido a través de POST. GraphQL NO es una práctica RESTful de HTTP, y en realidad se puede usar completamente por separado de HTTP. Este es un ejemplo de una consulta GraphQL:

Este es un ejemplo de la respuesta correlacionada (HTTP Response):

MessagePack

MessagePack se puede considerar como una representación binaria 1:1 de JSON. Cualquier documento JSON se puede representar como MessagePack, lo que significa que se puede usar con API JSON o GraphQL. Se elimina cualquier espacio en blanco superfluo y también se eliminan algunas otras redundancias, como los caracteres de comillas y dos puntos. La representación binaria suele ser más pequeña y puede ser más rápida de serializar y deserializar.

Observa el siguiente documento. Este archivo tiene 109 bytes (sin contar espacios en blanco). Es un objeto con tres propiedades, la primera es un identificador, la segunda una cadena y la tercera una matriz de dos valores numéricos:

El siguiente es el mismo contenido del archivo anterior, pero en formato MessagePack reducido a 78 bytes (72% del anterior).

JSON RPC (Remote Procedure Call)

JSON RPC es un paradigma muy diferente del HTTP RESTful que hemos estado viendo en todas estas publicaciones. En lugar de abstraer los datos en recursos y realizar operaciones CRUD en ellos, simplemente puede exponer las funciones y sus parámetros y permitir que los clientes llamen a estas funciones semi-directamente. Este patrón se llama RPC. JSON RPC es entonces

De forma similar a GraphQL, si usa estas solicitudes a través de HTTP, probablemente use un endpoint único, acepte las solicitudes a través de una solicitud POST y responda. JSON RPC también puede funcionar completamente fuera de HTTP, por ejemplo con TCP o IPC (Inter-Process Communications).

Aquí hay un ejemplo de una solicitud JSON RPC. El documento es muy simple; la solicitud requiere un número de versión (propiedad “jsonrpc“), un identificador (para correlacionar los request con los response, ya que no estamos casados con HTTP). También nombramos el método RPC (propiedad “method“) que queremos ejecutar y proporcionamos argumentos en la propiedad “params”. Los parametros pueden ser una matriz o un objeto, correlacionando a parámetros de función normal o parámetros nombrados, respectivamente.

La respuesta (HTTP Response) también contiene una versión y un identificador correspondiente a la solicitud (HTTP Request). La propiedad importante es “result” que contiene el resultado de la operación.

Artículos relacionados:

 

Fuentes:

Introducción al diseño de una API HTTP (Parte 3 de 4) : HTTP Body

Este tercer artículo abarca una breve introducción sobre como estructuramos el contenido (cuerpo) de nuestras respuestas HTTP (HTTP Body).

¿Qué es JSON?

JSON (JavaScript Object Notation) es el formato de serialización preferido para el intercambio de información entre las API HTTP más populares de hoy. La especificación para elaborar documentos JSON es extremadamente simple, con unos ejemplos te convertirás en un experto. Esencialmente, un documento JSON contiene objetos de pares clave: valor, donde los valores pueden ser cadenas de texto, números, valores booleanos, nulos o matrices / objetos de más valores.

Esta facilidad de uso y flexibilidad contrasta con otros formatos de datos como XML (muy usados en SOAP), que a menudo requiere una compleja lógica de serialización y una sintaxis detallada para consultar datos. El siguiente es un documento JSON de ejemplo:

Nombre de Atributo

Cuando usas JSON o en realidad cualquier otro formato, una consideración importante es elegir la forma como vas a nombrar a los atributos (nombre de las claves). Puedes elegir entre estas tres opciones:

snake_case: Este formato usa más bytes pero es más fácil de leer.
PascalCase: Este formato no es tan común, obliga a tener más letras mayúsculas.
camelCase: Este formato es legible y usa menos bytes.

Ninguno de los formato es mejor o peor que los demás. Independientemente del formato que elija, es importante ser coherente a lo largo de sus solicitudes y respuestas. Nunca mezcle tipos de formato, ya que será confuso para los desarrolladores recordar qué formato usar en qué situación. Incluso si su servicio es una fachada para varios servicios diferentes y esos tipos de mezcla de servicios, su servicio deberá elegir un único formato y traducir las propiedades. Es común que los proyectos coincidan con el mismo caso de su API externa con variable interna o nombres de propiedad, sin embargo, no hay beneficio para el consumidor en hacerlo.

Valores booleanos

Siempre que utilice propiedades booleanos en su documento JSON, siempre use palabras “positivas”. El cambio entre contrapartes positivas o negativas es confuso y requerirá que el desarrollador revise con frecuencia la documentación. Aquí hay unos ejemplos:

Use enabled en vez de disabled
Use public en vez de private

Por ejemplo, puede indicar que la información que estas devolviendo de un usuario (en formato JSON), esta habilitado (enabled: TRUE) o no esta habilitado (enabled: FALSE).

Cuando se nombran propiedades booleanas, generalmente es excesivo indicar que una propiedad es booleana con el nombre. Como ejemplo, no hay necesidad de llamar a una propiedad is_cool o cool_flag, basta con decir cool:TRUE o cool:FALSE. Si eliges usar un flag, hazlo consistente en toda la API.

Timestamps

Hay algunos estándares diferentes para serializar un timestamp (marca de tiempo). Sin embargo, un formato gobierna sobre todos ellos, y ese formato es ISO 8601. Aquí hay algunos ejemplos de este formato:

"2017-08-15T09:38:46+00:00": Fecha basada en el desplazamiento del UTC.
"2017-08-15T09:39:46Z": Usando “Zulu” UTC time.
"2017-08-15T09:37:46.987Z": Usando precisión de milisegundos.

Este formato de Timestamp siempre se representa como una cadena. Una buena característica es que, suponiendo que cada timestamp se encuentre en la misma zona horaria, cuando se ordenan alfabéticamente, se ordenarán en función de la ocurrencia. También son muy fáciles de leer para los humanos y, sin embargo, son bastantes flexibles.

Una alternativa común es usar el  Unix Epoch Time, que es un formato ilegible y ambiguo, es como representar una fecha/hora en formato numérico. Un ejemplo de este formato es 1493268311123, que incluye una precisión de milisegundo, que es diferente de 1493268311, que no lo hace. Es imposible conocer la precisión sin contener información de fuera de banda, como por ejemplo, indicar la precisión con anticipación en la documentación.

Versiones

Al construir y mantener una API HTTP, es común realizar cambios que causarán incompatibilidades en los consumidores. Cuando se realizan estos cambios, es vital que permitamos que los clientes antiguos continúen trabajando con la versión anterior de la API, mientras que admiten una nueva versión en paralelo. Esto permite a los clientes migrar a la nueva versión cuando llegue el momento oportuno.

Aquí hay tres métodos populares para versionar una API HTTP:

URL Segment (como lo usan en LinkedIn, Google+, Twitter): https://api.example.org/v1/*
Accept Header (GitHub): Accept: application/json+v1
Custom Header (Joyent CloudAPI): X-Api-Version: 1

Las nuevas versiones de las API solo se deben realizar cuando las operaciones que normalmente funcionarían con la versión anterior fallarían. Como ejemplo, agregar una nueva propiedad opcional a los recursos o una nueva colección no debería requerir una nueva versión. Cambiar un tipo de valor, agregar un parámetro requerido, eliminar un parámetro o colección, cambiar las opciones predeterminadas de filtrado (por ejemplo, Infinity por defecto a 100 resultados por página) son todos ejemplos de cambios hacia atrás.

Cuando desapruebes una versión anterior de la API, dales a los consumidores suficiente tiempo y atención. Intente notificarles de manera proactiva del cambio e incluso, si es posible, proporcione una guía de migración. Siempre dé una fecha límite si la antigua API se desactivará por completo.

Artículos relacionados: