08 agosto 2011

Dependency Injection en aplicaciones N-Layer

Hola a tod@s,

Hace unos días publique un post publiqué un post sobre como realizar IoC usando DI o bien usando SL, en este post llegamos a la conclusión que es mucho mas efectivo hacer DI para conseguir IoC. Yo me quede convencido que IoC es una buena opción para mis proyectos y que DI era la mejor alternativa pero NO vimos como implementarlo, en este post vamos a bajar a las trincheras y veremos como podemos hacer IoC usando DI en una aplicación N-Layer de forma práctica.

Para ello imaginemos que queremos hacer una aplicación multicapa con la siguiente arquitectura:

Architecture

  • La aplicación simplemente publica un servicio que, dado un nombre, nos devuelve un saludo. Por ejemplo, si le pasamos “josep maria” el servicio nos devolverá “Hola Josep Maria!”
  • La aplicación se divide en 4 capas, presentación, servicios distribuidos, aplicación y dominio.
  • En la capa de presentación tenemos un cliente web y una aplicación WCF que van a consumir el servicio de la capa de aplicación Greeting.
  • La capa de servicios distribuidos expone mediante WCF el servicio Greeting de la capa de aplicación para que este pueda ser consumido por clientes remotos.
  • La capa de aplicación expone un servicio Greeting que, simplemente, devuelve un saludo personalizado al usuario. El servicio Greeting de la capa de aplicación usa un servicio de la capa de dominio (Capitalize) para poner en mayúsculas las primeras letras del nombre del usuario.
  • Los clientes web acceden directamente al servicio Greeting de la capa de aplicación.
  • Los clientes WCF acceden a una adaptador del servicio Greeting en la capa de servicios distribuidos.

La aplicación en si es una chorrada y, como veis, esta un poco/un mucho “sobrearquitecturada” (¿solo un poco? Vaya tela de tio!) Su pretensión es hacer una prueba de concepto para mostrar como se pueden inyectar los servicios de aplicación y/o de dominio a los controladores de la capa de presentación MVC y a los servicios distribuidos WCF sin hacer muchas cosas raras, dejando que sea la infraestructura de MVC y de WCF la que inyecte estos servicios. Los principios que aplicamos a esta aplicación “chorra” se pueden extrapolar a cualquier aplicación N-Layer compleja donde seguro que tendrán mucho mas sentido.

Como la literatura no es lo mi fuerte os pongo el código que queremos que tenga nuestra aplicación en cada una de las capas:

Capa de presentación:

Slide2

Capa de servicios distribuidos:

Slide3

Capa de servicios de aplicación:

Slide4

Capa de servicios del dominio

Slide5

Resumiendo, lo que queremos lograr es:

  • Conseguir IoC por medio de DI con inyección de dependencias en el constructor en toda la aplicación.
  • Registrar los dos servicios (Greeting y Capitalize) en un contenedor de IoC (Unity en nuestro caso) des de un único punto para toda la aplicación.
  • Conseguir que la capa de presentación web, implementada mediante ASP.NET MVC3, use Unity como contendor de IoC para resolver las dependencias. Definir una dependencia en el constructor del controlador web al servicio Greeting de la capa de aplicación (que a su vez tiene otra dependencia con el servicio Capitalize de la capa de dominio) y que sea la infraestructura de MVC la que las resuelva.
  • Conseguir que la capa de servicios remotos, implementada mediante WCF, use Unity como contendor de IoC para resolver las dependencias. Definir una dependencia en el constructor del servicio remoto al servicio Greeting de la capa de aplicación (que a su vez tiene otra dependencia con el servicio Capitalize de la capa de dominio) y que sea la infraestructura de WCF la que las resuelva.
  • No hacer ningún Resolve explícito des de la aplicación.

Dependency injection en la capa de presentación con ASP.NET MVC 3

El código del controlador que queremos evitar seria similar a este:

    public class HomeController : Controller
    {
        private IGreetingService greetingService;
        
        public HomeController(IUnityContainer container)
        {
            this.greetingService = container.Resolve<IGreetingService>();
        }
        
        public ActionResult Index()
        {
            ViewBag.Message = greetingService.SayHello(Request.Params["name"]);
            return View();
        }        
    }


fijaros que con este código estaríamos haciendo IoC pero mediante SL y no mediante DI (consultad el post para ver porqué se prefiere el uso de DI al uso de SL).



