28 de enero de 2012

API fluent para configurar Windows Azure ACS

La versión en inglés de este post puede ser encontrada en http://blogs.southworks.net/jrowies/2012/01/28/fluent-api-for-setting-up-windows-azure-acs/

Durante los últimos meses he estado trabajando para la gente de p&p de Microsoft, desarrollando la aplicación de ejemplo para una nueva guía acerca de la integración de aplicaciones híbridas en Windows Azure.

Uno de los componentes de la solución de ejemplo es una aplicación de consola que realiza todos los pasos requeridos para tener los namespaces de ACS y Service Bus correctamente configurados. Esta aplicación usa una serie de wrappers alrededor de la API de management de ACS.

Mientras que esto es un gran paso en dirección de simplificar la vida del desarrollador, luego de mirar el código resultante me di cuenta que mi vida se podría haber simplificado aún más. Esa es la razón por la cual decidí invertir algo de mi tiempo desarrollando una API fluent para configurar los namespaces de ACS.

Así es como se ve la API:

var namespaceDesc = new AcsNamespaceDescription(
 "somenamespace", "ManagementClient", "T+bQtqP21BaCLO/8D1hanRdKJF8ZYEV8t32odxP4pYk=");

var acsNamespace = new AcsNamespace(namespaceDesc);

acsNamespace
 .AddGoogleIdentityProvider()
 .AddServiceIdentity(
  si => si
   .Name("Vandelay Industries")
   .Password("Passw0rd!"))
 .AddRelyingParty(
  rp => rp
   .Name("MyCoolWebsite")
   .RealmAddress("http://mycoolwebsite.com/")
   .ReplyAddress("http://mycoolwebsite.com/")
   .AllowGoogleIdentityProvider()
   .SwtToken()
   .TokenLifetime(120)
   .SymmetricKey(
    Convert.FromBase64String("yMryA5VQVmMwrtuiJBfyjMnAJwoT7//fCuM6NwaHjQ1="))
   .AddRuleGroup(rg => rg
    .Name("Rule Group for MyCoolWebsite Relying Party")
    .AddRule(
     rule => rule
      .Description("Google Passthrough")
      .IfInputClaimIssuer().Is("Google")
      .AndInputClaimType().IsOfType(ClaimTypes.Email)
      .AndInputClaimValue().IsAny()
      .ThenOutputClaimType().ShouldBe(ClaimTypes.Name)
      .AndOutputClaimValue().ShouldPassthroughFirstInputClaimValue())
    .AddRule(
     rule => rule
      .Description("ACS rule")
      .IfInputClaimIssuer().IsAcs()
      .AndInputClaimType().IsAny()
      .AndInputClaimValue().IsAny()
      .ThenOutputClaimType().ShouldPassthroughFirstInputClaimType()
      .AndOutputClaimValue().ShouldPassthroughFirstInputClaimValue())));

acsNamespace.SaveChanges(logInfo => Console.WriteLine(logInfo.Message));


Pueden encontrar más información en el repositorio del proyecto en github.

Ah, y por favor no olviden pegarle una mirada a la guía sobre integración de aplicaciones híbridas cuando la misma esté disponible (se puede ir descargando un borrador desde aquí), estos muchachos están haciendo un trabajo increíble !

Eso es todo, espero que sea útil :)

17 de enero de 2012

Windows Azure Caching Service: Como funcionan en conjunto el cache distribuido y el cache local

La versión en inglés de este post puede ser encontrada en http://blogs.southworks.net/jrowies/2012/01/17/windows-azure-caching-service-how-distributed-and-local-caches-work-together/

Introducción

La intención de este post es mostrar cómo el cache distribuido y el cache local de Windows Azure Caching Service funcionan en conjunto. Vamos a cubrir las implicancias de habilitar el cache local, la interacción entre el cache local y el distribuido, y cómo prevenir errores inesperados que puedan ser causados por ser el cache local un cache que guarda su estado en memoria.

