27 de diciembre de 2012

Problemas de performance con relaciones lazy en Entity Framework

La versión en inglés de este post puede ser encontrada en http://blogs.southworks.net/jrowies/2012/12/27/entity-framework-performance-issues-with-lazy-relationships/

Durante las últimas semanas estuve trabajando en una aplicación que usa Entity Framework (code-first) y tuve algunos problemas de performance que pensé que podría ser útil compartirlos.

Luego de implementar algunos features en la aplicación noté ciertos problemas de performance, así que decidí agregar MiniProfiler y ver qué estaba pasando bajo el capó. La causa de los problemas de performance era que EF estaba ejecutando muchas queries innecesarias (o al menos eso pensaba) contra la BD. Así que, luego de investigar un poco y hacer algunas preguntas, descubrí que EF tiene una forma muy particular de manejar relaciones lazy "muchos a uno".

Déjenme explicar el problema con un ejemplo, supongamos que tenemos el siguiente modelo de dominio:

public class Order
{
    public Guid Id { get; set; }
 
    public string Description { get; set; }
 
    public virtual Customer Customer { get; set; }
}
 
public class Customer
{
    public Guid Id { get; set; }
 
    public string Name { get; set; }
}

Mapeado a las siguientes tablas:

 

En este modelo, si necesito acceder al ID del customer desde una order, es necesario pasar a través de la propiedad Customer, por ejemplo order.Customer.Id

El problema con esto es que EF va a cargar el objeto Customer desde la BD en cuanto tratemos de acceder a cualquier propiedad del objeto Customer. Ya que todo lo que necesitamos es el ID del customer, uno podría asumir que EF podría obtenerlo desde la FK Customer_Id en la tabla Orders sin necesidad de ejecutar queries extra contra la BD, pero desafortunadamente este no es el caso ...

Código como este:

foreach (var order in context.Orders)
{
    Console.WriteLine("Order {0}, Customer {1}", order.Description, order.Customer.Id);
}

Terminará ejecutando una gran cantidad de queries contra la BD, sólo para recuperar el ID del customer:

SELECT 
1 AS [C1], 
[Extent1].[Id] AS [Id], 
[Extent1].[Description] AS [Description], 
[Extent1].[Customer_Id] AS [Customer_Id]
FROM [dbo].[Orders] AS [Extent1]

exec sp_executesql N'SELECT 
[Extent2].[Id] AS [Id], 
[Extent2].[Name] AS [Name]
FROM  [dbo].[Orders] AS [Extent1]
INNER JOIN [dbo].[Customers] AS [Extent2] ON [Extent1].[Customer_Id] = [Extent2].[Id]
WHERE ([Extent1].[Customer_Id] IS NOT NULL) AND 
    ([Extent1].[Id] = @EntityKeyValue1)',N'@EntityKeyValue1 uniqueidentifier',@EntityKeyValue1='FF947EF3-5A3F-4A26-BDB9-039C49F559A7'

exec sp_executesql N'SELECT 
[Extent2].[Id] AS [Id], 
[Extent2].[Name] AS [Name]
FROM  [dbo].[Orders] AS [Extent1]
INNER JOIN [dbo].[Customers] AS [Extent2] ON [Extent1].[Customer_Id] = [Extent2].[Id]
WHERE ([Extent1].[Customer_Id] IS NOT NULL) AND 
    ([Extent1].[Id] = @EntityKeyValue1)',N'@EntityKeyValue1 uniqueidentifier',@EntityKeyValue1='BDC1430B-A46B-4486-A946-2F3DAC3B69F5'

// ... and so on ...


Como en el pasado he estado trabajando con NHibernate (quien maneja este caso como es esperado - sin ejecutar queries innecesarias), para mí fue muy sorprendente encontrarme con este comportamiento.

Luego de investigar un poco encontré que la estrategia recomendada para estos casos es agregar una propiedad FK en el modelo de dominio.

public class Order
{
    public Guid Id { get; set; }
 
    public string Description { get; set; }
 
    public Guid CustomerID { get; set; }
 
    [ForeignKey("CustomerID")]
    public virtual Customer Customer { get; set; }
}

