6.14.2009

Versionando Aplicaciones – CastleWindsor – Parte 2

Tal y como lo mencioné en el post pasado, la forma más adecuada para manejar versionamiento de aplicaciones ( diversas funcionalidades por cliente dentro de una aplicación) es utilizando IoC. En este post voy a examinar como manejar el versionamiento utilizando la librería CastleWindsor – Windsor Container - como contenedor de IoC.

Para examinar los tres frameworks de IoC, voy a utilizar el mismos ejemplo. El ejemplo consiste en una interface para manejar alertas, la cual especifica en su contrato una propiedad para almacenar el mensaje y un método para enviar la alerta. Vamos a tener dos clases implementando este contrato, una que teoóricamente envía alertas por email, y otra por SMS. Vamos a tener además una clase para hacer búsquedas la cual enviará una alerta cada vez que se haga una búsqueda ( suponiendo que en la búsqueda van palabras que pueden ser consideras “peligrosas ). El diagrama de clases es el que se presenta a continuación:

image

Utilizando Castle Windsor

El Windsor Container es un contenedor de control de inversión el cual a grandes rasgos, extiende la funcionalidad de su homólogo en el mismo framework – MicroKernel – agregando soporte por configuración.

En este caso vamos a crear una aplicación de consola que contenga las clases y la interface antes mencionada y vamos a instanciar las alertas utilizando el IoC CastleWindsor.

Para poder utilizar CastleWindsor debemos agregar las referencias siguientes:

image

Seguidamente procedemos a crear la interface IAlert, la cual va a tener el contrato de la funcionalidad de la alerta:

image

El código de esta interface es el siguiente:

public interface IAlert
{
string Message { get; set; }
string SendAlert( );
}


El siguiente paso es crear la clase EmailAlert para enviar alertas por correo.



public class EmailAlert : IAlert
{
private string m_Message;

public string Message
{
get
{
return m_Message;
}
set
{
m_Message = value;
}
}

public string SendAlert( )
{
if (!string.IsNullOrEmpty(Message))
return Message;
return "Alerta enviada por mail";
}


}


Ahora vamos a crear la clase SMSAlert para “enviar” alertas a través de SMS.



public class SMSAlert : IAlert
{
private string m_Message;
public string Message
{
get
{
return m_Message;
}
set
{
m_Message = value;
}
}

public string SendAlert( )
{
if (!string.IsNullOrEmpty(Message))
return Message;
return "Enviando Alerta por SMS";
}
}



A continuación vamos a crear la clase SearchManager, la cual es la que realiza las búsquedas y dispara las alertas.



public class SearchManager
{
public IAlert Alert { get; set; }

public SearchManager( IAlert pAlert )
{
Alert = pAlert;
}

public string DoSearch( string criteria )
{
//Notificamos la búsqueda
if (Alert != null)
return Alert.SendAlert( );
return "Error!";
}
}


En primera instancia vamos a registar los componentes a instanciar desde el código ( algo que no tiene mucho sentido si se lo que se desea es versionar, pero es una buena forma de entender como funciona este contenedor). El código siguiente muestra como llevar a cabo esta tarea.



class Program
{
static void Main( string[] args )
{
IWindsorContainer contenedor = new WindsorContainer( );
contenedor.AddComponent("Email.Alert", typeof(IAlert), typeof(EmailAlert));
contenedor.AddComponent("Sms.Alert", typeof(IAlert), typeof(EmailAlert));


IAlert AlertaEnviada = contenedor["Email.Alert"] as EmailAlert;
//IAlert AlertaEnviada = contenedor["Sms.Alert"] as EmailAlert;

Console.WriteLine("Alerta: {0}", AlertaEnviada.SendAlert( ));

}
}



Primeramente, podemos ver que se instancia el contenedor de componentes, seguidamente registramos los componentes que queremos que esten disponibles para el contenedor, y por último instanciamos la clase que deseamos utilizar en el código. Como se puede ver en el proceso de crear la instancia, lo único que tenemos que hacer es pedirle al contenedor que me devuelva una instancia de la clase utilizando la llave que se utilizó para agregar la definición del componente al contenedor – en el ejemplo anterior Email.Alert y Sms.Alert. Al ejecutar la aplicación de consola recién creada obtenemos el siguiente resultado:



image



Seguidamente vamos a llevar a cabo esta instanciación y el registro de componentes via configuración, en donde el manejo versionamiento empieza a tener sentido.



Utilizando Archivos de Configuración


Por supuesto que el registrar componentes vía código, no es la forma en que podemos aprovechar este patrón para poder versionar nuestra aplicación. Lo que realmente buscamos, es poder registrar componentes sin recompilar la aplicación, y que la aplicación compilada busque los servicios en los contratos especificados vía configuración.



Para esto WindsorCastle tiene la posiblidad de registrar los componentes vía archivo de configuración, y al ser la aplicación de ejemplo una aplicación de consola, este archivo es el app.config. Para registrar los componentes vía archivo de configuración, tenemos que agregar los siguientes elementos a este archivo:



