RavenDB VII Consultas e indices estáticos..

 

Map

En la anterior entrada hicimos la primera introducción de lo que considero uno de los puntos fuertes, a mayores del API cliente, de RavenDB y este no es otro que su sistema de consultas. En esta entrada, hicimos una introducción sobre los índices que utiliza por detras RavenDB y que, como ya sabemos, se basan en Lucene.NET. Pero, los índices que vimos, eran dinámicos o bien generados automáticamente por RavenDB, si detecta un numero alto de consultas sobre el temporal. Estos índinces dinámicos no suelen ser una buena solución porque pueden tener una alta latencia, se ejecutan al vuelo y por lo tanto pueden tener un tiempo de ejecución alto, y no son demasiado flexibles en cuanto a las necesidades que podemos tener, trabajo con datos espaciales, búsquedas de texto completo, proyecciones etc.

 

Un índice estático no es más que la definición de  una estructura llamada Raven.Abstractions.Indexing.IndexDefinition cuyo cuerpo podemos ver a continuación:

 


















































 

Si observa la definición anterior, hay una serie de puntos muy interesantes, como son los miembros Map, Reduce, TransformResult y Analyzers. A lo largo de esta entrada, intentaremos, aunque sea de forma somera, ver todos y cada uno de ellos, seguramente juntándolos con algún otro elemento necesario. Aunque es posible crear directamente esta estructura, por lo general, la misma se suele crear a partir de una clase llamada IndexDefinitionBuilder, como podemos ver en el siguiente ejemplo de código.

 




















 

Con esto acabamos de ordenar al servidor de RavenDB crear un nuevo índice, llamado PostByTitle,sobre la colección de posts, utilizando para ellos los métodos de comandos de base de datos que nos ofrece nuestra interface IDocumentStore.  Para el índice anterior podemos ver como hacer la búsqueda de los documentos que nos interesan y a mayores sobre que campos vamos a hacer la búsqueda de nuestros documentos, en este caso solamente la propiedad Title.

 

Ahora, si quisiéramos hacer una consulta sobre este índice podríamos hacer algo tal cual lo que vemos en el siguiente tip de código:

 












 

Analyzers

Bueno, parece que más o menos está claro, sin embargo, seguro que a muchos se os ocurren algunas preguntas. La primera que a mi me vino a la mano, es como es la creación de ese índice en el campo Title, es decir, la tokenización de los valores se puede hacer de muchas maneras, desechando terminos terminales, pudiendo poner sinónimos, etc…. Seguramanete, esta pregunta me la hice porque no conocía demasiado bien el concepto de Analizadores en Lucene.NET. Pues bien, una vez comprendidos los analizadores la siguiente pregunta es ver ¿como podemos decirle a los índices de RavenDB que ciertos campos estén marcados por un analizador? Pues si revisamos el codigo de creación de nuestro índice podemos ver lo sencillo que resulta esta tarea, como se puede ver a continuación.

 




















Observe como el diccionario Analyzers nos permite, de una forma bastante sencilla, incorporar una analizador específico a uno de los campos del Map. Puesto que nos pide el nombre de un tipo de analizador una forma sencilla de hacerlo sin tener que acordarse de todo el fullname es referenciar al tipo y extraer su FullName. Este tipo, está en la librería Lucene.NET que tendrá que referenciar puesto que no forma parte del API cliente de RavenDB.

 

Bien, puesto que seguramente, si ha seguido este post y ha modificado el índice se habrá dado cuenta de que una vez creado no podemos volver a ejecutar PutIndex, puesto que esto falla, al existir uno lógicamente. Para prevenir estos casos y hacer el bootstraping de los índices de una forma más sencilla, RavenDB nos ofrece un mecanismo alternativo que pasaremos a comentar a continuación.  Con el fin de facilitar la definición y reutilización de indices RavenDB nos propone definir los mismos en una clase que extienda AbstractIndexCreationTask<TDocument> como por ejemplo podría ser la siguiente:

 






















 

 

Ahora, para hacer el botstrapper solamente tendríamos que ejecutar la siguiente instrucción, sin preocuparnos si los indices son nuevos, modificaciones de los existentes o ya existen con esa misma definición.

 


 

Reduce

 

