RavenDB(V) Actualizaciones, concurrencia, patch y otras hierbas

En esta quinta entrega de la serie, puede ver las anteriores entradas aquí, I,-IIIII y IV, vamos a hablar sobre actualizaciones y los mecanismos de gestión de la concurrencia que tenemos, por supuesto intentando bajar lo más posible, para mi estas entradas son también una fuente de aprendizaje. Empezaremos la casa por dónde debemos, o sea que lo primero que haremos será hacer una pequeña inserción de un documento más o menos complejo, ya que esto nos dará juego para alguna cosa interesante, basado en las siguientes clases que podemos ver a continuación:

 

Actualizaciones

 

 

En concreto, la inserción la haremos con los siguientes datos:

 


Bien, llega la hora de empezar a hablar de actualizaciones. Como ya dijimos en la primera  introducción a RavenDB, este ha sido creado apoyándose en patrones y experiencias provenientes de ORMs, muy especialmente de NHibernate, aunque algo también hay de otros….Uno de estos patrones recogidos de frameworks como NH o EF es UnitOfWork, el cual a estas alturas seguro que todos conocemos… Por lo tanto si un IDocumentSession es un UnitOfWork entonces la actualización de un documento debería de ser tan facil como lo siguiente:

 


 

Aunque por ahora seguimos sin hablar de la parte de consulta, fíjese en le método Load, el cual, como seguramente habrá deducido, nos permite obtener un documento en función de su clave, en este caso, estamos utilizando la convención por defecto ( HiLo ). Si miráramos con Fiddler este proceso la primera entrada sería precisamente esta carga:

 

GET http://ravendbserver/docs/posts/1 HTTP/1.1

Accept-Encoding: deflate,gzip

Content-Type: application/json; charset=utf-8

Host: portblackcode

Connection: Keep-Alive

 

Ahora, puesto que cualquier IDocumentSession es un UnitOfWork, cualquier cambio en cualquier objeto consultado, y por lo tanto trackeado por la infraestructura de RavenDB, es llevado al servidor una vez realizada la llamada a SaveChanges, todos los cambios,inserciones,etc de una sesión se hacen en batch para reducir roundtrips.

 

POST http://ravendbserver/bulk_docs HTTP/1.1

Accept-Encoding: deflate,gzip

Content-Type: application/json; charset=utf-8

Host: portblackcode

Content-Length: 513

Expect: 100-continue

[{"Key":"posts/1","Method":"PUT","Document":{"Title":"RavenDB(V): actualizaciones, concurrencia y otras hierbas!","Body":"En esta quinta entrega de la serie, puede ver las ….","Comments":[{"UserName":"preguntoncojonero","Text":"cualquier pregunta…."},{"UserName":"Unai","Text":"Pues efectivamente preguntoncojonero…"}]},"Metadata":{"Raven-Entity-Name":"Posts","Raven-Clr-Type":"sampleupdate.Post, sampleupdate","@etag":"00000000-0000-0200-0000-000000000001","Last-Modified":"/Date(1323811962000+0100)/"}}]

 

 

Fijese en la traza anterior como el cuerpo de nuestro POST contiene la información de la operación a realizar y el nuevo documento a establecer en RavenDB. Esto es una nota muy importante, es decir, es todo el documento que vamos a modificar no es la modificación en si. Esto, por ejemplo, tiene mucho impacto cuando estamos hablando de concurrencia, en concreto de gestión de concurrencia optimista, porque si dos personas incluyen un comentario en un post de forma concurrente podríamos tener problemas. Esto es una de las cosas que quizás mas impacta con respecto a lo que sabemos de modelos relaciones, ¿un problema de concurrencia en una inserción, porque en realidad es lo que estamos haciendo??…  Pues si, ahora no hay tablas y filas, hay un documento, una estructura compleja, que cambia, y cambia, en este caso, completamente, por lo tanto, si tenemos dos personas insertando un comentario sino manejamos el problema de concurrencia podríamos perder comentarios….

Concurrencia y ETag

Como podemos solventar el caso anterior, o más bien, como podemos enterarnos de un posible problema de concurrencia??? Bueno, a estas alturas ya todos sabemos que RavenDB es Restfull ¿verdad? y los que sabéis de http sabéis que existe el concepto de ETag para identificar un estado de un recurso en un momento dado. Pues bién, RavenDB utiliza este mismo concepto para gestionar la concurrencia de los documentos. Si volvemos a fijarnos en la trama de actulización veremos como junto con la metadata del documento tenemos una propiedad llamada etag, que en nuestro caso es la siguiente:

 

"@etag":"00000000-0000-0200-0000-000000000001"

 

