Unity Application Block 1.2. Caso Práctico.

Jorge Ruiz | 04 de marzo de 2009 | Añadir comentario

Más sobre: ASP.NET, Ingeniería del Software | Tags: , , , , , ,

Microsoft Unity Application Block 1.2 es un contenedor de inyección de código que soporta inyección por constructor, por propiedad (setter) y por método, ofreciendo a los desarrolladores las siguientes ventajas:

  • Provee una manera simplificada de creación de objetos, especialmente para crear objetos de una jerarquía de clases, simplificando el código de la aplicación.
  • Soporte abstracción de requerimientos, lo que permite a los desarrolladores especificar dependencias en tiempo de ejecución o mediante un fichero de configuración, simplificando el manejo del código transversal en las aplicaciones.
  • Incrementa la flexibilidad, dejando la configuración de los componentes al contenedor.
  • Dispone de capacidades para la localización de servicios. Esto permite a los clientes almacenar o cachear el contenedor. Esto es especialmente interesante en aplicaciones web ASP.NET donde los desarrolladores pueden persistir el contenedor en la memoria de sesión o de aplicación.

Unity Application Block permite a los desarrolladores realizar ingeniería del software basada en componentes. Las aplicaciones modernas están formadas por objetos de negocio particulares y por componentes que realizan tareas genéricas o específicas dentro de la aplicación. Además, están formadas por componentes individuales ejecutados de forma transversal como traceo, autenticación, autorización, cacheo y manejo de excepciones.

Una de las claves para conseguir desarrollar con éxito este tipo de aplicaciones es conseguir un diseño de código desacoplado o lo menos acoplado posible. Con un diseño y desarrollo software desacoplado conseguiremos aplicaciones más flexibles y fáciles de mantener.

Existen diferentes técnicas para permitir el diseño y desarrollo de software desacoplado. En este artículo nos centraremos en los patrones de diseño Inversión del Control (IoC – Inversion of Control) e Inyección por Dependencia (DI – Dependency Injection) pero existen otras como el patrón de diseño Intercepción (Interception pattern).

Inversion of Control (IoC) – Dependency Injection (DI)

El patrón Inversion of Control (IoC) es un patrón de diseño genérico que describe técnicas para soportar arquitecturas con la capacidad de intercambio de software (plug-in) donde los objetos pueden buscar instancias de otros objetos que éstos necesitan.

El patrón de diseño Dependency Injection (DI) es un caso especial del patrón IoC y es una técnica de programación basada en interfaces consistente en alterar el comportamiento de la clase sin cambiar internamente ésta. El desarrollador genera código contra un interface para la clase y usa un contenedor que inyecta la instancia de la clase, basándose en el interface o el tipo del objeto. Las técnicas para inyectar instancias son: inyección por interface, inyección por constructor, inyección por propiedad (setter) e inyección por método.

Caso Práctico

Realizar con éxito un proyecto de desarrollo software es una empresa complicada. presupuestos limitados, diferentes tecnologías, resistencia al cambio, necesidades mal definidas y cambiantes, carencia de profesionales cualificados, son algunos de los problemas con los que se encuentra un equipo de ingenieros del software cuando pretenden ofrecer una solución que resuelva o al menos minimice el impacto de estos y otros problemas posibles.

Con asiduidad nos hemos enfrentado ante casos en los que se plantea una funcionalidad transversal utilizada desde diferentes puntos del sistema y que además puede cambiar y ser ampliada con frecuencia.

Ante estos casos no sólo es deseable disponer de un buen diseño orientado a objetos que nos permita poder actuar sobre la funcionalidad cambiante y poder ampliar a nuevos casos sino que también es interesante la actualización “en caliente” de estos cambios. Sería deseable que cuando nuestro sistema esté en producción, ante un cambio de funcionalidad o extensión de ésta, no tuviéramos que abrir nuestro compilador con el proyecto entero, codificar los cambios, parar el aplicativo en producción (y las personas que trabajan sobre él), hacer el cambio y volver a levantar el entorno.

Una solución deseable consistiría en implementar el cambio o la nueva funcionalidad en una DLL, implantar esta DLL en producción y configurar la creación de los objetos que disparan esa funcionalidad desde un fichero XML.