Para una descripción completa acerca de qué es Windows Azure Caching Service y cómo puede ser configurado, por favor leer:
Introducing the Windows Azure Caching Service
Azure AppFabric Caching Service
Building Windows Azure Applications with the Caching Service

Implicancias de habilitar el cache local

Cuando un cliente habilita el cache local, se crea un cache que guarda su estado en el mismo espacio de memoria donde el cliente está ejecutándose. Esto mejora drásticamente el rendimiento al guardar y recuperar objetos del cache, pero esta mejora no viene sin un costo asociado. Al habilitar el cache local en cada cliente, habrá más réplicas de los mismos datos, ya que habrá copias en la fuente de datos original (por ej. base de datos SQL), en el cache distribuido (en la nube) y también en el cache local de cada cliente. Manejar la replicación de datos no es algo trivial, así que decidir sobre habilitar caching local o no, dependerá del escenario en el que nos encontremos.

Interacción entre el cache distribuido y el local

Algo importante a saber es cómo los caches local y distribuido interactúan entre sí. Debajo pueden encontrar diagramas mostrando cómo dos clientes (azul y verde) realizan acciones contra los caches, y las consecuencias que cada acción tiene sobre los datos de los mismos.

El cliente azul guarda la clave “A” en el cache:


El cliente verde recupera la clave “A” desde el cache


El cliente azul recupera la clave “A” desde el cache


El cliente azul actualiza el valor de la clave “A”


El cliente azul elimina del cache la clave “A”


El cliente verde recupera la clave “A” desde el cache


NOTA: para los casos de uso mostrados en los diagramas, estamos asumiendo que los objetos guardados en los caches no van a expirar antes que los clientes traten de recuperarlos.

Un cache que guarda su estado en memoria

Dado que el cache local reside en memoria, hay que tener en cuenta que cada cambio a un objeto luego de que el mismo fue guardado en el cache, modificará también el objeto cacheado (ya que ambos objetos son el mismo). Claramente, esto no es así con el cache distribuido, ya que una representación serializada del objeto es guardada en ese caso.

Guardando un nuevo objeto Movie en el cache:


Cambiar las propiedades del mismo objeto Movie modificará el objeto guardado en el cache local también:


Eso es todo, espero que sea de utilidad!

9 de enero de 2012

Alto consumo de memoria en WaWorkerHost.exe

La versión en inglés de este post puede ser encontrada en http://blogs.southworks.net/jrowies/2012/01/09/high-memory-consumption-in-waworkerhost-exe/

Cuando el consumo de memoria del proceso WaWorkerHost.exe comienza a subir sin razón aparente, y estamos usando Tasks en nuestro worker role, puede que haya llegado el momento de verificar excepciones no manejadas y comenzar a manejarlas correctamente.

Lanzar excepciones desde una Task en un worker role y NO manejarlas correctamente:

    public class WorkerRole : RoleEntryPoint
    {
        public override void Run()
        {
            while (true)
            {
                Task.Factory.StartNew(() => { throw new NotImplementedException(); });
                Thread.Sleep(100);
            }
        }
    }

Puede causar este consumo elevado de memoria en el proceso WaWorkerHost.exe:


NOTA: WaWorkerHost.exe es el proceso encargado de alojar los worker roles cuando los mismos corren en el emulador.

Debajo se puede encontrar una manera de manejar excepciones lanzadas en Tasks.

    public class WorkerRole : RoleEntryPoint
    {
        public override void Run()
        {
            while (true)
            {
                Task.Factory.StartNew(
                    () => 
                    { 
                        throw new NotImplementedException(); 
                    })
                    .ContinueWith((t) =>
                    {
                        Trace.WriteLine(string.Format("Exception in Worker Role: {0}", 
                            t.Exception.InnerException.ToString()));
                    },
                    TaskContinuationOptions.OnlyOnFaulted);

                Thread.Sleep(100);
            }
        }
    }

6 de enero de 2012

