REST Complejo / Compuesto / Recursos anidados [cerrado]

177

Estoy tratando de entender la mejor manera de abordar los conceptos en una API basada en REST. Los recursos planos que no contienen otros recursos no son un problema. Donde me encuentro con problemas son los recursos complejos.

Por ejemplo, tengo un recurso para un cómic. ComicBooktiene todo tipo de propiedades sobre ella como author, issue number, date, etc.

Un cómic también tiene una lista de 1..nportadas. Estas cubiertas son objetos complejos. Contienen mucha información sobre la portada: el artista, una fecha e incluso una imagen codificada de base 64 de la portada.

Para una GETen ComicBooktan sólo pudiera volver el cómic, y todas las cubiertas incluyendo sus imágenes base64'ed. Probablemente no sea un gran problema para obtener un solo cómic. Pero supongamos que estoy construyendo una aplicación cliente que quiere enumerar todos los cómics en el sistema en una tabla.
La tabla contendrá algunas propiedades del ComicBookrecurso, pero ciertamente no vamos a querer mostrar todas las portadas de la tabla. La devolución de 1000 cómics, cada uno con varias portadas, daría como resultado una cantidad ridículamente grande de datos que se cruzan por el cable, datos que no son necesarios para el usuario final en ese caso.

Mi instinto es hacer Coverun recurso y tener ComicBookportadas. Entonces ahora Coveres un URI. GETen el cómic funciona ahora, en lugar del enorme Coverrecurso, enviamos un URI para cada cubierta y los clientes pueden recuperar los recursos de la cubierta según lo requieran.

Ahora tengo un problema con la creación de nuevos cómics. Seguramente voy a querer crear al menos una cubierta cuando creo una Comic, de hecho, esa es probablemente una regla de negocios.
Así que ahora estoy atascado, yo tampoco obligar a los clientes a cumplir las reglas de negocio mediante la presentación de una primera Cover, para obtener el URI para que la cobertura, a continuación, POSTing una ComicBookcon la URI de la lista, o mi POSTde ComicBooktoma en un recurso mirando diferente de lo que escupe fuera. Los recursos entrantes para POSTy GETson copias profundas, donde los salientes GETcontienen referencias a recursos dependientes.

El Coverrecurso es probablemente necesario en cualquier caso porque estoy seguro como cliente que me gustaría abordar la dirección de las cubiertas en algunos casos. Por lo tanto, el problema existe en forma general, independientemente del tamaño del recurso dependiente. En general, ¿cómo maneja los recursos complejos sin obligar al cliente a simplemente "saber" cómo están compuestos esos recursos?

jgerman
fuente
¿ Tiene sentido usar DESCUBRIMIENTO DE SERVICIO RESTFUL ?
Treecoder
1
Estoy tratando de adherirme a HATEAOS que, en mi opinión, va en contra del uso de algo así, pero lo echaré un vistazo.
jgerman
Pregunta diferente en el mismo espíritu. Sin embargo, la propiedad es diferente a la solución propuesta (la de la pregunta). stackoverflow.com/questions/20951419/…
Wes

Respuestas:

64

@ray, excelente discusión

@jgerman, no olvides que solo porque es REST, no significa que los recursos tengan que ser inamovibles desde POST.

Lo que elija incluir en cualquier representación dada de un recurso depende de usted.

Su caso de las portadas a las que se hace referencia por separado es simplemente la creación de un recurso principal (cómic) cuyos recursos secundarios (portadas) pueden tener referencias cruzadas. Por ejemplo, es posible que también desee proporcionar referencias a autores, editores, personajes o categorías por separado. Es posible que desee crear estos recursos por separado o antes del cómic que los hace referencia como recursos secundarios. Alternativamente, es posible que desee crear nuevos recursos secundarios al crear el recurso primario.

Su caso específico de las portadas es un poco más complejo ya que una portada realmente requiere un cómic y viceversa.

Sin embargo, si considera un mensaje de correo electrónico como un recurso y la dirección de origen como un recurso secundario, obviamente puede hacer referencia a la dirección de origen por separado. Por ejemplo, obtenga todo de las direcciones. O cree un nuevo mensaje con una dirección anterior. Si el correo electrónico fuera REST, podría ver fácilmente que muchos recursos con referencias cruzadas podrían estar disponibles: / mensajes-recibidos, / borradores-mensajes, / de-direcciones, / a-direcciones, / direcciones, / asuntos, / archivos adjuntos, / carpetas , / tags, / categories, / labels, et al.

