Windows Azure Storage Tables, usando JSON para una respuesta más ligera

Introducción

Windows Azure Tables es la solución de Microsoft para almacenar grandes cantidades de datos estructurados, eso no quiere decir que sean relacionales. Es lo que llama una base de datos NoSQL. Este servicio se puede utilizar sin necesidad de estar dentro del data center de Microsoft y prácticamente desde cualquier tecnología porque utiliza una API REST con una respuesta AtomPub en XML.

¿Cómo funciona?

Windows Azure Table se compone de los siguientes conceptos:

clip_image001

Donde cada cuenta de Windows Azure Storage se identifica a través de una URL única con este formato: http://<storage account>.table.core.windows.net/<table>

Dentro de la cuenta de Storage existe una colección de tablas. Esas tablas no tienen esquema definido si no que son a su vez, colecciones de entidades. Estas entidades son también colecciones de pares de nombre valor, que conforman las columnas y los valores de esa entidad en concreto.

  • Tabla: Una colección de entidades. Pueden contener millones de entidades (registros) y están particionadas a través de la propiedad PartitionKey.
  • Entidad: Es una colección de propiedades, similar a la fila de una base de datos. El tamaño máximo permitido es de 1MB.
  • Propiedades: Es un par de nombre valor. Cada entidad pueden ser como máximo 252 entidades. Todas las entidades tienen siembre tres propiedades comunes y ya definidas. PartitionKey, RowKey y TimeStamp.

SDK

Para acceder a este servicio se disponen de una serie de SDK’s en diferentes lenguajes. De esta manera el desarrollador se puede abstraer completamente de los detalles técnicos de cómo se transmite la información entre el servido y el cliente.

En ese sentido se ha comentado anteriormente que Windows Azure Tables utiliza una basada en REST que responde XML AtomPub. La novedad es que ahora se puede configurar el SDK para que el tipo de respuesta del servidor sea JSON, lo que mejora el tamaño de la respuesta por parte del servidor además de bajar, también, la latencia entre los extremos.

JSON

JSON o, JavaScript Object Notation, es un formato de texto que permite serializar información estructurada. Pero el problema de utiliza este formato es que se tiene que especificar muy bien la convención a la hora de definir los nombres y valores de las entidades. Internamente la librería de Windows Azure Storage utiliza OData, y es justamente la cual se encarga de mapear las entidades de JSON, existen tres formas de serializar una colección de entidades con OData.

  • nometadata: Como indica el nombre, esta forma de serialización no incluye ningún metadato y el cliente deberá de saber cómo interpretar los datos (por ejemplo teniendo los tipos en cliente y en servidor).
  • minimalmetadata: Este formato contiene información sobre el tipo de datos de las propiedades y como tiene que ser interpretado. Es interesante si el cliente no es consciente del tipo de respuesta o si se está haciendo una exploración de las tablas. Es el valor predeterminado de Windows Azure Storage Client 3.0
  • fullmetadata: Este formato es usado para respuestas genéricas de OData que requiere la definición de todos los tipos.

¿Qué mejoras se consiguen con respecto a AtomPub?

Las mejoras son evidentes, puesto que JSON es un formato mucho más ligero que AtomPub, de hecho en la nueva librería de Windows Azure Storage Client 3.0 viene activada por defecto.

A continuación se muestran los resultados de hacer una serie de pruebas sobre el servicio. Las pruebas han sido, 6 peticiones las cuales incluyen comprobar si existe una tabla, insertar 3 entidades y hacer una consulta de todas las entidades de una tabla.

Format

Request Header Size

Request Body Size

Response Header Size

Response Body Size

% Savings in HTTP Body Size only vs. AtomPub

% Savings in total HTTP transfer vs. AtomPub

AtomPub

3,611

2,861

3,211

8,535

N/A

N/A

JSON MinimalMetadata

3,462

771

3,360

2,529

71%

44%

JSON NoMetadata

3,432

771

3,330

1,805

77%

49%

Como se puede observar en los resultados los beneficios de la respuesta son más que evidentes. Los beneficios se pueden resumir así:

  • JSON reduce los datos necesarios para una respuesta lo que se traduce en una reducción de precio por el ancho de banda usado.
  • Combinando JSON y CORS se pueden desarrollar aplicaciones escalables donde se pueden acceder a datos almacenados en Windows Azure Storge desde un navegador directamente ejecutando código en JavaScript.
  • Otro beneficio de JSON sobre AtomPub es que algunas aplicaciones ya utilizan JSON como modelo de objetos para serializar y deserializar información, haciendo la posibilidad de consultar los datos en JSON de que se haga una migración más rápida.

