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. ComicBook
tiene todo tipo de propiedades sobre ella como author
, issue number
, date
, etc.
Un cómic también tiene una lista de 1..n
portadas. 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 GET
en ComicBook
tan 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 ComicBook
recurso, 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 Cover
un recurso y tener ComicBook
portadas. Entonces ahora Cover
es un URI. GET
en el cómic funciona ahora, en lugar del enorme Cover
recurso, 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, POST
ing una ComicBook
con la URI de la lista, o mi POST
de ComicBook
toma en un recurso mirando diferente de lo que escupe fuera. Los recursos entrantes para POST
y GET
son copias profundas, donde los salientes GET
contienen referencias a recursos dependientes.
El Cover
recurso 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?
fuente
Respuestas:
@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.
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.
fuente
El tratamiento de las cubiertas como recursos definitivamente está en el espíritu de REST, particularmente HATEOAS. Entonces, sí, una
GET
solicitudhttp://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 cómics. Si su regla comercial era que un libro tendría 0 o más portadas, entonces no tiene ningún problema:
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:
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:
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.
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í:
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.
fuente