28 nov 2010

Inyección de Dependencias con Unity

Voy a iniciar este tema, ya que comenzó a interesarme a raíz de un draft que estoy leyendo de microsoft, sobre arquitecturas orientadas al dominio (Domain Driven Design).
Lo pueden bajar desde aquí.
La idea de inyectar dependencias es un tema muy interesante que nos va a servir en el momento de desacoplar capas y facilitar los procesos de test.
Para armar mi solución me basé en el proyecto del link que transcribo arriba, la cual simpliqué bastante, a modo de poder realizar rápidamente las pruebas que me interesaban.
Mi solución consta de los siguientes proyectos:
  • Business.MainModule. Contiene los servicios, que son el punto de entrada de las aplicaciones ASP .NET MVC, WinForm o RIA y las interfaces de los contratos para los repositorios.
  • Business.MainModule.Entities. Contiene las entidades del dominio. Basándome en mi ejemplo, fueron generadas gracias las plantillas T4 de Entity Framework 4. El tipo de entidades que generé fueron del tipo POCO (Plain CLR Objects). Este tipo de entidades no está ligado a ninguna tecnología en particular, o sea, no tienen referencia a ninguna library específica de modo que queden relacionados a la capa de datos. Prefería este tipo de plantillas a las IPOCO, ya que si bien estas últimas vienen preparas para WCF, me parecieron más complejas de implementar.
  • Data. Es la capa de infraestructura de acceso a Datos. Contiene el modelo .edmx de EF 4 y la clase T4 para generación del contexto. Esta capa implementa los repositorios que definimos en Business.MainModule.
  • Data.Mock. Es mi proyecto de test, donde se ve la potencia de DI, al reemplazar "on the fly" la capa de infraestructura por un "fake", para realizar los test.
  • TestDomainDrivenDesign. Es mi capa de presentación, desde donde realizo las pruebas.
No voy a entrar en detalle, ya que en el pdf que nombré más arriba está más que claro. Mi modelo es más simplificado puesto que no uso proyectos Core en Business o en Data.
Aquí expongo como se vería el proyecto:


Mi idea al probar unity es NO hacer referencia desde la capa de presentación, en este caso, TestDomainDrivenDesign hacia la capa Data, es decir, no instanciar directamente los repositorios, sino que sea el contenedor de Unity el que nos de la instancia.
La idea es la siguiente: cuando te pida la interfaz del tipo "IInterfaz" devolveme "Clase".
Hay varias formas de establecer esta relación, o mejor dicho, este mapeo entre interfaz y clase implementadora, yo opté por la opción de hacer estas definiciones en el App.Config o Web.Config (dependiendo de nuestra capa de presentación).
Primeramente hay que bajarse las dlls de Unity, lo pueden hacer desde aquí.
A su capa de presentación deberan agregar las referencias a: Microsoft.Practices.Unity y Microsoft.Practices.Configuration.
Pasemos a la configuración de nuestro archivo de configuración ejemplo:



























Como podemos ver,hay varias secciones que nos interesan:
  1. Tenemos dentro de configSections una sección llamada "unity". Luego la definiremos más abajo y es la que contendrá la definición de los mapeos. Es más recomendable hacer referencia a un archivo externo que tenga la configuración del unity y no mezclarlo con el que usamos en nuestra aplicación. En este caso, como es una aplicación pequeña opté por tenerlo todo en junto. Pero es algo a tener en cuenta.
  2. Tengo una sección connectionString con la conexión al modelo de Entity Framework 4.
Lo que primero debemos hacer es definir nuestros "alias", que son las clases e interfaces que vamos a usar en el mapeo y en que assemblies están definidas.







Luego, viene la parte donde le decimos que interface se mapea a que clase y algo MUY importante el "name".






Ahora veamos como hacer la llamada al contenedor de unity para que nos devuelva una instancia del repository "real", en este caso, CustomerRepository. El main, junto con las pruebas a realizar se verías más o menos así:

public class Program
{
static void Main(string[] args)
{
IUnityContainer container = new UnityContainer();

var section = (UnityConfigurationSection)ConfigurationManager.GetSection("unity");

section.Configure(container,"RootContainer");

ICustomerRepository repo = container.Resolve<icustomerrepository>("CustomerMappging");

//creates a new customer
Customer customer = new Customer { CustomerCode = "123", CountryId = 1, CompanyName = "Prueba", ContactName = "Miguel", ContactTitle = "Ing" };

//adds a new customer
repo.AddCustomer(customer);

//retrieves all the customers
List<customer> list = (List<customer>)repo.GetAllCustomers();

foreach (Customer c in list)
{
Console.WriteLine("Id: {0}, Name: {1}", c.CustomerId, c.ContactName);
}

}
}

Si nos fijamos nunca hicimos referencias a la clase, siempre contra la interface.
Por ello sería muy válido, y ahora viene la parte interesante y que nos va a ayudar en el momento de las pruebas, que es, reemplazar nuestro repositorio por otro "fake".
Bastará con hacer este simple cambio en el App.Config:














Listo! no tuvimos que cambiar nada NI en la capa de presentación. El contenedor nos devuelve una instancia de CustomerRepositoryMock con nuestra implementación "fake" los métodos de la interfaz.
Por último: para hacer las pruebas en el bin\debug hay que pegar las .dlls del proyecto de Data y Data.Mock, sino el reflection no encontraría las clases que definimos en el App.Config.

Pueden descargar el código completo para Microsoft Visual Studio 2010 desde aquí.
Aquí dejo el script que deberán correr sobre una base de datos para que la aplicación quede 100% funcional.
Saludos,
Mike

3 comentarios: