Mostrando entradas con la etiqueta Unity. Mostrar todas las entradas
Mostrando entradas con la etiqueta Unity. Mostrar todas las entradas

12.02.2010

Utilizando Unity para Resolver un Problema de Negocio - 4

Continuando con la serie de posts acerca de como utilizar Unity y aplicarlo a un problema de negocio, vamos a agregar en este post la funcionalidad necesaria para permitir agregar nuevos tipos de usuario sin tener que compilar nuestra capa de negocios. La idea con este post es permitirle al desarrollador crear componentes que puedan ser asociados a la lógica de negocios sin tener que modificar la misma a través de su código, y que simplemente con agregar el nuevo tipo al archivo de configuración de nuestra aplicación, este pueda utilizarlo. Para ejemplificar mejor el problema voy a mostrar un diagrama de como se vería físicamente nuestro programa. El estado actual de la aplicación a nivel de lógica de negocios es el siguiente:

image

La idea de este post es crear un nuevo usuario que se va a llamar usuario federado, el cual no va a formar parte de nuestro proyecto de Core.LogicaDeNegocios, si no que será un dll aparte el cual implementa una clase del tipo IUsuario llamada Usuario_Federado. Nuestro esquema debería verse de la siguiente manera:

image

Como podemos ver en la figura anterior, ahora vamos a agregar un nuevo dll en donde va a residir nuestro nuevo usuario – en color rojo. Lo interesante con esta solución es que vamos a poder agregar en nuestro BL nuevos usuarios sin tener que ir a recompilar ni modificar nuestra lógica de negocios; esto es lo que vamos a hacer en el post del día de hoy – para que el cambio se pueda manejar de la misma manera en la capa de UI vamos a utilizar Prism en post posteriores.

Solución

En primera instancia vamos a crear un nuevo proyecto del tipo librería y le vamos a poner Core.LogicaDeNegocios.Usuario_Federado. Seguidamente procedemos a crear la clase Usuario_Federado la cual es similar a las demas clases que implementan IUsuario definidas dentro de la librería Core.LogicaDeNegocios.dll. El dll que acabamos de crear requiere una referencia al dll donde esta definido el tipo IUsuario, es decir Core.LogicaDeNegocios.dll. El código de esta nueva clase es el siguiente:

using Core.LogicaDeNegocios;

using System.Diagnostics;

namespace Core.LogicaDeNegocios.Usuario_Federado
{
public class Usuario_Federado : IUsuario
{
IEmpresa _empresa;
public IEmpresa Empresa
{
get
{
return _empresa;
}
set
{
_empresa = value;
}
}

public IUsuario Autenticar()
{
Trace.WriteLine("Autenticar Usuario_Federado");
return this;
}

public void Autorizar(object _form)
{
Trace.WriteLine("Autorizar Usuario Federado");
}
}
}


Ahora vamos a modificar el UI para que se puedan crear instancias de la clase residente en este nuevo dll. Agregamos la “empresa D” la cual estará asociada con el Usuario_Federado y luego procedemos a agregar el siguiente código en el botón con el cual estamos cargando el usuario de acuerdo a los datos ingresados.



string _tipoUsuario = string.Empty;
if (cmbEmpresa.SelectedItem.ToString() == "Empresa A")
_tipoUsuario = "usuario";
else if (cmbEmpresa.SelectedItem.ToString() == "Empresa B")
_tipoUsuario = "usuarioWeb";
else if (cmbEmpresa.SelectedItem.ToString() == "Empresa C")
_tipoUsuario = "otroUsuario";
else
_tipoUsuario = "usuarioFederado";
UsuarioActual = AdministradorUsuario.ObtenerInstancia(_tipoUsuario, txtNombreUsuario.Text, txtPassword.Text).Autenticar();



El paso siguiente es modificar el App.Config para mapear el tipo, es decir IUsuario a Usuario_Federado:



      <register type="IUsuario" name="usuarioFederado" mapTo="Usuario_Federado" />


Por último, ejecutamos la aplicación y podemos ver que si seleccionamos la empresa D, obtenemos una instancia del tipo Usuario_Federado.



image



Como se puede ver en la figura anterior tenemos una instancia de Usuario_Federado, yal y como lo esperabamos. Nótese sin embargo que la empresa esta en nulo pese a que tenemos implementada la propiedad Empresa del tipo IEmpresa ¿Qué nos falta? Pues si vamos a utilizar las empresas ya definidas en nuestro dll de Core.LogicaDeNegocios.dll simplemente tenemos que agregar el mapeo de la propiedad y listo.



 <register type="IUsuario" name="usuarioFederado" mapTo="Usuario_Federado" >
<
property name="Empresa" dependencyName ="empresaMap" dependencyType="IEmpresa"></property>
</
register>



El resultado será el siguiente:



image