Realmente no es algo que me guste ya que el modelo de dominio se va a contaminar con propiedades que no deberían estar allí, pero mientras tanto no encuentre una forma mas elegante de solucionar este problema, creo que voy a tener que vivir con esto ;)

Aquí pueden encontrar una aplicación simple de consola que compara los diferentes comportamientos de Entity Framework y NHibernate en cuanto al manejo de relaciones lazy "muchos a uno"

1 de febrero de 2012

Cómo construir una interfaz fluent básica en 8 pasos

La versión en inglés de este post puede ser encontrada en http://blogs.southworks.net/jrowies/2012/02/01/how-to-build-a-basic-fluent-interface-in-8-steps/

Aclaración: el objetivo de este post no es mostrar una lista completa de las diferentes técnicas existentes para escribir una API fluent ya que en la web se puede encontrar mucha información al respecto. Si estás interesado en el tema, te recomiendo que leas el libro de Martin Fowler Domain-Specific Languages.

Ahora que me saqué ese peso de encima, empecemos a escribir una interfaz fluent.

Imaginemos que estamos desarrollando una API para publicar blog posts en múltiples plataformas (Blogger, WordPress, TypePad, etc.), es muy probable que nuestro (super simplificado) modelo del dominio sea algo similar a esto:



Entonces, crear un blog post debería ser algo así:

var post = new BlogPost();
post.Title = "How to build a basic fluent interface in 8 steps";
post.Body = "...";
var author = new Author(); // We should check the authors repository
                           // before creating a new one
author.Name = "John Doe";
author.Email = "johndoe@email.com";
author.Twitter = "@johndoe";
post.Author = author;
post.Tags = "Fluent API, Internal DSL";

Nuestro modelo del dominio es muy sencillo y el código necesario para crear un blog post es fácil de leer y escribir asi que, en este caso implementar una interfaz fluent podría ser un tanto innecesario; pero... es perfecto para que podamos aprender a escribir una API fluent. Comenzamos?
 
Paso 1 - Escribiendo un borrador de la interfaz

Escribí (en notepad) un borrador de la interfaz fluent mostrando cómo querés que se vea (no pienses demasiado acerca de como vamos a implementar la interfaz, sólo jugá un poco con las ideas)

Esto es lo que escribí yo:

var post = Post
  .Title("How to build a basic fluent interface in 8 steps")
  .Body("...")
  .Author()
    .Name("John Doe")
    .Email("johndoe@email.com")
    .Twitter("@johndoe")
  .Tags("Fluent API, Internal DSL");

Paso 2 - Usando un enfoque "test-first"

Una vez que encontraste una opción que te satisface, pasala a un test en Visual Studio y agregá el código que será necesario para validar el resultado generado por la interfaz fluent. Dependiendo del tamaño de la API, quizás sea recomendable hacer esto gradualmente. En este caso vamos a dejar de lado, por ahora, la parte del Autor.

En el siguiente código estamos creando dos instancias de BlogPost, la primera usando la API standard y la segunda usando nuestra nueva API fluent. Luego de que ambas instancias son creadas, se verifica su equivalencia usando el método CheckEquivalence. Este método debe verificar si ambas instancias de BlogPost tienen el mismo título, cuerpo y tags.
[TestCase]
public void ConfiguringPostWithoutAuthor()
{
    var expected = new BlogPost();
    expected.Title = "How to build a basic fluent interface in 8 steps";
    expected.Body = "...";
    expected.Tags = "Fluent API, Internal DSL";

    var post = Post
        .Title("How to build a basic fluent interface in 8 steps")
        .Body("...")
        .Tags("Fluent API, Internal DSL");
    BlogPost actual = post.Build();

    Assert.IsTrue(TestsHelper.CheckEquivalence(expected, actual));
}

Este test, por supuesto, aún ni compila.
 
Paso 3 - Comenzando a implementar la interfaz

Ahora, comencemos a implementar la clase Post. Observar que el método Title es estático para que pueda ser invocado sin necesidad de crear una instancia de la clase Post.
public class Post
{
    public static Post Title(string title)
    {
        var post = new Post();
        post.TitleValue = title;
        return post;
    }
}

Paso 4 - Agregando más métodos a la interfaz fluent

Una vez que tenemos nuestro punto de partida, continuemos agregando el resto de los métodos (esta vez como métodos de instancia en lugar de estáticos).
public class Post
{
    ...

    public Post Body(string body)
    {
        this.BodyValue = body;
        return this;
    }

    public Post Tags(string tags)
    {
        this.TagsValue = tags;
        return this;
    }
}

Devolver la misma instancia del objeto que estamos construyendo, luego de cada invocación a un método, es una de las técnicas mas comunes usadas para escribir interfaces fluent, la misma es llamada Method Chaining.
Paso 5 - Completando la primer iteración y corriendo tests

Ahora, implementemos el método Build creando una instancia de BlogPost con los valores provistos a través de la interfaz fluent.
public class Post
{
    ...

    public BlogPost Build()
    {
        var blogPost = new BlogPost();

        blogPost.Title = this.TitleValue;
        blogPost.Body = this.BodyValue;
        blogPost.Tags = this.TagsValue;

        return blogPost;
    }
}

Nuestro test debería estar en verde ahora!  

Paso 6 - No nos olvidemos del Autor

Acá viene la parte un poco complicada, agreguemos al Autor...

Si continuamos lo mismo que veníamos haciendo con los métodos Title, Body y Tags, deberíamos hacer que el método Author devuelva una instancia de la clase Post; pero si hacemos eso, tendríamos que agregar los métodos Name, Email y Twitter en la clase Post, pero eso no es algo que querramos hacer. Lo que queremos es tener esos métodos en una clase separada, así que en lugar de devolver una instancia de Post, vamos a devolver una instancia de una nueva clase (AuthorSpec). Veamos cómo sería.
public class Post
{
 ...

 public AuthorSpec Author()
 {
  var authorSpec = new AuthorSpec();
  return authorSpec;
 }
}

public class AuthorSpec
{
 public AuthorSpec Name(string name)
 {
  this.NameValue = name;
  return this;
 }

 public AuthorSpec Email(string email)
 {
  this.EmailValue = email;
  return this;
 }

 public AuthorSpec Twitter(string twitter)
 {
  this.TwitterValue = twitter;
  return this;
 }
}

Ahora creemos un nuevo test, agregando el código de "Author":
public void ConfiguringPostWithAuthor()
{
 var expected = new BlogPost();
 expected.Title = "How to build a basic fluent interface in 8 steps";
 expected.Body = "...";
 expected.Tags = "Fluent API, Internal DSL";
 var author = new Author();
 author.Name = "John Doe";
 author.Email = "johndoe@email.com";
 author.Twitter = "@johndoe";
 expected.Author = author;

 var post = Post
  .Title("How to build a basic fluent interface in 8 steps")
  .Body("...")
  .Author()
   .Name("John Doe")
   .Email("johndoe@email.com")
   .Twitter("@johndoe")
  .Tags("Fluent API, Internal DSL");
 BlogPost actual = post.Build();
  
 Assert.IsTrue(CheckEquivalence(expected, actual));
}

Esperá! El compilador dice que el método Tags no es reconocido como un miembro de la clase AuthorSpec. Eso es porque estamos invocando al método Tags sobre el valor de retorno del método Twitter, el cual es una instancia de AuthorSpec y no de Post.

Al escribir APIs fluent, es muy común encontrarse con este tipo de problemas, las soluciones más comunes a esto son terminar la especificación del Autor con un método "end", o pasar un "nested closure" al método Author.
Paso 7 - Cerrando la especificación de Author y corriendo más tests

Opción A, usando un método "End".
public class Post
{
    ...

    public AuthorSpec Author()
    {
        this.authorSpec = new AuthorSpec(this);
        return authorSpec;
    }
}

public class AuthorSpec
{
    public AuthorSpec(Post parent)
    {
        this.parent = parent;
    }

    ...

    public Post End()
    {
        return this.parent;
    }
}

Cómo se ve:
var post = Post
    .Title("How to build a basic fluent interface in 8 steps")
    .Body("...")
    .Author()
        .Name("John Doe")
        .Email("johndoe@email.com")
        .Twitter("@johndoe")
        .End()
    .Tags("Fluent API, Internal DSL");

Opción B, usando un "nested closure".
public class Post
{
    ...

    public Post Author(Action<AuthorSpec> spec)
    {
        this.authorSpec = new AuthorSpec();
        spec(authorSpec);
        return this;
    }
}

Cómo se ve:
var post = Post
    .Title("How to build a basic fluent interface in 8 steps")
    .Body("...")
    .Author(a => a
        .Name("John Doe")
        .Email("johndoe@email.com")
        .Twitter("@johndoe"))
    .Tags("Fluent API, Internal DSL");

Yo personalmente prefiero la opción B. Creo que es ligeramente más fácil de leer y el programador no tiene que recordar llamar al método "End".
Luego de implementar una de estas dos opciones, nuestro test debería dar verde.

Paso 8 - Entrando en terrenos peligrosos

Imaginemos que queremos forzar a la gente que utiliza nuestra API para que asignen el nombre del autor primero y luego configuren la dirección de correo ó la cuenta de twitter, pero no ambos (un "o exclusivo"). Esto se puede hacer usando interfaces y así mostrar sólo un subconjunto de los métodos a medida que se va armando la cadena de métodos.
public class Post
{
    ...

    public Post Author(Action<IAuthorSpecAfterCreation> spec)
    {
        this.authorSpec = new AuthorSpec();
        spec(authorSpec);
        return this;
    }
}

public interface IAuthorSpecAfterCreation
{
    IAuthorSpecAfterName Name(string name);
}

public interface IAuthorSpecAfterName
{
    IAuthorSpecAfterEmailOrTwitter Email(string email);
    IAuthorSpecAfterEmailOrTwitter Twitter(string twitter);
}

public interface IAuthorSpecAfterEmailOrTwitter
{
}

public class AuthorSpec : IAuthorSpecAfterCreation, 
  IAuthorSpecAfterName, IAuthorSpecAfterEmailOrTwitter
{
    public IAuthorSpecAfterName Name(string name)
    {
        this.NameValue = name;
        return this;
    }

    public IAuthorSpecAfterEmailOrTwitter Email(string email)
    {
        this.EmailValue = email;
        return this;
    }

    public IAuthorSpecAfterEmailOrTwitter Twitter(string twitter)
    {
        this.TwitterValue = twitter;
        return this;
    }
}
Esta técnica puede volverse muy útil, pero la complejidad de la API puede crecer rápidamente con tantas interfaces aquí y allá... es recomendable usarlo con precaución.
Los fuentes completos pueden ser tomados desde acá.

