Servicios RESTful Parte II: Limitaciones y objetivos

Servicios RESTful Parte II: Limitaciones y objetivos

En la Parte I de esta serie escribí sobre HTTP y sus construcciones que se aplican al diseño de servicios web.

HTTP es solo una pequeña parte de lo que implica la escritura de servicios web modernos.

Esta publicación trata sobre cómo aplicar estas construcciones para crear servicios robustos y fáciles de mantener.

Definición de REST

REST son las siglas de RE presentational S tate T ransfer. Es un estilo arquitectónico. Esto significa que REST no impone un estándar formal para determinar si un servicio web es RESTful o no. Más bien, tiene un conjunto de restricciones amplias , cada una con un objetivo específico en mente.

Estas restricciones fueron introducidas por primera vez por Roy Fielding, quien fue uno de los coautores de la especificación HTTP en 2000.

Restricciones de Fielding

Fielding creó estas restricciones con el objetivo final de hacer que las aplicaciones sean más rápidas, más confiables y más fáciles de escalar.

Como diseñador de servicios web, su servicio debe intentar cumplir con estas restricciones lo más fielmente posible para aprovechar sus beneficios. Así que sumergámonos en ellos.

Restricción n. ° 1: Arquitectura cliente-servidor

La primera restricción propuesta por REST es la separación del servidor de su cliente. Debe fomentar la separación de preocupaciones entre su servidor y los clientes siempre que sea posible. Su objetivo debe ser maximizar la división del trabajo y minimizar la superposición entre los dos.

El servidor, o back-end, suele ser responsable de almacenar los datos persistentes de su aplicación, junto con toda la lógica empresarial necesaria para interactuar con ella. Esto podría incluir autenticación de usuario, autorización, validación de datos, etc.

El cliente, o front-end , es responsable de realizar solicitudes al servicio y luego hacer algo significativo con la respuesta que recibe.

El propio cliente puede ser un servicio web, en cuyo caso simplemente consume los datos. Alternativamente, puede ser de cara al usuario. Un ejemplo de esto sería una aplicación web o móvil. Aquí, también es responsable tanto de presentar los datos al usuario como de presentar una interfaz para que el usuario interactúe con ellos .

Debería poder tratar cada uno de estos dos componentes como una caja negra con respecto al otro. De esta forma, se pueden modificar de forma independiente. Esto fomenta la modularidad dentro de la aplicación.

Este concepto no es exclusivo de las aplicaciones RESTFul, ni siquiera de las aplicaciones web. La mayoría de los desarrolladores intentan dividir sus proyectos en componentes independientes de todos modos. Pero al afirmar esto como una restricción explícita del diseño RESTful, Fielding fomenta aún más esta práctica.

Por último, reducir la cantidad de cosas de las que el servidor es responsable reduce la cantidad de lógica necesaria. Esto, a su vez, permite una mejor escalabilidad y un mayor rendimiento.

Restricción # 2: Apatridia

La siguiente limitación importante propuesta por REST es la apatridia.

En términos generales, el objetivo principal de un servicio sin estado es hacer que las solicitudes entrantes sean autosuficientes y ejecutarlas de forma completamente aislada.

Cada solicitud debe tener toda la información que el servidor pueda necesitar para procesarla y responder adecuadamente. En otras palabras, el servidor no necesita utilizar información de solicitudes anteriores. La responsabilidad de mantener el estado de la aplicación de un cliente se transfiere así al cliente mismo.

Para entender esto, considere un servicio web muy simple responsable de responder a las consultas de búsqueda de un usuario. La representación exacta de la entidad que se busca es irrelevante. Lo importante es que, en lugar de devolver cientos de resultados de búsqueda de una sola vez, el servidor emplea la paginación : devuelve solo 10 resultados a la vez de un conjunto de resultados arbitrariamente grande.

En un modelo de desarrollo tradicional "con estado", el servidor puede estar diseñado de tal manera que realice un seguimiento de todos sus clientes, junto con todas las páginas a las que ya han accedido.

