Después de la última sacudida recibida por la encapsulación del Container que hicimos en el artículo anterior, sacamos este Service Pack 1 y de paso cuento un poco cómo llegamos aquí.
Cuando me pasan estas cosas, siempre recuerdo el pasaje de una lectura que tengo por casa…
“Denis Cochin preparo un estudio sobre Química y lo presento a Pasteur. El trabajo comenzaba con las palabras. “Se sabe que…“
– ¿Que es lo que se sabe? – Interrumpió Pasteur al leerlo. – No se sabe nada.
– Pero señor – Contesto Cochin – lo que iba a citar es un trabajo de usted.
– No importa – replico Pasteur. – Yo podría haberme equivocado. Empiece Ud. de nuevo”
Tras los comentarios de Unai y Eduard más la referencia al artículo de Mark sobre el patrón o anti-patrón Service-Locator (Martin Fowler, 2004), había que profundizar más en el tema, así que sin pensarlo dos veces me compré el libro de “Dependency Injection in .NET” el cual recomiendo muchísimo por la claridad en el contenido, además de estar orientado directamente a NET.
En el libro, cuando Mark habla del Service Locator como un anti-patrón, dice: “Some people consider it a proper design pattern, whereas others (me included) consider it an anti-pattern.”
Tras esta definición no puedo evitar preguntarme ¿Y en qué bando me pongo yo? Es evidente que la opinión de Eduard, Unai, Mark y seguramente la de muchos otros, pesa muchísimo, así que lo más probable es que termine más rápido si busco alguna deficiencia en la implementación anterior que me lleve finalmente a verlo como ellos.
Tras no mucho tiempo… me imaginé la siguiente situación:
Tengo varios controladores (MVC) que usan inyección de dependencia con uno o varios servicios (Application services), tengo servicios que usan dependencias a uno o más repositorios, tengo varios repositorios que usan dependencia a una unidad de trabajo, tengo una unidad de trabajo que depende de una cadena de conexión. En esa situación, tendré en mis controladores llamadas al Resolve del container para crear los servicios, en los servicios llamadas al Resolve para crear instancias de los repositorios, tendré también llamadas al Resolve en los repositorios para recuperar la unidad de trabajo y así en toda mi arquitectura…
Salta a simple vista que mis controladores dependen de mis servicios y del Service Locator. Los servicios dependen de los repositorios y del Service Locator, los repositorios dependen de la unidad de trabajo y del Service Locator. ¿Qué pasa si quiero reutilizar los repositorios? ¿O si quiero reutilizar los servicios? ¿O si quiero reutilizar mi unidad de trabajo? Pues que en todo momento dependeré del Service Locator…
No seguí buscando… mi objetivo era estar desacoplado del Framework de IoC y terminé atando toda la arquitectura.
¿Cómo soluciono esto entonces?
-Constructor Inyection
-Property Injection
-…
Si mis controladores recibieran los servicios que necesitan para trabajar mediante el constructor, no necesitaría una referencia al Service Locator… y lo mismo pasa en toda la cadena de inyección.
¿Cómo funciona esto? Imaginen que tengo…
Controller(IService srv) – Service(IRepository repo) – Repository(IUnitofWork uow) – UnitOfWork()
1- Un controlador necesita una instancia de un servicio, se intenta crear una instancia de ese servicio.
2- Para crear el servicio se necesita un repositorio, se intenta crear una instancia del repositorio.
3- Para crear el repositorio se necesita una unidad de trabajo, se intenta crear la unidad de trabajo.
4- Se crea la unidad de trabajo (no depende de nadie).
5- A partir de aquí, se inyecta la unidad de trabajo al repositorio, el repositorio al servicio y el servicio al controlador.
Este algoritmo me dice que todo empieza desde un punto único. A este punto Mark lo llama Composition Root. “A COMPOSITION ROOT is a (preferably) unique location in an application where modules are composed together.”
Un DI Container es quien me dice quién es el Container utilizado en mi aplicación y el encargado de componer todo el grafo de objetos. Este debe ser referenciado únicamente desde el Composition root (De aquí que Eduard y Unai no vean la necesidad de abstraer el Container) y se inicializa solo una vez en todo el ciclo de vida de la aplicación.
Conociendo un poco más que ayer, decidí hacer “refactoring” a todo lo visto ayer (Por llamarle de una forma menos dura al hecho de borrar todas las interfaces e implementaciones de mi Service Locator). Después de un rato, me quedó esto:
Mi DI Container. …
public static class UnityContainerFactory
{
private static readonly IUnityContainer _container;
static UnityContainerFactory()
{
_container = new UnityContainer();
Configure();
}
private static void Configure()
{
var section = (UnityConfigurationSection)ConfigurationManager.GetSection("unity");
section.Configure(_container);
// Aditional Configuration
// Container.RegisterType<IBaseType, ModuleAType>("moduleA")
// Container.RegisterType<IBaseType, ModuleBType>("moduleB")
}
public static IUnityContainer GetContainer()
{
return _container;
}
}
¿Quién sería mi Composition Root? Pues ya esto depende del tipo de aplicación que se vaya a crear, ya que cada aplicación puede tener una definición diferente para su “único punto de entrada”. Por ejemplo, para una aplicación MVC, Mark aconseja un IControllerFactory, aunque si es para MVC3, yo prefiero el IDependencyResolver.
Mi Composition Root para MVC3 sería:
public class UnityDependencyResolver : IDependencyResolver
{
private readonly IUnityContainer _container;
#region Implementation of IDependencyResolver
public UnityDependencyResolver(IUnityContainer container)
{
_container = container;
}
public object GetService(Type serviceType)
{
return _container.IsRegistered(serviceType) ? _container.Resolve(serviceType) : null;
}
public IEnumerable<object> GetServices(Type serviceType)
{
return _container.ResolveAll<object>().Where(s => s.GetType() == serviceType);
}
}
Aquí puedes encontrar un pequeño post de Steve explicando la implementación anterior.
Para el caso de un servicio WCF me gustó la forma en que lo implementaron en la guía de arquitectura N Layer donde se implementa la interfaz IInstanceProvider:
public class UnityDependencyProvider : IInstanceProvider
{
private readonly IUnityContainer _container;
private readonly Type _serviceType;
public UnityDependencyProvider(Type serviceType)
{
if (serviceType == null) throw new ArgumentNullException("serviceType");
_serviceType = serviceType;
_container = UnityContainerFactory.GetContainer();
}
#region Implementation of IInstanceProvider
public object GetInstance(InstanceContext instanceContext)
{
return GetInstance(instanceContext, null);
}
public object GetInstance(InstanceContext instanceContext, Message message)
{
return _container.Resolve(_serviceType);
}
public void ReleaseInstance(InstanceContext instanceContext, object instance)
{
if (instance is IDisposable) ((IDisposable)instance).Dispose();
}
#endregion
}
Y luego creamos el atributo con el que marcaremos los servicios WCF que necesiten inyección de dependencias…
public class UnityDependencyProviderServiceBehavior : Attribute, IServiceBehavior
{
#region Implementation of IServiceBehavior
public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
}
public void AddBindingParameters(ServiceDescription serviceDescription,
ServiceHostBase serviceHostBase,
Collection<ServiceEndpoint> endpoints,
BindingParameterCollection bindingParameters)
{
}
public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
foreach (var dispatcher in serviceHostBase.ChannelDispatchers.OfType<ChannelDispatcher>())
{
dispatcher.Endpoints.ToList().
ForEach(endpoint =>
{
endpoint.DispatchRuntime.InstanceProvider = new UnityDependencyProvider(serviceDescription.ServiceType);
});
}
}
#endregion
}
«As you can see, it took me a couple of years of intense use to realize the shortcomings of SERVICE LOCATOR and that better alternatives existed. For this reason, I find it easy to understand why so many developers find it attractive…” (Gracias Mark)
Los test…
El problema de los test está en que no tenemos un evento al cual nos podamos subscribir ni interfaz que implementar… Entonces, ¿Cómo creo los test sobre el IoC Container? by Scott…
Basados en las interfaces IA, IB y las clases A y B que escribimos en el artículo anterior, tendríamos los siguientes test:
[TestClass]
public class IocTest
{
private readonly IUnityContainer _ioc;
/// <summary>
/// En los test, este es mi punto de entrada (Composition root)
/// </summary>
public IocTest()
{
_ioc = UnityContainerFactory.GetContainer();
}
[TestMethod]
public void TestCreateObjetWithoutConstructorParameters()
{
var a = _ioc.Resolve<IA>();
Assert.IsNotNull(a);
Assert.AreEqual(1, a.ObjectId);
}
[TestMethod]
public void TestCreateObjetWithConstructorParameters()
{
//var a = new A(); Ya no necesitamos hacer esto...
//el framework de IoC se encarga de construir IA
var b = _ioc.Resolve<IB>();
Assert.IsNotNull(b);
Assert.AreEqual(1, b.ParamInjector.ObjectId);
}
}
Los elementos de configuración, ya podemos inyectarlos tal y como nos pedía Juanma:
[TestClass]
public class ConfigTest
{
private readonly IUnityContainer _ioc;
public ConfigTest()
{
_ioc = UnityContainerFactory.GetContainer();
}
[TestMethod]
public void TestConfiguration()
{
var global = new Global(new AppSettingsHelper());
var settings = global.Settings;
Assert.AreEqual("My Project DDD", settings.Name);
Assert.AreEqual("es-ES", settings.LanguageDefault);
Assert.AreEqual("dd-MM-yyyy HH:mm", settings.DateTimeFormat);
Assert.AreEqual(1, settings.TimeZoneOffset);
Assert.IsInstanceOfType(global.SettingsHelper.GetBoolean("bool"), typeof(bool));
}
[TestMethod]
public void TestIocConfiguration()
{
var global = _ioc.Resolve<IGlobalSettings>();
Assert.IsNotNull(global);
Assert.IsNotNull(global.Settings);
Assert.IsNotNull(global.SettingsHelper);
Assert.IsInstanceOfType(global.Settings, typeof(AppConfigurationElement));
Assert.IsInstanceOfType(global.SettingsHelper, typeof(AppSettingsHelper));
}
}
Gracias a Unai y a Eduard por hacer posible esta mejora…
PD: Aún no he probado los módulos implementados para MVC3 o para WCF… ya os contaré
Salu2