Con lo anterior, aunque no sea muy profundamente, ya hemos terminado la parte referida al Map, pero como ya seguramente sabrá, y hemos dicho, esta no es la única parte importante de un índice puesto que también podemos especificar una función de Reduce. En palabras parcas, un Reduce es una paso posterior a la selección o mapeo de documentos, sobre la podemos hacer opero tstaciones ( asociativas ) de agregación. A mayores, la idea es que estas operaciones se puedan hacer sobre subconjuntos de los seleccionados, con el fin de paralelizarlas, por eso la consideración de que sean asociativas las funciones de agregación. Hay una muy buena referencia visual acerca de cual es la teoría de Map/Reduce en este post que seguro le introducirá en este aspecto de buena manera.

 

Una vez que nos hemos preocupado por saber que es un Reduce, concepto que por supuesto tenemos en todas las bases de datos documentales como MongoDB, por ejemplo, pasaremos a ver algún ejemplo de esta característica. Supongamos por ejemplo que deseamos obtener la suma de los comentarios agrupadas por identificador de blog, puesto que la “cuenta” es una operación asociativa, podríamos crear un índice como sigue:

 

 










































 

Y podríamos consultar esto de la siguiente forma en el API de RavenDB.

 
























 

Bueno, creo que son ya bastantes cosas como para ponerse a jugar sin esperar más no?? yo creo que si, o sea que dejaremos para otra entrada algunas cosillas que nos faltan como multi-maps, funciones de transformación etc. etc..

 

Saludos

Unai

Raven DB VI, La hora de las consultas…

Por fin,  seguro que ha sido la frase de muchos al oir que ya llegábamos a las consultas… Bien, pues si, con esta entrada inauguramos el trabajo con las consultas en RavenDB. Después ya de unos cuantos posts sobre el tema creo que ya es hora de tocar una de las cosas más importantes y que mejor hace RavenDB, que es las consultas.

Como siempre, ponemos un pequeño modelo de clase para realizar los distintos ejemplos. En esta occasion reutilizaremos las tipicas Blog, Post y Comment que ya hemos visto otras veces, y daremos por supuesto que tenemos ya una cantidad de documentos incluídos en RavenDB antes de procesar las consultas.










































* Como ya se ha comentado alguna vez, los modelos de las entradas tienen una finalidad lectiva y por lo tanto no se tiene porque ajustar a la realidad

 

Por defecto, y en la práctica, toda consulta que hacemos a RavenDB representa un índice sobre el almacen de RavenDB utilizando el proyecto Lucene .NET. Lucene, es un motor de búsqueda basado en .NET, en realidad es un port de java, basado en un índice invertido que funciona realmente rápido y dispone de muchas características importantes.  Por suerte, RavenDB no nos obliga a conocer la sintaxis de Lucene, aunque aprendiendo Lucene entenderíamos muchos de los comportamientos del motor de consultas, puesto que nos ofrece un completo soporte de Linq, pudiendo escribir todas nuestras consultas en Linq y dejando al API de cliente que se encargue de la traducción de las mismas a la sintaxis de Lucene.

Con el fin de mostrar el primer ejemplo, supongamos el siguiente código.














 

En la consulta anterior podemos ver un ejemplo de consulta dinámica tal cual la podríamos hacer contra otro almacén como una colección de objetos o una tabla con EF. Lógicamente, esta consulta genera una petición http al servidor de RavenDB, en concreto, la siguiente petición:

 

GET http://ravenserver/indexes/dynamic/Posts?query=-Title%253ARavenDB%2520AND%2520Title%253A*&start=0&pageSize=128&aggregation=None HTTP/1.1
Accept-Encoding: deflate,gzip
Content-Type: application/json; charset=utf-8

Si examinamos la trama podemos observar los siguientes elementos:

 

  • Por defecto, las consultas se hacen sobre un indice, en este caso, como no hemos especificado cual se hace sobre un índice dinámico
  • Por defecto las consultas nos protegen contra resultados potencialmente grande, agregando por defecto un parámetro llamado pageSize , cuyo valor por defecto es 128.

Untitled

 

Bien, la primera condición era algo que ya sabíamos, es decir, si RavenDB está basado en Lucene tiene que existir algún tipo de índece. RavenDB, puede generar un índice dinámico para aquellas consultas que no se hacen sobre un índice estático. A mayores, si considera que se ha utilizado una consulta un numero alto de veces, podría materializar el indice dinámico en un índice estático. Para provocarlo, realize esa consulta un número de veces y diríjase a nuestra herramienta de administración de RavenDB, Raven Management Studio y podrá ver algo similar a lo mostrado en la imagen de la derecha. Por supuesto, más adelante veremos como crear los índices de forma estática.

 

El otro punto que hacíamos notar es como RavenDB por defecto nos protegía de las consultas potencialmente grandes restringiendo el número de resultados a 128, puesto que, por defecto el valor de pageSize es este. Si, nosotros queremos traer un conjunto mayor de resultados podríamos hacer uso de este operador en la consulta,  de forma similar a como vemos a continuación:

 

 
















 

Otro nivel de protección que se incluye en RavenDB es que, por defecto, tampoco nos permite obtener más de 1000 resultados por consulta. En este caso, la protección no está en el cliente sino en el servidor y por lo tanto, modificar esto no es tan sencillo como acabamos de ver con el operador Take. Para hacerlo, tenemos que incluir en la configuración del servidor una nueva clave de configuración que se llama MaxPageSize.

 


A mayores, y como elemento de valor, RavenDB incluye algunos métodos de extensión a nuestras consultas que nos permitirán desde customizar las consultas hasta obtener información acerca de las mismas. El primero de los métodos que veremos es Statistics, gracias al cual podremos obtener información acerca del número total de documentos en la base de datos y otra información de valor.

 






















Untitled2

RavenQueryStatistics, nos ofrece, como puede observar, la siguiente información:

 

  • El nombre del índice que se ha utilizado en la consulta
  • Si los datos son state
  • El número total de documentos en la base de datos
  • El número de documentos omitidos ( hablaremos sobre esto)
  • La fecha de ejecución del índice
  • La fecha, a partir de la cual los datos son state

 

 

Un elemento importante de estas estadísticas es el de SkippedResults. Este valor nos ofrece la información de aquellos elementos que no se han tenido en cuenta para calcular el número total de resultados. Por ejemplo porque se ha aplicado una claúsula Distinct. Con esto, queremos decir que por ejemplo, si estamos paginando y hemos obtenido una primera pagina, incluyendo 5 en el valor de SkippedResults la siguiente página tendria que pedirse de la siguiente forma: pageCount + SkippedResults.

Otro de los métodos que podemos incluir en nuestras consultas es el método de Customize. En esta ocasión, este método no nos permite obtener más información sino que, nos permite customizar cierto grado de comportamiento de nuestras consultas. Imáginese por ejemplo que usted en una consulta dinámica no puede permitirse la consulta de datos state,  pues bien, con Customize podemos indicar esto de una forma sencilla.

 






















 

 

Si observa durante un momento los métodos de los que disponemos en IDocumentQueryCustomization , el tipo del parámetro de entrada en la Action que establecemos en Customize, podemos ver que disponemos de diferentes sobrecargas y posibilidades para la espera de resultados no state.

 

  • WaitForNonStaleResultsAsOf: Nos permite establecer una fecha de corte, que lógicamente no tiene porque ser en este momento, para el cual los datos no puedan ser stale. Por supuesto, también nos permite indicar un tiempo máximo de espera.
  • WaitForNonStaleResultsAsOfNow: Nos permite especificar la fecha de corte como ahora.
  • WaitForNonStaleResultsAsOfLastWrite: Nos permite especificar la fecha de corte como la última en la que se ha escrito por parte de la aplicación.
  • WaitForNonStaleResults: Nos permite indicar que el procesado de la query debe de esperar por resultados no stale. Podemos indicar un tiempo máximo de espera si deseamos no estar bloqueando at infinitum. Solamente se suele utilizar dentro de tests unitarios, tiene demasiadas implicaciones como para que exista su uso fuera de este ámbito.

 

La propina

Bueno, la entrada ya se acerca a su final. En la misma, hemos visto los principios de las consultas en RavenDB y los índices dinámicos. Para la siguiente entrada, hablaremos acerca de los índices estáticos, funciones map-reduce y analizadores. No obstante me gustaría dejar una pequeña propina…. fíjese en el siguiente código.

 

 














 

Al igual que el método Include en EF, este método nos permite en RavenDB indicarle que además de la carga de los documentos de la consulte carge adicionalmente la referencia indicada, en nuestro caso el blog asociado, de esta manera, tras el siguiente request:

 