Y así, cuando llega una solicitud para una nueva página, el servidor puede buscar al cliente en su sistema y determinar la página más reciente que recibió.

Luego, el servidor puede proceder a responder con la página siguiente y actualizar su sistema para reflejar esto. Esto continúa mientras el cliente continúa navegando por el conjunto de resultados.

En un enfoque alternativo, sin estado, la responsabilidad de mantener su estado se descentraliza y se transfiere al cliente. Luego, el cliente debe especificar los números de página reales del resultado que desea, en lugar de pedir la página siguiente. Por ejemplo:

GET //my-awesome-web-service.com/pages/1GET //my-awesome-web-service.com/pages/3

Un enfoque sin estado trae consigo un par de ventajas importantes. En primer lugar, realizar un seguimiento del estado del cliente se vuelve cada vez más complicado en un servidor a medida que aumenta el número de clientes.

En segundo lugar, y lo que es más importante, un servicio sin estado también es fácilmente distribuible . Si un servidor es responsable de mantener la información sobre el estado de una aplicación, también es imperativo que las solicitudes futuras se enruten al servidor que almacena esta información.

Si hay cientos de servidores responsables de procesar las solicitudes entrantes, entonces debe existir algún mecanismo para garantizar que las solicitudes de un cliente específico siempre terminen en una instancia de servidor específica.

En el caso de que una instancia de servidor deje de funcionar, toda la información sobre el estado de un cliente que se almacenó en ese servidor también se desactivará.

Por supuesto, podría idear una arquitectura en la que las instancias de servidor puedan compartir datos entre ellas. Pero esto agrega bastante complejidad.

Un servicio sin estado, por el contrario, hace que sea mucho más sencillo agregar y eliminar instancias de servidor de forma ad-hoc. Luego, puede equilibrar aún más la carga entre ellos según sea necesario.

Dado que los servidores son independientes de las solicitudes entrantes, la ampliación es solo una cuestión de agregar más servidores al equilibrador de carga. De manera similar, matar servidores, intencionalmente o de otro modo, no afecta la confiabilidad de un servicio.

Por supuesto, esta simplicidad tiene un costo. Hacer que el cliente adjunte datos idénticos con cada solicitud es una fuente potencial de redundancia. El ancho de banda no es gratuito, por lo que cualquier información adicional transferida agrega cierta cantidad de gastos generales.

Restricción n. ° 3: caché

La tercera restricción es la de la capacidad de almacenamiento en caché explícita. La idea es marcar los mensajes devueltos por un servicio como explícitamente almacenables en caché o no almacenables en caché. Si se pueden almacenar en caché, el servidor debería determinar la duración durante la cual la respuesta es válida.

Si el cliente tiene acceso a una respuesta en caché válida para una solicitud determinada, evita repetir la misma solicitud. En cambio, usa su copia en caché. Esto ayuda a aliviar parte del trabajo del servidor y, por lo tanto, contribuye a la escalabilidad y el rendimiento.

Esta es una forma de replicación optimista, también conocida como replicación diferida, en la que el servicio no intenta garantizar una coherencia del 100% entre él y sus clientes a menos que sea absolutamente crítico. En cambio, hace este sacrificio a cambio de una ganancia en el desempeño percibido.

Por ejemplo, una API correspondiente a una plataforma de blogs puede optar por hacer que la lista de publicaciones de blog se pueda almacenar en caché durante un par de minutos si sabe que la frecuencia con la que las personas intentan acceder a las publicaciones supera con creces la frecuencia con la que se crean nuevas publicaciones. Como resultado, es posible que a los usuarios ocasionalmente se les presenten datos obsoletos, pero el sistema en su conjunto funciona mejor.

Por supuesto, la capacidad de almacenamiento en caché de un recurso y su duración no son universales y requieren cierta consideración. Si elige incorrectamente, esto puede frustrar a sus usuarios.

Los servicios web generalmente logran la capacidad de almacenamiento en caché utilizando el encabezado estándar Cache-Control . A veces hacen esto junto con otros encabezados especificados por HTTP.

