Manejo de requests repetidos en API

已查看 14 次
跳至第一个未读帖子

Juan Manuel Cuello

未读,
2017年9月14日 18:14:292017/9/14
收件人 rub...@googlegroups.com
Supongamos que un servidor expone una API RESTful para crear items. Cada item se crea al recibir un request tipo POST en el endpoint correspondiente. Esta API la consumen diferentes clientes (web, Android, iOS, etc) para crear items.

Dependiendo del cliente que haga uso de la API, puede ser que, ante una falla de cierto tipo, la misma librería del cliente mande nuevamente el request, a modo de re-intento. Por ejemplo, la librería Volley, de Android, lo hace.
 
¿Alguna experiencia o recomendación para no terminar con items repetidos en el server?

Angel Java Lopez

未读,
2017年9月15日 05:35:292017/9/15
收件人 rub...@googlegroups.com
Hola gente!

Lo que vi que se usa en esos casos, es tener en los datos del POST algo unico, por ejemplo, un POST de alta de usuario, tendra username, y este se controla en el servidor, para ser unico.

Si no hay algo unico, nada impide enviar el ID de la entidad, generada en el cliente, por ejemplo un GUID. Algo asi hacian los clientes de mongodb para enviar sus nuevas entidades, que recuerde.

Hay varias discusiones en la web, una que explica un posible retorno de un caso duplicado:


Tambien sirve para otros casos, p. ej. enviar un POST de un pago, y la cuenta no tiene saldo suficiente para pagar, algo asi

Nos leemos!

Angel "Java" Lopez
@ajlopez


--
Has recibido este mensaje porque estás suscrito al grupo "rubysur" de Grupos de Google.
Para cancelar la suscripción a este grupo y dejar de recibir sus mensajes, envía un correo electrónico a rubysur+unsubscribe@googlegroups.com.
Para acceder a más opciones, visita https://groups.google.com/d/optout.

Mariano Matayoshi

未读,
2017年9月15日 08:14:452017/9/15
收件人 rub...@googlegroups.com
Cuando me encuentro con este tipo de cosas, hago que la validación sea a nivel de la base de datos.

Algo así:
add_index :your_table, :your_unique_attribute, unique: true

Luego en tu controller deberías tener algo así:
def create
@object = YourModel.new(object_params)
  begin
@object.save!

render json: {
status: 200,
message: “Created”
}
rescue ActiveRecord::RecordNotUnique => e
render json: {
status: 422,
message: “Validation error: YOUR_ATTRIBUTE has already been taken”,
}
end
end
Para cancelar la suscripción a este grupo y dejar de recibir sus mensajes, envía un correo electrónico a rubysur+u...@googlegroups.com.

Para acceder a más opciones, visita https://groups.google.com/d/optout.

--
Has recibido este mensaje porque estás suscrito al grupo "rubysur" de Grupos de Google.
Para cancelar la suscripción a este grupo y dejar de recibir sus mensajes, envía un correo electrónico a rubysur+u...@googlegroups.com.

Para acceder a más opciones, visita https://groups.google.com/d/optout.
--
Mariano Matayoshi
Sent with Mailtrack

Nicolás Oga

未读,
2017年9月15日 11:47:272017/9/15
收件人 rubysur
Dependiendo de las reglas acerca del retry y si en en lado de la API las acciones se corren dentro de una transacción no veo porque habrían de haber duplicados. Por ejemplo, si solo se reintentara el request en caso de un status 5xx.

El tema es que esto no te va a salvar nunca de por ejemplo cuando tenés un timeout, y no podés saber si la operación se completó o no.
Si realmente necesitás algo así de poderoso, podrías crear una interfaz de transacciones de verdad, como plantean acá:
... pero probablemente sea como matar una mosca con un lanzamisiles.

Juan Manuel Cuello

未读,
2017年9月17日 20:41:142017/9/17
收件人 rub...@googlegroups.com
Nicolás, algunas librerías podrían mandar varias veces el mismo request, ante fallas de cierto tipo, por ejemplo, por un timeout. Como decía, Volley, en Android, lo hace. También OkHttp. Si bien se podrían quitar los re-intentos, muchas veces es deseable, para poder salvar ciertos problemas de conexión. Es por eso que desde la API sería bueno poder contemplar que lleguen requests duplicados.

Como varios sugirieron, creo que el camino sería generar un UUID en el lado del cliente y guardar este valor en la DB con un "unique constraint", para asegurarnos que en la DB nunca haya más de uno. Si por algún motivo la librería del cliente decide mandar el request más de una vez, la API retornaría un error y en la DB no se guardaría.

Lo malo de esto es que sería un campo más en la tabla correspondiente, representando un ID, que para eso ya existe en mi caso un ID auto-incremental usado como primary key. Ahora quedarían dos campos representando más o menos lo mismo, usando el UUID solo para evitar duplicados. Bueno, supongo que sería un trade-off aceptable.

Muchas gracias a todos por las respuestas.
 