Afortunadamente ASP.NET MVC3 está muy preparado para usar un contendor de IoC para resolver las dependencias, y hay mucha información en la red explicando paso a paso como hacer-lo. Básicamente y de forma resumida, los pasos a realizar serian los siguientes:





  1. Crear nuestro UnityDependencyResolver que utilice Unity para resolver los tipos extendiendo de IDependencyResolver.

  2. Crear el contenedor de Unity.

  3. Registrar todos los controladores de la aplicación en este contenedor.

  4. Registrar todos los tipos que deba resolver la aplicación en este contenedor.

  5. Indicar a MVC3 que use nuestro UnityDependencyResolver para resolver todas las dependencias.


Podemos hacer estos pasos manualmente pero en el ejemplo me he decantado por usar el package Unity.Mvc3 de Paul Hiles que ya nos proporciona muchas de estas tareas:





  • Nos proporciona una extensión de Unity para registrar todos los controllers de la aplicación en el contenedor de IoC.

  • Añade una clase Bootstrapper al proyecto web que se encarga de crear el contenedor de Unity, registrar los controladores (mediante la extensión), configurar Unity como dependency resolver de MVC y registrar los tipos de nuestra aplicación. Esta clase Bootstrapper se llama des de Application_Start() del Global.asax


el código de la clase Bootstrapper generada por Unity.Mvc3 tiene el siguiente aspecto:

    public static class Bootstrapper
    {
        public static void Initialise()
        {
            var container = BuildUnityContainer();
            DependencyResolver.SetResolver(new UnityDependencyResolver(container));
        }
        private static IUnityContainer BuildUnityContainer()
        {
            IUnityContainer container = new UnityContainer();           
            container.RegisterControllers();
            IoCComponentsRegistration.RegisterApplicationTypes(container);
            
            return container;
        }
    }


para hacer la configuración del proyecto MVC3 usando esta librería hemos seguido los pasos descritos en Integrating The Unity.Mvc3 1.1 NuGet Package From Scratch con la única salvedad que hacemos el registro de nuestros tipos en el método estático IoCComponentsRegistration.RegisterApplicationTypes en una librería de clases independiente y no en el Bootstrapper directamente, con esto conseguimos centralizar este registro de tipos y no tener dependencias en nuestro proyecto MVC con todos los proyectos de la aplicación:

    public class IoCComponentsRegistration
    {
        public static void RegisterApplicationTypes(IUnityContainer container)
        {
            container.RegisterType<IGreetingService, GreetingService>();
            container.RegisterType<ICapitalizeService, CapitalizeService>();
        }
    }


Nuestro controlador quedará así:

    public class HomeController : Controller
    {
        private IGreetingService greetingService;
        
        public HomeController(IGreetingService service)
        {
            this.greetingService = service;
        }
        
        public ActionResult Index()
        {
            ViewBag.Message = greetingService.SayHello(Request.Params["name"]);
            return View();
        }        
    }


con esto ya tenemos Unity configurado y listo para resolver todas la dependencias de la aplicación! Fijaros que se están resolviendo todas las dependencias con Unity, DI y inyección de dependencias en el constructor que es el objetivo que perseguimos.



Nota: Unity.Mvc3 también proporciona un Dependency Resolver de Unity para registrar los tipos que implementan IDisposable (por ejemplo los contextos de Entity Framework), tenéis más información en Introducing The Unity.Mvc3 NuGet Package To Reconcile MVC3, Unity and IDisposable.



Dependency injection en la capa de servicios remotos con WCF



Igual que para el caso de los controladores de la capa web, el código del servicio remoto que queremos evitar es este:

    public class GreetingService : IGreetingService
    {
        private ApplicationServices.IGreetingService greeting;
        public GreetingService(IUnityContainer container)
        {
            this.greeting = container.Resolve<IGreetingService>();
        }
        public string SayHello(string name)
        {
            return this.greeting.SayHello(name);
        }
    }


ya que con este código estaríamos usando SL y no DI. Para hacerlo vamos a usar los puntos de extensión que nos proporciona WCF.



Primero tenemos que definir un proveedor de instancias que utilice Unity para resolver los tipos. Para esto creamos una clase que implementa la interfaz IInstanceProvider de WCF y que devolverá las instancias configuradas en Unity cuando WCF necesite resolver un tipo:

    public class UnityInstanceProvider : IInstanceProvider
    {
        private readonly Type serviceType;
        private IUnityContainer container;
        public UnityInstanceProvider(Type serviceType)
        {
            this.serviceType = serviceType;
            this.container = new UnityContainer();
            IoCComponentsRegistration.RegisterApplicationTypes(container);
        }
        public object GetInstance(InstanceContext instanceContext, Message message)
        {
            return this.container.Resolve(serviceType);
        }
        public object GetInstance(InstanceContext instanceContext)
        {
            return this.GetInstance(instanceContext, null);
        }
        public void ReleaseInstance(InstanceContext instanceContext, object instance)
        {
            this.container.Teardown(instance);            
        }
    }