Usando JSON con la librería de Windows Azure Storage Client 3.0

Para aprovechar todos los beneficios de JSON en los desarrollos, se va a proceder a mostrar un ejemplo de cómo se hace.

Lo primero de todo es recordar que en esta nueva versión de la librería el soporte de JSON viene configurado de manera predeterminada. Pero si se desea cambiar esa configuración se hace así:

CloudTableClient tableClient = new CloudTableClient(baseUri, cred)
{ 
    // Values supported can be AtomPub, Json, JsonFullMetadata or 
    JsonNoMetadata PayloadFormat = TablePayloadFormat.JsonNoMetadata
};

Resolución de tipos para JSON NoMetadata

Cuando se utiliza la opción de JSON sin metadatos, el problema que surge en el cliente es que no se tienen ningún tipo de información sobre cómo tratar los tipos en el cliente. Así que de alguna manera se tiene que proporcionar esa información para que el cliente parsee lo datos de manera adecuada.

Para solucionar este problema se puede definir en la petición de una operación un PropertyResolver en donde en una función se define en base a los nombre de las propiedades que tipo de dato se espera en la respuesta para el cliente de OData los trate de manera adecuada.

Aquí se puede ver un ejemplo de este PropertyResolver:

TableRequestOptions options = new TableRequestOptions()    
{ 
    PropertyResolver = (partitionKey, rowKey, propName, propValue) =>    
    {    
        if(propName == "CustomerSince")    
        {    
            return EdmType.DateTime;    
        }
        else if(propName == "Rating")    
        {    
            return EdmType.Int32;    
        }    
        else    
        {    
            return EdmType.String;    
        }    
    };    
};    
 
TableQuery<DynamicTableEntity> query = (from ent in complexEntityTable.CreateQuery<DynamicTableEntity>() select ent)
.WithOptions(options);

Ejemplos de PayLoad en JSON

Para ilustrar todo lo que se ha comentado en este post, se muestran los tres tipos de respuesta del servidor para que se pueda comparar.

AtomPub

<?xml version="1.0" encoding="utf-8"?>
<feed xml:base="http://someaccount.table.core.windows.net/" xmlns="http://www.w3.org/2005/Atom" xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" xmlns:georss="http://www.georss.org/georss" xmlns:gml="http://www.opengis.net/gml">
<id>http://someaccount.table.core.windows.net/Customers</id>
<title type="text">Customers</title>
<updated>2013-12-03T06:37:21Z</updated>
<link rel="self" title="Customers" href="Customers" />
<entry m:etag="W/&quot;datetime'2013-12-03T06%3A37%3A20.9709094Z'&quot;">
<id>http://someaccount.table.core.windows.net/Customers(PartitionKey='Jonathan',RowKey='Foster')</id>
<category term="someaccount.Customers" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
<link rel="edit" title="Customers" href="Customers(PartitionKey='Jonathan',RowKey='Foster')" />
<title />
<updated>2013-12-03T06:37:21Z</updated>
<author>
<name />
</author>
<content type="application/xml">
<m:properties>
<d:PartitionKey>Jonathan</d:PartitionKey>
<d:RowKey>Foster</d:RowKey>
<d:Timestamp m:type="Edm.DateTime">2013-12-03T06:37:20.9709094Z</d:Timestamp>
<d:Address>1234 SomeStreet St, Bellevue, WA 75001</d:Address>
<d:Email>Jonathan@fourthcoffee.com</d:Email>
<d:PhoneNumber>425-555-0101</d:PhoneNumber>
<d:CustomerSince m:type="Edm.DateTime">2005-01-05T00:00:00Z</d:CustomerSince>
<d:Rating m:type="Edm.Int32">3</d:Rating>
</m:properties>
</content>
</entry>
<entry m:etag="W/&quot;datetime'2013-12-03T06%3A37%3A21.1259249Z'&quot;">
<id>http://someaccount.table.core.windows.net/Customers(PartitionKey='Lisa',RowKey='Miller')</id>
<category term="someaccount.Customers" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
<link rel="edit" title="Customers" href="Customers(PartitionKey='Lisa',RowKey='Miller')" />
<title />
<updated>2013-12-03T06:37:21Z</updated>
<author>
<name />
</author>
<content type="application/xml">
<m:properties>
<d:PartitionKey>Lisa</d:PartitionKey>
<d:RowKey>Miller</d:RowKey>
<d:Timestamp m:type="Edm.DateTime">2013-12-03T06:37:21.1259249Z</d:Timestamp>
<d:Address>4567 NiceStreet St, Seattle, WA 54332</d:Address>
<d:Email>Lisa@northwindtraders.com</d:Email>
<d:PhoneNumber>425-555-0101</d:PhoneNumber>
<d:CustomerSince m:type="Edm.DateTime">2003-01-05T00:00:00Z</d:CustomerSince>
<d:Rating m:type="Edm.Int32">2</d:Rating>
</m:properties>
</content>
</entry>
<entry m:etag="W/&quot;datetime'2013-12-03T06%3A37%3A20.7628886Z'&quot;">
<id>http://someaccount.table.core.windows.net/Customers(PartitionKey='Walter',RowKey='Harp')</id>
<category term="someaccount.Customers" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
<link rel="edit" title="Customers" href="Customers(PartitionKey='Walter',RowKey='Harp')" />
<title />
<updated>2013-12-03T06:37:21Z</updated>
<author>
<name />
</author>
<content type="application/xml">
<m:properties>
<d:PartitionKey>Walter</d:PartitionKey>
<d:RowKey>Harp</d:RowKey>
<d:Timestamp m:type="Edm.DateTime">2013-12-03T06:37:20.7628886Z</d:Timestamp>
<d:Address>1345 Fictitious St, St Buffalo, NY 98052</d:Address>
<d:Email>Walter@contoso.com</d:Email>
<d:PhoneNumber>425-555-0101</d:PhoneNumber>
<d:CustomerSince m:type="Edm.DateTime">2010-01-05T00:00:00Z</d:CustomerSince>
<d:Rating m:type="Edm.Int32">4</d:Rating>
</m:properties>
</content>
</entry>
</feed>

