martes, 28 de octubre de 2008

IoC o el poder de ceder el control

Hablando con colegas de profesión, me he dado cuenta de que muchos de ellos no terminan de comprender el patrón IoC o las ventajas que su uso comporta… Así que sin ánimo de sentar cátedra he decidido escribir este post, por si le sirve a alguien…
IoC, que corresponde a las siglas de Inversion Of Control, agrupa a varios patrones que tienen en común que el flujo de ejecución del programa se invierte respecto a los métodos de programación tradicionales (wikipedia dixit). Generalmente el programador especifica las acciones (métodos) que se van llamando, pero en IoC lo que se especifican son las respuestas a determinados eventos o sucesos, dejando en manos de un elemento externo todas las acciones de control necesarias cuando lleguen estos sucesos.

Un claro ejemplo de IoC se encuentra en el modelo de eventos de Windows Forms. Cuando vinculamos una función gestora a un evento (p.ej. el Click de un Button), estamos definiendo la respuesta a un determinado evento y nos despreocupamos cuando se llamará a nuestra función gestora. Es decir, cedemos el control del flujo al framework para que sea él que llame cuando sea necesario a nuestro método.

Una de las principales ventajas de usar patrones IoC es que reducimos el acople entre una clase y las clases de las cuales depende, y eso cuando queremos utilizar por ejemplo tests unitarios es muy importante (no seré yo quien hable ahora de las ventajas de TDD cuando ya lo han hecho xxx). De los distintos patrones IoC me interesa comentar específicamente dos: Service Locator y cómo podemos usarlo en .NET. Para más adelante dejo Dependency Injection, otra forma de IoC que también es extremadamente útil.

Id a la nevera, coged una Voll-Damm o cualquier otra cervecilla y sentaos que este post es un poco largo…

Service Locator

El objetivo de Service Locator es reducir las dependencias que tenga una clase. Imaginemos una clase, que depende de otras dos clases (p.ej. ServicioSeguridad y ServicioLogin). Podríamos tener un código tal como:

class Client

{

static void Main(string[] args)

{

new Client().Run();
}

private void Run()

{

// En este punto necesitamos objetos de las clases ServicioSeguridad y ServicioLogger

ServicioSeguridad ss = new ServicioSeguridad();
ServicioLogger sl = new ServicioLogger();
//...

}

}

En este punto tenemos un fuerte acople entre la clase Client y las clases ServicioSeguridad y ServicioLoggger. Seguiríamos teniendo este mismo acople incluso aunque utilizáramos interfaces porque deberíamos hacer el "new":

IServicioSeguridad ss = new ServicioSeguridad();

IServicioLogger sl = new ServicioLogger();


Las principales desventajas de esta situación son:
  1. Las clases que implementan las dependencias (en nuestro caso ServicioSeguridad y ServicioLogger) deben estar disponibles en tiempo de compilación (no nos basta con tener solo las interfaces).
  2. El fuerte acople de la clase con sus dependencias dificulta de sobremanera su testing. Si queremos utilizar Mocks para de ServicioSeguridad o ServicioLogger vamos a tener dificultades
El patrón de ServiceLocator soluciona estos dos puntos, sustituyendo las dependencias de la clase Client por dependencias a los interfaces y a un elemento externo, que llamaremos Contenedor encargado de devolver las referencias que se le piden.

Cuando la clase necesita un objeto en concreto, lo pide al contenedor:

IServicioSeguridad ss = container.Resolve<IServicioSeguridad>("servicioseguridad");

IServicioLogger sl = container.Resolve<IServicioLogger>("serviciologger");

El método Resolve devolvería una referencia del tipo especificado en el parámetro genérico de acuerdo con un identificador. Evidentemente falta alguien que cree el contenedor y que agregue los servicios a él. Es decir, en algún sitio habrá algo como:

container = new IoCContainer();

container.Add<IServicioSeguridad, ServicioSeguridad>(new
ServicioSeguridad(), "servicioseguridad");

container.Add<IServicioLogger, ServicioLogger>(new
ServicioLogger(), "serviciologger");