El encabezado Cache-Control sirve efectivamente como un interruptor, determinando si un navegador debe almacenar en caché la respuesta en cuestión.

Los recursos marcados como privados son almacenados en caché solo por el cliente y, por lo tanto, están limitados a ese único cliente.

Los recursos marcados como públicos, por otro lado, pueden ser almacenados en caché por uno o más proxies intermedios entre el servicio y el cliente.

Como resultado, estos recursos pueden potencialmente ser servidos a múltiples usuarios. Alternativamente, se puede pasar el argumento no-cache y detener por completo cualquier almacenamiento en caché del recurso.

Así es como se ve uno de estos encabezados de Cache-Control:

Cache-Control: public;max-age=3431901

El encabezado también le permite especificar la duración durante la cual el recurso es válido. Esto le permite al cliente saber cuándo debe dejar de usar su copia en caché y solicitar una nueva copia.

Aquí está la lógica detrás de esto:

Aparte de esto, HTTP también tiene mecanismos para realizar lo que se conoce como solicitud condicional. El objetivo aquí es que el servidor devuelva ciertos recursos al cliente solo cuando se cumplan condiciones específicas .

Suponiendo que el cliente tiene una copia guardada de un recurso en su caché, puede realizar una solicitud al servidor para determinar si hay una copia actualizada de ese mismo recurso. Si hay uno, el servidor devuelve la nueva copia. De lo contrario, le dice al cliente que siga usando su copia local.

Esto ayuda a prevenir la transferencia redundante de datos a través de la red, al tiempo que se asegura de que el cliente tenga acceso a datos nuevos en todo momento.

Hay un par de formas en que HTTP le permite lograr esto:

Enfoque de almacenamiento en caché n. ° 1: If-Modified-Since / Last-Modified

Junto con cada respuesta que envía el servidor, puede optar por adjuntar una marca de tiempo de Última modificación . Esto indica cuándo se modificó por última vez el recurso.

Cuando el cliente necesita solicitar el recurso nuevamente en el futuro, realiza la solicitud al servidor como lo haría normalmente, pero con un encabezado If-Modified-Since relevante . Esto le dice al servidor que devuelva la nueva copia del recurso, si existe.

De lo contrario, el servidor devuelve el código de estado 304, quele indica al cliente que siga usando la copia que ya tiene.

Enfoque de almacenamiento en caché n. ° 2: If-None-Match / ETag

Este esquema funciona de manera similar al anterior, excepto por la forma en que se identifican los recursos. En lugar de utilizar marcas de tiempo, el servidor devuelve con cada respuesta un hash único que explica el estado del recurso en ese momento (conocido como ETag).

Para solicitudes futuras, el cliente envía la ETag correspondiente al servidor. Si existe un recurso con la misma ETag, el servidor le dice al cliente que siga usando la copia en caché. De lo contrario, el servidor envía uno nuevo al cliente.

El almacenamiento en caché es complicado. A medida que su servicio comience a agregar más usuarios, querrá aprender más sobre el almacenamiento en caché y cómo puede usarlo para su beneficio.

Restricción # 4: Interfaz uniforme

La interfaz uniforme (o contrato uniforme ) le dice a unServicio RESTful qué servir, en forma de documento, imagen, objeto no virtual, etc.

Sin embargo, REST no dicta cómo elige interactuar con estos recursos, siempre que sean coherentes y se entiendan bien.

En general, antes de que un cliente pueda interactuar con un servicio RESTful, debe acordar:

  1. Identificación: debe haber una forma de identificar de forma única cada recurso que el servicio tiene para ofrecer.
  2. Manipulación: debe haber un conjunto estándar de operaciones que se puedan realizar en cualquier recurso dado con resultados predecibles. Los resultados de estas operaciones también deben ser autodescriptivos y entenderse de forma única.

HTTP, por ejemplo, utiliza URL para la identificación de recursos. También utiliza un puñado de verbos de acción y códigos de estado bien documentados para facilitar la interacción con el recurso. (Para obtener una explicación más detallada de las construcciones de HTTP, puede volver atrás y leer la Parte I de esta serie).