Es importante darnos cuenta que hemos agregado una nueva entidad de negocios a nuestra lógica de negocios sin necesidad de recompilar la misma, esto nos dará muchas ventajas cuando queramos crear aplicaciones listas para evolucionar en el futuro. En el próximo post vamos a analizar los beneficios de este tipo de arquitecturas y cuales frameworks y aplicaciones utilizan este tipo de diseño.



Technorati Tags: ,,,,

11.29.2010

Utilizando Unity para Resolver un Problema de Negocio - 3

Siguiendo con el uso de Unity para trabajar problemas de negocios de una forma menos dependiente entre los componentes que desarrollamos, vamos ahora a tratar el caso en donde la clase recién instanciada tiene una referencia a otra clase que a su vez debe ser resuelta por el contenedor de IoC.

Supongamos que cada usuario tiene una relación de agregación con la interface IEmpresa y que dependiendo de la empresa se paga un cargo diferente cuando se realiza alguna transacción. La interface IEmpresa tendrá un método que solamente le aplica el cargo a la transacción. La relación de clases en el problema actual es la siguiente:

image

En nuestro código la implementación de nuestras clases lucirá así:

Interface IEmpresa

namespace Core.LogicaDeNegocios
{
public interface IEmpresa
{
void AplicarCargos();
}
}



Clase Oficina: Implementa IEmpresa



using System.Diagnostics;

namespace Core.LogicaDeNegocios
{
public class Oficina: IEmpresa
{
public void AplicarCargos()
{
Trace.WriteLine("Aplicar Cargos Oficina");
}
}
}


Clase EmpresaExterna: Implementa IEmpresa



using System.Diagnostics;

namespace Core.LogicaDeNegocios
{
public class EmpresaExterna: IEmpresa
{
public void AplicarCargos()
{
Trace.WriteLine("Aplicar Cargos Empresa Externa");
}
}
}



En la interface IUsuario agregamos la relación de agregación antes representada:



public interface IUsuario
{
IEmpresa Empresa { get; set; }

IUsuario Autenticar();
void Autorizar(object _form);
}



Ahora modificamos la clase Usuario, para que tenga una implementación de IEmpresa. Nótese que la clase debe implementar la propiedad expuesta en la interface y no necesariamente el mismo tipo en el atributo, ya que con solo tener una clase que implemente la interface, ya podemos crear la asociación.



public class Usuario : IUsuario
{
Oficina _oficina;

public Usuario(string pNombreUsuario, string pPassword)
{
Trace.WriteLine(string.Format("Datos del usuario. NombreUsuario: {0}, Password: {1}", pNombreUsuario, pPassword));
}

public IUsuario Autenticar()
{
Trace.WriteLine("Autenticar Usuario");
return this;
}

public void Autorizar(object _form)
{
Trace.WriteLine("Autenticar Usuario");
}

public IEmpresa Empresa
{
get
{
return _oficina;
}
set
{
_oficina = value as Oficina;
}
}

}



Lo mismo ocurrirá con la clase Usuario_Web, con la diferencia que en Usuario_Web si vamos a crear un atributo del tipo IEmpresa.



public class Usuario_Web: IUsuario
{
IEmpresa _empresa;

public Usuario_Web(string pNombreUsuario, string pPassword)
{
Trace.WriteLine(string.Format("Datos del usuario. NombreUsuario: {0}, Password: {1}", pNombreUsuario, pPassword));
}

public virtual IUsuario Autenticar()
{
Trace.WriteLine("Autenticar Usuario Web");
return this;
}

public void Autorizar(object _form)
{
Trace.WriteLine("Autorizar Usuario Web");
}

public IEmpresa Empresa
{
get
{
return _empresa;
}
set
{
_empresa = value;
}
}
}


Por otro lado, vamos a crear una clase que hereda de Usuario_Web y que implementa su propia implementación de IEmpresa.



public class OtroUsuario : Usuario_Web
{
IEmpresa _empresa;

public OtroUsuario(string pNombreUsuario, string pPassword)
:base(pNombreUsuario, pPassword)
{
Trace.WriteLine(string.Format("Datos del usuario. NombreUsuario: {0}, Password: {1}", pNombreUsuario, pPassword));
}

public override IUsuario Autenticar()
{
Trace.WriteLine("Autenticar Otro Usuario Web");
return this;
}

public IEmpresa Empresa
{
get
{
return _empresa;
}
set
{
_empresa = value;
}
}

}



Por último agregamos el nuevo mapping para tenerlo entre la lista de clases que pueden ser resueltas.



  <unity xmlns="http://schemas.microsoft.com/practices/2010/unity">
<
alias alias="IUsuario" type="Core.LogicaDeNegocios.IUsuario, Core.LogicaDeNegocios" />
<
namespace name="Core.LogicaDeNegocios" />
<
assembly name="Core.LogicaDeNegocios" />
<
container>
<
register type="IUsuario" name="usuario" mapTo="Usuario" />
<
register type="IUsuario" name="usuarioWeb" mapTo="Usuario_Web" />
<
register type="IUsuario" name="otroUsuario" mapTo="OtroUsuario" />
</
container>
</
unity>