--
Has recibido este mensaje porque estás suscrito al grupo "rubysur" de Grupos de Google.
Para cancelar la suscripción a este grupo y dejar de recibir sus mensajes, envía un correo electrónico a rubysur+unsubscribe@googlegroups.com.

Joaquín Vicente

未读,
2017年9月18日 12:06:322017/9/18
收件人 rub...@googlegroups.com
Podés tener una tabla aparte para los UUID, no necesariamente una columna más del modelo que estás persistiendo. De esta manera sería una validación genérica ante request repetidos.
Como la idea de esto es evitar un problema temporal, esa tabla la podés ir limpiando periódicamente.
Si usás Redis, podés guardar estos registros ahí y pedirle expiren después de determinado tiempo (ej: 1 hora)

Juan Manuel Cuello

未读,
2017年9月19日 08:54:282017/9/19
收件人 rub...@googlegroups.com
Es cierto, podría ser una tabla aparte solo para eso y así evitar una columna más en cada tabla en donde se quieran validar repetidos. De todas maneras, si se usa una tabla genérica para todos los resources, puede estar bueno también querer saber a qué tipo de resource se refiere el UUID, e incluso el ID usado como PK, por las dudas que se necesite algún tipo de debug, entonces habría que tener también en esa tabla algo que indique estos datos. 

No uso Redis, solo PostgreSQL.

Por ahora, creo que la opción del UUID en cada tabla es la que más me convence.

Geronimo Diaz

未读,
2017年9月19日 09:57:052017/9/19
收件人 rub...@googlegroups.com
Hola Juan Manuel,

Entro tarde en la conversación, no te es posible hacer que la creacion de items sea atomica ? es decir, si falla algo que no se cree el item ? pregunto sin conocer los detalles, pero quizas podrias evaluar una manipulacion de items atomico contra la implementacion de un UUID.

Geronimo Diaz
Ruby/RoR Dev
Para cancelar la suscripción a este grupo y dejar de recibir sus mensajes, envía un correo electrónico a rubysur+u...@googlegroups.com.

Juan Manuel Cuello

未读,
2017年9月19日 17:30:522017/9/19
收件人 rub...@googlegroups.com
Geronimo, la idea es evitar que se creen items repetidos cuando exactamente el mismo request llega más de una vez, debido a re-intentos del cliente. Usar un UUID en el item a crear seria un forma de detectar si ya fue creado anteriormente, para evitar crearlo de nuevo.

  

Angel Java Lopez

未读,
2017年9月19日 20:41:332017/9/19
收件人 rub...@googlegroups.com
Si, por lo que entiendo del problema planteado:

- El problema NO ES el servidor. Este puede hacer todo bien
- Pero el CLIENTE puede mandar reenvio. Por ejemplo, si se le escapa por alguna razon la respuesta del servidor

Gabriel Hubermann

未读,
2017年9月20日 19:51:562017/9/20
收件人 rub...@googlegroups.com

Hola. 

Te dejo por si te sirve, un fragmento de la pagina 15 del libro "Principios de diseño de APIs REST

https://leanpub.com/introduccion_apis_rest

Saludos.

Una forma de crear nuevos recursos es mediante PUT. Simplemente hacemos PUT a una URI que no existe, con los datos iniciales del recurso y el servidor creará dicho recurso en la URI especificada.

La petición es indistinguible de una actualización. El hecho de que se produzca una actualización o se cree un nuevo recurso depende únicamente de si dicho recurso, identificado por la URL, existe ya o no en el servidor.

El código de respuesta no es ni 200 ni 204, sino 201, indicando que el recurso se creó con éxito. Opcionalmente, como en el caso del ejemplo, se suele devolver el contenido completo del recurso recien creado. Es importante fijarse en la cabecera Location que indica, en este caso de forma redundante, la URL donde se ha creado el nuevo recurso.

Otro método para crear nuevos recursos usando POST. En este caso hacemos POST no sobre la URI del nuevo recurso, sino sobre la URI del recurso padre.

En este caso la cabecera Location no es superflua, ya que es el servidor quien decide la URL del nuevo recurso, no el cliente como en el caso de PUT. Además cuando creamos un nuevo recurso con POST, éste siempre queda subordinado al recurso padre. Esto no tendría porque ser así con PUT.


POST tiene como ventaja que la lógica de creación URIs no está en el cliente, sino bajo el control del servidor. Esto hace a nuestros servicios REST más interoperables, ya que el servidor y el cliente no se tienen que poner de acuerdo ni en que URIs son válidas y cuáles no, ni en el algoritmo de generación de URIs. Como gran desventaja, POST no es idempotente.

La gran ventaja de usar PUT es que sí es idempotente. Esto hace que PUT sea muy útil para poder recuperarnos de problemas de conectividad. Si el cliente tiene dudas sobre si su petición de creación se realizó o no, sólo tiene que repetirla. Sin embargo esto no es posible con POST, ya que duplicaríamos el recurso en el caso de que el servidor sí atendió a nuestra petición y nosotros no lo supiéramos
 


 




Programador.
Sistemas, Aplicaciones web.

回复全部
回复作者
转发
0 个新帖子