Buenas prácticas en el Diseño de peticiones API REST.

Podemos pensar que el uso peticiones API Rest solo cabe en el ámbito de aquellas peticiones que van exponer servicios de nuestras aplicaciones de manera pública. Lo que me ha mostrado el tiempo es que la gran mayoría de las acciones desarrolladas en nuestro programas deberían ofrecer un mecanismo estándar de comunicación, tanto los servicios que van a ser expuestos como los que realizan operaciones a nivel del mismo programa.

La principal ventaja es que si tenemos todas nuestras acciones diseñadas de esta manera, podremos reutilizarlas rápidamente, por ejemplo cuando se deba cambiar de plataforma de desarrollo,  es decir, podemos aprovecharlas tanto para una aplicación Web, en una aplicación de escritorio o una para dispositivos móviles, también se podría lanzar nuevas versiones de las mismas peticiones sin afectar las anteriores, entre otras ventajas.

Aunque existen una gran cantidad de opiniones alrededor de este tema voy a tocar una serie aspectos que se han convertido en buenas prácticas al momento de trabajar con este tipo de peticiones.

Recuerda que la gran mayoría de componentes Web trabajan con respuestas tipo JSON, por tanto lo mejor es diseñar acciones que estén enfocadas a estar dentro de nuestra API para así poder conectarlas fácilmente a estos componentes.

Cuando comenzamos a diseñar nuestras peticiones de esta manera, se puede dificultar al principio la forma en que se debe recibir y devolver la información solicitada. Puedes estas acostumbrado a devolver HTML y retornar todo a plantillas, como en el caso de Symfony sería a Twig.

Espero que igual como me ocurrió a la larga veas las grandes ventajas de desarrollar así y mas si en algún momento te quieres inclinar por el desarrollo para otras plataformas, como dispositivos móviles.

Organiza tus peticiones Rest en recursos lógicos.

Los principios fundamentales de las peticiones REST se basan en separar tu API en recursos lógicos. Estos recursos se usan a través de peticiones HTTP donde cada una de los métodos del protocolo (GET, POST, PUT, PATCH, DELETE) tienen un significado muy específico.

Debes tener claro que al nombrar los recursos en API no se usan verbos sino sustantivos que tengan un sentido a la hora de ser usados.

En nuestro caso, los recursos que vamos a acceder inicialmente serían las entidades de nuestra aplicación (disponibilidad, agenda, usuario).

Luego de definir los recursos a acceder luego miramos qu operaciones vamos a realizar sobre estos. Podemos centrarnos para empezar en las operaciones CRUD usando los métodos HTTP de la siguiente manera:

Ejemplo con Disponibilidades.

  • GET /disponibilidades  Devuelve todas las disponibilidades
  • GET /disponibilidades/1  Devuelve la disponibilidad identificada con el id 1
  • POST /disponibilidades   Crea una nueva disponibilidad
  • PUT /disponibilidades/1 Actualiza la disponibilidad identificada con el id 1
  • PATCH /disponibilidades/12 Actualiza parcialmente la disponibilidad identificada con el id 1
  • DELETE /disponibilidades/11  Elimina la disponibilidad identificada con el id 1

Al ver este tipo de peticiones lo que vemos es que a través de los métodos HTTP tradicionales estamos implementando una gran cantidad de funcionalidades y todas a través de un mismo punto de entrada en este caso /disponibilidades. Devuelvete y medita un poco lo que acabo de escribir en las peticiones y cuando comencemos a construir nuestras peticiones API Rest vas a ver el potencial de usar esto.

Algo que debe quedar claro es que el punto de entrada lo debes escribir en plural. Puede llegarse a presentar confusión al momento de consultar:

GET /disponibilidades/1 

Para nuestra lógica debería ser:

GET /disponibilidad/1 

Pero lo que buscamos es tener nuestras peticiones lo mas consistentes por eso lo mas recomendable es usar todo en plural.

Manejo de las relaciones.

Podemos hacer uso de Rest cuando debamos traer recursos que involucren relaciones entre las entidades.

En nuestro caso una cita en la agenda se da cuando un usuario ocupa una disponibilidad, para este caso las rutas a construir se verian de la siguiente manera:

  • GET /disponibilidades/1/usuarios Devuelve el usuario que tienen asignada la disponibilidad 1
  • POST /disponibilidades/1/usuarios/2 – reserva la disponibilidad 1 al usuario 2
  • PUT /disponibilidades/1/usuarios/2 – Actualiza la disponibilidad 1 con el usuario 2
  • DELETE /disponibilidades/1/usuarios/2 – Borra la disponibilidad 1 asignadas al usuario 2

3. Usa SSL en todos lados – todo el tiempo

El uso de las APIs Web se han extendido de una manera impresionante, y pueden ser accedidas desde cualquier parte. Aunque inicialmente tus peticiones internas pueden estar protegidas por un contexto de seguridad enmarcado en una sesión creada por un usuario y clave  asignado, la realidad es que dichas peticiones igualmente viajaran en texto plano y una persona con suficiente interés y conocimiento podría llegar a  accederlas.

Usar SSL  te garantiza que las peticiones viajen encriptadas.

Documenta muy bien tu API.

Diseñar una buena documentación de nuestra API es el éxito de la misma. Para estos casos en Symfony puedes usar NelmioApiDocBundle.

Versiona la API.

Esto te va a ayudar  a extender tu aplicación facilmente. Colocate en esta primera situación:

Debes realizar una mejora a un método de una acción, si lo haces sin contar con el versionado, puede que llegues a cometer un error y haces que todos aquellos desarrolladores que estén usando tu API presenten fallos en sus programas. Versionar la API te va a dar la posibilidad de realizar las pruebas suficientes para que un método nuevo este bien probado.

Colocate ahora en esta otra:

Tienes la posibilidad de mejorar una acción con nuevas funcionalidades; pero debes garantizar que las funcionalidades actuales sigan funcionando. Con el versionado puedes seguir ofreciendo el recurso anterior y asi poder promover el nuevo como una mejora, dando asi tiempo a los desarrolladores que realicen los cambios necesarios sin interrumpir la operación normal de sus programas.

Siempre versiona tu API. El versionado te ayuda a iterar más rápido y evitar peticiones inválidas desde endpoints actualizados. Además te ayuda a resolver cualquier transición importante de versión de la API mientras continúas ofreciendo viejas versiones de la API por un período de tiempo.

Una API siempre sufrirá cambios. Lo importante es cómo vamos a gestionarlos.

Como extender los resultados solicitados a la API

Es importante mantener la URL base de nuestra API lo mas limpia posible. Vamos a ver como podría llegar a realizarse filtrados, ordenamiento y búsquedas a través de la API.

Filtrados.

Para los filtrados lo que puedes hacer es usar parametros en la url. Si quieres llegar a filtrar las disponibilidades activas la URL llegaría a ser de la siguiente manera:

GET /disponibilidades?estado=activo  Devuelve todas las disponibilidades activas.

Trata de usar filtros estándar y basados en un mismo genero asi tus peticiones serán concisas, es decir, aunque la naturaleza de la palabra disponibilidad es femenina, si la mayoría de tus entidades tienden a un genero, los filtros los puedes colocar masculinos, por eso el estado es activo y no activa. Así evitas confusiones con las peticiones y estandarizas las palabras usadas.

Ordenanimento.

Al igual que en el filtrado se usa un parametro para implementar el ordenamiento, en este caso para que quede mas clara la petición puedes utilizar signos para representar el ordenamiento.

Veamos un ejemplo:

GET /disponibilidades?ordenamiento=-estado Acá le estamos diciendo que ordene por el atributo estado de la entidad disponibilidad y con el signo le decimos que sea descendente.

Además podemos aprovechar para ordenar por otros atributos enviándolos separados por comas.

GET /disponibilidades?ordenamiento=-estado,fecha Acá estamos consultando todas las disponibilidades ordenadas descendentemente por estado y ascedente por fecha.

Búsqueda.

Para la búsqueda igualmente pasamos un parámetro que comúnmente se puede llamar q. Coloquemos un ejemplo combinando todas las posibilidades:

GET /disponibilidades?q=2016&estado=activo&ordenamiento=-estado Acá estamos consultando todas las disponibilidades ordenadas descendentemente por estado y que dentro de los atributos de la entidad disponibilidad contengan la cadena 2016.

Limitando los campos que serán devueltos en la API

