Tengo tres preguntas sobre el diseño de REST API que espero que alguien pueda arrojar algo de luz. He buscado incansablemente durante muchas horas, pero no he encontrado respuestas a mis preguntas en ningún lado (¿tal vez no sé qué buscar?).
Pregunta 1
Mi primera pregunta tiene que ver con acciones / RPC. He estado desarrollando una API REST por un tiempo y estoy acostumbrado a pensar en términos de colecciones y recursos. Sin embargo, me he encontrado con un par de casos en los que el paradigma no parece aplicarse y me pregunto si hay una manera de conciliar esto con el paradigma REST.
Específicamente, tengo un caso en el que modificar un recurso hace que se genere un correo electrónico. Sin embargo, en un momento posterior, el usuario puede indicar específicamente que desea reenviar el correo electrónico que se envió anteriormente. Al reenviar el correo electrónico no se modifica ningún recurso. Ningún estado ha cambiado. Es simplemente una acción que debe ocurrir. La acción está vinculada al tipo de recurso específico.
¿Es apropiado mezclar algún tipo de llamada a la acción con un URI de recursos (por ejemplo /collection/123?action=resendEmail
)? ¿Sería mejor especificar la acción y pasarle la identificación del recurso (por ejemplo /collection/resendEmail?id=123
)? ¿Es esta la forma incorrecta de hacerlo? Tradicionalmente (al menos con HTTP) la acción que se realiza es el método de solicitud (GET, POST, PUT, DELETE), pero en realidad no permiten acciones personalizadas con un recurso.
Pregunta 2
Utilizo la parte de la cadena de consulta de la URL para filtrar el conjunto de recursos devueltos al consultar una colección (por ejemplo /collection?someField=someval
). Luego, dentro de mi controlador API, determino qué tipo de comparación va a hacer con ese campo y valor. He descubierto que esto realmente no funciona. Necesito una forma de permitir que el usuario de la API especifique el tipo de comparación que desea realizar.
La mejor idea que se me ha ocurrido hasta ahora es permitir que el usuario de la API lo especifique como un apéndice del nombre del campo (por ejemplo /collection?someField:gte=someval
, para indicar que debe devolver recursos donde someField
sea mayor o igual que (> =) sea lo que someval
sea ¿Es una buena idea? ¿Una mala idea? Si es así, ¿por qué? ¿Hay una mejor manera de permitir que el usuario especifique el tipo de comparación que se realizará con el campo y el valor dados?
Pregunta 3
A menudo veo URI que se parecen a algo así como /person/123/dogs
obtener el person
s dogs
. En general, he evitado algo así porque al final me imagino que al crear un URI como ese, en realidad solo está accediendo a una dogs
colección filtrada por una person
ID específica . Sería equivalente a /dogs?person=123
. ¿Hay alguna vez una buena razón para que un URI REST tenga más de dos niveles de profundidad ( /collection/resource_id
)?
Respuestas:
Prefiero modelar eso de una manera diferente, con una colección de recursos que represente los correos electrónicos que se enviarán; el envío será procesado por los internos del servicio a su debido tiempo, momento en el cual se eliminará el recurso correspondiente. (O el usuario podría ELIMINAR el recurso antes de tiempo, provocando la cancelación de la solicitud para realizar el envío).
Hagas lo que hagas, ¡no pongas verbos en el nombre del recurso! Ese es el sustantivo (y la parte de la consulta es el conjunto de adjetivos). Sustantivo verbos weirds REST!
Prefiero especificar una cláusula de filtro general y tener eso como un parámetro de consulta opcional en cualquier solicitud para recuperar el contenido de la colección. El cliente puede especificar exactamente cómo restringir el conjunto devuelto, de la forma que desee. También me preocuparía un poco la capacidad de detección del lenguaje de filtro / consulta; cuanto más rico lo hagas, más difícil será para los clientes arbitrarios descubrirlo. Un enfoque alternativo que, al menos en teoría, se ocupa de ese problema de detección es permitir que los recursos de restricción de la colección se obtengan, que los clientes obtienen al PUBLICAR un documento que describe la restricción al recurso de la colección. Sigue siendo un abuso leve, ¡pero al menos es uno que claramente puedes descubrir!
Este tipo de capacidad de descubrimiento es una de las cosas que encuentro menos fuertes con REST.
Cuando la colección anidada es realmente una subcaracterística de las entidades miembro de la colección externa, es razonable estructurarlas como un recurso secundario. Por "subfunción" me refiero a algo como la relación de composición UML, donde destruir el recurso externo naturalmente significa destruir la colección interna.
Otros tipos de colección se pueden modelar como una redirección HTTP; por lo tanto,
/person/123/dogs
se puede responder haciendo un 307 que redirige a/dogs?person=123
. En este caso, la colección no es en realidad composición UML, sino más bien agregación UML. La diferencia importa; es significativo!fuente
resendEmail
acción podría manejarse creando una colección y PUBLICANDO en ella, eso parece menos natural. De hecho, no almaceno nada en la base de datos cuando se reenvía un correo electrónico (no es necesario). No se modifica ningún recurso, por lo tanto, es simplemente una acción que tiene éxito o falla. No pude devolver un ID de recurso que existe más allá de la vida de la llamada, lo que hace que dicha implementación sea un truco en lugar de ser RESTful. Simplemente no es una operación CRUD.Es comprensible estar un poco confundido acerca de cómo usar REST correctamente en función de todas las formas en que he visto a grandes empresas diseñar sus API REST.
Tiene razón en que REST es un sistema de recopilación de recursos. Es sinónimo de Transferencia de Estado de representación. No es una gran definición si me preguntas. Pero los conceptos principales son los 4 VERB HTTP y no tener estado.
La pieza importante a tener en cuenta es que solo tiene 4 VERBOS con REST. Estos son GET, POST, PUT y DELETE. Su
resend
ejemplo sería agregar un nuevo verbo a REST. Esto debería ser una bandera roja.Pregunta 1
Es importante darse cuenta de que la persona que llama desde su API REST no debería saber que realizar una búsqueda
PUT
en su colección podría generar un correo electrónico. Eso huele a una fuga para mí. Lo que podrían saber es que realizar unPUT
resultado podría generar tareas adicionales que podrían consultar más tarde. Lo sabrían realizando unGET
recurso en el recurso creado recientemente. EsoGET
devolvería el recurso y todos losTask
ID de recursos asociados con él. Luego puede consultar esas tareas para determinar su estado e incluso enviar una nuevaTask
.Tienes pocas opciones.
REST: enfoque basado en recursos de tareas
Cree un
tasks
recurso en el que pueda enviar tareas específicas a su sistema para realizar acciones. A continuación, puede realizarGET
la tarea en función de laID
devolución para determinar su estado.O puede mezclar un
SOAP over HTTP
servicio web para agregar algo de RPC a su arquitectura.consultar todas las tareas para un recurso específico
GET http://server/api/myCollection/123/tasks
ejemplo de recurso de tarea
PUT http://server/api/tasks
==> devuelve el id de la tarea
223334
GET http://server/api/tasks/223334
REST- Uso de POST para desencadenar acciones
Siempre puede
POST
datos adicionales a un recurso. En mi opinión, esto violaría el espíritu de REST, pero seguiría cumpliendo.Puedes hacer una POST similar a esta:
POST http://server/api/collection/123
{ "action" : "send-email" }
Actualizará el recurso 123 de la colección con datos adicionales. Esos datos adicionales son esencialmente una acción que le dice al servidor que envíe un correo electrónico para ese recurso.
El problema que tengo con esto es que un
GET
en el recurso devolverá estos datos actualizados. Sin embargo, esto resolvería sus requisitos y aún sería RESTful.SOAP: servicio web que acepta recursos obtenidos de REST
Cree un nuevo servicio web en el que pueda enviar correos electrónicos basados en el ID de recurso anterior de la API REST. No entraré en detalles sobre SOAP aquí, ya que la pregunta original es sobre REST y estos dos conceptos / tecnologías no deben compararse, ya que se trata de manzanas y naranjas .
Pregunta 2
También tiene algunas opciones aquí:
Parece que muchas compañías más grandes que publican API de REST exponen una
search
colección que realmente es solo una forma de pasar parámetros de consulta para devolver recursos.GET http://server/api/search?q="type = myCollection & someField >= someval"
Lo que devolvería una colección de recursos REST completamente calificados como:
O puede permitir algo como MVEL como parámetro de consulta.
Pregunta 3
Prefiero los subniveles que tener que volver a subir y consultar el otro recurso con un parámetro de consulta. No creo que haya ninguna regla de una forma u otra. Puede implementar ambas formas y permitir que la persona que llama decida cuál es más apropiado en función de cómo ingresó por primera vez al sistema.
Notas
No estoy de acuerdo con los comentarios de legibilidad de otros. A pesar de lo que algunos podrían pensar, REST todavía no es para consumo humano. Es para el consumo de la máquina. Si quiero ver mis Tweets, uso el sitio web normal de Twitters. No realizo un REST GET con su API. Si quiero hacer algo programáticamente con mis tweets, entonces uso su API REST. Sí, las API deben ser comprensibles, pero tu
gte
no es tan malo, simplemente no es intuitivo.La otra cosa principal con REST es que debería poder comenzar en cualquier punto dado en su API y navegar a todos los demás recursos asociados SIN conocer la URL exacta de los otros recursos con anticipación. Los resultados del
GET
VERBO en REST deberían devolver la URL REST completa de los recursos a los que hace referencia. Entonces, en lugar de una consulta que devuelve la ID de unPerson
objeto, devolvería la URL totalmente calificada comohttp://server/api/people/13
. Entonces siempre puede navegar programáticamente los resultados incluso si la URL ha cambiado.Respuesta al comentario
En el mundo real, de hecho, hay cosas que deben suceder que no crean, leen, actualizan o eliminan (CRUD) un recurso.
Se pueden tomar acciones adicionales sobre los recursos. Las bases de datos relacionales típicas admiten el concepto de procedimientos almacenados. Estos son comandos adicionales que se pueden ejecutar en un conjunto de datos. REST no tiene ese concepto inherentemente. Y no hay razón para que así sea. Este tipo de acciones son perfectas para los servicios web RPC o SOAP.
Este es el problema general que veo con las API REST. A los desarrolladores no les gustan las limitaciones conceptuales que rodean REST, por lo que lo adaptan para hacer lo que quieran. Sin embargo, eso deja de ser un servicio RESTful. Esencialmente, esas URL se convierten en
GET
llamadas en servlets de tipo pseudo-REST.Tienes pocas opciones:
POST
de datos adicionales al recurso para realizar una acción.Si usara un parámetro de consulta, ¿qué VERBO HTTP usaría para reenviar el correo electrónico?
GET
- ¿Esto reenvía el correo electrónico Y devuelve los datos del recurso? ¿Qué pasaría si un sistema almacenara en caché esa URL y la tratara como la URL única para ese recurso? Cada vez que llegan a la URL, reenvían un correo electrónico.POST
- En realidad no envió ningún dato nuevo al recurso, solo un parámetro de consulta adicional.De acuerdo con todos los requisitos dados, resolver un problema
POST
en el recurso conaction field
datos POST como.fuente
Pregunta 1: ¿Es apropiado mezclar algún tipo de llamada a la acción con un URI de recurso [o] sería mejor especificar la acción y pasarle la identificación del recurso?
Buena pregunta. En este caso, le aconsejaría que utilice el último enfoque, es decir, especifique la acción y le pase un ID de recurso. De esta manera, cuando su recurso se modifica por primera vez, a su vez llama a la
/sendEmail
acción (nota al margen: no es necesario llamarlo "reenviar") como una solicitud RESTful separada (que luego puede llamar una y otra vez, independientemente del recurso que se está modificando )Pregunta 2: con respecto al uso de un operador de comparación así:
/collection?someField:gte=someval
Si bien esto es técnicamente correcto, probablemente sea una mala idea. Uno de los principios clave de REST es la legibilidad. Le sugiero que simplemente pase el operador de comparación como otro parámetro, por ejemplo:
/collection?someField=someval&operator=gte
y, por supuesto, diseñe su API para que atienda un caso predeterminado (en el caso de que eloperator
parámetro quede fuera del URI).Pregunta 3: ¿Hay alguna vez una buena razón para que un URI REST tenga más de dos niveles de profundidad?
Sip; para la abstracción He visto un par de API REST que utilizan capas de abstracción a través de múltiples niveles de URI, por ejemplo:
/vehicles/cars/123
o/vehicles/bikes/123
que a su vez le permite trabajar con información útil relacionada con ambos/vehicles
y las/vehicles/bikes
colecciones. Dicho esto, no soy un gran admirador de este enfoque; rara vez necesitará hacer esto en la práctica, y es probable que pueda rediseñar la API para usar solo 2 niveles.Y sí, como sugieren los comentarios anteriores, en el futuro sería mejor dividir sus preguntas en publicaciones separadas;)
fuente
/collection?field1=someval&field1Operator=gte&field2=someval&field2Operator=eq
.Para la pregunta 2, una alternativa diferente puede ser más flexible: considere cada búsqueda como un recurso que el usuario construye antes de usar.
supongamos que tiene un contenedor de "búsquedas", allí hace una
POST /api/searches/
con la especificación de consulta en el contenido. podría ser un documento JSON, XML o incluso un documento SQL, lo que sea más fácil para usted. Si la consulta se analiza correctamente, se crea una nueva búsqueda como un nuevo recurso con su propio URI, digamos/api/searches/q123/
Entonces, el cliente puede simplemente
GET /api/searches/q123/
recuperar los resultados de la consulta.Finalmente, puede pedirle al cliente que elimine la consulta o purgarla después de cerrar la sesión.
fuente
No, no es apropiado, ya que los IRI son para identificar recursos y no operaciones (sin embargo, ppl utiliza este método de anulación de enfoque por un tiempo, en casos en los que no se admiten métodos que no son POST y GET). Lo que puede hacer es buscar un método HTTP apropiado o crear uno nuevo. POST puede ser tu amigo en estos casos (por favor, úsalo si no pueden encontrar un método apropiado y la solicitud no se recupera). Otro enfoque para
POST /emails
generar recursos a partir del envío de correo electrónico y, por lo tanto, puede enviar los correos electrónicos sin crear un recurso real. Por cierto. La estructura de URI no lleva semántica, por lo que desde una perspectiva REST realmente no importa qué tipo de URI use. Lo que importa son los metadatos (por ejemplo, relación de enlace ) asignados a los enlaces que envió a los clientes.No tiene que crear un lenguaje de consulta propio. Prefiero usar uno ya existente y agregar alguna descripción de consulta a los metadatos del enlace. Probablemente debería usar un tipo de medio RDF (por ejemplo, JSON-LD) para hacer eso o usar un tipo MIME personalizado (afaik no hay formato que no sea RDF que lo admita). El uso de estándares existentes desacopla a su cliente del servidor, de eso se trata la restricción de interfaz uniforme.
Como mencioné anteriormente, la estructura URI no importa desde una perspectiva REST. Podrías usar
/x71fd823df2
por ejemplo. Todavía tendría sentido para los clientes porque verifican los metadatos asignados a los enlaces y no la estructura de URI. El objetivo principal de URI es identificar recursos. En el estándar URI, afirman que la ruta contiene datos jerárquicos y que la consulta contiene datos no jerárquicos. Pero puede ser muy subjetivo lo que es jerárquico. Es por eso que cumple con múltiples niveles de URI profundos y URI con consultas largas.Debe leer al menos las restricciones REST de la disertación Fielding , el estándar HTTP y probablemente las API web de tercera generación de Markus.
fuente