La gran diferencia es que esto no tiene porque estar en la clase Client. Simplemente pasándole a la clase Client una referencia al contenedor, eliminamos todas las dependencias de la clase Client con las clases que implementan los servicios. Y donde creamos el contendor? Pues depende… si estamos en nuestra aplicación, lo podemos crear en el método que inicialice la aplicación, pero si queremos probar la clase Client con tests unitarios podemos crear el contenedor en la inicialización del test…. Y lo que es mejor: rellenarlo con Mocks de los servicios!

Así, nuestro programa podría tener una clase Bootstrapper que crea el contenedor de IoC y lo inicializa con los objetos necesarios:

class Bootstrapper

{

static void Main(string[] args)

{

IIoCContainer container = new IoCContainer();
container.Add<IServicioSeguridad, ServicioSeguridad>(new
ServicioSeguridad(), "servicioseguridad");

container.Add<IServicioLogger, ServicioLogger>(new
ServicioLogger(), "serviciologger");
new Client(container).Run();

}

}

Pero si queremos usar tests unitarios de la clase Client, podemos cambiar los objetos por Mocks fácilmente:

[ClassInitialize]

public static
void Init(TestContext context)

{

container = new IoCContainer();

container.Add<IServicioSeguridad, ServicioSeguridadMock>(new
ServicioSeguridadMock(), "servicioseguridad");

container.Add<IServicioLogger, ServicioLoggerMock>(new
ServicioLoggerMock(), "serviciologger");

}

[TestMethod]

public void TestMethod1()

{

Client c = new Client(container);

c.Run(); // Este método Run usará los Mocks!

}


Fijaos que podemos lanzar tests unitarios sobre la clase Client, sin necesidad alguna de cambiar su código y utilizando Mocks. Además, la clase Client no tiene ninguna dependencia con las implementaciones de los servicios que utiliza, así que no es necesario ni que existan para poder crear la clase Client (sólo necesitamos las interfaces).

Unity

Aunque existen varios contenedores IoC open source para .NET, Microsoft tiene el suyo, también open source, llamado Unity (http://www.codeplex.com/unity/). Unity proporciona soporte para los patrones Service Locator y Dependency Injection.
Para que veais un poco su uso he dejado un proyecto de herramienta de línea de comandos para listar los ficheros de un directorio (vamos un dir, jejejee…. :P).

La solución está dividida en varios proyectos:
  1. DemoIoC: Contiene el Bootstrapper, la clase que inicializa el contenedor de Unity, agrega la clase llamada ServicioFicheros y utiliza un objeto Cliente para realizar las acciones de la línea de comandos.
  2. Cliente: Contiene la implementación de la clase Cliente.
  3. Interfaces: Contiene las interfaces del servicio (en este caso sólo una)
  4. Implementations: Contiene las implementaciones del servicio (en este caso sólo una)
  5. Mocks: Contiene las implementaciones de los Mocks (en este caso sólo una)
  6. UnitTests: Contiene tests sobre la clase Cliente, usando un Mock del servicio ServicioFicheros.
El ejecutable (proyecto DemoIoC, assembly DemoIoC.exe) es un archivo de línea de comandos que acepta dos parámetros:
DemoIoC –l para listar todos los ficheros (del directorio actual)
DemoIoC –e:fichero.ext Indica si el fichero "fichero.ext" existe (en el directorio actual).
Si lo ejecutáis veréis que NO funciona bien: lista los ficheros correctamente, pero devuelve que un fichero existe cuando no es cierto y viceversa. Si miráis el código de la clase Cliente, encontrareis el error, ya que es obvio, pero lo bueno es que el proyecto UnitTest, testea este método de la clase Cliente, usando un Mock del ServicioFicheros y el UnitTest detecta el error. Observad lo fácil que ha sido sustituir el ServicioFicheros por un Mock sin alterar la clase Cliente, gracias al uso del patrón Service Locator!
Saludos!!!
PD: Estooo... que me dejado de adjuntar el código de ejemplo!! :P Lo encontrareis aquí! :)

Referencias

Os dejo algunas referencias para su lectura por si os interesa profundizar un poco en el tema tratado:

martes, 21 de octubre de 2008

[WCF] Y finalmente, llegó el cliente!