GET http://ravenserver/indexes/dynamic/Posts?query=&start=0&pageSize=128&aggregation=None&include=BlogId HTTP/1.1
Accept-Encoding: deflate,gzip
Content-Type: application/json; charset=utf-8

Podemos ver los siguientes resultados,  dónde además de los post correspondientes se incluyen los Blogs referenciados.

 

HTTP/1.1 200 OK
Cache-Control: private
Content-Type: application/json; charset=utf-8
ETag: c171861e-497b-1bc0-a8c1-78c1da9f6070
Server: Microsoft-IIS/7.5
Raven-Server-Build: 573
X-AspNet-Version: 4.0.30319
Persistent-Auth: true
X-Powered-By: ASP.NET
Date: Mon, 19 Dec 2011 18:08:59 GMT
Content-Length: 1589

{«Results»:[{«BlogId»:»blogs/1″,»Title»:»The curegame, making off!»,»Body»:»This is the first post about thecuregame..»,»Comments»:[],»@metadata»:{«Raven-Entity-Name»:»Posts»,»Raven-Clr-Type»:»RavenQueryI.Post, RavenQueryI»,»@id»:»posts/1″,»Temp-Index-Score»:1.0,»Last-Modified»:»2011-12-19T17:06:05.6520000″,»@etag»:»00000000-0000-0500-0000-000000000005″,»Non-Authoritive-Information»:false}},{«BlogId»:»blogs/2″,»Title»:»The curegame, making off!»,»Body»:»For me, this is the first entry about…»,»Comments»:[],»@metadata»:{«Raven-Entity-Name»:»Posts»,»Raven-Clr-Type»:»RavenQueryI.Post, RavenQueryI»,»@id»:»posts/2″,»Temp-Index-Score»:1.0,»Last-Modified»:»2011-12-19T17:06:05.6520000″,»@etag»:»00000000-0000-0500-0000-000000000006″,»Non-Authoritive-Information»:false}}],»Includes»:[{«Title»:»O Bruxo mobile!»,»About»:»About this blog post…»,»@metadata»:{«Raven-Entity-Name»:»Blogs»,»Raven-Clr-Type»:»RavenQueryI.Blog, RavenQueryI»,»@id»:»blogs/1″,»Last-Modified»:»2011-12-19T17:03:56.0000000″,»@etag»:»00000000-0000-0400-0000-000000000002″,»Non-Authoritive-Information»:false}},{«Title»:»La masa, el ladrillo, la bota y el bocadillo!»,»About»:»About this blog post…»,»@metadata»:{«Raven-Entity-Name»:»Blogs»,»Raven-Clr-Type»:»RavenQueryI.Blog, RavenQueryI»,»@id»:»blogs/2″,»Last-Modified»:»2011-12-19T17:03:56.0000000″,»@etag»:»00000000-0000-0400-0000-000000000003″,»Non-Authoritive-Information»:false}}],»IsStale»:false,»IndexTimestamp»:»2011-12-19T17:06:05.6520000″,»TotalResults»:2,»SkippedResults»:0,»IndexName»:»Temp/Posts»,»IndexEtag»:»00000000-0000-0500-0000-000000000006″}

 

Saludos y hasta la siguiente entrada,

 

Unai

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

RavenDB (IV) La identidad de los documentos

En las anteriores entradas hemos trabajado bastante con los documentos, desde una pequeña introducción hasta elementos más avanzados como el trabajo con la metadata y la serialización de los mismos. Sin embargo, hay una cosa sobre la que hemos pasado de puntillas, intencionalmente, pero que trataremos en detalle en esta entrada, y es el tratamiento de la identidad de los documentos. Los documentos, como cualqiuer otro elemento en una colección, tienen que tener un mecanismo que nos permita disitnguirlos de forma unívoca. RavenDB hace un gran aporte en este tema, al dejarnos muchas posiblidades para gestionar las identidades adhoc o delegar en RavenDB, y sus distintos mecanismos, la gestión de la misma.

 

La propiedad Id

La propiedad Id, sin especificar por ahora el tipo de la misma, juega un papel muy importante dentro de RavenDB ya que por defecto, disponemos de una convención que asocia a esta como la propiedad que marca la identidad de un documento. Por supuesto, estas convenciones pueden modificarse, para ello, solamente tendríamos que especificar los valores de los delegados FindIdentityPropertyNameFromEntityName y FindIdentityProperty.

 