En los ejemplos anteriores el resultado nos devuelve todos los atributos de la entidad disponibilidad. Podemos activar un parámetros campos donde pasemos los campos que requerimos de la entidad, así damos mas flexibilidad a la API y además si la persona que la consulta tiene esta opción disminuiremos el tráfico de la red, dado que solo viajaran los paquetes con la información necesaria.

GET /disponibilidades?campos=fecha,hora&ordenamiento=-estado&estado=activo Acá estamos consultando la fecha y hora de todas las disponibilidades activas ordenadas descendentemente por estado.

Cuando se ejecute un PUT, POST o PATCH la API deberá responder con el recurso creado o modificado.

Con esto garantizamos la creación del objeto además de ahorrar un tiempo en otra consulta, dado que comunmente este tipo de peticiones requieren de la respuesta del objeto creado. Asi el desarrollador podrá acceder inmediatamente al recurso como quedó luego de su creación o modifcación.

 Nuestras APIs solo deberían responder en formato JSON

El uso de JSON se ha extendido por encima de otros formatos dado a su fleixibilidad y claridad en el uso. Las empresas de tecnología mas grande estan migrando todas sus peticiones a este formato, dejando a un lado algunos como XML.

Ya sea que tu cliente lo exija siempre utiliza JSON por encima de cualquier otro.

Autenticación

Nuestra API debería ser sin estado, es decir, no depender de una sesión en Symfony o de Cookies para estar asegurada. Para poder asegurarla cada petición de la API debe contener alguna forma de autorización.

En este caso lo más recomendable es usar OAuth 2  para facilitar el paso de un token seguro. OAuth 2 usa tokens Bearer y además depende de SSL para su encriptación.

Presentación de Errores

Una API debe proveer mensajes de error útiles  en un formato conocido. En el protocolo HTTP tenemos dos tipos de errores a manejar: Lo errores enmarcados en la serie 400 de códigos para problemas en el cliente y la serie 500 para problemas en el servidor.

Un ejemplo de presentación de errores en JSON sería el siguiente:

{
“codigo” : 1234,
“mensaje” : “Ha ocurrido un error”,
“descripción” : “Mensaje descriptivo del error”
}

Cuando sean errores de validación para las peticiones PUT, PATCH y POST necesitarán encapsularse en un campo errores:

{
“codigo” : 422,
“mensaje” : “Hay campos con errores”,
“errores” : [
{
“código” : 567,
“campo” : “documento”,
“mensaje” : “Debe ser un campo númerico”
},
{
“código” : 890,
“campo” : “correo_electronico”,
“mensaje” : “No es un correo válido”
},
]
}

Tabla de códigos del prótocolo HTTP

HTTP cuenta con un paquete de códigos de estado que pueden ser manejado por una API.  A continuación hay una lista de los que seguramente llegarás a  utilizar cuando construyas una API:

  • 200 OK – Respuesta exitosa a los estados GET, PUT, PATCH o DELETE.
  • 201 Created – [Creada] Respuesta exitosa a un POST que ha creado un objeto.
  • 204 No Content – [Sin Contenido] Respuesta exitosa que no devuelve un body (como por ejemplo una petición DELETE)
  • 400 Bad Request – [Petición Incorrecta] La petición no puede devolver una respuesta correcta, dado que la petición no fue bien estructurada.
  • 401 Unauthorized – [Desautorizada] Credenciales de autorización no son válidas.
  • 403 Forbidden – [Prohibida] Cuando la autenticación es exitosa pero el usuario no tiene permisos de acceso al recurso solicitado.
  • 404 Not Found – [No encontrada] Cuando un recurso solicitado no existe.
  • 405 Method Not Allowed – [Método no permitido] Cuando un método HTTP que está siendo consultado no está permitido para el usuario.
  • 415 Unsupported Media Type – [Tipo de contenido no soportado] Si el formato del contenido solicitado es incorrecto o no esta disponible.
  • 422 Unprocessable Entity – [Entidad improcesable] Se utiliza para errores de validación de los datos enviados.
  • 429 Too Many Requests – [Demasiadas peticiones] Cuando el número de peticiones a la API ha llegado a su límite.

En conclusión

Una API es una interfaz diseñada por desarrolladores y usada por desarrolladores. Es muy importante esforzarse en que nuestras APIs sean funcionales y amigables.