Na primeira parte desse post, discutimos de forma breve o que é Inversão de Controle (IoC), apresentando 2 formas bem conhecidas de aplicar esse princípio:
• ‘Service Locator Pattern’
• ‘Dependency Injection Pattern’.
Nele citamos algumas vantagens que nos levaram a escolher DI como a nossa solução de IoC. Já nessa segunda parte, vamos apresentar com detalhes as funcionalidades do container Unity, que foi a API usada no nosso projeto.
Porque Unity?
Existem diversas implementações de containers de IoC no mercado. Esta comparação mostra o desempenho e as features de mais de 20 containers diferentes em .NET. No entanto, como essa era uma ferramenta nova (para nós do M3) e já utilizávamos o Enterprise Library Data Application Block para fazer operações com o banco de dados, acabamos optando pelo Unity, até agora com ótimos resultados.
Se no momento da decisão de utilizar o container nós soubéssemos que tantas outras implementações existiam – teríamos considerado além do Unity, apenas algumas das bibliotecas mais populares, como Castle Windsor, Spring.NET e NInject – ainda assim é provável que tivéssemos optado pelo Unity. No entanto, uma ótima segunda opção teria sido o Simple Injector, pois tem um grande conjunto de features úteis e ainda assim é extremamente rápido.
Utilizando o container
Para instalar o container Unity e começar a utilizá-lo em um projeto, basta procurar por “Unity” no ‘Package Manager’ do nuget e instalar o pacote. Para informações sobre essa ferramenta e como utilizá-la consulte o site oficial do nuget.
Com a referência no lugar, estamos prontos para programar utilizando Unity. A interface do container é bastante similar a da nossa classe fake “Container” do primeiro exemplo. Obviamente isso foi pré-condicionado ao fato de que é esse que estamos usando. Contudo, isso não quer dizer que as demais implementações se desviam muito desta: a maioria dos containers trabalha com os mesmos conceitos e utiliza uma camada pública similar.
Vejamos então como fica o código anterior de registro das classes, utilizando este novo container:
using Microsoft.Practices.Unity;
static class Program
{
private static IUnityContainer _container;
[STAThread]
static void Main()
{
_container = new UnityContainer();
_container
.RegisterType<ICommandValidator, CommandValidator>()
.RegisterType<CommandHandler>()
.RegisterType<Form1>();
Application.Run(_container.Resolve<Form1>());
}
}
Até o momento falamos superficialmente sobre as funcionalidades básicas de um container, mas o Unity tem diversas opções adicionais que podem vir a ser úteis no desenvolvimento do dia a dia. Vejamos algumas dessas features e como o container Unity as implementa:
- Modo de configuração:
Em todos os exemplos criados, nós utilizamos o código C# para registrar as nossas classes no container. Geralmente esse modo de registro chamado de ‘programático’ é suportado por todas as implementações de containers e é uma forma bastante satisfatória de efetuar os registros, tanto por não ser muito verbosa quanto por oferecer verificação em tempo de compilação sobre os tipos. No entanto, essa não é a única forma de se registrar classes no container Unity: Existem aplicações que precisam de mais flexibilidade para alternar entre implementações de uma mesma interface. Estas precisam que isso seja feito sem requerer uma recompilação do projeto. Sendo assim, a configuração via arquivos ‘.config’ suportada pelo Unity é uma boa opção. Apesar de perder a verificação em tempo de compilação, ela nos permite fazer essas trocas dinâmicas sem afetar a aplicação. Em ambientes mais avançados é possível inclusive persistir esse tipo de configuração em um banco de dados ou no registro do Windows, utilizando um IConfigurationSource customizado e os overloads de LoadConfiguration (às vezes é difícil achar bons exemplos sobre o Enterprise Library, mas esse artigo bastante antigo deve dar uma ideia de como funciona esse mecanismo genérico de ‘configuration sources’) . O exemplo abaixo utiliza um arquivo de configuração ‘app.config’ para fazer os mesmos registros da versão em código:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, Microsoft.Practices.Unity.Configuration"/>
</configSections>
<unity xmlns="http://schemas.microsoft.com/practices/2010/unity">
<assembly name="WindowsFormsApplication1"/>
<namespace name="WindowsFormsApplication1"/>
<container>
<register type="ICommandValidator" mapTo="CommandValidator" />
<register type="CommandHandler" />
<register type="Form1" />
</container>
</unity>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>
</configuration>
Para mais informações sobre a configuração do container via arquivo de configuração consulte a página oficial no MSDN.
Enquanto o novo código, baseado agora em uma configuração externa, fica assim:
using Microsoft.Practices.Unity;
using Microsoft.Practices.Unity.Configuration;
static class Program
{
private static IUnityContainer _container;
[STAThread]
static void Main()
{
_container = new UnityContainer();
_container.LoadConfiguration();
Application.Run(_container.Resolve<Form1>());
}
}
Ao utilizar os tipos em Microsoft.Practices.Unity.Configuration, a dll System.Configuration deve ser também incluída no projeto.
Note como o método “LoadConfiguration” utiliza a informação presente no app.config para criar os registros no container. Esse método tem alguns overloads que recebem um configSection e/ou o nome de um container mas, como definimos apenas uma configuração dele e utilizamos o nome da section como “unity” (que é o padrão), não foi necessário informar nenhum desses parâmetros. Esse tipo de flexibilidade pode ser relevante em um cenário mais complexo onde existem diversos containers na mesma aplicação, ou quando é preciso separar logicamente diversas configurações no ‘.config’.
Além da diferenciação entre runtime (configuração em código) e designtime (através de configuração via IConfigurationSource ou arquivo ‘.config’), existe uma outra categoria de registro de tipos, chamada de ‘Registro por convenção’.
Ela se baseia em critérios de avaliação para fazer os registros em diversas dlls ao mesmo tempo no container, sendo assim um facilitador quando existem dezenas de classes a serem registradas. Esse mecanismo só foi introduzido no Unity na versão 3.0. Esse blog explica como a feature funciona nessa nova versão.
Como a versão 3.0 do Unity precisa do .Net Framework 4.5, o nosso time não pode ainda desfrutar de seus benefícios (o novo .Net não é mais suportado em plataformas que ainda são essenciais aos nossos clientes, como Windows Server 2003) e, portanto, optamos por uma solução alternativa para utilizar o registro por convenção, o ‘Unity Auto Registration’. Voltaremos a este assunto quando falarmos das alterações no M3 na terceira parte desse post.
- Modos de injeção:
Ainda não foi mencionado exatamente como o container constrói as instâncias das nossas classes e injeta as suas dependências. O Unity segue um algoritmo relativamente simples para isso. Vejamos os passos em alto nível desse processo:
1 – ‘Constructor Injection’
No primeiro momento, o construtor com o maior número de dependências é procurado, via reflection, no tipo solicitado. Quando é encontrado, o mesmo processo se repete recursivamente para todas as suas dependências, até que seja encontrado um construtor completamente independente, que é então invocado. Nesse ponto, a classe é criada e o processo retorna criando todas as demais instâncias que antes não podiam ser construídas por falta de parâmetros, obtendo no fim uma instância com todas as dependências criadas. Essa primeira etapa é conhecida como ‘Constructor Injection’, e é a forma mais robusta de injeção por garantir que o objeto será criado em um estado válido. Se por alguma razão não for possível resolver algum dos parâmetros de um construtor em toda a cadeia, uma exceção é disparada indicando onde o problema ocorreu.
É possível controlar qual construtor será chamado pelo container, sobrescrevendo a regra citada anteriormente. Isso é feito através do atributo InjectionConstructor e pode ser interessante ou até necessário em alguns casos onde existam múltiplos construtores para o mesmo tipo.
2 – ‘Property Injection’
Em seguida, com o objeto já construído, o container passa para uma segunda etapa, chamada de ‘Property Injection’. Todas as propriedades com um setter público e que foram marcadas com o atributo Dependency são resolvidas automaticamente. A diferença nesse ponto é que essas dependências por propriedades são consideradas ‘dependências opcionais’, significando que, caso não seja possível resolvê-las, ‘null’ é retornado (em vez de uma exceção ser disparada).
3 – ‘Method Injection’
Por último, a etapa de ‘Method Injection’ é processada. Aqui, os métodos públicos marcados com o atributo InjectionMethod são chamados da mesma forma que os construtores, resolvendo todas as dependências especificadas. Estas dependências são obrigatórias (por padrão) nesses métodos de injeção, da mesma forma que os parâmetros do construtor. Note que é possível que uma classe tenha diversos métodos marcados com esse atributo, apesar de isto não ser uma prática comum. Nesse caso, é importante não assumir que eles serão chamados em uma ordem específica, pois a ordenação das chamadas depende da ordenação criada pelos métodos em System.Reflection, que não garantem ordenação.
Tanto na primeira etapa quanto na terceira, pode-se especificar que uma dependência é opcional (ou seja, deve retornar null em vez de disparar uma exceção) utilizando o atributo OptionalDependency no argumento desejado.
Depois da execução dessas três etapas com sucesso, o nosso objeto está pronto e é retornado ao “chamador”.
Apesar de toda essa flexibilidade oferecida pela API do Unity, é importante ressaltar aqui algumas boas práticas em relação ao uso de containers em geral. Um dos grandes incentivos para a utilização desse design pattern é o nível baixo de intrusão no código (sendo esse um fator decisivo na nossa escolha no M3 por ele no lugar de um ServiceLocator). Em boa parte das situações, o único ponto do código que irá se referir a um container é o ponto de configuração – que, como dito antes, costuma ser um ponto central de inicialização. Isso é bem proveitoso, pois faz com que as nossas classes continuem independentes do container e nos permite inclusive trocar a biblioteca de container com pouquíssimo esforço. No nosso exemplo até aqui, mudar a aplicação para utilizar o Simple Injector ou o Castle Windsor seria extremamente trivial.
Mas, quando começamos a utilizar esses controles customizados, deixamos nossas classes dependentes da API específica do container. Ao utilizar Property Injection, por exemplo, somos obrigados a incluir a biblioteca Unity (caso a classe em questão não esteja no mesmo projeto da classe onde o container é configurado, o que é bastante comum), o namespace Microsoft.Practices.Unity e o atributo Dependency. Apesar de esses mecanismos existirem em praticamente todas as APIs de containers, o uso excessivo deles é considerado uma má prática, sendo preferível sempre utilizar a injeção padrão via construtor. Apesar desse aviso, existem situações reais onde de fato ficamos impedidos de utilizar a injeção via construtor, e então, esses controles extras passam a ser a nossa única opção. Voltaremos a falar sobre esse problema mais adiante quando os ajustes feitos no M3 forem discutidos.
- Ciclo de vida das instâncias no container
Assim como demais containers, o Unity nos permite, no momento do registro de um tipo, especificar como instâncias serão tratadas durante a execução da aplicação. Esse comportamento pode ser especificado através de overloads dos métodos de registro que recebem um . O LifetimeManager é a classe base utilizada em toda a API para implementação de políticas de gerenciamento do ciclo de vida das instâncias/tipos cadastrados no container. O Unity já vem com algumas implementações distintas dessa classe, contemplando diversos cenários sem que seja necessário criar um LifetimeManager customizado. Esses gerenciadores embutidos na API são os seguintes:
. ExternallyControlledLifetimeManager:
Registra o tipo sem que haja qualquer influência do container em nenhum ponto do seu ciclo de vida. Pode ser interessante para registrar membros de bibliotecas externas que já são mantidos por outros mecanismos. Provavelmente o gerenciador mais especializado e o menos utilizado.
Um objeto novo é construído cada vez que é necessário resolver o tipo. Esse é o modo padrão de registro quando um LifetimeManager não é especificado.
. PerResolveLifetimeManager />:
Apenas um objeto do tipo é criado durante um Resolve. Inicialmente parece equivalente ao TransientLifetimeManager, mas difere em um ponto importante: durante um Resolve, é possível que um tipo seja uma dependência de mais de uma classe da cadeia sendo construída. Nesse caso, esse objeto será criado uma única vez durante o processo e compartilhado entre essas classes.
Apenas um objeto do tipo é criado para cada thread que acessa o container.
. ContainerControlledLifetimeManager:
Apenas uma instância do tipo é criada em todo o container. Requisições subsequentes retornam sempre a mesma instância. Tem uma função semelhante ao design pattern Singleton. Bastante útil para instâncias que são custosas de construir ou que mantém um estado que deve ser compartilhado entre diversas classes. Utilizado em diversos pontos na migração do M3 onde existiam classes singletons.
. HierarchicalLifetimeManager:
Especialização do ContainerControlledLifetimeManager que, em vez de sempre retornar a mesma instância para uma hierarquia de containers, faz com que cada container filho receba um objeto diferente.
Como citado acima, ao utilizar o método RegisterType o LifetimeManager aplicado é o TransientLifetimeManager por padrão. Contudo, chamar RegisterType não é a única forma de cadastrar tipos no container. Existe também o RegisterInstance que, em vez de criar uma instância do tipo no momento do Resolve, espera uma instância já pronta no momento do registro. Nesse caso o ContainerControlledLifetimeManager é utilizado por padrão.
- Pontos de Extensão:
A API Unity, como toda biblioteca bem projetada, apresenta diversos pontos de extensão para que seja possível adequá-la a praticamente qualquer sistema existente. Por exemplo, é possível criar um LifetimeManager que trate as instâncias de uma forma alternativa aos LifetimeManagers padrão ou criar novos blocos xml de configuração utilizando o mesmo configSection original através de extensões do schema padrão e utilizar esses novos elementos de configuração em outras extensões.
. UnityContainerExtension:
O ponto mais comum de extensão, no entanto, é via implementação e registro da classe UnityContainerExtension. O mecanismo de extensão do container utilizando essa classe é razoavelmente complexo, pois requer um conhecimento tanto de estruturas internas quanto de padrões utilizados na implementação do container. Através dessa classe, existem basicamente dois pontos iniciais de extensão, que são os métodos Initialize (utilizado quando a extensão é adicionada ao container), e Remove (chamado quando a extensão é removida). A partir daí, boa parte do pipeline de criação é exposto através de uma propriedade na classe base, Context.
A classe ExtensionContext exposta via ‘Context’ por sua vez possui alguns eventos que podem ser utilizados para fazer operações em pontos específicos (como ChildContainerCreated ou Registering) e expõe, via propriedades, coleções importantes, em especial BuildStrategies. O pipeline de criação implementa o Chain of Responsibility Pattern através dessa coleção, ou seja, estratégias de manipulação são adicionadas a essa coleção e posteriormente chamadas em sequência até que uma delas marque ‘BuildComplete = true’ no contexto de build. Essas estratégias são encapsuladas na classe base BuildStrategy. São bastante comuns extensões do container Unity que adicionam uma estratégia no momento de criação (Initialize) para tratar tipos em um certo ponto do pipeline. Veremos um exemplo de uma extensão nesse formato mais adiante, quando discutirmos a integração do container no M3.
Quando uma extensão é criada, basta utilizar um dos métodos de registro de extensões, AddNewExtension (que registra um tipo como uma extensão equivalentemente ao RegisterType) ou o AddExtension (que registra uma extensão já criada previamente similar ao RegisterInstance). Da mesma forma que o restante das características de configuração do container, o registro das extensões pode também ser feito em design time, via arquivo de configuração.
Uma peculiaridade do método AddNewExtension é que o próprio container é utilizado para construir a instância da extensão, sendo possível então a injeção de dependências também nessas extensões caso se faça necessário. Nessa situação, como a inicialização é feita logo após a chamada ao Add, é esperado que essas dependências tenham sido registradas em um ponto anterior do código. Note que, por esse motivo, ao utilizar injeção nas extensões não é possível diretamente utilizar apenas um conjunto de registros no arquivo de configuração, pois através dele, as extensões são sempre registradas antes dos tipos. Assim, é preciso registrar as dependências previamente no container (antes de chamar LoadConfiguration) ou optar por dois trechos de configuração no .config e consequentemente duas chamadas ao LoadConfiguration em sequência. O fato de o LoadConfiguration ser aditivo torna esse tipo de configuração possível, como demonstra esse exemplo:
<unity xmlns="http://schemas.microsoft.com/practices/2010/unity">
<container name="MyExtensionDependencies">
<register type="IDependency" mapTo="Dependency" />
</container>
<container>
<register type="ITypeProcessedByMyExtension" mapTo="TypeProcessedByMyExtension" />
<extension type="MyApplication.MyExtension, MyApplication" />
</container>
</unity>
O novo código, agora baseado em uma configuração com , fica assim:
private static IUnityContainer _container;
[STAThread]
static void Main()
{
_container = new UnityContainer();
_container
.LoadConfiguration("MyExtensionDependencies")
.LoadConfiguration();
Application.Run(_container.Resolve<Form1>());
}
Ou configurado diretamente em código fica assim:
private static IUnityContainer _container;
[STAThread]
static void Main()
{
_container = new UnityContainer();
_container
.RegisterType<IExtensionDependency, ExtensionDependency>()
.AddNewExtension<MyExtension>()
.RegisterType<ITypeProcessedByMyExtension, TypeProcessedByMyExtension >();
Application.Run(_container.Resolve<Form1>());
}
Os registros no trecho acima serão feitos na seguinte ordem: IDependency, MyExtension, ITypeProcessedByMyExtension.
Conclusão
Essa segunda parte deve ser suficiente para dar uma noção mais realista de como um Container de IoC funciona. Vimos que o nível de flexibilidade do Unity é bastante grande (a maioria das features discutidas aqui existem também em outros containers, e alguns deles têm ainda mais opções!), mas vimos também que a curva de aprendizado para utilizar todo o potencial da ferramenta é significativa. O próprio time do M3 ainda não experimentou alguns pontos mais avançados, como o mecanismo de interceptação que permite ampliar a funcionalidade de classes através de comportamentos automaticamente acoplados via proxies dinâmicos (similar ao objetivo de AOP (Aspect Oriented Programming)), ou o uso de uma hierarquia de containers (os child containers no caso do Unity), que dá o poder de controlar camadas diferentes da aplicação com containers específicos.
A próxima e última parte falará sobre os problemas encontrados e como eles foram solucionados no contexto do projeto M3, utilizando o container Unity. Ficará claro também que, ao aplicar esse padrão, diversos outros problemas que antes estavam “dormentes” passam a ficar evidentes e novamente passíveis de refatoração, sendo este um ponto extremamente positivo para a saúde do projeto, voltando ao propósito inicial de utilizar os princípios SOLID.
Escrito por Juliano Gonçalves.