El caso que nos ocupa trata de resolver un problema (simplificado) de gestión de alertas en un proyecto software. Podríamos decir que vamos a diseñar una solución orientada a objetos que nos permita definir una alerta con la capacidad de ejecutarse en un día, hora y minuto concreto. En la solución final cada alerta tendrá una implementación concreta. Además, existirá un planificador que será el encargado de ejecutar las alertas en el momento adecuado, por ejemplo, ejecutándose desde un servicio windows.

Para la implementación de este caso práctico simplificado utilizaremos implementación de DLLs, que trata de concentrar en un fichero funcionalidades concretas, Microsoft Unity Application Block 1.2, que nos ofrece un contenedor para la inyección de código mediante constructor (parametrós con arrays), y configuración de inyección mediante fichero XML.

Ejemplo

Toda la funcionalidad la vamos a implementar en una DLL llamada Solusoft.DC. En esta DLL vamos a tener tanto la funcionalidad de las alertas como la del planificador. Realmente no debería ser así y deberían estar en dos DLLs separadas para dividir aún más el código.

A continuación se muestra una implementación de las Alertas utilizando un interfaz y una jerarquía de clases. Nos aprovecharemos del método ejecutar() para lanzar la tarea cuando se cumplan las condiciones de estar en el día, hora, minuto correctos. Nótese que los tipos devueltos por el interfaz son de tipo string por comodidad a la hora de hacer la inyección de código.

// namespace Solusoft.DC.Alertas.Interfaces
public interface IAlertable
{
       void Ejecutar();
       System.String DiaEjecucion();
       System.String HoraEjecucion();
       System.String MinutoEjecucion();
}

// namespace Solusoft.DC.Alertas
public abstract class Alerta
{
    protected System.String dia;
    protected System.String hora;
    protected System.String minuto;

       
    public Alerta(System.String dia, System.String hora, System.String minuto)
    {
        this.dia = dia;
        this.hora = hora;
        this.minuto = minuto;
    }
}

public class AlertaX : Alerta , Interfaces.IAlertable
{
    public AlertaX(System.String dia, System.String hora, System.String minuto)
    : base(dia, hora, minuto)
    {
        ;
    }
       
    public void Ejecutar()
    {
        Console.WriteLine("Se ha ejecutado la alerta X");
    }

    public System.String DiaEjecucion()
    {
        return this.dia;
    }

    public System.String HoraEjecucion()
    {
        return this.hora;
    }

    public System.String MinutoEjecucion()
    {
        return this.minuto;
    }    
}

public class AlertaY : Alerta, Interfaces.IAlertable
{

    public AlertaY(System.String dia, System.String hora, System.String minuto)
                           : base(dia, hora, minuto)
    {
        ;
    }

    public void Ejecutar()
    {
        Console.WriteLine("Se ha ejecutado la alerta Y");
    }

    public System.String DiaEjecucion()
    {
        return this.dia;
    }

    public System.String HoraEjecucion()
    {
        return this.hora;
    }

    public System.String MinutoEjecucion()
    {
        return this.minuto;
    }
}

El planificador es algo más sencillo pero tiene una característica importante y es la de incorporar en el constructor un array de referencias a objetos que implementen el interfaz IAlertable. Esto nos servirá para poder indicarle al contenedor cuáles son las instancias concretas de alertas que se deben planificar.

// namespace Solusoft.DC.Alertas.Interfaces
public interface IPlanificable
{
        void Ejecutar();      
}

// namespace Solusoft.DC.Alertas
public class Planificador : Interfaces.IPlanificable
{
    //Array de alertas
    private Interfaces.IAlertable [] Alertas;

    public Planificador(Interfaces.IAlertable [] alertas)
    {
        this.Alertas = alertas;           
    }

    public void Ejecutar()
    {
        Console.WriteLine("Se ha ejecutado el planificador");
        for (int i = 0; i < Alertas.Length; i++)
        {   // Comprobar si ejecutar
            Alertas[i].Ejecutar();
        }
     }

     public void quienSoy()
     {
          Console.WriteLine("Soy el planificador");
     }
}