En la parte del UI, agregamos el nuevo tipo de usuario a través de la empresa C.



private void btnLogin_Click(object sender, EventArgs e)
{
string _tipoUsuario = string.Empty;
if (cmbEmpresa.SelectedItem.ToString() == "Empresa A")
_tipoUsuario = "usuario";
else if (cmbEmpresa.SelectedItem.ToString() == "Empresa B")
_tipoUsuario = "usuarioWeb";
else
_tipoUsuario = "otroUsuario";

UsuarioActual = AdministradorUsuario.ObtenerInstancia(_tipoUsuario, txtNombreUsuario.Text, txtPassword.Text).Autenticar();

}



Si ejecutamos el código tal y como esta – y llevando la continuación del post anterior – veremos lo siguiente:



image



Como vemos en la figura, la instancia de empresa del tipo que resolvió Unity esta en nulo. Sin duda no es el comportamiento que queremos. Pero como hacer para que la clase que estamos instanciando pueda resolver la empresa que desea en tiempo de ejecución? Para este problema en específico, Unity me permite crear inyección para las propiedades con lo cual puedo definir dentro del registro del tipo para mapear, como debe de cargarse una propiedad de la clase que se va a resolver. Para lograr esto vamos a modificar nuestro archivo de configuración de la siguiente forma:



  <unity xmlns="http://schemas.microsoft.com/practices/2010/unity">
<
alias alias="IUsuario" type="Core.LogicaDeNegocios.IUsuario, Core.LogicaDeNegocios" />
<
namespace name="Core.LogicaDeNegocios" />
<
assembly name="Core.LogicaDeNegocios" />
<
container>
<
register type="IEmpresa" name="empresaMap" mapTo="EmpresaExterna" />
<
register type="IEmpresa" name="oficinaMap" mapTo="Oficina"
/>
<
register type="IUsuario" name="usuario" mapTo="Usuario" >
<
property name="Empresa" dependencyName ="oficinaMap" dependencyType="IEmpresa"></property
>
</
register
>
<
register type="IUsuario" name="usuarioWeb" mapTo="Usuario_Web" />
<
register type="IUsuario" name="otroUsuario" mapTo="OtroUsuario" >
<
property name="Empresa" dependencyName ="empresaMap" dependencyType="IEmpresa"></property
>
</
register
>
</
container>
</
unity>



Como podemos ver en el texto en negrita, primera vamos a crear dos mapas nuevos para los tipos recién creados que implementan la interface IEmpresa.



<register type="IEmpresa" name="empresaMap" mapTo="EmpresaExterna" />
<
register type="IEmpresa" name="oficinaMap" mapTo="Oficina" />



Seguidamente vamos a modificar los registros para los usuarios deseados y vamos a establecer la instancia que queremos sea inyectada en la propiedad del tipo de IEmpresa.



<register type="IUsuario" name="otroUsuario" mapTo="OtroUsuario" >
<
property name="Empresa" dependencyName ="empresaMap" dependencyType="IEmpresa"></property>
</
register>



      <register type="IUsuario" name="usuario" mapTo="Usuario" >
<
property name="Empresa" dependencyName ="oficinaMap" dependencyType="IEmpresa"></property>
</
register>





En este caso hemos establecido que cuando se vaya a crear una instancia del tipo Usuario la propiedad IEmpresa se instancie con el tipo Oficina, y si por lo contrario creamos una instacia del tipo OtroUsuario vamos a tener la propiedad IEmpresa con una instancia del tipo EmpresaExterna. Si creamos una instancia de Usuario_Web la instancia de la propiedad IEmpresa no se podrá resolver y por lo tanto será null.



Si seleccionamos Empresa C –> OtroUsuario el resultado será:



image



Si seleccionamos Empresa A –> Usuario el resultado será:



image



Y si seleccionamos Empresa B –> el resultado será:



image







Technorati Tags: ,,

11.23.2010

Utilizando Unity para Resolver un Problema de Negocio - 2

En el post anterior presentamos un problema el cual resolvimos usando Unity, sin embargo, este problema se presentó de una manera muy simple y un poco corto en realidad. En este post vamos a agregarle un par de situaciones que se presentan normalmente en este tipo de sistemas.

En primer lugar, cuando creamos la instancia del usuario que necesitamos, le tuvimos que indicar de forma explícita al método AdministradorDeUsuario.ObtenerInstancia utilizando un generic, algo que por supuesto no es deseado ya que obliga al cliente de la aplicación a estar conciente de los tipos de usuario que existen y esto limita la aplicación a trabajar con los usuarios existentes y no nos permite agregar nuevos usuarios sin tener que recompilar la aplicación.

En segundo lugar estamos utilizando clases para los usuarios que no contienen datos y por lo tanto no son inicializados cuando se crea la instancia, por lo que el método Autorizar no funcionaría en un escenario real ya que no tiene los datos que el usuario utilizó para loguearse.