JSON minimalmetadata

{
"odata.metadata":"http://someaccount.table.core.windows.net/$metadata#Customers",
"value":[
{
"PartitionKey":"Jonathan",
"RowKey":"Foster",
"Timestamp":"2013-12-03T06:39:56.6443475Z",
"Address":"1234 SomeStreet St, Bellevue, WA 75001",
"Email":"Jonathan@fourthcoffee.com",
"PhoneNumber":"425-555-0101",
"CustomerSince@odata.type":"Edm.DateTime",
"CustomerSince":"2005-01-05T00:00:00Z",
"Rating":3
},
{
"PartitionKey":"Lisa",
"RowKey":"Miller",
"Timestamp":"2013-12-03T06:39:56.7943625Z",
"Address":"4567 NiceStreet St, Seattle, WA 54332",
"Email":"Lisa@northwindtraders.com",
"PhoneNumber":"425-555-0101",
"CustomerSince@odata.type":"Edm.DateTime",
"CustomerSince":"2003-01-05T00:00:00Z",
"Rating":2
},
{
"PartitionKey":"Walter",
"RowKey":"Harp",
"Timestamp":"2013-12-03T06:39:56.4743305Z",
"Address":"1345 Fictitious St, St Buffalo, NY 98052",
"Email":"Walter@contoso.com",
"PhoneNumber":"425-555-0101",
"CustomerSince@odata.type":"Edm.DateTime",
"CustomerSince":"2010-01-05T00:00:00Z",
"Rating":4
}
]
}

JSON nometadata

"value":[
{
"PartitionKey":"Jonathan",
"RowKey":"Foster",
"Timestamp":"2013-12-03T06:45:00.7254269Z",
"Address":"1234 SomeStreet St, Bellevue, WA 75001",
"Email":"Jonathan@fourthcoffee.com",
"PhoneNumber":"425-555-0101",
"CustomerSince":"2005-01-05T00:00:00Z",
"Rating":3
},
{
"PartitionKey":"Lisa",
"RowKey":"Miller",
"Timestamp":"2013-12-03T06:45:00.8834427Z",
"Address":"4567 NiceStreet St, Seattle, WA 54332",
"Email":"Lisa@northwindtraders.com",
"PhoneNumber":"425-555-0101",
"CustomerSince":"2003-01-05T00:00:00Z",
"Rating":2
},
{
"PartitionKey":"Walter",
"RowKey":"Harp",
"Timestamp":"2013-12-03T06:45:00.5384082Z",
"Address":"1345 Fictitious St, St Buffalo, NY 98052",
"Email":"Walter@contoso.com",
"PhoneNumber":"425-555-0101",
"CustomerSince":"2010-01-05T00:00:00Z",
"Rating":4
}
]
}

Luis Guerrero.

Technical Evangelist Windows Azure

@guerrerotook