Fijaros que estamos llamando al mismo método (IoCComponentsRegistration.RegisterApplicationTypes) que en el caso de la capa de presentación para registrar las instancias en el contenedor de Unity!



Una vez hecho esto debemos configurar WCF para que use nuestro proveedor de instancias en vez del proveedor de instancias por defecto, para ello crearemos un nuevo “comportamiento” (service behavior) para nuestros servicios implementando la interfaz IServiceBehavior de WCF:

    public class UnityServiceBehavior : Attribute, IServiceBehavior
    {
        public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
        {
            foreach (var item in serviceHostBase.ChannelDispatchers)
            {
                var dispatcher = item as ChannelDispatcher;
                if (dispatcher != null) 
                {
                    dispatcher.Endpoints.ToList().ForEach(endpoint =>
                    {
                        endpoint.DispatchRuntime.InstanceProvider = new UnityInstanceProvider(serviceDescription.ServiceType);
                    });
                }
            }
        }
        public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters)
        {
        }
       
        public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
        {            
        }        
    }
}


Básicamente este comportamiento insta a WCF a usar nuestro proveedor de instancias para todos los Endpoints de todos los canales.



Una vez hecho esto tenemos que asociar este comportamiento nuestro servicio distribuido, esto lo podemos hacer mediante atributos, mediante un host de servicio propio o mediante configuración. Si lo hacemos mediante atributos debemos crear la clase que implementa el servicio distribuido decorada con el atributo [UnityServiceBehavior], la implementación del servicio WCF queda así:

    [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
    [UnityServiceBehavior]
    public class GreetingService : IGreetingService
    {
        private ApplicationServices.IGreetingService greeting;
        public GreetingService(ApplicationServices.IGreetingService greeting)
        {
            this.greeting = greeting;
        }
        public string SayHello(string name)
        {
            return this.greeting.SayHello(name);
        }
    }


Lo último que nos queda es crear un host propio que registre el comportamiento definido al hacer un Open:

    public class UnityServiceHost : ServiceHost
    {        
        public UnityServiceHost(Type serviceType, params Uri[] baseAddresses)
            : base(serviceType, baseAddresses)
        {
        }
        protected override void OnOpening()
        {
            base.OnOpening();
            if (this.Description.Behaviors.Find<UnityServiceBehavior>() == null)
            {
                Description.Behaviors.Add(new UnityServiceBehavior());
            }            
        }
    }


crear la factoría de hosts que nos devolverá la instancia de host del tipo UnityServiceHost:

    public class UnityServiceHostFactory : ServiceHostFactory
    {
        public ServiceHost CreateServiceHost(Type serviceType, string baseAddress)
        {
            return CreateServiceHost(serviceType, new[] { new Uri(baseAddress) });
        }
        protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
        {
            return new UnityServiceHost(serviceType, baseAddresses);
        }
    }


y configurar el servicio remoto Greeting para que use esta factoría de hosts:

<%@ ServiceHost Language="C#" 
Debug="true" Service="DistributedServices.GreetingService" CodeBehind="GreetingService.svc.cs" 
Factory="DistributedServices.UnityServiceHostFactory"
%>


y listos, nuestra capa de servicios WCF estará usando Unity para resolver todas las dependencias!



Fijaros, por último, que también aquí se están resolviendo todas las dependencias con Unity, DI y inyección de dependencias en el constructor: el servicio Greeting de la capa de aplicación tiene una dependencia con el servicio Capitalize de la capa de dominio.



Nota: esta solución se basa en el post Integrating StructureMap with WCF de Jimmy Bogard adaptado al caso en que usemos Unity como contenedor de IoC. En el blog de Johan Danforth también tenéis una entrada interesante que explica como hacer test unitarios de vuestros servicios WCF usando Unity: Integration Testing WCF Services with Unity



Resumen



En el post hemos visto una prueba de concepto para hacer IoC mediante DI y inyección de dependencias en el constructor en aplicaciones que sigan una arquitectura N-Layer. Es importante notar que solo realizamos la configuración del contenedor de IoC en los puntos de entrada de la aplicación: los servicios distribuidos y la aplicación web y dejamos a la infraestructura de MVC y WCF la responsabilidad de resolver las dependencias => NO accedemos al contenedor de IoC en ningún punto de la aplicación.



Saludos, Josep Maria

No hay comentarios: