
¿Que es HATEOAS?
HATEOAS es un acrónimo de Hypermedia as the Engine of Application State. El objetivo de HATEOAS es que las operaciones que se pueden realizar con un API sean auto-descubribles a través de hipervínculos que el propio API sirve al cliente. De esta forma, el cliente REST no necesita conocer de antemano la forma de interactuar con el servidor, tan sólo debe saber interpretar los hipervínculos.
El uso de HATEOAS y la adopción de patrones REST, supone que el estado de la aplicación lo mantiene el cliente y que los enlaces facilitados por el servidor deben ayudar en esta tarea mostrando en cada momento todas las acciones posibles en el siguiente paso. Además se asume que todos estas acciones son descubribles desde un principio.
HATEOAS y REST requieren un único punto de entrada
La primera implicación es que los APIs que decidan utilizar este enfoque hasta sus últimas consecuencias debieran tener un único punto de entrada desde el cuál fuese posible iniciar toda la navegación. Ese punto de entrada podría ser la raíz del API:
https://api/domain.com/
Desde este endpoint, al realizar una petición GET, podríamos devolver una lista de hipervínculos con todos los endpoints con los que resultaría lógico interactuar por primera vez.
¿Cómo devolvemos los enlaces?
Supongamos el endpoint/localitiesoffsety limit. Supongamos que nos encontramos en la tercera página de un listado con 25 elementos por página y un total de 132 elementos. Aplicando HATEOAS podríamos devolver una lista de enlaces para facilitar al cliente paginar, llendo hacia las páginas siguiente o anterior, primera y última.
Una primera solución es devolver los enlaces en el cuerpo del mensaje:
{ ... "data" : [ ..../*the list of localities paginated*/ ], "links" : [ { "rel" : "previous", "href": "https://api.domain.com/v1/localities?offset=50&limit=25" }, { "rel" : "next", "href": "https://api.domain.com/v1/localities?offset=100&limit=25" }, { "rel" : "first", "href": "https://api.domain.com/v1/localities?offset=0&limit=25" }, { "rel" : "last", "href": "https://api.domain.com/v1/localities?offset=125&limit=25" } ] }
De esta forma el cliente, haciendo únicamente caso al tipo de relación (atributo rel del enlace) del enlace, puede saber cómo navegar a través de las distintas páginas localidades. Si las especificaciones del API cambian la nomenclatura de la paginación, siempre y cuando se mantuviese la semántica del enlace a través del tipo de relación, el cliente no tendría por qué enterarse. Así se puede desacoplar en cierta medida el cliente del servidor.
Alternativamente al uso del cuerpo del mensaje para incluir los hipervínculos, se puede usar la cabecera HTTP Link. El uso de esta cabecera puede ser muy útil desde el punto de vista del cliente ya que le evita tener que procesar el cuerpo del mensaje para acceder a los enlaces.
Se puede añadir múltiples cabeceras Link:
Link: <https://api.domain.com/v1/localities?offset=50&limit=25>; rel="previous" Link: <https://api.domain.com/v1/localities?offset=100&limit=25>; rel="next" Link: <https://api.domain.com/v1/localities?offset=0&limit=25>; rel="first" Link: <https://api.domain.com/v1/localities?offset=125&limit=25>; rel="last"
o se puede hacer con una única cabecera Link donde los valores estén concatenados con comas:
Link: <https://api.domain.com/v1/localities?offset=50&limit=25>; rel="previous", <https://api.domain.com/v1/localities?offset=100&limit=25>; rel="next", <https://api.domain.com/v1/localities?offset=0&limit=25>; rel="first", <https://api.domain.com/v1/localities?offset=125&limit=25>; rel="last"
Enriqueciendo los enlaces de HATEOAS
Los enlaces, ya se sirvan a través de la cabecera o en el cuerpo del mensaje deben seguir la notación Atom especificada en la sección acerca del elemento atom:link de la RFC 4287. A parte de los habituales href y rel, los enlaces pueden incluir un type para indicar el tipo de datos Media Type que va a ser devuelto desde esa URI.
Si queremos enriquecer nuestros enlaces con esta información, lo más lógico es definir Media Types propios que identifiquen nuestras entidades. Dependiendo de la estrategia de versionado elegida, puede que impacte en la representación del Media Type.
Es casi seguro que los valores aceptados del atributo rel no cubran la semántica de nuestro API. IANA es quién define los posibles valores del atributo rel. Tendremos que definir nuevos tipos rel para representar esta información. IANA ya define los tipo rel que hemos utilizado en el ejemplo, pero si quisiéramos definirlos en nuestro namespace lo haríamos con las siguientes URIs.
http://api.domain.com/rel/next http://api.domain.com/rel/previous http://api.domain.com/rel/first http://api.domain.com/rel/last
y a la hora de construir el link lo haríamos de la siguiente manera:
Link: <https://api.domain.com/v1/localities?offset=50&limit=25>; rel="http://api.domain.com/rel/previous"
La promesa de HATEOAS
Nos debemos preguntar si la promesa que nos ha hecho HATEOAS se cumple o no. Es decir, ¿de verdad el API es auto-descubrible y se desacopla el cliente y el servidor?.
El API es auto-descubrible por un humano al igual que lo es una aplicación Web dirigida por hipervínculos. En cualquier caso, si el humano quiere desarrollar un cliente para el API, tendrá que echar mano de la documentación.
Por otro lado, HATEOAS ofrece un nivel de indirección entre acciones y la URI que nos permite realizar la acción. De esta forma, si la URI cambia, el cliente no tendría por qué enterarse.
Además, desde el punto de vista del cliente, supone grandes facilidades a la hora de construir las peticiones, ya que no tiene que construir las URLs a partir del nombre del servidor y los identificadores de los recursos.
Problemas para el proveedor
Pero desde el punto de vista del proveedor del API, una completa implementación de HATEOAS puede suponer quebraderos de cabeza. HATEOAS representa un problema «transfuncional » ya que HATEOAS necesita una solución contrapuesta al flujo de ejecución de una llamada a un endpoint.
Es un problema a resolver con programación orientada a aspectos o usando mecanismos de cada lenguaje o framework que permitan aplicar una funcionalidad de forma selectiva a la ejecución de un endpoint: como por ejemplo, los filtros de CXF o de los Servlets de Java. La alternativa es tener la lógica que soluciona este problema fragmentada a lo largo de todo el backend.
El módulo que resuelva la problemática de HATEOAS no sólo debe conocer el endpoint que se está invocando sino que tiene que tener información global del sistema, y al contexto de la petición, incluyendo acceso al principal de seguridad. Por ejemplo, no se presentarán los mismos enlaces a un usuario con un rol de administración que a uno con un rol de usuario.
Creo que la implementación de HATEOAS debe relajarse para obtener una relación entre el coste y el beneficio adecuado.
[…] razones, podría resultar poco práctico aplicar un diseño de Intefaz basado enteramente en HATEOAS[1], es muy importante que las respuestas incluyan enlaces que permitan al cliente (que asi lo […]
[…] tema nuevo y no lo domino muy bien) pueden profundizar en estos links genbeta, adictos al trabajo, byteflair y acá tiene un ejemplo básico en la página de […]