Mejoras

Para atacar el primer problema vamos a cambiar la forma en que le solicitamos nuestras instancias a Unity. Con los contenedores de IoC tenemos la gran ventaja de que podemos solicitarles que resuelvan el tipo en base al nombre del mapeo que queremos utilizar. El primer paso es cambiar el archivo de configuración para que trabaje con un solo contenedor de la siguiente forma:

<?xml version="1.0" encoding="utf-8" ?>
<
configuration>
<
configSections>
<
section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, Microsoft.Practices.Unity.Configuration" />
</
configSections>
<
unity xmlns="http://schemas.microsoft.com/practices/2010/unity">
<
alias alias="IUsuario" type="Core.LogicaDeNegocios.IUsuario, Core.LogicaDeNegocios" />
<
namespace name="Core.LogicaDeNegocios" />
<
assembly name="Core.LogicaDeNegocios" />
<
container>
<
register type="IUsuario" name="usuario" mapTo="Usuario"
/>
<
register type="IUsuario" name="usuarioWeb" mapTo="Usuario_Web"
/>
</
container
>
</
unity>
</
configuration>



Seguidamente quitamos el parámetro del tipo que estamos solicitando en el método ObtenerInstancia ya que como dije anteriormente vamos a dejar que Unity resuelva el tipo en base al nombre del mapa que queremos utilizar. Los posibles nombres para los mapas son:



<register type="IUsuario" name="usuario" mapTo="Usuario" />
<
register type="IUsuario" name="usuarioWeb" mapTo="Usuario_Web" />



Además al método ObtenerInstancia le vamos a agregar dos parámetros más, el usuario y el password para que pueda agregarlos como valores al usuario que se vaya a crear – problema 2. La invocación del método desde nuestra pantalla de login ahora se ve así:



string _tipoUsuario = string.Empty;
if (cmbEmpresa.SelectedItem.ToString() == "Empresa A")
_tipoUsuario = "usuario";
else
_tipoUsuario = "usuarioWeb";

UsuarioActual = AdministradorUsuario.ObtenerInstancia(_tipoUsuario, txtNombreUsuario.Text, txtPassword.Text).Autenticar();



El siguiente paso y para trabajar en el problema 2, es crear un constructor para cada tipo de usuario de forma tal que reciba el usuario y el password como parámetros y que pueda hacer la autenticación con los mismos. Las clases modificadas son las siguientes:



public class Usuario_Web: IUsuario
{

public Usuario_Web(string pNombreUsuario, string pPassword)
{
Trace.WriteLine(string.Format("Datos del usuario. NombreUsuario: {0}, Password: {1}", pNombreUsuario, pPassword));
}


public IUsuario Autenticar()
{
Trace.WriteLine("Autenticar Usuario Web");
return this;
}

public void Autorizar(object _form)
{
Trace.WriteLine("Autorizar Usuario Web");
}
}



public class Usuario : IUsuario
{
public Usuario(string pNombreUsuario, string pPassword)
{
Trace.WriteLine(string.Format("Datos del usuario. NombreUsuario: {0}, Password: {1}", pNombreUsuario, pPassword));
}


public IUsuario Autenticar()
{
Trace.WriteLine("Autenticar Usuario");
return this;
}

public void Autorizar(object _form)
{
Trace.WriteLine("Autenticar Usuario");
}
}



Ahora solo nos queda modificar el método ObtenerInstancia para que a través de Unity se pueda resolver el tipo que deseamos instanciar en base al tipo de usuario que queremos utilizar y además que los datos del usuario puedan ser inyectados a través del constructor de la clase a instanciar:



using Microsoft.Practices.Unity;
using Microsoft.Practices.Unity.Configuration;

namespace Core.LogicaDeNegocios
{
public static class AdministradorUsuario
{
public static IUsuario ObtenerInstancia(string mapName, string pUsuario, string pPassword)
{
using (IUnityContainer container = new UnityContainer())
{
container.LoadConfiguration();
return container.Resolve<IUsuario>(mapName, new ParameterOverrides { { "pNombreUsuario", pUsuario }, { "pPassword", pPassword } });

}
}
}
}



Con esto ya podemos ejecutar nuestra aplicación y crear la instancia que deseamos con los datos enviados.



image



image



Technorati Tags: ,,,,

11.22.2010

Utilizando Unity para Resolver un Problema de Negocio - 1

En días pasados me encontré con un problema en el cual se podría utilizar IoC para resolverlo de forma sencilla y elegante. En este post voy a reproducir este fix con un problema un tanto diferente pero en el cual se puede ver como de forma sencilla podemos aprovecharnos de las bondades de Unity y a la vez hacer nuestra aplicación más versátil y fácil de hacer crecer.

El Problema