Jejejee... pues sí! Finalmente veremos como crear un cliente para nuestros servicios WCF. En las sesiones anteriores, hemos visto como crear un servicio, configurarlo y hospedarlo. Pero de què serviria todo esto sin un cliente que consuma el servicio? Sinceramente para muy poco, así que vamos a ver como en un plis-plas creamos un cliente que se conecte a nuestro servicio...
Para este post voy a suponer que partimos de una solución que tiene ya tres proyectos:
  • Una WCF Service Library, es decir una dll con el código del servicio
  • Una console application con el servidor
  • Una winforms application con el esqueleto inicial del cliente.
El código para esta solución inicial lo podeis encontrar en: http://cid-6521c259e9b1bec6.skydrive.live.com/self.aspx/BurbujasNet/Wcf1/ServiciosCalculo|_INICIAL.zip

Si cargais la solución en VS2008, vereis los tres proyectos. Si les echais una ojeada observareis que:
  1. El proyecto ClienteCalculo solo tiene el formulario creado, sin código (lógico, es lo que vamos a hacer)
  2. El proyecto ServiciosCalculo tiene el código servicio (la interfaz y la clase que la implementa). El fichero de configuración del servicio no está en este proyecto
  3. El proyecto Servidor, tiene el código para hospedar el servicio, así como el fichero de configuración del servicio que indica los endpoints que tiene nuestro servicio. Si os fijais el proyecto de Servidor tiene una referencia al proyecto ServiciosCalculo.
Ahora estamos listos para empezar a codificar el cliente...
El primer paso será agregar una "Service Reference" desde el proyecto del cliente hacia el servidor. Para ello, haceis click con el botón derecho en el nodo "references" i le dais a "Add Service Reference". Esto nos despliega un cuadro de diálogo donde básicamente Visual Studio nos pregunta la URL en la que está corriendo el servicio.
Porqué necesita Visual Studio que el servicio esté corriendo? Muy fácil: para poder descargarse el archivo de metadatos que todo servicio WCF expone y que le permite a Visual Studio generar el proxy de forma automática. Bueno, dado que necesitamos que esté corriendo el servicio, vamos a poner en marcha el Servidor. Os vais a la carpeta bin\debug\ del proyecto Servidor y ejecutais Servidor.exe (jejeje.... si no habiais compilado la solución cerrad el cuadro de dialogo y compiladla ;-)). Ahora sí, con el servidor corriendo, os vais de nuevo al Visual Studio, y entrais la URL donde está el servicio (fijaos que el Servidor os dice la URL por la cual escucha el servicio). Si todo ha ido bien (que es de esperar que sí), Visual Studio os mostrará información sobre el servicio (básicamente que métodos tiene):
Entrad el namespace en el que quereis que se generen las clases proxy (p.ej. CalculadoraService) y cuando le deis a "Ok" visual studio generará las clases para poder acceder a nuestro servicio.
Que es lo que ha hecho visual studio en este punto?
  1. Ha generado unas clases proxy (dentro del namespace que nosotros le hemos dicho) para poder acceder al servicio wcf
  2. Ha añadido o modificado el fichero app.config de la aplicación cliente, para añadir la configuración WCF necesaria.
El segundo punto igual os sorprende: no estaba la configuración en el servidor? La respuesta es que está en los dos lados: en el servidor se definen todos los endpoints que soporta el servicio, mientras que en el cliente se define cual de los endpoints queremos usar.
Una vez generado todo esto, lo que queda es trivial: basta con usar las clases generadas dentro del namespace que le hemos indicado para llamar al servicio, como si de una llamada normal a .NET se tratara.
Por ejemplo, en el evento click del boton de sumar vinculais el método:
private void CmdSuma_Click(object sender, EventArgs e)
{
    double op1 = Double.Parse(txtOp1.Text);
    double op2 = Double.Parse(txtOp2.Text);
    // LLamamos al servicio
    ClienteCalculo.CalculadoraService.CalculadoraClient srv =
        new ClienteCalculo.CalculadoraService.CalculadoraClient();
    double resultado = srv.sumar(op1, op2);
    txtResultado.Text = resultado.ToString();
}
Fijaos como instanciamos la clase "CaluladoraClient" (la clase proxy generada por visual studio) y llamamos a sus métodos (en este caso sumar) que son los definidos en la interfaz del servicio.
Para probarlo, poneis en marcha el Servidor y ejecutais el cliente... y listos!

