Pregunta:
Varios conceptos relacionados con REST entran en conflicto en mi cabeza cuando intento implementarlo.
Tengo un sistema de API de back-end REST 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: Arquitectura de sistemas y hipermedia ) sé que no debería exponer identificadores sin procesar de mis entidades, sino devolver hipervínculos con rel="self"
.
Considere el ejemplo. La API REST tiene un recurso que devuelve una persona:
<Person>
<Links>
<Link href="http://my.rest.api/api/person/1234"/>
</Links>
<Pets>
<Link 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 atributo href
? ¿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 llevar a la aplicación web porque es el sistema que aloja la interfaz de usuario. - El
href
debe tener alguna identificación de la entidad porque la aplicación web debe poder dirigirse 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
de la URL de respuesta de la API porque, como cliente RESTful, debería tratarlo como si fuera algo así como http://my.rest.api/api/AGRIDd~ryPQZ^$RjEL0j
. Por otro lado, debo proporcionar alguna URL que lleve a mi aplicación web y sea suficiente para que la aplicación restaure de alguna manera la URL original de la API y use esa URL para acceder a los recursos de la API.
La forma más sencilla probablemente sea simplemente usar las URL de API de los recursos como sus identificadores de cadena. Pero las URL de páginas web como 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. Dado que viven de forma continua, pueden simplemente mantener las URL en la memoria junto con los objetos de servicio durante la vida útil de la aplicación y utilizarlas cuando sea necesario.
Con una aplicación web, puedo imaginar varios enfoques, pero todos parecen raros:
- Reemplace el host en las URL de la API y conserve 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 ID sin procesar en la API REST junto con los enlaces, utilícelos para crear las URL de la aplicación web y luego utilice los ID 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 desde un navegador. Para un recurso algo anidado, esto podría resultar costoso.
- Almacene todas
self
URLself
devueltas por la API en una asignación persistente (¿DB?) En el servidor de la aplicación web. Genere algunos ID para ellos, use los ID 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, guardo la URLhttp://my.rest.api/pet/678
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 todo esto que las API RESTful no pueden servir como backends para aplicaciones web?
Respuesta:
Editado para abordar actualizaciones de preguntas, respuesta anterior eliminada
Al revisar los cambios a su pregunta, creo que entiendo un poco más el problema al que se enfrenta. 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 hay que determinar es si alguna vez una mascota tiene sentido sin un dueño. Si podemos tener una mascota sin dueño, entonces yo 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 el ID directamente, ya que el ID del recurso real aún estaría escondido en un enlace que el cliente REST no analizaría. Con eso en mente, nuestro recurso para mascotas puede verse así:
<Entity type="Pet">
<Link href="http://example.com/pets/1" />
<Link href="http://example.com/people/1" />
<UniqueName>Spot</UniqueName>
</Entity>
Ahora podemos actualizar el nombre de esa mascota de Spot a Fido sin tener que meternos con ningún ID de recurso real en toda la aplicación. Asimismo, 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:
http://example.com/GUI/owners/John/pets/1 (primera mascota en la lista para John)
Una pequeña nota, si tanto las Mascotas como las Personas pueden existir por separado, no convertiría el punto de entrada para la API en el recurso "Personas". En su lugar, 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:
<Entity type="ResourceList">
<Link href="http://example.com/api/people" />
<Link href="http://example.com/api/pets" />
</Entity>
Entonces, al conocer solo el primer punto de entrada a la API y no procesar ninguna de las URL para averiguar 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 tener este aspecto:
<Entity type="Person">
<Link href="http://example.com/api/people/1" />
<Pets>
<Link href="http://example.com/api/pets/1" />
<Link href="http://example.com/api/pets/2" />
</Pets>
<UniqueName>John</UniqueName>
</Entity>
<Entity type="Person">
<Link href="http://example.com/api/people/2" />
<Pets>
<Link href="http://example.com/api/pets/3" />
</Pets>
<UniqueName>Jane</UniqueName>
</Entity>
La GUI recorrerá cada recurso e imprimirá un elemento de lista para cada persona que use el UniqueName como "id":
<a href="http://example.com/gui/people/1">John</a>
<a href="http://example.com/gui/people/2">Jane</a>
Mientras hace esto, también podría procesar cada enlace que encuentre con un rel de "mascota" y obtener el recurso de mascota como:
<Entity type="Pet">
<Link href="http://example.com/api/pets/1" />
<Link href="http://example.com/api/people/1" />
<UniqueName>Spot</UniqueName>
</Entity>
Al usar esto, puede imprimir un enlace como:
<!-- Assumes that a pet can exist without an owner -->
<a href="http://example.com/gui/pets/Spot">Spot</a>
o
<!-- Assumes that a pet MUST have an owner -->
<a href="http://example.com/gui/people/John/pets/Spot">Spot</a>
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 como esto en la GUI:
- Se abre la página y se solicita el Spot de mascota.
- Cargue la lista de recursos desde el punto de entrada de la API.
- Cargue el recurso relacionado con el término "mascotas".
- Examine cada recurso de la respuesta "mascotas" y encuentre uno que coincida con Spot.
- Muestra la información del lugar.
Usar el segundo enlace sería una cadena de eventos similar con la excepción de que Personas 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 todas las mascotas que pertenecen a esa persona (usando la etiqueta rel nuevamente) y busque la que se llama Spot para que podamos mostrar la información específica relacionada con ella.