E ai, pessoal?

Estou de volta com a série “Padrões de DI”. Na primeira parte, vimos uma introdução sobre o conceito de Dependency Injection e seus três aspectos: composição, ciclo de vida e interception.

Nesta parte, veremos o conceito chamado de Composition Root e como aplicá-lo.

Então vamos nessa!

1. O QUE É

Composition Root é o componente da aplicação onde é feita a composição dos objetos.

Este componente fica no ponto de entrada da aplicação, que varia de acordo com a tecnologia. Por exemplo, em uma aplicação do tipo console, a entrada é o famoso método “main” enquanto que em uma aplicação ASP.NET MVC, a entrada é um IControllerFactory.

Ainda dependendo da tecnologia e se você estiver usando um container de DI, é normal que o código do Composition Root seja “splitado” em duas partes: uma que registra todas as dependências (que é chamada uma única vez, quando a aplicação sobe) e outra que efetivamente resolve/libera as dependências (logo mais, um exemplo de implementação).

2. DI CONTAINERS

Normalmente utilizamos no Composition Root um DI Container, que nada mais é do que uma biblioteca com uma série de facilidades no que diz respeito a DI, dando suporte, na maioria das vezes, aos três aspectos de DI vistos no post anterior. Há uma infinidade de containers disponíveis e até benchmarks (para .Net) para auxiliar na escolha de um.

Não entrarei em detalhes sobre algum container em específico para não fugir do foco deste post, mas veremos o uso básico de um no exemplo a seguir.

3. IMPLEMENTANDO UM COMPOSITION ROOT NO ASP.NET MVC

Neste exemplo, usaremos um container de DI. Tudo o que precisamos fazer é instanciá-lo, registrar as dependências e permitir que o container resolva e libere as dependências após seu uso.

Vamos ao exemplo (utilizando o Ninject). Começamos pelo global.asax:

public class MvcApplication : System.Web.HttpApplication
{
   protected void Application_Start()
   {
      var container = new StandardKernel(new NinjectWebAppModule());
      ControllerBuilder
        .Current
        .SetControllerFactory(new NinjectControllerFactory(container));
   }
}

No start da aplicação, instanciamos o container (new StandardKernel()), passando o objeto que possui o registro das dependências (new NinjectWebAppModule()).

Em seguida, para resolvermos as dependências, precisamos utilizar o seam fornecido pelo ASP.NET MVC: a interface IControllerFactory. Para isso, implementamos essa interface e configuramos a implementação NinjectControllerFactory como a nova factory de controllers, usando o método SetControllerFactory().

Esta é a implementação do módulo NinjectWebAppModule:

public class NinjectWebAppModule : NinjectModule
{
   public override void Load()
   {
      Kernel.Bind<IConversor>().To<ConversorPdf>();
   }
}

No caso do Ninject, tudo que é preciso fazer para implementar um módulo é herdar de “NinjectModule” e sobrescrever o método Load(). O método Bind() do container é responsável por registrar a dependência. No exemplo, o container é “ensinado”a resolver a dependência “IConversor” com o tipo concreto “ConversorPdf”.

[NOTA]
A implementação de um módulo, assim como o próprio nome “módulo”, varia de um container para outro. O Windsor, por exemplo, chama um “módulo” de “installer”. No entanto, a ideia é a mesma.

Outros, como o SimpleInjector, sequer possuem esse conceito e chamamos os métodos para registrar direto pelo objeto container:

//.....
var container = new Container();
container.Register(typeof(IConversor), typeof(ConversorPdf));
//.....

[/NOTA]

Por fim, segue a implementação do NinjectControllerFactory:

public class NinjectControllerFactory : DefaultControllerFactory
{
    private readonly StandardKernel _container;

    public NinjectControllerFactory(StandardKernel container)
    {
        if (container == null) throw new ArgumentNullException("container");
         _container = container;
    }

    protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
    {
         return _container.Get(controllerType) as IController;
    }

    public override void ReleaseController(IController controller)
    {
        _container.Release(controller);
    }
}

Por simplicidade, ao invés de implementar IControllerFactory, a factory herda de DefaultControllerFactory. Feito isso, basta sobrescrevermos os métodos GetControllerInstance() e ReleaseController().

No método GetControllerInstance(), pedimos ao container para nos entregar o controller requerido, resolvendo todas as dependências exigidas por ele. No Ninject, o método Get() é o responsável por resolver dependências embora o nome mais comum nos containers (e que faz mais sentido) é Resolve().

No método ReleaseController(), pedimos ao container para liberar o controller e todas as suas dependências, por meio do método Release(). Isso, em geral, é feito pelo Garbage Collector do .Net, mas é uma boa ideia chamar o Release() pois podem haver dependências não-gerenciadas, ou seja, que precisam ter seus métodos Dispose() chamados o mais rápido possível.

É isso. Implementamos um composition root com apenas duas classes novas (NinjectWebAppModule e NinjectControllerFactory) e ligamos tudo no Application_Start() da aplicação.

4. DI EM AÇÃO

Para ver a mágica em funcionamento, basta criar as classes IConversor e ConversorPdf e injetar o IConversor em um controller qualquer:

public class HomeController : Controller
{
    private readonly IConversor _conversor;
    public HomeController(IConversor conversor)
    {
        _conversor = conversor;
    }
}

Ao solicitar o controller acima, o método GetControllerInstance() da NinjectControllerFactory será chamado e pedirá ao container para entregar o controller desejado resolvendo todas as dependências existentes (no caso, apenas IConversor).

Você deverá notar, então, que no construtor de HomeController será injetado um objeto do tipo “ConversorPdf”.

5. ENCERRANDO

Nesta segunda parte da série “Padrões de DI”, vimos o conceito conhecido por Composition Root e uma forma de implementá-lo usando um DI container em uma aplicação ASP.NET MVC.

Este padrão nos diz ONDE compor os objetos: no start da aplicação.

Ao longo do post, vimos indiretamente dois outros padrões – Register-Resolve-Release e Constructor Injection – que discutiremos nos posts seguintes.

[]s e até lá!