<?xml version="1.0" encoding="utf-8" ?>
<
configuration>
<
configSections>
<
section
name="castle"
type="Castle.Windsor.Configuration.AppDomain.CastleSectionHandler, Castle.Windsor" />
</
configSections>
<
castle>
<
components>
<
component
id="Email.Alert"
service="PruebaCastleWindsor.IAlert, PruebaCastleWindsor"
type="PruebaCastleWindsor.EmailAlert, PruebaCastleWindsor" />

<
component
id="Sms.Alert"
service="PruebaCastleWindsor.IAlert, PruebaCastleWindsor"
type="PruebaCastleWindsor.SMSAlert, PruebaCastleWindsor" />
</
components>

</
castle>

</
configuration>


Como podemos ver, primero debemos crear una sección donde se registra el Handler de CastleWidows – llamada castle – y luego en la sección de componentes de castle procedemos a registrar nuestros componentes. En este caso, PruebaCastleWindsor es el namespace que estoy utilizando en la aplicación de ejemplo. Seguidamente procedemos a cambiar el método Main en el archivo Program.cs de la siguiente manera:



using System;
using Castle.Core.Resource;
using Castle.Windsor;
using Castle.Windsor.Configuration.Interpreters;

namespace PruebaCastleWindsor
{
class Program
{
static void Main( string[] args )
{
IWindsorContainer contenedor = new WindsorContainer(new XmlInterpreter(new ConfigResource("castle")));
//IAlert AlertaEnviada = contenedor["Email.Alert"] as EmailAlert;
//IAlert AlertaEnviada = contenedor["Sms.Alert"] as EmailAlert;
IAlert AlertaEnviada = contenedor[typeof(IAlert)] as IAlert;
Console.WriteLine("Alerta: {0}", AlertaEnviada.SendAlert( ));
contenedor.Release(AlertaEnviada);
}
}
}


Primeramente, agregamos las referencias a las librerías necesarias para poder utilizar el contenedor vía configuración, seguidamente creamos la instancia del contenedor pero esta vez, le indicamos que busque el recurso de configuración en la sección llamada “castle”. Por último, invocamos el componente de la misma forma que hicimos en el código anterior, solo que esta vez estamos cargando la instancia por medio del tipo del servicio, igualmente puedo cargar la instancia utilizando la llave utilizada para registrar el componente en el archivo config. Al ejecutar el código anterior, el resultado es:



image Hay varias cosas que se pueden destacar de este resultado. Primero, veamos que la alerta que se envía es la de correo, pero ¿por qué? bueno, esto sucede por que en el archivo de configuración EMailAlert fue el primero componente registrado, y como no le indicamos al contenedor que tipo instanciar, el automáticamente toma el primero en la lista.



<components>



<component id="Email.Alert" service="PruebaCastleWindsor.IAlert, PruebaCastleWindsor" type="PruebaCastleWindsor.EmailAlert, PruebaCastleWindsor" />



<component id="Sms.Alert" service="PruebaCastleWindsor.IAlert, PruebaCastleWindsor" type="PruebaCastleWindsor.SMSAlert, PruebaCastleWindsor" />



</components>



Seguidamente podemos ver que aunque instanciamos un tipo específico, en ningún momento atamos el código con la alerta de correo, ni con la alerta de SMS, por lo que trabajando con los servicios – interfaces – podemos ejecutar la instancia del componente que deseamos.



Seleccionando la Instancia de la Clase que Deseamos Crear


Ahora le vamos a decir al buscador que seleccione vía configuración, como debe enviar la alerta, ya sea por correo o por SMS. Para lograr esto, vamos a registrar la clase SearchManager en los componentes de CastleWindsor, y vamos a crear la instancia desde el contenedor. Primeramente, agregamos SearchManager al conjunto de componentes registrados en CastleWindsor.



<castle>
<
components>
<
component
id="SearchManager"
type="PruebaCastleWindsor.SearchManager, PruebaCastleWindsor"
/>

<
component
id="Email.Alert"
service="PruebaCastleWindsor.IAlert, PruebaCastleWindsor"
type="PruebaCastleWindsor.EmailAlert, PruebaCastleWindsor" />

<
component
id="Sms.Alert"
service="PruebaCastleWindsor.IAlert, PruebaCastleWindsor"
type="PruebaCastleWindsor.SMSAlert, PruebaCastleWindsor" />
</
components>

</
castle>



El siguiente paso, es instanciar la clase SearchManager desde el contenedor y llamar al método DoSearch, que al mismo tiempo, es el método que envía la alerta.



static void Main( string[] args )
{
IWindsorContainer contenedor = new WindsorContainer(new XmlInterpreter(new ConfigResource("castle")));
SearchManager manager = contenedor[typeof(SearchManager)] as SearchManager;
Console.WriteLine(manager.DoSearch("búsqueda"));

}


De nuevo, si ejecutamos este código el resultado va a ser igual al resultado anterior como podemos ver en esta imagen.



image La razón es igual a la anterior, el componente EmailAlert esta registrado primero que el SMSAlert, y como podemos ver en la siguiente imagen, cuando se crea la instancia del SearchManager, CastleWindsor crea una instancia del parámetro que se desea tomando el orden de registro del componente, en este caso, tomando la clase EmailAlert.



image



Seleccionando la Alerta a Enviar


Por último, vamos a seleccionar vía configuración cual es la alerta que queremos enviar desde la clase SearchManager; esto desde nuestro problema de versionamiento sería como instanciar la clase del cliente que corresponde simplemente configurandola desde el archivo de configuración.





<component
id="SearchManager"
type="PruebaCastleWindsor.SearchManager, PruebaCastleWindsor" >
<
parameters>
<
Alert>${Sms.Alert}</Alert>
</
parameters>

</
component>



En esta ocasión, estamos creando un elemento parameters que va a contener la colección de parámetros con que queremos instanciar la clase SearchManager. En este caso queremo enviarle el tipo de alerta que queremos trabajar, por lo que creamos una etiqueta con el nombre de la propiedad que va a almacenar nuestra instancia de Alerta, en nuestro caso se llama Alert. Vamos a cambiar un poco la clase, por que aunque podemos utilizar el parámetro del contructor, no es necesario, ya que CastleWindsor busca la propiedad especificada en la etiqueta, y le crea una instancia con el contenido de la misma, la cual en este caso nos dice que quiere una instancia del componente registrado con el id “Sms.Alert”.



namespace PruebaCastleWindsor
{
public class SearchManager
{
public IAlert Alert { get; set; }

public SearchManager( ) { }

public string DoSearch( string criteria )
{
//Notificamos la búsqueda
if (Alert != null)
return Alert.SendAlert( );
return "Error!";
}
}
}


Si ejecutamos el código anterior, vemos que el mensaje lanzado desde Alert.SendAlert no es el de la alerta por mail como ocurria antes por defecto, si no que es el de alerta por SMS, la cual es la que estamos configurando por parámetro. De nuevo, el código de la clase program no se cambia y el resultado de la ejecución es el siguiente:



image



Conclusión


La idea con este patrón es poder crear instancias de clases por medio de configuración y polimorfismo. Como vimos en este post, esto es posible a través del uso de CastleWindsor, el cual nos permite crear instancias de clases configuradas en el archivo App.config. Esto nos permite crear interfaces con el core de funcionalidades a implementar, e implementar clases que lleven funcionalidad específica para el cliente, las cuales se instancia de acuerdo a lo que se necesite por la aplicación y lo que definamos en el archivo config. En el siguiente post vamos a llevar a cabo la misma tarea, pero utilizando Autofac.



Technorati Tags: ,,

5 comentarios:

Gerson dijo...

Impresionante que a través de un parámetro castle pueda deducir la implementación de IAlert, tengo mucha curiosidad como se haría esta con Unity o seguro podre decir que Castle es un framework más maduro,

Felicitaciones!

Diego Rojas dijo...

Pues creo que CastleWindsor es un framework bastante maduro, pero igualmente el Unity se aprovechó de muchas de las lecciones aprendidas en los software factories creados anteriormente para los app blocks, entonces creo que son similares. El que si es un poco diferente ( hablando positivamente ) es Autofac.

Saludos y gracias por el comentario

cristian dijo...

Se debe preferir SIEMPRE la versión fuertemente tipificada sobre la versión no tipificada (que retorna Object).

En el ejemplo anterior se lograría mediante la interface fluida de Windsor:

http://gist.github.com/134182

En Unity también se pueden lograr match de nombres de parámetros, para eso se utiliza las "Match rules" de las cuales tenemos varias.

Windsor, al igual que Unity y muchos otros, no son solo IoC Containers o Dependency Injectors, sino que logran ir mucho más allá creando algo llamado "Component Wrapping" a las dependencias.

Por cierto, con IoC logramos el LC de nuestros componentes, no el versionamiento :P


Saludos!

Diego Rojas dijo...

Gracias Cristian por el comentario. Efectivamente, Castle Windsor Unity y Autofac tiene mas funcionalidades que IoC o dependency Injection, pero la idea con estos post es demostrar como se debe manejar el versionamiento de componentes entre aplicaciones que tiene diversos comportamientos dependiendo del cliente en donde se van a instalar, y asi evitar problemas tales como "copy paste" de componentes para tener diversas "versiones" de funcionalidad. Tal vez versionamiento no es la palabra correcta si se usa sola, deberia ser mas bien, versionando el comportamiento de un componente por medio de IoC.

Saludos

DiegoFox dijo...

Hola.

A más de tres años de la publicación sobre tu artículo quisiera hacer una consulta si todavía aplica.

Me cuesta visualizar un beneficio de IoC que no esté presente directamente con la creación de un Factory Method propio que, según información residente en base de datos, seleccione la creación de uno u otro objeto.
De esta manera gano el mismo benificio de no recompilar pero además gano el beneficio de no tocar el .config en ningún cliente.
Es posible que me esté faltando alguna premisa o esté desconociendo algún escenario específico donde no aplica mi idea.

Te agradecería mucho compartir tu opinión al respecto para poder tener otro punto de vista.

Cordial saludo y gracias por tanto aporte al conocimiento colectivo.