Evidentemente nos han quedado cosas por ver (como llamadas asíncronas)... ya iremos profundizando un poco más en WCF, pero de momento... ya tenemos lo básico!!! :)
Saludos!
PD: El código final lo teneis en http://cid-6521c259e9b1bec6.skydrive.live.com/self.aspx/BurbujasNet/Wcf1/ServiciosCalculo|_FINAL.zip (jejejeee... el cliente no está completado, eh? sólo suma, pero vamos el resto es trivial...).

martes, 14 de octubre de 2008

[WPF] Library project file cannot specify ApplicationDefinition element

Imagina la siguiente situación: Tienes un proyecto en WPF, con varias ventanas o controles WPF creados, y de repente te da por reorganizarlo todo un poco. Así, que añades un proyecto de tipo "Class Library" a la solución, y luego arrastras desde el Solution Explorer, algunas de las ventanas y/o controles al nuevo proyecto.
Cuando más o menos lo tienes todo, le das a compilar y Visual Studio se queja con dos errores:
  • error MC1002: Library project file cannot specify ApplicationDefinition element.
  • error BG1003: The project file contains a property value that is not valid.
Además aunque le des doble-click en la ventana de errores, Visual Studio no está dispuesto a decirte en que línea o al menos que fichero es el causante de los dos errores.
El error se produce cuando al arrastrar los controles xaml al nuevo proyecto, Visual Studio cambia la "Build Action" de los controles que hayas arrastrado de "Page" a "ApplicationDefinition", y una librería no puede tener ningún control o ventana xaml con "ApplicationDefinition". Así pues, seleccionas en el "Solution Explorer" los ficheros xaml que hayas arrastrado (si arrastras más de un archivo te los cambia todos) y en propiedades, pones "Build Action" a "Page"... y listos!
Saludos!
PD: El fichero que tiene la Build Action como "ApplicationDefinition" es aquel que proporciona el punto de entrada de la aplicación y por lo tanto solo es válido en ejecutables (suele ser el App.xaml).
Crosspost desde mi blog de geeks.ms.

viernes, 10 de octubre de 2008

[Catdotnet] Ya pasó El Guille por Igualada :)

Bueno... ya pasó El Guille por Igualada, en la sexta etapa de la Guille Community Tour.
La verdad es que para un grupo de usuarios como CatDotNet fue un honor que un ponente de su categoría, se acercara a hacernos una visita, y deleitarnos a todos con su magnífica presentación sobre las novedades que trae Visual Basic 2008 (o VB9), amenizada con numerosas bromas y simpáticos ejemplos. Nos contó la anécdota de "MiAmiguito" con el fondo rosa que menciona Jose Luis Latorre, pero nuestro proyector funcionaba bien, así que no pudimos verla, jejejeee... :P
Como no podía ser menos batimos todos los récords de asistencia, con unas 35 personas más o menos, y El Guille demostró porque es uno de los mejores ponentes de toda España.
En fin, fue una magnífica presentación (como no podía ser menos)... solo aprovechar para decir que si hay alguien de Igualada o cerquita, o simplemente interesado en saber qué movidas vamos organizando, que se ponga en contacto conmigo o con Jose Luis Torres y vamos hablando!!!!!
Nos vemos!!!!
PD: Uuuuups.... no tengo las fotos a mano ahora, pero tan buen punto las tenga, colgaré algunas para que podais verlas ;-)
(Crossposting desde geeks.ms)

martes, 7 de octubre de 2008

[Catdotnet] El Gulle en Igualada

Hola a todos!
Sólo una reseña, para comentaros que "El guille", uno de los MVPs más antiguos y autor de uno de los portales más visitados de desarrollo en .net, se pasará este jueves 09 de octubre para Igualada, en el marco de su gira "Guille Community Tour 2008".
No hay que decir que se trata de una oportunidad única para poder ver a uno de los mejores ponentes de la actualidad, quien nos presentará todas las novedades de la última versión de Visual Basic...
¿Te lo vas perder?
Cuando: Este jueves (09/10/2008) a las 19:00
Donde: Centre d'atenció a les empreses (Av Mestre Muntaner 86 - Igualada)