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

21 nov 2010

Gestión de Excepciones - Parte II

  • No manejar errores atrapando excepciones no específicas, como aquellas que heredan de System.Exception. El siguiente es un mal uso:

public class MalManejoExcepciones
{
public void DoWork(){//Hacer algo que dispare una excepción}
}

public void MethodWithBadHandler()
{
try
{
DoWork();
}
catch (Exception e)
{
//Atrapar la excepción y continuar ejecutando
}
}

  • Hay ocasiones en las que no se deben atrapar las excepciones y dejar que la aplicación "explote" y termine.
  • Atrapar las excepciones que sabemos que debemos hacer en cada caso. No atrapar excepciones no específicas y re lanzarlas.
  • Atrapar las excepciones que sabemos que podemos continuar con la ejecución de nuestra aplicación. Por ejemplo: si recibimos una excepción del tipo "FileNotFoundException", se puede atrapar y permitirle al usuario que especifique un archivo diferente. En cambio si se recibe un error desconocido y no sabemos si es seguro que la aplicación en ese estado continúe no se debe atrapar la excepción.
  • Usar el bloque try-finally preferentemente al try-catch. El bloque catch permite atrapar excepciones, mientras que el finally liberar recursos.
  • Disparar únicamente utilizando la palabra "throw", de esta manera preservamos el call stack trace. Ejemplo:


public void DoWork(Object anObject)
{
// Do some work that might throw exceptions.
if (anObject == null)
{
throw new ArgumentNullException("anObject","Specify a non-null argument.");
}
// Do work with o.
}

public void MethodWithBadCatch(Object anObject)
{
try
{
DoWork(anObject);
}
catch(ArgumentNullException e)
{
System.Diagnostics.Debug.Write(e.Message);
// Incorrecto
throw e;
// Debería ser:
// throw;
}
}

Gestión de Excepciones - Parte I

En este artículo voy a explicas unas guías para tener en cuenta al momento de trabajar con excepciones.

Las excepciones son el mecanismo estándar para reportar errorres. Las aplicaciones y librerías no deberían usar códigos de errores para devolver errores. Además permite disparar errores desde miembros como constructores que no tienen un tipo de datos de retorno.

Guías para disparar excepciones
Las excepciones se disparan cuando un miembro no puede hacer con éxito aquello para lo cual fue diseñado. Por ejemplo
si tengo que conectarme a un end point de WCF y el mismo no responde, disparo una excepción.
A tener en cuenta:

  • No devolver códigos de error. Las excepciones son el medio primario para reportar errores.
  • No usar excepciones para controlar el flujo normal de la aplicación. Siempre que se pueda tratar de no pensaren programar para disparar excepciones. Si se pueden evitar, mejor. Ejemplo:


public class Disparo
{
public static void ShowMessage(string mensaje)
{
if(mensaje == null)
{
throw new ArgumentNullException("mensaje");
}
}

}

public class Testeo
{
public static void Procesar(List<string> mensajes)
{
foreach(string mensaje in mensajes)
{
if(mensaje !=null)
{
Disparo.ShowMessage(mensaje);
}
}
}
}


  • Tener en cuenta el gasto de performance que implica disparar excepciones.
  • No usar miembros públicos que disparen una excepción basado en un código de error. Ejemplo: void ParseoString(string value, bool dispararExcepcion);
  • Usar métodos helpers de construcción de excepciones ya que la misma excepción puede dispararse de diferentes lugares. Pero ojo, estos helpers no deben disparar la excepción, solo inicializar propiedades, sino alteraríamos el stack trace.
  • No disparar excepciones desde la cláusula finally.