Varios conceptos relacionados con REST entran en conflicto en mi cabeza cuando intento implementarlo.
Tengo un sistema de API de fondo REST-ful que contiene la lógica empresarial y una aplicación web que proporciona la interfaz de usuario. De varios recursos sobre REST (particularmente, REST en la práctica: hipermedia y arquitectura de sistemas ) sé que no debo exponer los identificadores sin formato de mis entidades, sino más bien devolver hipervínculos con rel="self"
.
Considera el ejemplo. La API REST tiene un recurso que devuelve una persona:
<Person>
<Links>
<Link rel="self" href="http://my.rest.api/api/person/1234"/>
</Links>
<Pets>
<Link rel="pet" href="http://my.rest.api/api/pet/678"/>
</Pets>
</Person>
El problema surge con la aplicación web. Supongamos que devuelve una página que contiene un hipervínculo a los navegadores:
<body class="person">
<p>
<a href="http://my.web.app/pet/???????" />
</p>
</body>
¿Qué debo poner en el href
atributo? ¿Cómo mantengo la URL de la entidad API en la aplicación web para poder obtener la entidad cuando un usuario abre la página de destino?
Los requisitos parecen contradictorios:
- El hipervínculo
href
debe conducir a la aplicación web porque es el sistema que aloja la IU - El
href
debe tener algún Identificación de la entidad debido a la aplicación web debe ser capaz de hacer frente a la entidad cuando se abre la página de destino - La aplicación web no debe analizar / construir URL REST porque no es REST-ful, dice el libro mencionado
Los URI deben ser opacos para los consumidores. Solo el emisor del URI sabe cómo interpretarlo y asignarlo a un recurso.
Por lo tanto, no puedo simplemente tomar 1234
la URL de respuesta de la API porque, como cliente RESTful, debería tratarla como si fuera algo así http://my.rest.api/api/AGRIDd~ryPQZ^$RjEL0j
. Por otro lado, debo dar alguna URL que conduzca a mi aplicación web y sea suficiente para que la aplicación de alguna manera restaure la URL original de la API y use esa URL para acceder a los recursos de la API.
Probablemente, la forma más sencilla es usar las URL de los recursos de la API como identificadores de cadena. Pero las URL de las páginas web http://my.web.app/person/http%3A%2F%2Fmy.rest.api%2Fapi%2Fperson%2F1234
son feas.
Todo parece bastante fácil para una aplicación de escritorio o una aplicación de JavaScript de una sola página. Como viven continuamente, solo pueden mantener las URL en la memoria junto con los objetos de servicio durante la vida útil de la aplicación y usarlas cuando sea necesario.
Con una aplicación web puedo imaginar varios enfoques, pero todos parecen extraños:
- Reemplace el host en las URL de API y mantenga solo el resultado. La gran desventaja es que requiere que la aplicación web maneje cualquier URL que genere la API, lo que significa un acoplamiento monstruoso. Además, no es RESTful nuevamente, porque mi aplicación web comienza a interpretar las URL.
- Exponga los identificadores sin procesar en la API REST junto con los enlaces, úselos para construir las URL de la aplicación web y luego use los identificadores en el servidor de la aplicación web para encontrar los recursos necesarios en la API. Esto es mejor, pero afectará el rendimiento del servidor de la aplicación web porque la aplicación web tendrá que pasar por la navegación del servicio REST emitiendo una cadena de solicitudes get-by-id de alguna forma para manejar cualquier solicitud de un navegador. Para un recurso algo anidado, esto puede ser costoso.
- Almacene todas las
self
URL devueltas por la API en una asignación persistente (DB?) En el servidor de aplicaciones web. Genere algunos identificadores para ellos, use los identificadores para crear las URL de la página de la aplicación web y obtener las URL de los recursos del servicio REST. Es decir, mantengo lahttp://my.rest.api/pet/678
URL en algún lugar con una nueva clave, digamos3
, y genero la URL de la página web comohttp://my.web.app/pet/3
. Esto parece una implementación de caché HTTP de algún tipo. No sé por qué, pero me parece extraño.
¿O significa que las API RESTful no pueden servir como backends para aplicaciones web?
fuente
Respuestas:
Editado para abordar actualizaciones de preguntas, se eliminó la respuesta anterior
Al revisar sus cambios a su pregunta, creo que entiendo el problema que enfrenta un poco más. Como no hay un campo que sea un identificador en sus recursos (solo un enlace), no tiene forma de referirse a ese recurso específico dentro de su GUI (es decir, un enlace a una página que describe una mascota específica).
Lo primero que debe determinar es si una mascota tiene sentido sin un dueño. Si podemos tener una mascota sin dueño, diría que necesitamos algún tipo de propiedad única en la mascota que podamos usar para referirnos a ella. No creo que esto violaría no exponer la ID directamente ya que la ID real del recurso aún estaría escondida en un enlace que el cliente REST no analizaría. Con eso en mente, nuestro recurso para mascotas puede verse así:
Ahora podemos actualizar el nombre de esa mascota de Spot a Fido sin tener que meterse con ninguna ID de recursos en toda la aplicación. Del mismo modo, podemos referirnos a esa mascota en nuestra GUI con algo como:
Si la mascota no tiene ningún sentido sin un dueño (o las mascotas no están permitidas en el sistema sin un dueño), entonces podemos usar al dueño como parte de la "identidad" de la mascota en el sistema:
Una pequeña nota, si tanto las mascotas como las personas pueden existir separadas entre sí, no convertiría el punto de entrada para la API en el recurso "Personas". En cambio, crearía un recurso más genérico que contendría un enlace a Personas y mascotas. Podría devolver un recurso que se parece a:
Entonces, al conocer solo el primer punto de entrada a la API y no procesar ninguna de las URL para descubrir los identificadores del sistema, podemos hacer algo como esto:
El usuario inicia sesión en la aplicación. El cliente REST accede a la lista completa de recursos de personas disponibles que pueden verse así:
La GUI recorrerá cada recurso e imprimirá un elemento de lista para cada persona usando UniqueName como "id":
Al hacer esto, también podría procesar cada enlace que encuentre con un rel de "mascota" y obtener el recurso de mascota como:
Utilizando esto, puede imprimir un enlace como:
o
Si vamos con el primer enlace y asumimos que nuestro recurso de entrada tiene un enlace con una relación de "mascotas", el flujo de control sería algo así en la GUI:
Usar el segundo enlace sería una cadena de eventos similar, con la excepción de que People es el punto de entrada a la API y primero obtendríamos una lista de todas las personas en el sistema, encontraríamos la que coincida y luego encontraríamos todas las mascotas que pertenecen a esa persona (usando la etiqueta rel nuevamente) y encuentre la que se llama Spot para que podamos mostrar la información específica relacionada con ella.
fuente
rel
s para elegir los enlaces, pero no deben asumir ningún conocimiento de la estructura de las URL. REST afirma que una API es libre de cambiar las URL a voluntad, siempre querel
sigan siendo las mismas. Analizar las URL nos acerca más a SOAP que a REST.Desafío si vale la pena diferenciar entre una API REST y una aplicación web. Su "aplicación web" debe ser sólo representaciones alternativas (HTML) de los mismos recursos - que equivale a decir, que no entiendo cómo o por qué se puede esperar para el acceso
http://my.rest.api/...
yhttp://my.web.app/...
, y que sean al mismo tiempo iguales y diferentes.Su "cliente" es el navegador en este caso y entiende HTML y JavaScript. Esa es la aplicación web en mi opinión. Ahora puede estar en desacuerdo y pensar que accede a dicha aplicación web usando foo.com y expone todo lo demás a través de api.foo.com, pero luego debe preguntar, ¿cómo me proporcionó foo.com la representación del recurso? El "back-end" de foo.com es perfectamente capaz de comprender cómo descubrir recursos de api.foo.com. Su aplicación web se ha convertido simplemente en un proxy, no diferente de si estuviera hablando con otra API (de otra persona) todos juntos.
Por lo tanto, su pregunta se puede generalizar a "¿Cómo puedo describir los recursos utilizando mis propios URI que existen en otros sistemas?" lo cual es trivial cuando considera que no es el cliente (el HTML / JavaScript) el que debe entender cómo hacer esto, sino el servidor. Si está de acuerdo con mi primer desafío, simplemente puede pensar en su aplicación web como una API REST separada que representa o delega a otra API REST.
Entonces, cuando su cliente accede
my.web.app/pets/1
, sabe que debe presentar la interfaz de mascota porque eso es lo que devolvió la plantilla del lado del servidor, o si se trata de una solicitud asincrónica de alguna otra representación (por ejemplo, JSON o XML), el encabezado de tipo de contenido lo indica .El servidor que proporciona esto es el responsable de comprender qué es una mascota y cómo descubrirla en el sistema remoto. La forma de hacerlo depende de usted: puede simplemente tomar la ID y generar otro URI, que es lo que considera inapropiado, o puede tener su propia base de datos que almacena el URI remoto y representa la solicitud. Almacenar este URI está bien, es equivalente a marcar como favorito. Harías todo esto solo para tener un nombre de dominio separado. Sinceramente, no sé por qué quieres esto: tus URI de API REST también deben poder agregar marcadores.
Ya ha mencionado la mayor parte de esto en su pregunta, pero siento que lo ha enmarcado de una manera que realmente no reconoce que es la forma práctica de hacer lo que quiere hacer (en base a lo que siento es un restricción arbitraria: que la API y la aplicación estén separadas). Al preguntar si las API REST no pueden ser back-end para aplicaciones web y sugerir que el rendimiento sería un problema, creo que te estás centrando en las cosas incorrectas. Es como decir que no puedes crear un Mashup. Es como decir que la web no funciona.
fuente
my.web.app/pets/3
sin analizar las URL de REST API'?my.web.app/pets/3
sin analizar la URL del recurso REST API correspondientemy.rest.api/v0/persons/2/pets/3
? ¿O qué pongo allí?3
enapp/pets/3
porqueapp/pets/3
es opaca, lo que apunta a los recursos de su aplicación web quiere. Si esa es una vista compuesta de varios otros recursos (en otros sistemas, su API es uno de ellos), entonces depende de usted almacenar los hipervínculos a esos sistemas dentro del servidor de aplicaciones web, y luego recuperarlos, resolverlos en sus representaciones ( JSON o XML) y luego servirlos como parte de su respuesta.board/1
apuntafacebook.com/post/123
ytwitter.com/status/789
, cuando vaya a proporcionar una representación de su tablero, tendrá que resolver esos URI a una representación con la que pueda trabajar. Caché donde sea necesario.Prefacio
Esta respuesta aborda específicamente la cuestión de cómo administrar su propio esquema de URL, incluidas las URL únicas que se pueden marcar para recursos para los cuales la API REST de fondo no expone explícitamente un identificador, y sin interpretar las URL proporcionadas por la API.
La capacidad de descubrimiento requiere una cierta cantidad de conocimiento, así que aquí está mi opinión sobre un escenario del mundo real:
Digamos que queremos una página de búsqueda en la
http://my.web.app/person
que los resultados incluyan un enlace a la página de detalles para cada persona. La única cosa que nuestro código de aplicaciones para usuario debe conocer a fin de hacer nada en absoluto es la URL base de su fuente de datos REST:http://my.rest.api/api
. La respuesta a una solicitud GET a esta URL podría ser:Como nuestra intención es mostrar una lista de personas, a continuación enviamos una
GET
solicitud a href desde elperson
enlace href, que podría devolver:Queremos mostrar los resultados de búsqueda, por lo que utilizaremos el servicio de búsqueda enviando una
GET
solicitud alsearch
enlace href, que podría devolver:Finalmente tenemos nuestros resultados, pero ¿cómo construimos nuestras URL front-end?
Eliminemos la parte que conocemos con certeza: la URL base de la API, y usemos el resto como nuestro identificador front-end:
http://my.rest.api/api
http://my.rest.api/api/person/1
/person/1
http://my.web.app
http://my.web.app/person/1
Nuestros resultados pueden verse así:
Una vez que un usuario sigue ese enlace frontal a la página de detalles, ¿a qué URL enviamos la
GET
solicitud de detalles específicosperson
? Conocemos nuestro método para mapear las URL de back-end en las URL de front-end, por lo que simplemente lo revertimos:http://my.web.app/person/1
http://my.web.app
/person/1
http://my.rest.api/api
http://my.rest.api/api/person/1
Si la API REST cambia de manera tal que
person
ahora hay una URLhttp://my.rest.api/api/different-person-base/person/1
y alguien había marcado previamentehttp://my.web.app/person/1
, la API REST debería (al menos por un tiempo) proporcionar compatibilidad con versiones anteriores respondiendo a la URL anterior con una redirección a la nueva. Todos los enlaces front-end generados incluirían la nueva estructura automáticamente.Como probablemente haya notado, hay varias cosas que debemos saber para navegar por la API:
person
relacionsearch
relacionNo creo que haya nada malo en esto; no estamos asumiendo una estructura de URL específica en ningún momento, por lo que la estructura de la URL de la entidad
http://my.rest.api/api/person/1
podría cambiar, y mientras la API proporcione compatibilidad con versiones anteriores, nuestro código seguirá funcionando.Preguntó cómo nuestra lógica de enrutamiento podría diferenciar entre dos URL de front-end:
http://my.rest.api/api/person/1
http://my.rest.api/api/pet/3
.Primero, señalaré que usó la base API en su comentario cuando, en nuestro ejemplo, usamos URL base separadas para la UI y la API REST. Continuaré el ejemplo usando bases separadas, pero compartir una base no es un problema. Podemos (o deberíamos poder) asignar métodos de enrutamiento de IU utilizando el tipo de medio del encabezado Aceptar de la solicitud.
En cuanto al enrutamiento a una página de detalles específica, no podemos diferenciar esas dos URL si somos estrictos para evitar cualquier conocimiento sobre la estructura de la
self
URL proporcionada por la API (es decir, la identificación de cadena opaca). Para que esto funcione, incluyamos otra de nuestras piezas de información conocidas, el tipo de entidad con el que estamos trabajando, en nuestras URL de front-end.Anteriormente, nuestras URL de front-end tenían el formato:
${UI base}/${opaque string id}
El nuevo formato podría ser:
${UI base}/${entity type}/${opaque string id}
Entonces, usando el
/person/1
ejemplo, terminaríamos conhttp://my.web.app/person/person/1
.Con este formato, nuestra lógica de enrutamiento de la interfaz de usuario funcionaría
/person/person/1
, y sabiendo que nosotros insertamos el primer token en la cadena, podemos extraerlo y enrutarlo a la página de detalles apropiada (persona, en este ejemplo) en función de él. Si se siente avergonzado acerca de esa URL, podríamos insertar un poco más allí; tal vez:http://my.web.app/person/detail/person/1
En cuyo caso, analizaríamos el
/person/detail
enrutamiento y usaremos el resto como la identificación de cadena opaca.Supongo que quiere decir que, dado que nuestra URL front-end generada contiene parte de la URL de la API, si la estructura de la URL de la API cambia sin admitir la estructura anterior, necesitaremos un cambio de código para traducir la URL marcada al marcador nueva versión de la URL de la API. En otras palabras, si la API REST cambia la ID de un recurso (la cadena opaca), entonces no podemos hablar con el servidor sobre ese recurso usando la ID anterior. No creo que podamos evitar un cambio de código en esa situación.
Puede usar cualquier estructura de URL que desee. Al final del día, una URL marcable para un recurso específico debe incluir algo que pueda usar para obtener una URL de API que identifique ese recurso de manera única. Si genera su propio identificador y lo almacena en caché con la URL de API como en su enfoque # 3, eso funcionará hasta que alguien intente usar esa URL marcada después de que esa entrada se borre de la caché.
La respuesta depende de la relación. De cualquier manera, necesitaría una forma de asignar el front-end a las URL de API.
fuente
person/1
,pet/3
), entonces ¿cómo podría saber que si un navegador abrehttp://my.rest.api/api/person/1
debe mostrar persona de interfaz de usuario, y si se abrehttp://my.rest.api/api/pet/3
, luego la interfaz de usuario de la mascota?Seamos realistas, no hay una solución mágica. ¿Has leído el modelo de madurez de Richardson ? Divide la madurez de la arquitectura REST en 3 niveles: recursos, verbos HTTP y controles hipermedia.
Esto es controles hipermedia. ¿Realmente lo necesitas? Este enfoque tiene algunos beneficios muy buenos (puede leer sobre ellos aquí ). Pero no hay comidas gratuitas y tendrá que trabajar duro (por ejemplo, su segunda solución) si desea obtenerlas.
Es una cuestión de equilibrio: ¿desea sacrificar el rendimiento (y hacer que su código sea más complicado) pero obtener un sistema que sea más flexible? ¿O prefiere mantener las cosas más rápidas y simples pero pagar después cuando introduce cambios en su api / modelo?
Como alguien que desarrolló un sistema similar (nivel de lógica empresarial, nivel web y clientes web) elegí la segunda opción. Dado que mi grupo desarrolló todos los niveles, decidimos que es mejor tener un poco de acoplamiento (al permitir que el nivel web conozca los identificadores de entidad y la creación de api urls) y, a cambio, obtenga un código que sea más simple. La compatibilidad con versiones anteriores tampoco era relevante en nuestro caso.
Si la aplicación web fue desarrollada por un tercero o si la compatibilidad con versiones anteriores era un problema, podríamos haber elegido de manera diferente porque entonces era muy valioso poder cambiar la estructura de la URL sin cambiar la aplicación web. Suficiente para justificar la complicación del código.
Creo que significa que no tiene que crear una implementación REST perfecta. Puede ir con su segunda solución, o exponer los identificadores de la entidad o tal vez pasar api urls . Está bien, siempre y cuando comprenda las implicaciones y las compensaciones.
fuente
Creo que si te apegas a algo similar a lo
Atom Syndication Format
que eres bueno.Aquí, los metadatos que describen la entrada que se representa se pueden especificar utilizando elementos / atributos adicionales:
Según [RFC4287] , contiene un URI que identifica de forma exclusiva la entrada
Según [RFC4287] , este elemento es opcional. Si se incluye, contiene el URI que un cliente debe usar para recuperar la Entrada.
Estos son mis únicos dos centavos.
fuente
No te preocupes por las URL, preocúpate por los tipos de medios.
Ver aquí (tercer punto en particular).
En el caso de una aplicación web típica, el cliente es un humano ; El navegador es solo un agente .
Entonces una etiqueta de anclaje como
corresponde a algo como
La URL todavía es opaca para el usuario, lo único que le importa son los tipos de medios (por ejemplo
text/html, application/pdf, application/flv, video/x-flv, image/jpeg, image/funny-cat-picture etc
). El texto descriptivo contenido en el ancla (y en el atributo de título) es solo una forma de extender el tipo de relación de una manera que sea inteligible para los humanos.La razón por la que desea que el URI sea opaco para los clientes es para reducir el acoplamiento (uno de los objetivos principales de REST). El servidor puede cambiar / reorganizar los URI sin afectar al cliente (siempre que tenga una buena política de almacenamiento en caché, lo que puede significar que no haya almacenamiento en caché).
En resumen
Solo asegúrese de que el cliente (humano o máquina) se preocupe por los tipos de medios y las relaciones en lugar de las URL y estará bien.
fuente
Creo que tienes razón, esa es la forma más simple. Puedes relativizar las URL
http://my.rest.api/api
para que sean menos feas:Si la URL proporcionada por la API no es relativa a esa base, se degrada a la forma fea:
Para ir un paso más allá, inspeccione la respuesta del servidor API para determinar qué tipo de vista desea presentar y deje de codificar el delimitador de segmento de ruta y los dos puntos:
fuente