Eso es todo, espero que sea útil :)

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(() =&gt; { 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(
                    () =&gt; 
                    { 
                        throw new NotImplementedException(); 
                    })
                    .ContinueWith((t) =&gt;
                    {
                        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 &lt; 100"),
    Name = "LowPrice"
};

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

var ruleHighPrice = new RuleDescription()
{
    Action = new SqlRuleAction("set PriceRange='High'"),
    Filter = new SqlFilter("TotalCost &gt;= 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

8 de diciembre de 2011

Principio de Inversión de Dependencias - SOLID

Para finalizar con la serie de posts sobre los principios SOLID, luego de unos meses bastante ajetreados que incluyeron una variedad de cambios (cambio de trabajo, cambio de pañales, cambio de sueño... :P) en esta oportunidad vamos a ver el principio DIP, Dependency Inversion Principle.

Este principio intenta solucionar algunos de los problemas más comunes del diseño orientado a objetos.

Rigidez: los cambios afectan a demasiadas partes del sistema
Fragilidad: cada cambio genera problemas en lugares inesperados
Inmobilidad: imposibilidad de reutilización

La definición del principio dice lo siguiente:

A. Módulos de niveles superiores no deben depender de módulos de niveles inferiores. Ambos deben depender de abstracciones.
B. Las abstracciones no deben depender de los detalles. Los detalles deben depender de las abstracciones.


Un ejemplo de un diseño que se vería beneficiado aplicando DIP es el siguiente:

Supongamos que tenemos una aplicación de ventas online hecha con ASP.NET MVC. Esta aplicación tiene un controlador con una acción que es ejecutada cuando el usuario confirma una compra. A su vez, cuando el usuario confirma la compra, se le envía un email con información sobre la misma.

public class ShoppingCartController : Controller
{

    public ActionResult Checkout()
    {
        ...
 
        customer = ...
        order = ...

        var emailSender = new EmailSender(server: "smtp.myserver.com");
        emailSender.SendOrderConfirmed(customer, order);

        ...
    }

}


Imaginemos que pasa el tiempo y tengo que modificar la clase EmailSender para poder especificar, además del servidor, el puerto que se va a utilizar para enviar los emails.

    

    var emailSender = new EmailSender(server: "smtp.myserver.com", port: 255);



Esto hace evidente el primer problema de nuestro diseño, estamos ante un diseño rígido, ya que el cambio en la clase EmailSender me obliga a modificar todos los lugares de mi aplicación donde la esté utilizando, caso contrario, mi aplicación no compilará.

Una posible solución es dejar el constructor de EmailSender tal como está actualmente y agregar una propiedad Port para poder indicar el puerto a utilizar al enviar mails.

    var emailSender = new EmailSender(server: "smtp.myserver.com");
    emailSender.Port = 255;



Este nuevo diseño tiene otro problema, es frágil, ya que si utilizo la clase EmailSender en varios lugares de mi aplicación, fácilmente puedo olvidar asignar el valor correcto a la propiedad Port, lo cual resultará en una aplicación que no siempre se comportará como es esperado (en algunos casos el envío de mails funcionará por tener el puerto asignado correctamente y en otros no, por utilizar el puerto default).

Para solucionar esto podemos mover la creación de la clase EmailSender a un lugar centralizado, y pasarle una instancia ya creada y correctamente configurada al controller:

    public ShoppingCartController(EmailSender emailSender)
    {
        this.emailSender = emailSender;
    }



La acción de pasarle al controlador sus dependencias, en este caso una instancia de EmailSender, es llamado inyección de dependencias (en inglés, dependency injection ó simplemente DI).

La inyección de dependencias puede hacerse por constructor, como es en este caso, o también por propiedades. Por lo general la inyección por constructor es la práctica más recomendable.

Vale la pena mencionar que la inyección de dependencias se puede hacer en forma manual o mediante algún contenedor de inversión de control (en inglés, inversion of control container ó ioc container). Algunos ioc containers son Castle Windsor, Unity o StructureMap, por mencionar algunos.

Hasta acá venimos bien salvo por el hecho que este diseño tiene otro problema, la "inmovilidad", ya que si quisiéramos agregar un nuevo tipo de mecanismo para enviar notificaciones, por ejemplo por SMS, no podríamos reutilizar el controlador tal cual está ya que el mismo depende de EmailSender.

Este problema puede ser solucionado aplicando el principio de inversión de dependencias, para esto debemos crear una abstracción de EmailSender (interfaz IMessageSender) y de esa manera podremos "invertir" la dependencia:





Esto es llamado inversión de dependencias (o inversión de control) ya que se podría decir que la dirección de la dependencia entre ShoppingCartController y EmailSender, al introducir la abstracción IMessageSender, fué invertida.

Finalmente, podemos ver que nuestro diseño final cumple DIP ya que:

A. El controller (módulo de nivel superior) ya no depende de EmailSender (módulo de nivel inferior) y tanto el controller como el EmailSender dependen de IMessageSender (una abstracción)
B. El controller y EmailSender (detalles) dependen de IMessageSender (una abstracción)

En mi primer post sobre el principio SRP podrán encontrar varios enlaces a las lecturas recomendadas sobre principios SOLID.

Espero que sea de utilidad, cualquier comentario será bienvenido.

Posts anteriores sobre principios SOLID
Principio de responsabilidad única - SRP
Principio abierto-cerrado - OCP
Principio de Substitución de Liskov - LSP
Principio de Separación de Interfaces - ISP