Este tutorial proporciona un gran ejemplo de recursos con referencias cruzadas. http://www.peej.co.uk/articles/restfully-delicious.html

Este es el patrón más común para los datos generados automáticamente. Por ejemplo, no publica un URI, ID o fecha de creación para el nuevo recurso, ya que estos son generados por el servidor. Y, sin embargo, puede recuperar el URI, la ID o la fecha de creación cuando recupere el nuevo recurso.

Un ejemplo en su caso de datos binarios. Por ejemplo, desea publicar datos binarios como recursos secundarios. Cuando obtiene el recurso primario, puede representar esos recursos secundarios como los mismos datos binarios, o como URI que representan los datos binarios.

Los formularios y parámetros ya son diferentes a las representaciones HTML de los recursos. Publicar un parámetro binario / archivo que da como resultado una URL no es exagerado.

Cuando obtiene el formulario para un nuevo recurso (/ comic-books / new), u obtiene el formulario para editar un recurso (/ comic-books / 0 / edit), está solicitando una representación específica del formulario del recurso. Si lo publica en la colección de recursos con el tipo de contenido "application / x-www-form-urlencoded" o "multipart / form-data", está solicitando al servidor que guarde ese tipo de representación. El servidor puede responder con la representación HTML que se guardó, o lo que sea.

Es posible que también desee permitir que se publique una representación HTML, XML o JSON en la colección de recursos, para fines de una API o similar.

También es posible representar sus recursos y flujo de trabajo como lo describe, teniendo en cuenta las portadas publicadas después del cómic, pero que requieren que los cómics tengan una portada. Ejemplo como sigue.

  • Permite la creación de portadas retrasadas
  • Permite la creación de cómics con la portada requerida
  • Permite que las cubiertas tengan referencias cruzadas
  • Permite múltiples portadas
  • Crear borrador de cómic
  • Crear borradores de portadas de cómics
  • Publicar borrador de cómic

GET / comic-books
=> 200 OK, obtenga todos los cómics.

GET / comic-books / 0
=> 200 OK, obtenga un cómic (id: 0) con portadas (/ covers / 1, / covers / 2).

GET / comic-books / 0 / covers
=> 200 OK, obtenga portadas para cómics (id: 0).

GET / covers
=> 200 OK, obtenga todas las portadas.

GET / covers / 1
=> 200 OK, obtenga cover (id: 1) con cómic (/ comic-books / 0).

GET / comic-books / new
=> 200 OK, obtenga el formulario para crear un cómic (formulario: POST / draft-comic-books).

POST / draft-comic-books
title = foo
author = boo
publisher = goo publishing
= 2011-01-01
=> 302 Encontrado, Ubicación: / draft-comic-books / 3, Redirigir al borrador de cómic (id: 3) con cubiertas (binarias).

GET / draft-comic-books / 3
=> 200 OK, obtenga un borrador de cómic (id: 3) con portadas.

GET / draft-comic-books / 3 / covers
=> 200 OK, obtenga portadas para el borrador del cómic (/ draft-comic-book / 3).

GET / draft-comic-books / 3 / covers / new
=> 200 OK, obtenga el formulario para crear la portada del borrador del cómic (/ draft-comic-book / 3) (formulario: POST / draft-comic-books / 3 / cubiertas).

POST / draft-comic-books / 3 / covers
cover_type = front
cover_data = (binary)
=> 302 Encontrado, Ubicación: / draft-comic-books / 3 / covers, Redirigir a una nueva portada para el borrador del cómic (/ draft-comic -book / 3 / covers / 1).

GET / draft-comic-books / 3 / publishing
=> 200 OK, obtenga el formulario para publicar el borrador del cómic (id: 3) (formulario: POST / updated-comic-books).

POST / updated-comic-books
title = foo
author = boo
publisher = goo publishing
= 2011-01-01
cover_type = front
cover_data = (binary)
=> 302 Encontrado, Ubicación: / comic-books / 3, Redirigir al cómic publicado (id: 3) con tapas.

Alex
fuente
Soy un novato en esto e intento aprenderlo rápidamente. Esto me pareció extremadamente útil. Sin embargo, en los otros blogs, etc. que he estado leyendo hoy, el uso de GET para realizar una operación (particularmente una operación que no es idempotente) estaría mal visto. Entonces, ¿no debería ser POST / draft-comic-books / 3 / Publish?
Gary McGill el
3
@GaryMcGill En su ejemplo, / draft-comic-books / 3 / Publish solo devuelve un formulario HTML (no modifica ningún dato).
Olivier Lalonde
@Olivier es correcto. La palabra publicar está ahí para denotar lo que hace el formulario. Sin embargo, debido a que desea mantener los verbos confinados a los métodos HTTP, debe publicar en un recurso para cómics publicados. ... Si se tratara de un sitio web, es posible que necesite un URI para que el formulario publique algo. ... Aunque, si la acción de publicación fue simplemente un botón en la página del cómic, ese formulario de un solo botón podría publicarse directamente en el URI / updated-comic-books.
Alex
@Alex, en la solicitud POST, en su lugar, devolvería un 201 Created, con la URL del nuevo recurso como Ubicación en los encabezados de respuesta.
ismriv
2
@Stephane, las redirecciones simplemente simplifican todo para los controladores. Incluso para una API, es más simple hacer que el controlador de creación devuelva la ubicación para el nuevo contenido y luego dejar que el controlador de la pantalla maneje la visualización del nuevo contenido. Sin embargo, es más agradable / simple para el cliente de la API obtener el contenido y no molestarse con los redireccionamientos.
Alex
45

El tratamiento de las cubiertas como recursos definitivamente está en el espíritu de REST, particularmente HATEOAS. Entonces, sí, una GETsolicitud http://example.com/comic-books/1le daría una representación del libro 1, con propiedades que incluyen un conjunto de URI para portadas. Hasta aquí todo bien.

Tu pregunta es cómo lidiar con la creación de cómics. Si su regla comercial era que un libro tendría 0 o más portadas, entonces no tiene ningún problema:

POST http://example.com/comic-books

con datos de cómic sin cubierta creará un nuevo cómic y devolverá la identificación generada por el servidor (digamos que vuelve como 8), y ahora puede agregarle portadas de la siguiente manera:

POST http://example.com/comic-books/8/covers

con la tapa en el cuerpo de la entidad.

Ahora tiene una buena pregunta: qué sucede si su regla de negocios dice que siempre debe haber al menos una cubierta. Aquí hay algunas opciones, la primera de las cuales identificó en su pregunta:

  1. Primero, fuerce la creación de una cubierta, ahora esencialmente haciendo que la cubierta sea un recurso no dependiente, o coloque la cubierta inicial en el cuerpo de la entidad de la POST que crea el cómic. Esto, como usted dice, significa que la representación que PUBLICA crear será diferente de la representación que OBTENGA.

  2. Defina la noción de una cobertura primaria, inicial, preferida o designada de otra manera. Es probable que sea un truco de modelado, y si lo hiciera, sería como ajustar su modelo de objetos (su modelo conceptual o de negocios) para que se ajuste a una tecnología. No es una gran idea

Debes sopesar estas dos opciones contra simplemente permitir cómics sin cobertura.

¿Cuál de las tres opciones debes tomar? Sin saber demasiado sobre su situación, pero responda la pregunta general de recursos dependientes 1..N, diría:

  • Si puedes ir con 0..N para tu capa de servicio RESTful, genial. Quizás una capa entre su SOA RESTful pueda manejar la restricción comercial adicional si se requiere al menos una. (No estoy seguro de cómo se vería eso, pero podría valer la pena explorarlo ... los usuarios finales no suelen ver el SOA de todos modos).

  • Si simplemente debe modelar una restricción 1..N, pregúntese si las portadas podrían ser recursos compartibles, en otras palabras, podrían existir en otras cosas que no sean los cómics. Ahora no son recursos dependientes y puede crearlos primero y proporcionar URI en su POST que crea cómics.

  • Si necesita 1..N y las cubiertas siguen siendo dependientes, simplemente relaje su instinto para mantener las representaciones en POST y GET iguales, o hacer que sean las mismas.

El último elemento se explica así:

<comic-book>
  <name>...</name>
  <edition>...</edition>
  <cover-image>...BASE64...</cover-image>
  <cover-image>...BASE64...</cover-image>
  <cover>...URI...</cover>
  <cover>...URI...</cover>
</comic-book>

Cuando publicas, permites uris existentes si las tienes (tomadas de otros libros) pero también colocas una o más imágenes iniciales. Si está creando un libro y su entidad no tiene una imagen de portada inicial, devuelva una respuesta 409 o similar. En GET puedes devolver URIs.

Básicamente, está permitiendo que las representaciones POST y GET sean "iguales" pero simplemente elige no "usar" la imagen de portada en GET ni la portada en POST. Espero que tenga sentido.

Ray Toal
fuente