Supongamos que tenemos un sistema que hace una seria de funciones para la empresa y que luego de una fusión con otra empresa, tenemos un sistema similar pero que además realiza unas funciones extras. Resulta que cada sistema tiene su propia tabla de usuarios ( así es, como siempre primero se hacen los sistemas y luego se piensa en la seguridad y no a la inversa como debería ser ) y se quiere hacer una versión de forma rápida que mezcle las funcionalidades de ambas aplicaciones. La aplicación va a tener un ComboBox en donde el usuario indica a cual empresa originalmente pertenece y el sistema debe resolver a donde debe de ir a autenticarse el usuario. En primera instancia, la primera aplicación es desktop y la segunda aplicación es Web. El problema radica en cómo manejar estos tipos diversos de usuario en una sola aplicación sin necesidad de andar previendo con cual tipo estamos trabajando.

La Solución

La solución a este problema esta por el lado del polimorfismo, ya que si definimos una interface IUsuario, podemos trabajar con la interface y no con el tipo concreto, lo cual nos ahorraría el peso de estar manejando un tipo de usuario X en la aplicación cuando se requiera. El siguiente diagrama nos muestra lo expuesto anteriormente.

image

El código de cada una de las clases es el que se presenta a continuación:

namespace Core.LogicaDeNegocios
{
//Este usuario se autentica en una base de datos para usuarios Web
public class Usuario_Web: IUsuario
{
public IUsuario Autenticar()
{
Trace.WriteLine("Autenticar Usuario Web");
return this;
}

public void Autorizar(object _form)
{
Trace.WriteLine("Autorizar Usuario Web");
}
}
}



namespace Core.LogicaDeNegocios
{
//Este usuario se autentica utilizando una base de datos de usuarios locales
public class Usuario : IUsuario
{
public IUsuario Autenticar()
{
Trace.WriteLine("Autenticar Usuario");
return this;
}

public void Autorizar(object _form)
{
Trace.WriteLine("Autenticar Usuario");
}
}
}



Ahora el punto es como hacer que la aplicación construya la instancia que corresponda – dependiendo de la selección del comboBox – sin necesidad de estar creando clases concretas por toda la aplicación. Para este caso es que se decidió utilizar Unity. Con Unity tenemos la posibilidad de registrar los tipos que deseamos y mapearlos con clases concretas para cuando solicitemos el tipo, se nos retorne la instancia que deseamos. Para registrar nuestras clases anteriores vamos a utilizar el archivo app.config de nuestra aplicación desktop y vamos a agregar la siguiente configuración.



<?xml version="1.0" encoding="utf-8" ?>
<
configuration>
<
configSections>
<
section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, Microsoft.Practices.Unity.Configuration" />
</
configSections>
<
unity xmlns="http://schemas.microsoft.com/practices/2010/unity">
<
alias alias="IUsuario" type="Core.LogicaDeNegocios.IUsuario, Core.LogicaDeNegocios" />
<
namespace name="Core.LogicaDeNegocios" />
<
assembly name="Core.LogicaDeNegocios" />
<
container>
<
register type="IUsuario" name="usuario" mapTo="Usuario"
/>
</
container
>
<
container name="UsuarioWeb"
>
<
register type="IUsuario" name="usuarioWeb" mapTo="Usuario_Web"
/>
</
container
>
</
unity>
</
configuration>



Como podemos ver en el xml anterior, estamamos registrando dos tipos con la interface IUsuario: Usuario y Usuario_Web, para que cuando solicitemos el tipo Unity tenga como resolver y pueda crear la instancia que deseamos. El siguiente paso es utilizar el contenedor de Unity para que nos resuelva el tipo y dado que no queremos que el UI se tenga que preocupar por este block, vamos a crear una clase estática que nos ayude retornando la instancia a partir de un tipo especificado como parámetro en una función que vamos a crear para crear la instancia de acuerdo al tipo que solicita el usuario.



//Unity
using Microsoft.Practices.Unity;

namespace Core.LogicaDeNegocios
{
public static class AdministradorUsuario
{
public static IUsuario ObtenerInstancia<T>() where T:IUsuario
{
using (IUnityContainer container = new UnityContainer())
{
return container.Resolve<T>();
}
}
}
}


Nótese el constraint en el Generic, el cual nos delimita los tipos de datos que pueden ser T, en este caso T tiene que implementar IUsuario. Por último procedemos a crear el código en la forma de log in, en donde de acuerdo a la opción seleccionada por el usuario vamos a seleccionar el tipo que queremos pasar como T a la función ObtenerInstancia<T>.



IUsuario _usuarioActual;

public IUsuario UsuarioActual
{
get { return _usuarioActual; }
set { _usuarioActual = value; }
}

private void btnLogin_Click(object sender, EventArgs e)
{
if (cmbEmpresa.SelectedItem == "Empresa A")
UsuarioActual = AdministradorUsuario.ObtenerInstancia<Usuario>().Autenticar();
else
UsuarioActual = AdministradorUsuario.ObtenerInstancia<Usuario_Web>().Autenticar();

}