Hasta este momento, hemos considerado que los servicios RESTful están estrictamente vinculados a HTTP. Con respecto a los servicios web, esto casi siempre es correcto.

Pero, en teoría, REST se puede implementar sobre cualquier protocolo que proporcione una forma decente de lograr las dos condiciones que describí anteriormente. Por esta razón, a veces también se hace referencia a REST como REST sobre HTTP para aclarar que se está utilizando en la web.

Restricción n. ° 5: un sistema en capas

Un sistema en capas se basa en la restricción cliente-servidor que discutimos anteriormente y aplica una separación aún mayor de preocupaciones. La arquitectura general de su servicio se puede separar en capas individuales , cada una con una función específica.

Más importante aún, cada capa debe actuar de forma independiente e interactuar solo con las capas inmediatamente adyacentes a ella. Esto obliga a que las solicitudes se propaguen de manera predecible, sin pasar por alto las capas.

Por ejemplo, para escalar, puede utilizar un proxy que se comporte como un equilibrador de carga. El único propósito del proxy sería reenviar las solicitudes entrantes a la instancia de servidor adecuada.

El cliente, por otro lado, no necesita estar al tanto de esta división. Simplemente continúa haciendo solicitudes a la misma URL, sin preocuparse por los detalles de cómo se procesan las solicitudes.

De manera similar, puede haber otra capa en la arquitectura responsable de almacenar en caché las respuestas para minimizar el trabajo que debe realizar el servidor.

Otra capa puede comportarse como una puerta de enlace y traducir las solicitudes HTTP a otros protocolos.

Una forma en que podría utilizar esto sería implementar un servidor FTP. El cliente continuaría realizando solicitudes a lo que percibe como un servidor HTTP, mientras que en realidad tienes un servidor FTP haciendo el trabajo bajo el capó.

Al igual que la distinción cliente-servidor, esta restricción del sistema en capas minimiza el riesgo de acoplar la funcionalidad en su servicio, pero a expensas de una sobrecarga adicional en el sistema.

Conclusión

Para resumir, hemos analizado las restricciones importantes que debe tener en cuenta al diseñar servicios web RESTful. También quiero enfatizar que, aunque estos son requisitos técnicamente difíciles que un servicio debe cumplir para ser considerado RESTful, en la práctica esto no siempre sucede.

La construcción de servicios reales se trata más de resolver los problemas en cuestión que de cumplir con las definiciones técnicas. Como resultado, estas restricciones se utilizan con mayor frecuencia como pautas por desarrolladores y arquitectos, quienes luego deciden qué reglas seguir en sus esfuerzos por alcanzar sus propios objetivos específicos.

Aquí es de donde provienen los términos parcialmente descansado y completamente descansado. Y de hecho,la mayoría de los servicios que encuentra en línea no son técnicamente completamente RESTful.

En la próxima y última parte de esta serie discutiré los principios de HATEOAS, así como el Modelo de Madurez de Richardson. Esto proporciona un enfoque un poco más cuantitativo para determinar qué tan RESTful es realmente un servicio web. ¡Encuéntrelo aquí!

Espero que esta sea una introducción útil a lo que implica la creación de una aplicación RESTful. Comprender los principios de REST seguramente lo ayudará cuando esté trabajando con muchas API de terceros. O incluso cuando está creando sus propias aplicaciones en la web, dispositivos móviles o en cualquier otro lugar.

Como beneficio adicional, también he subido una presentación relevante a este tema aquí. La presentación de diapositivas se tomó prestada de una breve charla que pronuncié hace un par de meses en mi universidad titulada "El impacto de una arquitectura REST en el rendimiento y la escalabilidad de las aplicaciones". Espero que le sea útil :)

Hágame saber en los comentarios si tiene algún comentario o no dude en comunicarse conmigo a través de mi LinkedIn.

Aquí hay algunos recursos para leer más sobre REST:

Principios clave de la arquitectura de software - MSDN

Descanso explicado, una presentación

Servicios web Restful - Sam Ruby

WhatIsRest.com

Descanse en la práctica