Interfaz Uniforme (API)
API REST
Para el diseño de una API REST se puede partir desde la comprensión del Modelo de Madurez de Richardson, en la cual se exponen y explican las características encontradas en una API REST.
- Richardson Maturity Model
- Autor: Martin Fowler (https://twitter.com/martinfowler)
- Richardson Maturity Model - Clasificación de Web APIs
- Modelo de Madurez de Richardson – REST
El modelo de madurez de Richardson mediante niveles establece 4 etapas de madurez durante el diseño y desarrollo de una API REST, del nivel 0 (más bajo) al nivel 3 (la gloria de REST). Por cuestiones de interpretación de la misma se consideran los tres primeros niveles como no REST, siendo el nivel 2 el comúnmente diseñado/desarrollado y encontrado en el mundo real. El nivel 3, asociado al termino HATEOAS (Hypermedia as the Engine of Application State) se consideraría, para algunos, como meramente teórico, impráctico u ortodoxo sin embargo en años muy recientes están surgiendo nuevas tecnologías para su implementación como:
- JSON API
- JSON for Linking Data e Hydra: Hypermedia-Driven Web APIs
- HAL.
- Swagger
- RESTful API Modeling Language (RAML)
- API Blueprint
Sobre HATEOAS:
- Entendiendo el principio HATEOAS
- HATEOAS: ¿qué y por qué?
- ¿Que es HATEOAS?
- Implementar HATEOAS en un API REST
- HATEOAS: servicios REST autodocumentados
Ejemplo
A continuación se plantea un ejemplo para abordar el desarrollo de una API REST tomando en cuenta su dominio.
Cabe mencionar que lo expuesto es subjetivo ya que REST no especifica pasos o metodologías para el desarrollo de una aplicación web basada en REST. Por otro lado se darán pautas o sugerencias que permitan solucionar aquellos puntos que puedan ser problemáticos durante el diseño.
Se partirá del siguiente ejemplo para situarnos ante consideraciones al momento de diseñar una API REST.
Aplicación para el control del historial academico
Una aplicación que permita llevar el control del historial académico de un alumno.
Condicionantes
El historial académico comprende:
- Un alumno, quien está identificado por su clave de alumno.
- Un docente, quien está identificado por su clave de docente.
- Materias, quienes están identificadas por su clave de materia.
Historias de usuario
Como docente deseo:
- Ver mis alumnos.
- Ver mis materias.
- Asignar una calificación a uno de mis alumnos, en una de sus materias.
Como alumno deseo:
- Ver mis docentes.
- Ver mis materias.
- Ver la calificación de una de mis materias, asignada por mi docente.
Diseño
El siguiente diseño se encontraría en el nivel 2 del modelo de madurez de Richardson el cual cuenta con las siguientes características:
- Uso de varias URLs para identificar y acceder a los recursos.
- Uso de varios métodos HTTP, reconociéndolos como verbos o acciones sobre los recursos.
- Creación de una interfaz uniforme basada en el uso de HTTP.
Los pasos a seguir (sugeridos) para llevar a cabo lo anteriormente expuesto serían:
Identificación de sujetos en las historias de usuario
Como docente deseo:
- Ver mis alumnos.
- Ver mis materias.
- Asignar una calificación a uno de mis alumnos en una de sus materias.
Como alumno deseo:
- Ver mis docentes.
- Ver mis materias.
- Ver la calificacion de una de mis materias, asignada por mi docente.
Se han identificado a:
- Docentes: identificados por clave de docente.
- Materias: identificadas por clave de materia.
- Calificaciones: asociadas a un docente, un alumno y una materia.
Convertir sujetos en recursos (URLs)
Aquellos sujetos que representen una colección:
- Docentes:
/docentes
- Alumnos:
/alumnos
- Materias:
/materias
- ¿¿¿Calificaciones:
/calificaciones
???
- Docentes:
Aquellos sujetos que representen un individuo dentro de una colección:
- Docente:
/docentes/{clave del docente}
- Alumno:
/alumnos/{clave del alumno}
- Materia:
/materias/{clave de materia}
- ¿¿¿Califición:
/calificaciones/{clave de calificación}
???
- Docente:
Como puede observarse, el sujeto identificado (¿¿¿ ???) no tiene cabida alguna como recurso ya que, por un lado, su existencia es generica y restringida a un conjunto de valores bien conocidos (números enteros) y, por otro lado, su existencia depende enteramente de la existencia de otros recursos (docente, materia y alumno) por lo que quedará fuera del conjunto de recursos (URLs).
Tomar en cuenta el URI scheme para la sintaxis de las URLs, a saber:
- No hacer uso de mayúsculas o camelCase, todo en minúsculas, así como no hacer uso de guión bajo.
Se evitaría la posible confusión a los clientes/programadores:
/Docentes/{clave del docente}/alumnos
/Alumnos/{clave del alumno}/docentes
Semantica de asociación entre recursos
En ocasiones nos encontraremos ante el hecho de que los sujetos-recursos están asociados o relacionados entre si, de esta forma podemos identificar nuevos recursos que nos permitan exponer la semantica de su relación:
- Un docente imparte materias:
/docentes/{clave del docente}/materias
. - Un docente enseña a alumnos:
/docentes/{clave del docente}/alumnos
. - Un alumno estudia materias:
/alumnos/{clave del alumno}/materias
. - Un alumno es instruido por docentes:
/alumnos/{clave del alumno}/docentes
. - Una materia es impartida por docentes:
/materias/{clave de la materia}/docentes
. - Una materia es impartida a alumnos:
/materias/{clave de la materia}/alumnos
. - etc... !!!
Como puede observarse, el conjunto de recursos (!!!) puede incrementar en cantidad, dependiendo de la semantica encontrada entre ellos. Lo importante a notar es que la relación entre recursos permite replantearnos la existencia de subrecursos o recursos dentro de otros recursos en base a la semantica:
- Una materia es impartida a un alumno en especifico:
/materias/{clave de la materia}/alumnos/{clave del alumno}
. - Una alumno estudia una materia en especifico:
/alumnos/{clave del alumno}/materias/{clave de la materia}
.
- Un docente imparte materias:
Usar los métodos de HTTP para exponer las operaciones sobre los recursos
Los métodos de HTTP pueden ser asociados o relacionados con las operaciones CRUD, comunes en bases de datos:
- POST es Create
- GET es Read
- PUT es Update
- DELETE es Delete
De esta forma:
- Una operación de creación sobre un recurso implica una solicitud POST sobre dicho recurso.
- Una operación de lectura o visualización sobre un recurso implica una solicitud GET sobre dicho recurso.
- Una operación de actualización o modificación sobre un recurso implica una solicitud PUT sobre dicho recurso.
- Una operación de borrado o eliminación sobre un recurso implica una solicitud DELETE sobre dicho recurso.
En base al ejemplo hasta ahorita abordado y dando solución a las historias de usuario:
- Como docente deseo:
- Ver mis alumnos:
GET /docentes/{clave del docente}/alumnos
- Ver mis materias:
GET /docentes/{clave del docente}/materias
- Asignar una calificación a uno de mis alumnos, en una de sus materias:
PUT /docentes/{clave del docente}/alumnos/{clave del alumno}/materias/{clave de la materia}
- Ver mis alumnos:
- Como alumno deseo:
- Ver mis docentes:
GET /alumnos/{clave del alumno}/docentes
- Ver mis materias:
GET /alumnos/{clave del alumno}/materias
- Ver la calificacion asignada por un docente, en una de sus materias:
GET /alumnos/{clave del alumno}/docentes/{clave del docente}/materias/{clave de la materia}
- Ver mis docentes:
Representación del recurso
La representación (formato) del estado actual del recurso es negociada mediante los tipos MIME:
- XML:
application/xml
- JSON:
application/json
Es importante hacer incapie en la negociación, el cliente es quien decide el formato en el cual ha de recibir el recurso. Lo anterior se hace usando las cabeceras
Accept
yContent-Type
de HTTP.No se hace uso de extensiones de archivos en los recursos expuestos, a pesar de que algunos frameworks para el desarrollo web lo sugieren:
/docentes.json
/docente.xml
/alumnos/{clave del alumno}/docentes.json
/alumnos/{clave del alumno}/docentes.xml
- etc...
Lo anterior se debe a que las URLs, conceptualmente, están identificando a distintos recursos o sujetos y esto quebrantaría la interfaz uniforme.
Ejemplos:
Solicitud (del cliente) GET /docentes Accept: application/json
Respuesta (del servidor)
Content-Type: application/json { "docentes": [ { "clave": "123" "nombre": "...", "apellidos": "...", "etc": "..." }, { "clave": "456" "nombre": "...", "apellidos": "...", "etc": "..." }, { "clave": "789" "nombre": "...", "apellidos": "...", "etc": "..." }, ... ] }
Solicitud (del cliente) GET /docentes Accept: application/xml
Respuesta (del servidor)
Content-Type: application/xml <docentes> <docente> <clave>123</clave> <nombre>...</nombre> <apellidos>...</apellidos> <etc>...</etc> </docente> <docente> <clave>456</clave> <nombre>...</nombre> <apellidos>...</apellidos> <etc>...</etc> </docente> <docente> <clave>789</clave> <nombre>...</nombre> <apellidos>...</apellidos> <etc>...</etc> </docente> ... </docentes>
Para la representación de los recursos se recomienda no usar camelCase o guión bajo para los atributos o propiedades del recurso, siendo consistentes con la sintaxis de URLs:
{ "claveDelDocente": "123" "nombre_del_docente": "...", apellidos: "...", "etc": "..." }
<docente> <claveDelDocente>123</claveDelDocente> <nombre_del_docente>...</nombre_del_docente> <apellidos>...</apellidos> <etc>...</etc> </docente>
- XML:
Usar los códigos de estado de HTTP para indicar el estado final de la solicitud
HTTP define códigos de estados (status codes) que nos permitirán indicar el tipo de respuesta y un mensaje asociado a la misma:
- 1xx: Respuestas informativas
- 2xx: Peticiones correctas
- 3xx: Redirecciones
- 4xx: Errores del cliente
- 5xx: Errores de servidor
De las cuales, en base al método de HTTP usado, el recurso y los posibles datos enviados por el cliente permitirán indicarle el estado final de la solicitud:
- 200 OK: ante solicitudes GET.
- 201 Created: ante solicitudes POST o PUT.
- 400 Bad Request: ante solicitudes en las cuales el cliente haya enviado datos (representación) sin sentido.
- 401 Unauthorized: ante solicitudes en las cuales el cliente no se esté autentificando para acceder al recurso.
- 403 Forbidden: ante solicitudes en las cuales el cliente, aún cuando se haya autentificado de forma correcta, no tiene permisos para acceder al recurso.
- 404 Not found: ante solicitudes en las cuales el recurso solicitado no exista, en ese momento.
- 405 Method not allowed: ante solicitudes que hagan uso de un método de HTTP no soportado para el recurso solicitado.
- 406 Not Acceptable: ante solicitudes GET en las cuales el cliente solicite una representación
(tipo MIME) no soportado para el recurso como valor de la cabecera
Accept
. - 409 Conflict: ante solicitudes PUT en las cuales la actualización del recurso entre en conflicto con su estado actual.
- 415 Unsupported Media Type: ante solicitudes POST o PUT en las cuales el cliente
envíe la representación del recurso a crear o actualizar en un tipo MIME no soportado
para el recurso como valor de la cabecera
Content-Type
. - 500 Internal Server Error: ante solicitudes en las cuales el servidor haya detectado algún error interno y ajeno al cliente.
Otras consideraciones
Páginación: el recurso, como colección, puede ser demasiado grande (bytes) para el cliente y su envío puede degradar el ancho de banda de ambos.
- Sugerencia:
- Siguientes al 111:
/recurso-muy-grande?next=20&to-item=111&inclusive=true
- Anteriores al 111:
/recurso-muy-grande?prev=20&to-item=111&inclusive=false
- Siguientes al 111:
- Sugerencia:
Ordenamiento: el recurso, como colección, carece de orden en los atributos o propiedades de sus individuos por lo que puede no serle demasiado útil al cliente.
- Sugerencia:
- Ordenado por atributo x de forma ascendente:
/recurso-como-coleccion?order-by=x&sorted-as=asc
- Ordenado por atributo y de forma descendente:
/recurso-como-coleccion?order-by=y&sorted-as=desc
- Ordenado por atributo x de forma ascendente:
- Sugerencia:
Filtrado / Busqueda: el recurso, como colección, puede ser demasiado grande (bytes) y el cliente solo requiere obtener aquellos que cumplan alguna condición en sus atributos o propiedades.
- Sugerencia:
- Filtrar por atributo x igual a "Hola":
/recurso-como-coleccion?filter-by=x&x=Hola
- Filtrar por atributo y igual a 678:
/recurso-como-coleccion?filter-by=y&y=678
- Filtrar por atributo x igual a "Hola":
- Sugerencia:
Representación personalizada: el cliente no requiere toda la representación del recurso, solo alguno de sus individuos o atributos.
- Sugerencias:
- Partiendo que el recurso cuente con los atributos aa, bb, cc, dd y ee:
- Solo los atributos aa y cc:
/recurso-como-coleccion/individuo?attributes=aa,cc
- Todos los atributos, exceptuando a aa y cc:
/recurso-como-coleccion/individuo?attributes=!(aa,cc)
- Solo los atributos aa y cc:
- Definiendo vistas que el cliente pueda solicitar:
- Algunos atributos:
/recurso-como-coleccion/individuo?view=brief
- Alguna coleccion de atributos:
/recurso-como-coleccion/individuo?view=personal-information
- Algunos atributos:
- Partiendo que el recurso cuente con los atributos aa, bb, cc, dd y ee:
- Sugerencias:
Versión de API
Es común que durante la implementación y ya en funcionamiento la API se encuentren detalles en ella que obliguen a su modificación, esto plantea un problema con respecto a los cliente. Los clientes al depender de la API, estarían programados para esperar que ella se comporte de la misma forma siempre por lo que una modificación en ella puede dejar inutilizable a los clientes y esto a su vez a los usuarios.
Para evitar el problema anterior se sugiere versionar la API y existen dos sugerencias al respecto:
- Versión de API en la URL:
/api/1.0/alumnos/{clave del alumno}/materias
- Versión de API en las cabeceras de HTTP:
Accept: application/vnd.api.escuela+1.0json
yContent-Type: application/vnd.api.escuela+1.0json
.
- Versión de API en la URL:
Documentación de una API
Una API debe contar con documentación para facilitar su uso a terceros (programadores de los clientes) y de esta forma promover su correcto uso.
Los puntos que uno aborda en la documentación son:
- URLs o endpoints: como acceder o que direcciones en la web pertenecen a la API.
- Descripción: que representan conceptualmente los recursos disponibles en la API.
- Prerequisitos: para un correcto uso y un trabajo sin problemas entre cliente y servidor, que debe cumplir el cliente al hacer las solicitudes.
- Solicitudes: en base a las URLs, que tipo de solicitudes (métodos de HTTP) y tipos de representación del recurso puede el cliente llevar a cabo en la API.
- Respuestas: en base a la solicitud del cliente, que tipo de representación del recurso se enviaría al cliente, así como indicaciones de los errores que podrían existir al momento de procesar la solicitud.