En este caso específico, estamos pasando el tipo concreto al método que nos devuelve una instancia de la clase solicitada, y como ambas clases deben de implementar la interface IUsuario podemos invocar el método Autenticar que es parte de este contrato. Este método nos devuelve una instancia de IUsuario el cual podemos utilizar en nuestra aplicación para operaciones posteriores de Autorización. Para cerrar el post vemos la estructura de proyecto utilizada, la cual como veremos en el siguiente post nos va a permitir cargar clases de lógica de  negocios dependiendo de la forma en que se configure el sistema.



image



Technorati Tags: ,,

8.25.2009

Manejo del Scope con Unity

Cuando se trabaja con IoC containers existe un factor que en muchas ocasiones se ignora a la hora de crear instancias dinámicamente: el scope del objeto que estamos creando. En este post vamos a analizar las opciones para el manejo del scope cuando utilizamos el Unity Application Block.

¿Qué es el Scope?

El scope – alcance – de un objeto es la cantidad de tiempo o las llamadas a los métodos en los cuales un objeto existe. Es decir, el scope es el contexto bajo el cual un identificador se refiere a la misma instancia. También podemos definir el scope como la cantidad de tiempo que el estado de un objeto persiste. Cuando el contexto del scope finaliza, cualquier objeto ligado a este scope queda fuera de alcance y no pueden inyectarse de nuevo en otras instancias.

El Scope y los IoC

La verdadera ventaja del scope en los IoC es que permite modelar el estado de los objetos de forma declarativa. Esto se logra diciendole al inyector que una llave en particular esta ligada a un scope, y por lo tanto se puede manipular la construcción y el lige entre objetos en el contexto del inyector.

El aplicar correctamente el manejo del scope significa que un código trabajando en un contexto particular no esta conciente del contexto en que se ejecuta. Es responsabilidad del inyector el administrar estos contextos. Esto significa que se tiene una separación entre la aplicación y la infraestructura, sino que también estos servicios pueden ser utilizado para muchos propósitos alternando simplemente el scope seleccionado. Para visualizar mejor esto vamos a trabajar con un ejemplo.

deptartment.Print("drojas", "Project Plan", injector.Resolve<Printer>());
deptartment.Print("jcalvo", "work items", injector.Resolve<Printer>());
deptartment.Print("palfaro", "todo's", injector.Resolve<Printer>());


Supongamos que en un departamento de una empresa trabajan 3 usuarios, y cada uno tiene un documento que mandar a imprimir. Basándonos en el ejemplo anterior podríamos decir que la impresora la utiliza primero drojas, seguidamente jcalvo y por último palfaro. Si la impresora es de poca capacidad y los documentos de drojas y jcalvo son muy grandes, puede ser que palfaro se quede sin tinta o sin hojas para imprimir.



image



En este caso el injector distribuye la tinta a través de la misma impresora – misma instancia - entre todos los que van imprimiendo. Este es un ejemplo del contexto: Todos los trabajadores del departamento comparten la misma área, y por lo tanto comparten la impresora. En este caso la impresora esta accesible a través de un scope del tipo singleton.





Otro ejemplo de contexto sería cuando cada uno de los trabajadores del departamento tienen su propia impresora. Teoricamente, si utilizamos el ejemplo anterior, podríamos decir que cada trabajador imprime en su impresora; y digo teoricamente porque esto no es absolutamente cierto, ya que si palafaro manda a imprimir dos veces el mismo documento, estaríamos utilizando dos impresoras diferentes:



deptartment.Print("drojas", "Project Plan", injector.Resolve<Printer>());



deptartment.Print("jcalvo", "work items", injector.Resolve<Printer>());



deptartment.Print("palfaro", "todo's", injector.Resolve<Printer>());



deptartment.Print("palfaro", "todo's", injector.Resolve<Printer>());



En este caso, estamos trabajando sin alcance, y lo que estamos diciendole al injector es que debe de crear una nueva instancia por llamado.



Controlando el Scope con Unity


El scope en Unity se puede controlar vía configuración o vía código. El contenedor de Unity administra la creación y la resolución de objetos basado en el scope que se especifica cuando se registra el tipo o de lo contrario se utiliza el scope por defecto – sin scope.



Cuando se registra un tipo utilizando el metodo RegisterType,  el comportamiento por defecto es crear una nueva instancia del tipo solicitado cada vez que se invoca el metodo Resolve o ResolveAll. El contenedor no mantiene guarda una referencia al objeto. Sin embargo, cuando se desea un comportamiento Singleton para los objetos que se van a instanciar, el contenedor mantiene una referencia a los objetos creados.



Cuando se registra un objeto existente utilizando el método RegisterInstance el comportamiento por defecto es que el contenedor tome la administración del tiempo de vida del objeto que se pasa como parámetro al objeto. Esto significa que el objeto se mantiene en el scope mientras el contenedor este en el scope; una vez que el contenedor salga del scope o cuando se elimina la instancia del contenedor explícitamente, el objeto registrado será inalcanzable.



Unity utiliza lo que se conoce como “lifetime managers” para controlar como se guardan las referencias a las instancias de los objetos y como el contenedor elimina estas instancias. Unity incluye 3 “lifetime managers” que pueden ser utilizados directamente en el código.





  1. ContainerControlledLifetimeManager: Unity retorna la misma instancia del tipo registrado cada vez que se llama al metodo Resolve, ResolveAll o cuando el mecanismo de dependencia inyecta instancias en otras clases. Este es el mecanismo para implementar un comportamiento Singleton. Este lifetime manager se utiliza por defecto para el metodo RegisterInstance si no se especifica algún otro. Si se desea un comportamiento singleton que se crea cuando se utiliza el método RegisterType, se debe de especificar de forma explícita.




  2. ExternallyControlledLifetimeManager: Este manager permite registrar tipos y objetos existentes en el contenedorm, pero mantiene una referencia “débil” a los objetos que se crean utilizando Resolve y ResolveAll. Unity retorna la misma instancia cuando se utilizan estos métodos, pero el contenedor no mantiene una referencia fuerte a los mismos luego de crearlos, por lo que el Garbage Collector puede eliminar los objetos si no existe código que mantenga una referencia fuerte a los objetos creados.




  3. PerThreadLifetimeManager: Unity retorna la misma instancia por thread del tipo registrado cada vez que se invoca el método Resolve o ResolveAll. Este lifetime manager implementan un singleton por thread, por lo tanto retorna diferentes objetos para cada thread.





    1. Si se utiliza el método RegisterType, Unity crea una nueva instancia la primera vez que se resuelve el tipo en un thread específico utilizando Resolve o ResolveAll. Las siguientes invocaciones retornan la misma instancia en el thread.




    2. Si se utiliza el método RegisterInstance, se obtiene el mismo comportamiento que ofrece el método RegisterType. Por esta razón no se recomienda utilizar el método RegisterInstance para registrar un objeto utilizando este lifetime manager.





A continuación vamos a ver el uso de estos lifetime managers. Inicialmente vamos a crear una interface que se llamará IPrinter la cual tiene un método para imprimir y una propiedad llamada Name donde vamos a almacenar el código hash de la instancia utilizada, esto nos permitira ver si la instancia es un singleton o no.



public interface IPrinter
{
string Name { get; set; }
string Print(string document);
}


Seguidamente vamos a crear dos clases que implementan esta interface: LaserPrinter e InkPrinter. Estas clases son las que vamos a registrar e instanciar utilizando Unity y sus lifetime managers.



public class LaserPrinter : IPrinter
{
public string Name { get; set; }
public LaserPrinter()
{
Name = this.GetHashCode().ToString();
}
public string Print(string document)
{
return "Imprimiendo el documento: " + document + " en laser";
}
}

public class InkPrinter : IPrinter
{
public string Name { get; set; }
public InkPrinter()
{
Name = this.GetHashCode().ToString();
}
public string Print(string document)
{
return "Imprimiendo el documento" + document + " en Tinta";
}
}



Seguidamente creamos en el método main, el código necesario para registrar y crear las instancias utilizando el contenedor de Unity.



static void Main(string[] args)
{
using (IUnityContainer unityContainer = new UnityContainer())
{
//La impresora laser la vamos a registrar como un singleton
unityContainer.RegisterType<IPrinter, LaserPrinter>("Laser", new ContainerControlledLifetimeManager());
//La impresora de tinta le vamos a dar un tiempo de vida pasajero, es decir, vamos a crear
//una instancia por llamado.
unityContainer.RegisterType<IPrinter, InkPrinter>("Ink");

//Impresora con hash propio
IPrinter printer = unityContainer.Resolve<IPrinter>("Ink");
Console.WriteLine(printer.Print("Tareas"));
//Impresora con singleton
IPrinter printer2 = unityContainer.Resolve<IPrinter>("Laser");
Console.WriteLine(printer2.Print("Errores"));
//Impresora con singleton
IPrinter printer3 = unityContainer.Resolve<IPrinter>("Laser");
//Impresora con hash propio
IPrinter printer4 = unityContainer.Resolve<IPrinter>("Ink");

Console.WriteLine(printer.Name);
Console.WriteLine(printer2.Name);
Console.WriteLine(printer3.Name);
Console.WriteLine(printer4.Name);
}
}



El resultado al ejecutar el código anterior es el siguiente:



image 



La pregunta que nos surge ahora es: ¿cuándo usar un singleton o cuando usar un alcance pasajero? Pues desde mi punto de vista, un buen lugar para utilizar un singleton es una capa de acceso a datos, ya que por lo general estas operaciones son unitarias y especificas – leer datos, escribir un dato – y no necesitamos realmente crear una nueva instancia para poder tener acceso a estos componentes. Igualmente, operaciones en donde las operaciones para inicializar un objeto son muy “pesadas” y en donde estas configuraciones de arranque son solo de lectura, son buenos candidatos para un singleton.



Technorati Tags: ,,,

7.26.2009

Versionando Aplicaciones – Unity – Parte 3

Continuando con la serie de post acerca de IoC – contenedores de dependency injection – en este post vamos a examinar Unity, el cual es un application block que esta disponible gratuitamente desde el sitio de codeplex o a través del sitio de pattern and practices.

Al igual que cualquier otro IoC container, lo que busca unity es desasociar o eliminar la dependencia que existe entre las clases y “mover” esa dependencia a las interfaces. La ventaja con esto en un IoC, es que básicamente al depender de una interface, podemos cambiar la implementación de esa interface en cualquier momento sin necesidad de recompilar la aplicación sino más bien a través de configuración – lo único que necesitamos es cumplir con el contrato.

Trabajando con Unity

En esta ocasión vamos a implementar el mismo ejemplo que presentamos con CastleWindsor del cual presentamos el modelo a continuación:

image El primer paso para hacer funcionar Unity es agregar las referencias necesarias para tener acceso a Unity.

image

Una vez agregadas las referencias, vamos a construir el código necesario para crear la instancia de la clase DoSearch con la alerta del tipo IAlert instanciada utilizando Unity.

image En el código anterior, procedemos a registrar un tipo para el contrato IAlert, en este caso SMSAlert, lo cual nos permite crear una instancia de la clase SearchManager y proceder a asignarle la alerta que corresponde, en este caso utilizamos el metodo Resolve del contenedor. Cuando ejecutamos el código anterior, Unity sabe cual clase tiene que instanciar y procede como se muestra en la siguiente figura.

image El ejemplo anterior no es muy funcional en el sentido de que solamente tenemos una clase para registrar y por lo tanto a la hora de resolver cual clase instanciar, el contenedor solamente debe de instanciar la clase default registrada con el tipo. En el siguiente ejemplo vamos a registrar ambas implementaciones de IAlert con lo cual tendremos que elegir que instancia crear en tiempo de ejecución. Si registramos otro tipo despues de la alerta SMS, entonces el tipo que se instanciaría sería el último registrado.

image El resultado sería el siguiente:

image

La solución para poder elegir cual clase instanciar es especificar un nombre para cada mapa que se va a crear, y a la hora de crear la instancia procedemos a elegir vía ese nombre de mapa, cual tipo instanciar.

image

En este caso especificamos los nombres de SMS y EMail para los mapas y decidimos utilizar SMS. El resultado es el siguiente:

image

Con lo especificado hasta este momento, podemos crear un software factory muy sencillo basándonos en el nombre de la etiqueta del mapa y guardando el nombre en un archivo de configuración o algún medio que permita cambiar la clase a instanciar en tiempo de ejecución.

Pese a lo anterior, Unity nos permite registrar los tipos que vamos a utilizar en un archivo .config y crear las instancias en base a los registrios de los tipos hechos en este archivo. El siguiente ejemplo nos muestra como llevar a cabo esta tarea.

image

En primera instancia se deben agregar las referencias para poder acceder a la configuración del app.config – System.Configuration – y a la configuración de Unity – Unity.Configuration.

image  El siguiente paso es codificar la instanciación de la clase deseada. Para esto, accedemos al archivo de configuración a través del ConfigurationManager y procedemos a configurar la sección de Unity. Seguidamente creamos la clase y procedemos a resolver el tipo. Nótese que en este caso, le indicamos a Unity a través del método Resolve, que queremos una instancia con la etiqueta “SMS”, la cual vamos a agregar al archivo config seguidamente.

image

Primeramente procedemos a registrar la sección que vamos a llamar unity – podríamos llamarla como queramos – y vamos a indicar que assembly para configuración de Unity vamos a utilizar.

Seguidamente procedemos a crear “alias” de los contratos que vamos a instanciar – en este caso IAlert. Por último, dentro de la sección container, agregamos los tipos mapeados para poder instanciarlos. Como podemos ver, en este caso mapeamos SMSAlert a IAlert y le pusimos de nombre SMS, el cual es el nombre que vamos a utililizar para crear la instancia. El nombre PruebaCastleWindsor es el nombre del assembly que se genera y en donde residen los tipos – en este caso una aplicación de consola – y lleva ese nombre porque en el mismo proyecto donde estoy haciendo uso de Unity, hice la prueba de CastleWindsor. El resultado al ejecutar la aplicación es el siguiente:

image

Como hemos visto, Unity también nos sirve para crear aplicaciones fáciles de versionar en tiempo de ejecución – al decir versionar me refiero a instanciar los componentes de negocio que necesitamos dependiendo de reglas de configuración pre establecidas. En post posteriores vamos a ahondar en el uso de Unity.

Technorati Tags: ,,