Inyección de dependecias e Inversión de control

La verdad, no soy consciente de cuántos son los desarrolladores que conocen el significado de Inyección de Dependencias (Dependency Injection) o Inversión de control (Inversion of Control). Para ser sincera, nunca trabajé con ello en ningún proyecto real pero si he intentado recopilar información y conceptos para poder aplicarlos a mis proyectos personales.

Generalmente, cuando tenemos una clase que depende de otras para ciertas acciones, necesitamos inicializar instancias de las mismas para poder utilizarlas posteriormente. En ocasiones, una de las alternativas puede ser crear un objeto de dichas clases de forma privada e inicializarlas, utilizando el constructor de la clase principal.

Si vemos estas acciones desde el punto de vista de la Inyección de Dependencias y la Inversión de Control, no sería la forma más óptima debido a que la clase que sufre estas dependencias no debería ser la responsable de la creación de las mismas.

¿QUÉ CONSEGUIMOS?

  • Desacoplamiento.
  • Mejora la posibilidad de testeo de la aplicación.
  • Mejor mantenimiento a la hora de realizar cambios de los componentes, gracias a la modularidad.
  • […]

Ambos conceptos están tan ligados que, en ocasiones, no se hace distinción. Se utiliza el concepto Inversión de Control para delegar en otro componente, un framework por ejemplo, la responsabilidad de crear las instancias necesarias en lugar de crearlas nosotros mismos. Por otro lado, la Inyección de Dependencias es el término utilizado cuando una clase depende de otra y, a través del constructor generalmente acepta un parámetro del tipo del cual depende. 

Para llevar a cabo el patrón de diseño de Inyección de Dependencias, es necesario el uso de interfaces y, lo más óptimo sería utilizar alguno de los frameworks disponibles para llevar a cabo la Inversión de Control. Algunos de estos frameworks son: Spring.NETWindsor Container, StructureMap, Unity, etcétera.

EJEMPLO A EVITAR

Por ver un ejemplo, supongamos que tenemos el siguiente código:

using System.Web.Mvc;
using IoC.Models;

namespace IoC.Controllers
{
[HandleError]
public class HomeController : Controller
{
private readonly ITwitterService _twitterService;
public HomeController()
{
_twitterService = new TwitterService();
}

public ActionResult Index()
{
return View(_twitterService.FetchTweets());
}
}
}

Tenemos un controlador, en una aplicación ASP.NET MVC, donde estamos haciendo uso de una librería que conecta con Twitter. Cuando se solicita la acción Index de este controlador, el controlador se crea, a través del constructor inicializa la variable _twitterService y realiza la llamada a FetchTweets. Esto funciona sin problemas, pero supone un inconveniente a la hora de realizar pruebas unitarias.

Por otro lado, si el día de mañana queremos, por ejemplo, utilizar otra clase que implemente ITwitterService bien porque hemos cambiado de librería, porque la forma de trabajar con Twitter es totalmente distinta, etcétera, deberíamos modificar a su vez este controlador(es) para modificar en el constructor la clase que implementa la interfaz a partir de ahora. Este es un caso bien simple pero ¿Y si nuestros controladores son dependientes de más de una clase y las mismas están en constante revisión, actualización, modificación, etcétera? La solución es bien simple:

using System.Web.Mvc;
using IoC.Models;

namespace IoC.Controllers
{
[HandleError]
public class HomeController : Controller
{
private readonly ITwitterService _twitterService;
public HomeController(ITwitterService twitterService)
{
_twitterService = twitterService;
}

public ActionResult Index()
{
return View(_twitterService.FetchTweets());
}
}
}

Gracias a la inyección de dependencias, liberamos al controlador de la carga de generar las instancias que necesita, lo abstraemos del tipo de clase que implementa la interfaz en este momento y conseguimos modularizar la aplicación.

PRUEBAS UNITARIAS

Si quisiéramos hacer un test, por ejemplo, que comprobara que al llamar a la acción Index el método FetchTweets es llamado, sin realizar la llamada real a Twitter e incluso sin hacer uso de la conexión que esto requiere a Internet, podríamos hacerlo de la siguiente manera:

using IoC.Controllers;
using IoC.Models;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Rhino.Mocks;

