RESTO complejo / compuesto / recursos nesteds

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

Por ejemplo, tengo un recurso para ComicBook. ComicBook tiene todo tipo de propiedades como autor, número de emisión, fecha, etc.

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

Para un GET en ComicBook, podría devolver el comic y todas las portadas, incluidas sus imágenes basadas en base64. Probablemente no sea un gran problema para obtener un solo comic. 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 recurso ComicBook, pero ciertamente no vamos a querer mostrar todas las carátulas de la tabla. La devolución de 1000 cómics, cada uno con varias cubiertas, daría como resultado una cantidad ridículamente grande de datos que llegarían a través del cable, datos que no son necesarios para el usuario final en ese caso.

Mi instinto es hacer que Cover sea un recurso y ComicBook contiene covers. Entonces, Cover es un URI. GET en el cómic funciona ahora, en lugar del gran recurso Cover, enviamos de regreso un URI para cada portada y los clientes pueden recuperar los recursos Cover a medida que 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 historieta, de hecho esa es probablemente una regla de negocios. Así que ahora estoy atascado, obligo a los clientes a hacer cumplir las reglas comerciales presentando primero una portada, obteniendo el URI para esa portada, luego publicando un cómic con ese URI en la lista, o mi POST en el cómic toma un aspecto diferente Recurso de lo que escupe. Los recursos entrantes para POST y GET son copias profundas, donde los GET salientes contienen referencias a recursos dependientes.

El recurso Cover es probablemente necesario en cualquier caso porque estoy seguro de que como cliente querría abordar la dirección de Covers en algunos casos. Entonces, el problema existe de forma general, independientemente del tamaño del recurso dependiente. En general, ¿cómo maneja los Recursos complejos sin forzar al cliente a simplemente “saber” cómo se componen esos recursos?

@ray, excelente discusión

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

Lo que elijas incluir en cualquier representación dada de un recurso depende de ti.

Su caso de las portadas a las que se hace referencia por separado es simplemente la creación de un recurso padre (cómic) cuyos recursos secundarios (portadas) pueden tener referencias cruzadas. Por ejemplo, también puede 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 haga referencia como recursos secundarios. Alternativamente, es posible que desee crear nuevos recursos secundarios al crear el recurso principal.

Su caso específico de las portadas es un poco más complejo en cuanto a 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 seguir haciendo referencia a la dirección de origen por separado. Por ejemplo, obtenga todas las direcciones. O crea un nuevo mensaje con una dirección anterior. Si el correo electrónico era REST, podría ver fácilmente que muchos recursos con referencias cruzadas podrían estar disponibles: / messages-messages, / draft-messages, / from-addresses, / to-addresses, / addresses, / subjects, / attachments, / folders , / tags, / categories, / labels, et al.

Este tutorial proporciona un excelente 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, el ID o la fecha de creación cuando recupera el nuevo recurso.

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

Las formas y los 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 / nuevo) u obtiene el formulario para editar un recurso (/ comic-books / 0 / edit), está solicitando una representación del recurso específica para formularios. Si lo publica en la colección de recursos con el tipo de contenido “application / x-www-form-urlencoded” o “multipart / form-data”, le está pidiendo al servidor que guarde esa representación de tipo. El servidor puede responder con la representación HTML que se guardó, o lo que sea.

También puede 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 usted describe, teniendo en cuenta las cubiertas publicadas después del cómic, pero que requieren que los cómics tengan una cubierta. Ejemplo de la siguiente manera.

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

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

GET / comic-books / 0
=> 200 Aceptar, obtener cómic (id: 0) con portadas (/ portadas / 1, / portadas / 2).

GET / comic-books / 0 / covers
=> 200 Aceptar, obtener cubiertas para cómic (id: 0).

GET / cubre
=> 200 OK, obtener todas las cubiertas.

GET / covers / 1
=> 200 OK, obtén portada (id: 1) con comic book (/ comic-books / 0).

GET / comic-books / nuevo
=> 200 Aceptar, obtener formulario para crear cómics (formulario: POST / draft-comic-books).

POST / draft-comic-books
title = foo
autor = abucheo
editor = goo
publicado = 2011-01-01
=> 302 Encontrado, Ubicación: / draft-comic-books / 3, Redirigir a borrador del cómic (id: 3) con carátulas (binario).

GET / draft-comic-books / 3
=> 200 OK, obtenga el borrador del cómic (id: 3) con cubiertas.

GET / draft-comic-books / 3 / covers
=> 200 Aceptar, obtener portadas para el cómic de borrador (/ draft-comic-book / 3).

GET / draft-comic-books / 3 / covers / new
=> 200 OK, obtenga un formulario para crear una cubierta para el cómic de borrador (/ draft-comic-book / 3) (formulario: POST / draft-comic-books / 3 / covers).

POST / draft-comic-books / 3 / covers
tipo_cubierta = frente
cover_data = (binario)
=> 302 Encontrado, Ubicación: / draft-comic-books / 3 / covers, Redirige a una nueva portada para el cómic de borrador (/ draft-comic-book / 3 / covers / 1).

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

POST / publicado-comic-books
title = foo
autor = abucheo
editor = goo
publicado = 2011-01-01
tipo_cubierta = frente
cover_data = (binario)
=> 302 Encontrado, Ubicación: / comic-books / 3, redirigir al cómic publicado (id: 3) con carátulas.

Tratar las cubiertas como recursos definitivamente está en el espíritu de REST, particularmente HATEOAS. Entonces sí, una solicitud GET a http://example.com/comic-books/1 le 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 comics. 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 los datos del cómic sin tapa, se creará un nuevo cómic y se devolverá el ID generado por el servidor (digamos que vuelve a ser 8), y ahora puedes agregarle portadas de la siguiente manera:

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

con la cubierta en el cuerpo de la entidad.

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

  1. Primero fuerce la creación de una portada, ahora esencialmente haciendo que la portada sea un recurso no dependiente, o coloque la portada inicial en el cuerpo de la entidad del POST que crea el cómic. Esto, como dices, significa que la representación que PUBLICAS para crear diferirá de la representación que obtienes.

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

Debes sopesar estas dos opciones en contra de simplemente dejar cómics sin tapa.

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

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

  • Si simplemente debe modelar una restricción 1.N, pregúntese si las cubiertas podrían ser recursos compartibles, en otras palabras, podrían existir en otras cosas que no sean cómics. Ahora no son recursos dependientes y puedes crearlos primero y suministrar URI en tu 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 OBTENGA lo mismo, o haga que sean iguales.

El último elemento se explica así:

  ... ... ...BASE64... ...BASE64... ...URI... ...URI...  

Cuando PUBLICA, permites uris existentes si los tienes (tomados 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 un 409 o una respuesta similar. En GET, puede devolver los URI.

Entonces, básicamente, estás permitiendo que las representaciones POST y GET sean “iguales”, pero simplemente eliges no “usar” la imagen de portada en GET ni cubrir en POST. Espero que tenga sentido.