Este valor es automaticamente modificado en el servidor cuando un documento se actualiza, por lo tanto, si tenemos un problema de concurrencia, lo que pasaría es que  el etag del documento en el servidor no se correspondería con el documento que nosotros enviamos, seria anterior, y por lo tanto podríamos suponer que estamos intentando actualizar algo que ya ha sido cambiado por otro proceso. Ok, ¿como se activa en RavenDB la comparación de los etag en los procesos de modificación?, pues muy sencillo, cualquier sesión, IDocumentSession, tiene un mecanismo para activar el chequeo de concurrencia de la siguiente forma:

 


 

Con este simple flag, podemos hacer la indicación de comprobación, en realidad lo que hace es que el documento que se envia incluye otra propiedad ETAG, a mayores de la que hay en metadata, para que el servidor sepa que tiene que hacer esta comprobación. Ahora, si ocurriera un problema de concurrencia, recibiríamos la siguiente respuesta del servidor:

 

HTTP/1.1 409 Conflict

Cache-Control: private

Content-Type: text/html

Server: Microsoft-IIS/7.5

Raven-Server-Build: 531

X-AspNet-Version: 4.0.30319

X-Powered-By: ASP.NET

Date: Tue, 13 Dec 2011 22:49:33 GMT

Content-Length: 219

{

  "Url": "/bulk_docs",

  "ActualETag": "00000000-0000-0300-0000-00000000000b",

  "ExpectedETag": "00000000-0000-0300-0000-00000000000a",

  "Error": "PUT attempted on document ‘posts/1’ using a non current etag"

}

 

Esta respuesta, provoca que se lance una excepción dentro del API de RavenDB, llamada ConcurrencyException que nos dará la información del problema que tenemos, y por lo tanto, refrescar, si así lo queremos nuestra operación.

 

Patching….

Bien, seguro que muchos estais pensando que, efectivamente, aunque la gestión de la concurrencia nos permite resolver nuestro problema, lo habitual ante la entrada de un post, es la coincidencia de muchos comentarios y por lo tanto tendremos muchos conflictos y demasiado trabajo para que estamos buscando hacer. Una alternativa para hacer esto es utilizar el concepto del verbo PATH en http. Hacer esto en RavenDB es realmente sencillo, como veremos a continuación, y nos permitirá, en el caso que estamos, crear nuevos comentarios a un post evitando problemas de concurrencia y mejorando el rendimiento ya que nos evitaremos tener que hacer un load de un documento.

 

Un patch request es, desde un punto de vista de código, tan sencillo como lo siguiente:

 


 

Si nos fijamos en el código anterior, un patch está representado por la clase PatchRequest, en la cual, podemos indicar el tipo de cambio a realizar, PatchCommandType nos ofrece comandos como ( Set, UnSet, Add, Modify,Rename …) la propiedad que modificaremos, en nuestro caso Comments y el cambio. En nuestro caso, el cambio es agregar un nuevo comentario, por lo tanto hacemos una transformación del objeto new Comment a Json por medio de la clase RavenJToken.

 

Una vez creado el request es suficiente con llamar a Patch definido en DatabaseCommands como podemos er a continuación:

 


 

Si prestamos un poco de atención vemos como el método Patch nos permite especificar la clave del documento sobre la que vamos a aplicar el cambio, en este caso posts/1. Si ejecutamos esto, y observamos fiddler veríamos algo similar a lo siguiente:

Untitled

POST http://ravendbserver/bulk_docs HTTP/1.1

Accept-Encoding: deflate,gzip

Content-Type: application/json; charset=utf-8

Host: portblackcode

Content-Length: 139

Expect: 100-continue

[{"Key":"posts/1","Method":"PATCH","Patches":[{"Type":"Add","Value":{"User":"Unai","Text":"mira señor pregunton…"},"Name":"Comments"}]}]

 

y una respuesta de confirmación de cambio:

HTTP/1.1 200 OK

Cache-Control: private

Content-Type: application/json; charset=utf-8

Server: Microsoft-IIS/7.5

Raven-Server-Build: 573

X-AspNet-Version: 4.0.30319

Persistent-Auth: true

X-Powered-By: ASP.NET

Date: Thu, 15 Dec 2011 19:40:37 GMT

Content-Length: 271

[{"Etag":"00000000-0000-0b00-0000-000000000001","Method":"PATCH","Key":"posts/1","Metadata":{"Raven-Entity-Name":"Posts","Raven-Clr-Type":"RavenUpdate.Post, RavenUpdate","Last-Modified":"2011-12-15T18:52:37.8760000Z","Non-Authoritive-Information":false,"@id":"posts/1"}}]

 

Otras hierbas

Este mismo mecanismo nos puede servir también para realización de un cambio en multiples documentos, para ello tenemos el método Batch en vez de Path, desde aquí os dejo a vosotros la tarea de investigación…

 

 

Saludos

Unai

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *