2011-08-18 30 views
154

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.Complejo REST/compuesto/recursos anidados

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

Un cómic también tiene una lista de portadas 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 de portada es probablemente necesario en cualquier caso porque estoy seguro de que como cliente me gustaría abordar la dirección de las cubiertas 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?

+0

¿Tiene sentido usar [DESCUBRIMIENTO DE SERVICIO RESTANTE] (http://barelyenough.org/blog/2008/01/restful-service-discovery-and-description/)? – treecoder

+1

Estoy tratando de adherirme a HATEAOS, lo cual, en mi opinión, va en contra de usar algo así, pero lo echaré un vistazo. – jgerman

+0

Diferente pregunta en el mismo espíritu. Sin embargo, la propiedad es diferente a la solución propuesta (la de la pregunta). http://stackoverflow.com/questions/20951419/what-are-best-practices-for-rest-nested-resources – Wes

Respuesta

37

Tratar las fundas como recursos definitivamente está en el espíritu de REST, particularmente HATEOAS. Entonces sí, una solicitud GET al 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 la regla de negocio era que un libro tendría 0 o más cubiertas, entonces no tienes problema:

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

con los datos de cómic sin cubierta va a crear un nuevo libro de historietas y de retorno al servidor de identificador generado (digamos vuelve como 8), y ahora usted puede agregar cubiertas a él como así:

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 cubierta.Aquí están algunas opciones, la primera de las cuales se identificó en su pregunta:

  1. forzar la creación de una cubierta en primer lugar, ahora esencialmente haciendo cubrir un recurso no depende, o se coloque la tapa 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.

Debe sopesar estas dos opciones en contra de simplemente permitir comics sin tapa.

¿Cuál de las tres opciones debe tomar? Sin saber demasiado acerca de su situación, pero responder a la pregunta recurso dependiente 1..N en general, diría:

  • Si usted puede ir con 0..N para su capa de servicios REST, muy bien. 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 explorar ... los usuarios finales no suelen ver SOA de todos modos.)

  • Si simplemente debe modelar una restricción 1.N, pregúntese si las portadas pueden ser solo recursos compartibles, en otras palabras, pueden 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 dependiendo, simplemente relaje su instinto para mantener las representaciones en POST y OBTENER lo mismo, o hacerlas iguales.

El último punto se explica de este modo:

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

Al publicar permitirá URI existente si los tiene (tomado de otros libros), pero también puso en 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 URI ...

Así que, básicamente, está permitiendo que las representaciones POST y GET sean "iguales", pero simplemente opta por no "usar" la imagen de portada en GET ni cubrir en POST. Espero que tenga sentido.

58

@ray, excelente discusión

@jgerman, no se olvide de que sólo porque es RESTO, no significa que los recursos tienen que ser inamovible de la POST.

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

Su caso de las cubiertas a las que se hace referencia por separado es meramente 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 cubiertas es un poco más complejo en el sentido de 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 correo electrónico 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.

Formas & 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 específica de formularios 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", 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.

Es posible que desee permitir también que una representación HTML, XML o JSON se publique 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 portadas publicadas después del cómic, pero que requieren cómics para tener una cubierta. Ejemplo de la siguiente manera.

  • permite la creación de cubierta retardada
  • Permite la creación de cómics con la cubierta necesaria
  • Permite cubiertas para tener referencia cruzada
  • permite múltiples cubiertas
  • Crear proyecto cómic
  • Crear proyecto cómic covers
  • Publicar borrador del cómic

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

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

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

OBTENER/cubiertas
=> 200 OK, obtener todas las cubiertas.

GET/covers/1
=> 200 Aceptar, obtener portada (id: 1) con cómic (/ cómic-libros/0).

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

de POST/proyecto-cómico-libros
title = foo
autor = Boo
editor = goo
publicó = 2011-01-01
=> 302 Found, Ubicación:/proyecto-comic-books/3, redirigir a borrador de cómic (id: 3) con cubiertas (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 OK, obtenga portadas para el cómic de borrador (/ draft-comic-book/3).

GET/proyecto-cómics/3/cubiertas/
nueva => 200 OK, Obtener formulario para crear la cubierta para el proyecto de cómic (/ proyecto-cómic/3) (forma: Enviar/BORRADOR comic-books/3/covers).

de POST/proyecto-cómics/3/cubre
cover_type = frente
cover_data = (binarios)
=> 302 Encontrado, Ubicación:/proyecto-cómics/3/cubiertas, redirigir a nuevo portada del borrador del cómic (/ draft-comic-book/3/covers/1).

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

de POST/publicado-cómico-libros
title = foo
autor = Boo
publisher = goo
publicó = 2011-01-01
cover_type = frente
cover_data = (binario)
= > 302 Encontrado, Ubicación:/comic-books/3, redirecciona al cómic publicado (id: 3) con portadas.

+0

Soy un novato total en esto, y trato de aprenderlo a toda prisa. Encontré esto 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) sería mal visto. Entonces, ¿no debería ser POST/draft-comic-books/3/publish? –

+3

@GaryMcGill En su ejemplo,/draft-comic-books/3/publish solo devuelve un formulario HTML (no modifica ningún dato). –

+0

@Olivier es correcto. La palabra publicar está allí para indicar 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 los cómics publicados. ... Si fuera un sitio web, es posible que necesite un URI para que el formulario publique algo. ... Aunque, si la acción de publicación fuera 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 de historietas publicadas. – Alex