namespace IoC.Tests.Controllers
{
[TestClass]
public class HomeControllerTest
{
[TestMethod]
public void HomeController_AlLlamarALaAccionIndex_FetchTweetsEsLlamado()
{
//Arrange
var mockTwitterService = MockRepository.GenerateMock<TwitterService>();
mockTwitterService.Stub(m => m.FetchTweets()).Return(null);
var homeController = new HomeController(mockTwitterService);

//Act
homeController.Index();

//Assert
mockTwitterService.AssertWasCalled(m => m.FetchTweets());
}
}
}

Nota: En este ejemplo he utilizado la librería RhinoMocks para crear la prueba unitaria.

Esto es realmente importante si queremos hacer pruebas unitarias de la aplicación, pero también es cierto que se nos presenta el siguiente problema ¿Cada vez que llame a HomeController voy a tener que encargarme y asegurarme en cada caso de que reciba una instancia de las interfaces que solicita el constructor? Aquí es donde entra en juego IoC y los numerosos frameworks existentes para este rol.

Para ver un pequeño ejemplo de cómo podríamos delegar esta funcionalidad en uno de los frameworks que soportan Inversión del Control, voy a utilizar StructureMap como demostración. Para descargar la última versión del framework podemos dirigirnos a este enlace.

CONFIGURANDO STRUCTUREMAP

En este ejemplo, vamos a configurar StructureMap de tal forma que sepa cómo actuar en el caso de requerir una instancia para una interfaz de tipo ITwitterService. Para ello, me he creado la siguiente clase:

using IoC.Models;
using StructureMap;

namespace IoC.StructureMapConfiguration
{
public static class BootStrapper
{
public static void SetupContainer()
{
ObjectFactory.Configure(s => s.For<ITwitterService>().Use<TwitterService>());
}
}
}

En una sola línea, le estoy indicando que para la interfaz ITwitterService, debemos usar una instancia de la clase TwitterService. La magia de todo esto es que, si el día de mañana esta interfaz es implementada por otra clase, y además esta interfaz es usada en numerosos sitios de nuestra aplicación, solamente debemos modificar esta línea para que la clase que la implementa comience a servirse como dependencia en los casos que lo requiera.

CONTROLADORES ASP.NET MVC YSTRUCTUREMAP

Por otro lado, si lo que queremos es trabajar con ASP.NET MVC, debemos realizar una serie de cambios: Cuando nosotros hacemos una petición en una aplicación con ASP.NET MVC, la clase ControllerBuilder genera de forma automática el controlador solicitado, se despacha la petición y el controlador finaliza. Para poder utilizar las propiedades de StructureMap, necesitamos crear una clase que herede de la factoría de controladores. De esta manera, controlaremos el momento en el cual se solicita una instancia de un controlador y, si este tiene dependencias, poder administrarlas con la configuración realizada anteriormente para StructureMap en la clase BootStraper.

using System;
using System.Web.Mvc;
using System.Web.Routing;
using StructureMap;

namespace IoC.StructureMapConfiguration
{
internal class StructureControllerFactory : DefaultControllerFactory
{
protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
{
if (controllerType != null)
return ObjectFactory.GetInstance(controllerType) as IController;

return base.GetControllerInstance(requestContext, controllerType);
}
}
}

INICIALIZANDO LA CONFIGURACIÓN DE STRUCTUREMAP Y ASOCIADO LA NUEVA FACTORÍA DE CONTROLADORES

Para finalizar, necesitamos inicializar tanto la configuración creada en BootStraper, para que StructureMap reconozca la interfaz especificada, como la asignación de la nueva factoría de controladores a la aplicación ASP.NET MVC. La mejor ubicación para este caso concreto, podría ser el archivo Global.asax.

using System.Web.Mvc;
using System.Web.Routing;
using IoC.StructureMapConfiguration;

namespace IoC
{
// Note: For instructions on enabling IIS6 or IIS7 classic mode,
// visit http://go.microsoft.com/?LinkId=9394801

public class MvcApplication : System.Web.HttpApplication
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = "" } // Parameter defaults
);

}

protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();

RegisterRoutes(RouteTable.Routes);

BootStrapper.SetupContainer();

ControllerBuilder.Current.SetControllerFactory(new StructureControllerFactory());

}
}
}

Si arrancamos la aplicación, comprobaríamos que efectivamente se crea un controlador con su dependencia y delegamos esta acción gracias a la Inversión de Control.

Adjunto el proyecto por si fuera de utilidad.

¡Saludos!

Gracias a Hadi Hariri por sus enseñanzas 🙂