Con esta implementación basada en interfaces y herencia estamos en predisposición de tener un software lo más desacoplado posible. Esto unido a la utilización de Microsoft Unity Application Block 1.2 mediante configuración por diseño nos va a permitir indicar incluso en tiempo de ejecución cuáles son las intancias concretas de las alertas que el planificador debe controlar para su ejecución.

A continuación, se muestra el fichero de configuración completo para este ejemplo:


  
    

La configuración de la sección Unity para este ejemplo incorpora los siguientes elementos:

  • Alias para tipos: Indicando alguno de los posibles ciclos de vida de los objetos propuestos por Microsoft (singleton y external) además de los tipos propios del aplicativo, IPlanificable, IAlertable, IArrayAlertable, Alerta. Estos alias son importantes porque hacen más fáciles de utilizar los tipos completos, nótese que hay que utilizar namespace más nombre de DLL. También se indica el tipo de array que recibirá el constructor del objeto planificador.
  • Dentro del contenedor definimos el mapeo contra la clase concreta que será utilizada para instanciar un objeto, dándole un nombre, por ejemplo, para el tipo IAlertable se podrán servir una instancia de la clase Solusoft.DC.Alertas.AlertaX si se le pide el objeto al contenedor por el nombre “AlertaX” o un objeto de la clase Solusoft.DC.Alertas.AlertaY si se le pide el objeto al contenedor por el nombre “AlertaY”. En estos casos se ve como además lleva inyección de código por parámetros de construcción, indicando para cada alerta (X o Y) los valores exactos para dia, hora, minuto.
  • El caso más interesante es el del planificador que resuelve las peticiones al tipo IPlanificable como una instancia de la clase Solusoft.DC.Alertas.Planificador (fíjese cómo es aquí donde podría cambiar el planificador de nuestro sistema si quisiéramos), permitiendo inyección por constructor con un parámetro que es un array, indicando las instancias concretas de alertas que se le pasaran por el constructor, en este caso mediante los nombres AlertaX, AlertaY.

Una vez que tenemos la estructura de clases/interfaces y el contenedor configurado tan solo nos queda saber utilizarlo desde el código de nuestra aplicación. El ejemplo es el siguiente:

IUnityContainer container = new UnityContainer();
var section =
           (UnityConfigurationSection)ConfigurationManager.GetSection("unity");
           section.Containers.Default.Configure(container);

// Alerta alerta = container.Resolve<Alerta>();

IAlertable alertaX = container.Resolve<IAlertable>("AlertaX");
Console.WriteLine(alertaX.MinutoEjecucion());
           
IAlertable alertaY = container.Resolve<IAlertable>("AlertaY");
Console.WriteLine(alertaY.MinutoEjecucion());

string objetoPlanificador = ConfigurationSettings.AppSettings[CFG_PLANIFICADOR] as string;

IPlanificable planificador = container.Resolve<IPlanificable>(objetoPlanificador);

planificador.Ejecutar();

Console.ReadLine();  

La utilización del contedor una vez configurado es muy sencilla. En primer lugar hay que decirle a nuestro aplicativo que se prepare para utilizar un contenedor y que lo haga cargando la configuración desde la sección Unity. En este ejemplo, el fichero de configuración es el App.Config de una aplicación en modo consola pero podría ser cualquier otro (consultar documentación para cargar la configuración de otro archivo).

Mediante llamadas container.Resolve conseguimos pedir al contenedor los objetos que hayan sido configurados previamente en diseño. Con esto hacemos la llamada a: IPlanificable planificador = container.Resolve_IPlanificable_(objetoPlanificador); para pedirle el objeto concreto que esté capacitado para planificar. Nótese como en este caso ya estará configurado con las alertas precisas a ejecutar y una llamada simple a planificador.Ejecutar(); permitirá su ejecución.

Conclusiones

En este artículo hemos visto alguna de las características de Microsoft Unity Application Block, concretamente aquellas que nos permiten resolver instancias de objetos del contenedor por medio de configuración en ficheros XML. Sólo con esto nuestras arquitecturas de software pueden ser desacopladas y muy versátiles.

Para más información puede consultar Microsoft Developer Network

Más sobre: ASP.NET, Ingeniería del Software | Tags: , , , , , ,