¿Qué patrones de diseño probados existen para las operaciones por lotes en recursos dentro de un servicio web de estilo REST?
Estoy tratando de lograr un equilibrio entre los ideales y la realidad en términos de rendimiento y estabilidad. Tenemos una API en este momento donde todas las operaciones se recuperan de un recurso de lista (es decir: GET / user) o en una sola instancia (PUT / user / 1, DELETE / user / 22, etc.).
Hay algunos casos en los que desea actualizar un solo campo de un conjunto completo de objetos. Parece un desperdicio enviar la representación completa de cada objeto de un lado a otro para actualizar el campo.
En una API de estilo RPC, podría tener un método:
/mail.do?method=markAsRead&messageIds=1,2,3,4... etc.
¿Cuál es el equivalente REST aquí? ¿O está bien comprometerse de vez en cuando? ¿Arruina el diseño para agregar algunas operaciones específicas donde realmente mejora el rendimiento, etc.? El cliente en todos los casos en este momento es un navegador web (aplicación javascript en el lado del cliente).
fuente
PATCH
, no hay necesidad de creatividad en este caso.En absoluto: creo que el equivalente REST es (o al menos una solución es) casi exactamente eso: una interfaz especializada diseñada para acomodar una operación requerida por el cliente.
Recuerdo un patrón mencionado en el libro de Crane y Pascarello Ajax in Action (un libro excelente, por cierto, muy recomendado) en el que ilustran la implementación de un tipo de objeto CommandQueue cuyo trabajo es poner en cola las solicitudes en lotes y luego publíquelos en el servidor periódicamente.
El objeto, si no recuerdo mal, esencialmente contenía una serie de "comandos", por ejemplo, para ampliar su ejemplo, cada uno un registro que contiene un comando "markAsRead", un "messageId" y tal vez una referencia a una devolución de llamada / controlador función - y luego de acuerdo con alguna programación, o con alguna acción del usuario, el objeto de comando se serializará y se publicará en el servidor, y el cliente manejará el consiguiente procesamiento posterior.
No tengo los detalles a mano, pero parece que una cola de comandos de este tipo sería una forma de manejar su problema; reduciría sustancialmente la conversación general y abstraería la interfaz del lado del servidor de una manera que podría encontrar más flexible en el futuro.
Actualización : ¡Ajá! Encontré un fragmento de ese mismo libro en línea, completo con ejemplos de código (¡aunque todavía sugiero recoger el libro real!). Eche un vistazo aquí , comenzando con la sección 5.5.3:
Aquí hay dos funciones pertinentes: una responsable de agregar comandos a la cola (
addCommand
) y otra responsable de serializar y luego enviarlos al servidor (fireRequest
):Eso debería ponerte en marcha. ¡Buena suerte!
fuente
Si bien creo que @Alex está en el camino correcto, conceptualmente creo que debería ser al revés de lo que se sugiere.
La URL es en efecto "los recursos a los que apuntamos", por lo tanto:
significa obtener el registro del correo con id 1 y
significa parchear el registro de correo con la identificación 1. La cadena de consulta es un "filtro", que filtra los datos devueltos desde la URL.
Así que aquí estamos solicitando todo el correo ya marcado como leído. Entonces, [PATCH] a este camino sería decir "parchear los registros ya marcados como verdaderos" ... que no es lo que estamos tratando de lograr.
Entonces, un método por lotes, siguiendo este pensamiento, debería ser:
Por supuesto, no digo que esto sea REST verdadero (que no permite la manipulación de registros por lotes), sino que sigue la lógica ya existente y en uso por REST.
fuente
[GET]
formato[PATCH] mail?markAsRead=true data: [{"id": 1}, {"id": 2}, {"id": 3}]
(o incluso simplementedata: {"ids": [1,2,3]}
)? Otro beneficio de este enfoque alternativo es que no se encontrará con errores de "414 Solicitar URI demasiado tiempo" si está actualizando cientos / miles de recursos en la colección.Su lenguaje, " Parece muy derrochador ...", para mí indica un intento de optimización prematura. A menos que se pueda demostrar que enviar la representación completa de los objetos es un gran impacto en el rendimiento (estamos hablando de que los usuarios son inaceptables> 150 ms), entonces no tiene sentido intentar crear un nuevo comportamiento API no estándar. Recuerde, cuanto más simple es la API, más fácil es usarla.
Para las eliminaciones, envíe lo siguiente ya que el servidor no necesita saber nada sobre el estado del objeto antes de que ocurra la eliminación.
El siguiente pensamiento es que si una aplicación se encuentra con problemas de rendimiento con respecto a la actualización masiva de objetos, se debe considerar dividir cada objeto en varios objetos. De esa forma, la carga útil de JSON es una fracción del tamaño.
Como ejemplo, al enviar una respuesta para actualizar los estados de "lectura" y "archivado" de dos correos electrónicos separados, deberá enviar lo siguiente:
Separaría los componentes mutables del correo electrónico (leído, archivado, importancia, etiquetas) en un objeto separado, ya que los otros (para, desde, asunto, texto) nunca se actualizarían.
Otro enfoque a tomar es aprovechar el uso de un PATCH. Para indicar explícitamente qué propiedades tiene la intención de actualizar y que todos los demás deben ignorarse.
Las personas afirman que PATCH debe implementarse proporcionando una serie de cambios que contengan: acción (CRUD), ruta (URL) y cambio de valor. Esto puede considerarse una implementación estándar, pero si observa la totalidad de una API REST, es una aplicación no intuitiva. Además, la implementación anterior es cómo GitHub ha implementado PATCH .
En resumen, es posible adherirse a los principios RESTful con acciones por lotes y aún tener un rendimiento aceptable.
fuente
La API de Google Drive tiene un sistema realmente interesante para resolver este problema ( ver aquí ).
Lo que hacen es básicamente agrupar diferentes solicitudes en una
Content-Type: multipart/mixed
solicitud, con cada solicitud completa individual separada por algún delimitador definido. Los encabezados y el parámetro de consulta de la solicitud por lotes se heredan de las solicitudes individuales (es decirAuthorization: Bearer some_token
) a menos que se anulen en la solicitud individual.Ejemplo : (tomado de sus documentos )
Solicitud:
Respuesta:
fuente
Me sentiría tentado en una operación como la de su ejemplo para escribir un analizador de rango.
No es mucho problema hacer un analizador que pueda leer "messageIds = 1-3,7-9,11,12-15". Sin duda, aumentaría la eficiencia para las operaciones generales que cubren todos los mensajes y es más escalable.
fuente
Buena publicación. He estado buscando una solución por unos días. Se me ocurrió una solución mediante el uso de pasar una cadena de consulta con un grupo de ID separados por comas, como:
... luego pasar eso a una
WHERE IN
cláusula en mi SQL. Funciona muy bien, pero me pregunto qué piensan los demás de este enfoque.fuente
DELETE /books/delete?id=1,2,3
cuándo el libro # 3 no existe:WHERE IN
ignorará silenciosamente los registros, mientras que generalmente esperaríaDELETE /books/delete?id=3
404 si 3 no existe.Desde mi punto de vista, creo que Facebook tiene la mejor implementación.
Se realiza una única solicitud HTTP con un parámetro por lotes y uno para un token.
En lote se envía un json. que contiene una colección de "solicitudes". Cada solicitud tiene una propiedad de método (get / post / put / delete / etc ...) y una propiedad relative_url (uri del punto final), además, los métodos post y put permiten una propiedad "body" donde se actualizan los campos se envían .
Más información en: API por lotes de Facebook
fuente