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.

 

using (IDocumentStore store = new DocumentStore() { Url = "http://portblackcode" })
{
    store.Conventions.FindIdentityPropertyNameFromEntityName = e => "Id";
    store.Conventions.FindIdentityProperty = pInfo => pInfo.Name == "Id";
 
    store.Initialize();
 
    using (IDocumentSession session = store.OpenSession())
    {
 
    }
}
 

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

 

public class Order
{
    public string Id { get; set; }
    public DateTime OrderDate { get; set; }
    public decimal Total { get; set; }
 
    public List<OrderDetail> Details { get; set; }
}
 
public class OrderDetail
{
    public int NumberOfItems { get; set; }
    public decimal ItemPrice { get; set; }
}

 

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.

 

    using (IDocumentStore store = new DocumentStore() { Url = "http://portblackcode" })
    {
        store.Conventions.FindIdentityPropertyNameFromEntityName = e => "Id";
        store.Conventions.FindIdentityProperty = pInfo => pInfo.Name == "Id";
     
        store.Initialize();
     
        using (IDocumentSession session = store.OpenSession())
        {
            var newOrder = new Order()
            {
                OrderDate = DateTime.UtcNow,
                Total = 1000M,
                Details = new List<OrderDetail>()
                {
                    new OrderDetail(){NumberOfItems =1,ItemPrice=20},
                    new OrderDetail(){NumberOfItems =1,ItemPrice=20},
                }
            };
     
     
            session.Store(newOrder); 
     
     
            session.SaveChanges(); 
     
     
            } 
     
    }

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

PUT http://XXX/docs/Raven/Hilo/orders
If-Match: 00000000-0000-0000-0000-000000000000
Accept-Encoding: deflate,gzip
Content-Type: application/json; charset=utf-8
Host: XXX
Content-Length: 19
Expect: 100-continue

{
  "Max": 1024
} HTTP/1.1

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:

 

[{"Key":"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:

 

 

using (IDocumentStore store = new DocumentStore() { Url = "http://portblackcode" })
{
    var hiloAlg = new MultiTypeHiLoKeyGenerator(store, capacity: 100);
 
    store.Conventions.DocumentKeyGenerator = (document) =>
    {
        return hiloAlg.GenerateDocumentKey(store.Conventions, document);
    };
         

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:

 

using (IDocumentStore store = new DocumentStore() { Url = "http://portblackcode" })
{
    store.Conventions.DocumentKeyGenerator = (document) =>
    {
        var typeTagName = store.Conventions.GetTypeTagName(document.GetType());
        var separator = store.Conventions.IdentityPartsSeparator;
        return string.Format("{0}{1}", typeTagName, separator);
    };
 
    store.Initialize();

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.

public class OrderDetail
{
    public string ProductId { get; set; }
    public int NumberOfItems { get; set; }
    public decimal ItemPrice { get; set; }
}

 

Untitled4

 

Una vez modificado esta entidad, si hacemos el ejercicio de incluir un documento de tipo Producto y referenciamos su identidad dentro de un nuevo pedido podríamos ver como el Raven Management Studio es capaz de reconocer estas referencias y dejarnos navegar por las mismas. Por no adelantarnos no lo haremos, pero en realidad, esto también es relativamente importante para nuestro proceso de consulta puesto que, nos permitirá hacer un eager loading de aquellas referencias que tengamos en nuestros documentos, de tal forma que nos podamos ahorrar round trips con la base de datos y por lo tanto mejorar el rendimiento de nuestro sistema. Curiósamente el método de carga de referencias se llama Include, igual que en Entity Framework para las propiedades de navegación.

 

Bueno, creo que no nos hemos dejado nada con respecto al tratamiento de las entidades ¿verdad?.  En la siguiente entrada sobre RavenDB, veremos un poco, solamente un poco de consultas para tratar los temas de actualización, concurrencia optimista y patches…..

 

 

 

 

 

 

 

 

Saludos

Unai

Published 12/12/2011 12:09 por Unai
Comparte este post:
http://geeks.ms/blogs/unai/archive/2011/12/12/ravendb-iv-la-identidad-de-los-documentos.aspx