Usando Rule Actions en subscripciones de Windows Azure Service Bus

La versión en inglés de este blog post puede ser encontrada en http://blogs.southworks.net/jrowies/2012/01/06/using-rule-actions-in-windows-azure-service-bus-subscriptions/

Asignar rule actions a subscripciones de Service Bus es un mecanismo útil para modificar automáticamente las propiedades de los BrokeredMessages cuando los mismos son enviados a un Topic.

La forma más sencilla de asignar una rule action a una subscripción es hacerlo cuando la subscripción está siendo creada:

namespaceManager.CreateSubscription(
    topicName, subscriptionName,
    new RuleDescription { Action = new SqlRuleAction("set dcount=[sys].DeliveryCount") });
Aquí estamos asignado el valor que tiene la propiedad DeliveryCount del objeto BrokeredMessage a una propiedad del encabezado del mensaje (las propiedades del encabezado del mensaje pueden ser utilizadas para enrutar los mismos a determinadas subscripciones). Si necesitamos asignar múltiples rule actions a una única subscripción, primero tenemos que crear la subscripción y luego crear las rule actions y asignarlas a la subscripción:
namespaceManager.CreateSubscription(topicName, subscriptionName);

var deliveryCountRule = new RuleDescription()
{
    Action = new SqlRuleAction("set dcount=[sys].DeliveryCount"),
    Name = "DeliveryCountRule"
};

var sizeRule = new RuleDescription()
{
    Action = new SqlRuleAction("set sz=[sys].Size"),
    Name = "SizeRule"
};

var subscriptionClient = messagingFactory.CreateSubscriptionClient(
    topicName, subscriptionName);

subscriptionClient.AddRule(deliveryCountRule);
subscriptionClient.AddRule(sizeRule);
Podemos combinar rule actions con filtros para asignar valores a propiedades, basándonos en condiciones lógicas:
namespaceManager.CreateSubscription(topicName, subscriptionName);

var ruleLowPrice = new RuleDescription()
{
    Action = new SqlRuleAction("set PriceRange='Low'"),
    Filter = new SqlFilter("TotalCost < 100"),
    Name = "LowPrice"
};

var ruleMediumPrice = new RuleDescription()
{
    Action = new SqlRuleAction("set PriceRange='Medium'"),
    Filter = new SqlFilter("TotalCost >= 100 AND TotalCost < 500"),
    Name = "MediumPrice"
};

var ruleHighPrice = new RuleDescription()
{
    Action = new SqlRuleAction("set PriceRange='High'"),
    Filter = new SqlFilter("TotalCost >= 500"),
    Name = "HighPrice"
};

var subscriptionClient = messagingFactory.CreateSubscriptionClient(
    topicName, subscriptionName);

subscriptionClient.AddRule(ruleLowPrice);
subscriptionClient.AddRule(ruleMediumPrice);
subscriptionClient.AddRule(ruleHighPrice);
Ya casi estamos... La última porción de código debería funcionar, salvo por el hecho de que cuando una subscripción es creada usando el método CreateSubscription, una regla por defecto con un TrueFilter es agregada, lo cual significa que todo mensaje va a aparecer en esa subscripción. Para evitar esto, debemos eliminar las reglas pre-existentes antes de agregar las nuestras.
var rules = namespaceManager.GetRules(topicName, subscriptionName);

...

var subscriptionClient = messagingFactory.CreateSubscriptionClient(
    topicName, subscriptionName);

foreach (var rule in rules)
    subscriptionClient.RemoveRule(rule.Name);

subscriptionClient.AddRule(ruleLowPrice);
subscriptionClient.AddRule(ruleMediumPrice);
subscriptionClient.AddRule(ruleHighPrice);

...
Información más detallada sobre las expresiones que pueden ser utilizadas al definir rule actions puede ser encontrada en http://msdn.microsoft.com/en-us/library/microsoft.servicebus.messaging.sqlruleaction.sqlexpression.aspx