Con la finalidad de ir mostrando las distintas alternativas que tenemos nos basaremos en una estructura de documento similar a la siguiente:

 


 

Fijese en una cuestión importante que suele escapar a aquellas personas que empiezan a trabajar con documentos y es que la entidad OrderDetail NO tiene identidad. Este forma parte del agregado y por lo tanto no puede vivir fuera de el, llevado a documentos podríamos decir que OrderDetail siempre es parte de un documento Order y por lo tanto la identidad no tiene sentido en el. Note que hablamos de identidad, nada impediría tener  algún tipo de secuencia para los detalles de los pedidos.

 

Sobre una instancia del documento anterior, si procedieramos a su inserción veríamos varios aspectos interesantes que comentaremos a continuación. Supongamos para ello el siguiente código de inserción.

 


Revisando la secuencia de peticiones con fiddler podemos observar las siguientes trazas, imagen de la derecha, que se corresponden con todo el trabajo necesario para realizar este almacenamiento del documento. La primera de las peticiones, la cual devuelve un 404 es una petición para obtener el valor del HiLo para la colección de Orders. Esto en realidad ya nos ha dicho mucho acerca de como se va a tratar la identidad. RavenDB dispone por defecto de una implementación del algorítmo HiLo que utiliza por defecto cuando disponemos de una propiedad Id. La tercera entrada de la traza se corresponde a la creción del primer valor de este HiLo, vemos el contenido de la traza en la siguientes lineas:

Untitled

En la cuarta traza de fiddler ya podemos ver la inserción del documento con una identidad formada por el nombre de la colección y el Lo calculado, en nuestro caso orders/1:

 

Esto está hecho así precisamente para garantizar que las claves sean human readables, como tenemos un API rest el documento anterior podría ser consultado de la siguiente manera:

 

http://ravendbserver/docs/orders/1

 

Seguro que no se sorprende si le digo que el algoritmo de HiLo puede ser customizado y/o sustituído por otro si así lo deseamos.  La convención DocumentKeyGenerator nos permite realizar esta tarea, como ejemplo demostrativo intentaremos establecer que el mecanismo de generación es el HiLo implementado en RavenDB pero cambiaremos la capacidad para ir de 100 en 100 en vez de 1024 en 1024.  Esto, es tan sencillo como se puede ver a continuación:

 

 


Bien, hasta ahora entonces podríamos resumir que si una entidad tiene una propiedad Id de tipo string entonces RavenDB se basa en la conbinación del nombre de la colección ( Raven-Entity-Name) y un algoritmo de HiLo para generarnos la identidad de los documentos de una forma sencilla y human readable. Vamos a ver un caso diferente,¿que pasa si la propiedad en vez de ser de tipo string  es de tipo int?. Nada, no pasa absolutamente nada, todo sigue igual, se sigue utilizando el mismo mecanismo de Hilo y la identidad es igual que para el caso anterior, aunque nosotros vemos en la entidad este valor como un entero sin el nombre de la colección en vez de como un string.

¿ Y si no quiero un HiLo y quiero un auto incremental?¿ Cómo hago entonces?? Bueno, en realidad esto es también muy sencillo de implementar en RavenDB solamente tendríamos que poner nuestra convención DocumentKeyGenerator como sigue:

 


Untitled2

 

¿Y si ponemos un Guid? En este caso RavenDB nos sigue ayudando y nos ofrece un generador automático para este tipo de propiedades, lógicamente la pega es que ahora este tipo de consultas por rest ya no son tan amigables como teníamos para los casos anteriores.

 

 

Untitled3

 

 

 

 

 

Por supuesto, otra posibilidad que tenemos es  nos especificar ninguna propiedad Id, y delegar por completo en la infraesetructura de RavenDB su generación, por supuesto, esta identidad no será visible en los documentos, por lo menos no directamente tendríamos que usar session.Advanced.GetDocumentId.

 

Las referencias

Durante todas las entradas hemos estado hablando acerca de como emparejábamos la definición de los documentos con la de agregados. Pues bien, los documentos, al igual que los agregados, pueden contener referencias a otros documentos. En nuestro caso, para ilustrarlo vamos a modificar la definición de nuestra entidad OrderDetail para agregar una referencia a un documento llamada Producto, para ello, incluiremos una propiedad llamada ProductId, fíjese en la convención por defecto.