17 comentarios en “Inyección de dependecias e Inversión de control”

  1. Excelente artículo Gisela. Por hacer un poco de abogado del diablo, no quiero poner en duda las ventajas de las pruebas unitarias, pero la complejidad añadida para poder testar una aplicación como la del ejemplo, me hace preguntarme hasta que punto merecerá la pena este esfuerzo si la función no es muy determinante. Además, sin quererlo se aumentan las dependencias ya que tienes que utilizar Rhino.mocks y el framework structureMap que por supuesto no controlas, creo que este es un claro ejemplo del porque este tipo de test suponen un aumento de complejidad importante con el agravante de que muchas veces te obligan incluso a modificar el propio código. Espero que en un futuro sea mas fácil realizar este tipo de pruebas.

    Un saludo.

  2. Gis,
    Yo estoy aplicando los patrones relacionados con IoC en más de un proyecto profesional. Te puedo decir que no ha sido fácil “educar” (así, entre comillas) al equipo en el uso de los distintos patrones que hay debajo de IoC y evitar el uso de ciertos antipatrones que tarde o temprano terminan por aparecer… Aunque ahora, a posteriori, puedo decir que el esfuerzo vale realmente la pena: un código muy desacoplado, que permite cambios con relativa facilidad, una clara separación de responsabilidades y la sensación de “que estamos haciendo las cosas bien”.

    Y ya centrándonos en MVC, es genial que el framework esté tan pensado para poder enchufar un contenedor IoC… probablemente el único punto que cojea un poco en todo este tema sea los custom filters, pero esto es más culpa del CLR que de MVC, mucho me temo…

    Saludos!

  3. Buen post Gisela, justamente esta semana pasada tuve una call en la que se habló precisamente de algo como lo que se comenta en los post, “la complejidad de IoC, si es que la hay, y los valores que aporta”. Creo que tanto tu como ciertos comentarios expresan de forma clara y concisa los verdaderos valores de IoC, ayuda a realizar una correcta separación de responsabilidades, mejora la mantenibilidad y el diseño. Por poner una pega, y así tambíen se responde a Juan Irigoyen podrías haber usado Pex y sus Moles para el mock ( con lo cual te quitarías una dependencia de terceros, por lo menos en vs2010)… 🙂

    Te seguiré leyendo

  4. @Unai, @Juan,

    El objetivo de la inyección de dependencias no son las pruebas únicamente. Lamentablemente es uno de los caso de uso más frecuente y muchas veces se confude con que es la única ventaja que tienen.

  5. Hadi, acaso has entendido esto de mi comentario?? creo que lo he dicho bien clarito 🙂

    “expresan de forma clara y concisa los verdaderos valores de IoC, ayuda a realizar una correcta separación de responsabilidades, mejora la mantenibilidad y el diseño”

  6. Y yo precisamente igual, es el mismo argumento expuesto por Juan sobre la complejidad el que rechazábamos en esa call, el tema de moles es una simple acolación al uso de Rhyno que para nada creo que tenga importancia en el contexto de lo que hablamos no?? 🙂

    a más ver
    Unai

  7. @Unai,

    Hombre tu sabes que yo no creo que esto tenga complejidad :).

    El que haga un recalco sobre Moles es porque tanto Moles, como TypeMock son herramientas que permiten evitar la inyección de dependencias si el único fín con la que se usa es su re-emplazo en pruebas unitarias. Lo que yo intentaba explicar con mi comentario, es que hay que evitar caer en la trampa de que esto sea únicamente para hacer pruebas e ignorar las demás ventajas y posibilidades que ofrece.

    Es importante entender que este “esfuerzo” (y pongo esfuerzo entre comillas porque realmente no lo es una vez que se entienda lo simple que es todo), no es para realizar pruebas. Si uno quiero hacer eso solamente, tal como comentabas correctamente tú, PEX y sus Moles o TypeMock les puede servir perfectamente.

  8. Gracias a todos por vuestros comentarios =)

    La verdad es que son temas que pueden dar mucho que hablar y muchas veces hay que ser conscientes de las magnitudes del proyecto y la complejidad del mismo para evaluar si es factible ponerlo en práctica.

    ¡Saludos!

  9. señora, señores, algún aplicación-ejemplo real con código fuente donde se aplique Ioc-ID, por ejemplo, aplicación que utilice applogic, agentes, servicios wcf (implementacion, dominio, ….)

    salu2grz

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *