Usando o Unity Application Block para construir aplicações com baixo acoplamento

A grande maioria das aplicações está repleta de objetos e componentes customizados responsáveis por executar tarefas específicas, genéricas ou complementares tais como envio de e-mail, geração de arquivo texto, logging, autenticação, autorização, manipulação de exceções, extração de informações de uma fonte qualquer, manipulação de dispositivos, entre outros. Na medida em que aumentamos a comunicação entre os módulos de uma aplicação (seja através de componentes, classes ou métodos) geramos uma crescente dependência aumentando sensivelmente sua complexidade de entendimento e tornando a tarefa de aplicação de testes mais extensa. Um dos resultados indesejáveis deste cenário é o alto acoplamento entre os módulos (ou subsistemas), e uma das técnicas mais utilizadas para reverter esse quadro é o chamado Padrão de Injeção de Dependência (Dependency Injection Pattern ou apenas DI).

O problema do alto acoplamento

Podemos medir o quão fortemente as classes de uma aplicação estão conectadas, possuem conhecimento ou dependem de outra classe observando o acoplamento existente em cada caso. Quanto mais acoplada uma classe estiver de outra, maior o risco de termos uma combinação dos seguintes problemas:

  • Mudanças numa classe relacionada forçam alterações locais à classe para que o funcionamento seja mantido;
  • O reuso da classe torna-se mais difícil uma vez que sua implementação depende de outras classes;
  • A coesão da classe diminui na medida em que aumenta o acoplamento caso ela assuma parte das responsabilidades de outra classe;
  • A classe não pode ser testada isoladamente já que depende de testes em conjunto com outras classes, aumentando inclusive o esforço de entendimento.

Percebemos rapidamente que diminuindo ao máximo o acoplamento, além de evitarmos os problemas citados, tornaremos nossa aplicação mais flexível e fácil de ser mantida, especialmente em equipes com paralelismo de desenvolvimento de atividades correlatas.

Utilizando as corretas técnicas e ferramentas para injeção de dependência, diversos componentes podem ser desenvolvidos paralelamente e sua aplicação se tornará mais plugável e flexível, exigindo um esforço menor para modificações. Pode-se aproveitar ainda de recursos como Mock Objects para auxiliar nos testes unitários.

Mock Ojects são objetos que simulam o comportamento de outros objetos ou controles complexos implementando a mesma interface das classes reais. Existem diversos frameworks de mock para .Net, dentre eles o NMock disponível em http://www.nmock.org. Esse tipo de objeto é muito utilizado em abordagens de desenvolvimento orientadas a testes, conhecidas como TDD (Test-Driven Development).

O conceito de Inversão de Controle (IoC)

Tomemos como exemplo uma classe chamada ClasseA que depende dos serviços Servico1 e Servico2, conforme a Figura 1.

Imagem
Figura 1. Dependência entre classes

Se em algum momento necessitarmos substituir ou atualizar alguma das suas dependências, a classe ClasseA sofrerá alterações em seu código, uma vez que está relacionada diretamente com as classes de serviço. Outro ponto é que as implementações concretas das dependências precisam estar disponíveis no momento da compilação da solução. Uma das soluções seria delegar a função de selecionar a implementação da classe concreta para um componente (ou um container) externo.

Em diversas literaturas podemos encontrar esta solução sendo descrita como Padrão de Inversão de Controle (Inversion of Control Pattern, ou simplesmente IoC), onde a Injeção de Dependência é uma das maneiras de a implementarmos (assim como o Service Locator Pattern), conforme observamos na Figura 2. Sendo assim, é correto afirmarmos que a DI é um caso particular de IoC.

Imagem
Figura 2. O padrão de injeção de dependência

A classe ClasseA ao invés de fazer referência a classe concreta, passa a referenciar uma interface IServicoA que por sua vez é implementada pelo serviço Servico1. O objeto identificado na Figura 2 como Builder fica encarregado de resolver a classe concreta e injetar a dependência na classe ClasseA.

O Unity Application Block permite aplicarmos o padrão de injeção de dependência, conforme veremos a seguir.

Apresentando o Unity Application Block

Disponibilizado a partir da versão 4.0 da Enterprise Library, em maio de 2008, e baseado na biblioteca ObjectBuilder2, o Unity Application Block pode ser brevemente definido como um container leve e extensível de injeção de dependência (lightweight container) que possibilita a construção de aplicações com baixo acoplamento através de facilidades como:

  • Simplicidade na criação de estruturas de objetos hierárquicos;
  • Especificação das dependências entre objetos, tanto em tempo de execução como em arquivo de configuração;
  • Flexibilidade para configuração do container;
  • Armazenamento e persistência do container em cache, muito útil para aplicações Web quando é necessário persistir o container em Session ou Application.

Aqui cabe uma pequena explicação para o termo “container”, que na programação orientada a objetos significa um objeto que contém outros objetos que podem ser incluídos ou removidos dinamicamente, em tempo de execução.

Diferentemente dos demais blocos, o Unity Application Block pode ser utilizado para desenvolvimento sem a necessidade de instalação da Enterprise Library e sua configuração pode ser realizada tanto pelos arquivos de configuração (familiares para quem já utilizou outros blocos, como o Data Application Block, Logging Application Block, Exception Handling Application Block, etc.) ou programaticamente, registrando suas dependências em tempo de execução. A exemplo da própria Enterprise Library, o Unity Application Block também é disponibilizado junto com o seu código fonte, permitindo modificações e extensões, assim figura como mais uma alternativa a outras bibliotecas semelhantes como Spring.Net, Castle Windsor, StructureMap entre outras.

Em dezembro de 2008 foi disponibilizado um port do Unity Application Block 1.2 para Silverlight. A biblioteca na qual se baseia este artigo é a de versão 1.2 de outubro de 2008 e, até a publicação deste, a mais recente disponível.

Para instalar o Unity Application Block você pode fazer o download da Enterprise Library 4.1 no site da Microsoft no endereço http://msdn.microsoft.com/entlib. Ou faça o download da versão standalone diretamente do CodePlex no seguinte link: http://www.codeplex.com/unity. A documentação para o Visual Studio 2008 pode ser obtida em: http://tinyurl.com/ac53vn.

O processo de instalação é simples e os requisitos para utilização do Unity Application Block são explicados detalhadamente na documentação acima. Veremos a seguir como podemos iniciar o uso de injeção de dependência com o Unity a partir de um exemplo prático, a cada novo tópico desse artigo entenderemos alguns conceitos da biblioteca e do padrão de DI.

Cenário

Um exemplo bastante simplificado é o ideal para iniciarmos o uso do Unity Application Block, pois com um foco maior no cenário poderemos tirar algumas conclusões mais claramente sobre a injeção de dependência. Usarei um projeto do tipo Class Library que implementará uma lógica de extração de arquivo e outro projeto do tipo Web Application para realizar as chamadas ao primeiro projeto. O objetivo será utilizarmos o Unity para injetarmos a dependência da Class Library no projeto Web de forma que o acoplamento entre as classes seja baixo.

Tomemos uma classe chamada Extrator do projeto do tipo Class Library que realiza a descompactação de um arquivo. Como sua implementação é menos importante que os conceitos que quero passar, omitirei do código fonte os trechos que não nos interessam conforme observamos na Listagem 1. A princípio veremos a implementação tradicional, onde a classe concreta será referenciada diretamente pelo método que precisa executar a tarefa de descompactar um dado arquivo.

Listagem 1. Código inicial da classe Extrator

            using System;

            namespace NetMag.DI.Core

            {
                public class Extrator

                {
                    public string Arquivo          { get; set; }

                    public string DiretorioDestino { get; set; }

                    public string Descompactar()

                    {
                        // Implementações de descompactação de arquivo...

                        return "Arquivo descompactado com sucesso.";
                    }
                }
            }
        

O método ExecutarCarga, presente no projeto web (que referencia o projeto class library) e ilustrado no Listagem 2, realiza a chamada diretamente à classe concreta Extrator. Como vimos no início deste artigo, neste tipo de implementação, modificações no tipo de extrator a ser utilizado implicará em alterações no código da classe chamadora. Outro ponto, é que para testarmos este método precisamos da implementação da classe Extrator, ou ao menos deveremos utilizar algum artifício, como por exemplo, stub methods, para podermos executá-los.

Listagem 2. Código inicial do método que chamará a classe Extrator

            public string ExecutarCarga()

            {
                Extrator extrator = new Extrator();

                extrator.Arquivo = @"c:\arquivo.zip";

                extrator.DiretorioDestino = @"c:\destino\";

                return extrator.Descompactar();            
            }
        

Aplicando o Unity

Para utilizarmos o Unity Application Block em nossa aplicação Web devemos adicionar no projeto as referências para as bibliotecas a seguir:

  • Microsoft.Practices.ObjectBuilder2.dll
  • Microsoft.Practices.Unity.dll
  • Microsoft.Practices.Unity.Configuration.dll

Estes arquivos estão disponíveis no diretório de instalação da Enterprise Library, que por padrão é “C:\Program Files\Microsoft Enterprise Library 4.1 - October 2008\Bin”. Se tudo foi instalado corretamente eles vão também aparecer na lista de referências do Visual Studio.

Faremos agora a inclusão dos namespaces indicados na Listagem 3 na classe que contém o método ExecutarCarga. Devemos providenciar também uma interface da classe Extrator, com isso garantiremos a assinatura da classe para outras implementações de extração de arquivos (como um contrato de utilização), esse passo é fundamental para podermos manter um baixo acoplamento entre as classes e aplicar a inversão de controle. Podemos ver na Listagem 4 como ficou a nossa interface IExtrator, e na Listagem 5 a mesma sendo utilizada pela classe concreta Extrator.

Listagem 3. Adicionando os namespaces do Unity Application Block

            using Microsoft.Practices.Unity;

            using Microsoft.Practices.Unity.Configuration;
        

Para extrair rapidamente a interface da classe Extrator basta apontar para o código da classe e clicar com o botão direito do mouse na palavra Extrator (linha 5 da Listagem 1). Escolha a opção Refactor, em seguida clique em Extract Method..., será exibida uma janela sugerindo o nome IExtrator e solicitando que você escolha os membros públicos. Clique no botão Select All, em seguida confirme clicando no botão Ok.

Listagem 4. A interface IExtrator

            using System;

            namespace NetMag.DI.Core
            {
                public interface IExtrator
                {
                    string Arquivo { get; set; }

                    string DiretorioDestino { get; set; }

                    string Descompactar();        
                }
            }
        
Listagem 5. Utilizando a interface IExtrator

            public class Extrator : NetMag.DI.Core.IExtrator
        

Embora precisemos apenas das referências ObjectBuider2 e Unity para os primeiros exemplos, mais adiante utilizaremos o Unity.Configuration para aplicarmos outros conceitos.

A partir deste ponto podemos voltar ao nosso método ExecutarCarga e alterá-lo para que faça a chamada ao container do Unity para que ele resolva a classe concreta que deverá ser chamada, conforme mostrado na Listagem 6.

Listagem 6. Modificando o método ExecutarCarga

            public string ExecutarCarga()

            {
                // Criamos o container que fará o registro

                // da interface e da classe concreta associada.

                IUnityContainer container = new UnityContainer();            

                container.RegisterType();

                // O container resolve a classe concreta.

                IExtrator extrator = container.Resolve<IExtrator>();

                extrator.Arquivo = @"c:\arquivo.zip";

                extrator.DiretorioDestino = @"c:\destino\";

                return extrator.Descompactar();
            }
        

A essa altura algumas dúvidas sobre a efetividade desta técnica podem aparecer já que o código ficou maior. Devemos tem em mente que estamos aplicando um padrão de desenvolvimento para diminuirmos a dependência do método ExecutarCarga na classe concreta Extrator e tornar a aplicação Web mais plugável, é justo adicionarmos mais alguns passos, não é verdade? Mais adiante veremos como podemos melhorar o código acima, mas antes vamos entender o que o Unity fez, vejamos o que significam algumas das linhas modificadas da Listagem 6:


            IUnityContainer container = new UnityContainer();

            container.RegisterType<IExtrator, Extrator>();
        

O método RegisterType registra o tipo desejado no container, no nosso exemplo queremos indicar que para a interface IExtrator estaremos utilizando a classe concreta Extrator, portanto o mapeamento será definido de IExtrator para Extrator. Quando solicitado, o container irá criar uma instância do tipo especificado, seja em resposta a injeção de dependência por meio de atributos de classe ou quando o método Resolve for chamado. O objeto também pode ter o seu tempo de vida configurado bastando indicá-lo como um dos parâmetros do método, mas caso não seja especificado o tipo será registrado como transitório, que significa que uma nova instância será criada em cada chamada do container (entre as opções existe a possibilidade de criar singletons). Veremos em outro tópico o funcionamento do tempo de vida do container ou lifetime com mais detalhes. Finalmente na linha seguinte o método Resolve define o valor da classe concreta para a dependência:


            IExtrator extrator = container.Resolve<IExtrator>();
        

O objeto extrator funcionará da mesma forma daqui por diante, o Unity se encarregou de construir o objeto sem o ônus da dependência direta da classe concreta, contudo o mapeamento foi definido dentro do código, e isso pode não ser muito interessante quando o requisito de facilidade de configuração tem um peso maior. Veremos a seguir como resolver esse problema utilizando outro método de configuração.

Configuração em tempo de design

No tópico anterior vimos como criar o container e registrar os tipos desejados programaticamente, contudo podemos deixar esta configuração no arquivo web.config da solução, conforme a Listagem 7. Isso deixará todas as configurações centralizadas em um único ponto. Na linha 4 você vê a configuração da seção de configuração. Atente para a versão do Unity que você está rodando, neste caso a versão 1.1 está sendo usada. Além disso, ela pode estar assinada, o que geraria um public key token.

Para permitir a criação do container de maneira mais automática, podemos colocar o código mostrado na Listagem 8 no arquivo Global.asax da aplicação Web.

Listagem 7. Configuração do arquivo Web.config da aplicação Web

            <?xml version=”1.0”?>

                <configuration>   

                    <configSections>

                        <section name=”unity” type=”Microsoft.Practices.Unity.

                        Configuration.UnityConfigurationSection, Microsoft.

                        Practices.Unity.Configuration, Version=1.1.0.0,

                        Culture=neutral, PublicKeyToken=null” />

                        // outras seções omitidas 

                </configSections>

                <unity>

                    <typeAliases>

                        <typeAlias alias=”IExtrator”

                        type=”NetMag.DI.Core.IExtrator, NetMag.DI.Core” />

                        <typeAlias alias=”Extrator”

                        type=”NetMag.DI.Core.Extrator, NetMag.DI.Core” />

                    </typeAliases>

                    <containers>

                        <container name=”containerExtrator”>

                            <types>

                                <type type=”IExtrator” mapTo=”Extrator” />

                            </types>

                        </container>

                    </containers>

                </unity>
                //restante do arquivo omitido
        
Listagem 8. Criando o container quando a aplicação for iniciada no arquivo Global.asax

            // Os seguintes namespaces devem ser inseridos

            using System.Configuration;

            using Microsoft.Practices.Unity;

            using Microsoft.Practices.Unity.Configuration;

            // O método Application_Start deve ser modificado da seguinte forma

            protected void Application_Start(object sender, EventArgs e)
            {
                IUnityContainer container = new UnityContainer();

                UnityConfigurationSection section =

                    (UnityConfigurationSection)ConfigurationManager.GetSection("unity");

                section.Containers["containerExtrator"].Configure(container);

                Application["container"] = container;

            }    
        

A Tabela 1 fornece uma descrição para os elementos indicados na Listagem 7 que serão utilizados pelo Unity para definir o mapeamento do container e os tipos utilizados. Existem outros itens de configuração possíveis além dos demonstrados, todos eles estão disponíveis na documentação referenciada neste artigo.

Tabela 1.Descrição dos elementos utilizados pelo Unity Application Block no arquivo Web.config.
Elemento Descrição
Unity Especifica a configuração do Unity Application Block, é um elemento requerido.
Bematech_DP_FechaPorta Fecha a porta de comunicação da impressora.
typeAliases Define uma coleção opcional de aliases (apelidos) que permitem especificar os tipos requeridos, lifetimes, instâncias e extensões do container.
typeAlias Define um único tipo a ser referenciado na configuração.
containers Define uma coleção de containers do Unity Application Block.
container Define um container específico.
types Indica uma coleção de mapeamentos registrados para um determinado container.
Type Define um tipo de específico de mapeamento.
lifeTime Contém os detalhes a cerca do tempo de vida a ser usado no mapeamento.

Observando a Listagem 9 podemos perceber que a utilização de configuração em tempo de design trouxe alguns benefícios pois a criação do container será realizada uma única vez no arquivo Global.asax (vide Listagem 8) e os tipos de container podem ser configurados no arquivo Web.config ficando a cargo do método ExecutarCarga apenas solicitar ao container que resolva a dependência para a interface IExtrator quando necessário.

Listagem 9. Utilizando a configuração em tempo de design

            namespace NetMag.DI.Web

            {
                public partial class Pagina : System.Web.UI.Page

                {
                    private IUnityContainer container;

                    protected void Page_Load(object sender, EventArgs e)

                    {

                        if (Application["container"] != null) {
                            container = (IUnityContainer)Application["container"];
                        } else

                        {

                            // Código para tratamento de exceções

                        }
                    }

                    public string ExecutarCarga()

                    {            

                        IExtrator extrator = container.Resolve<IExtrator>();

                        extrator.Arquivo = @"c:\arquivo.zip";

                        extrator.DiretorioDestino = @"c:\destino\";

                        return extrator.Descompactar();

                    }
                }
            }
        

Note que foram adicionadas algumas linhas no evento Page_Load para que seja verificado se o container armazenado na variável de aplicação “container” é nulo.

Iremos criar agora mais uma classe que implementa a interface IExtrator, deixaremos ela disponível para alterarmos a configuração do container pelo arquivo web.config sem a necessidade de modificação de código da aplicação Web. Para isso criaremos uma nova classe chamada NetMagExtrator conforme a Listagem 10.

Listagem 10. Nova classe NetMagExtrator que implementa a mesma interface IExtrator

            using System;

            namespace NetMag.DI.Core

            {
                class NetMagExtrator : NetMag.DI.Core.IExtrator

                {

                    #region IExtrator Members

                    public string Arquivo { get; set; }

                    public string DiretorioDestino { get; set; }

                    public string Descompactar()

                    {
                        // Implementações de descompactação de arquivo...

                        return "Arquivo descompactado com sucesso pelo NetMagExtrator.";
                    }

                    #endregion
                }
            }
        

Para alterarmos o mapeamento existente entre a interface IExtrator com a classe Extrator, adicionaremos a linha indicada na Listagem 11 (note que as linhas estão sendo puladas porque o código pré-existente continua presente) e faremos a atualização indicada na Listagem 12, linha 29 no arquivo web.config.

Listagem 11. Adicionando outro tipo de objeto

            <unity>

                <typeAliases>
                
                    <typeAlias alias=”NetMagExtrator”
                
                    type=”NetMag.DI.Core.NetMagExtrator, NetMag.DI.Core” />
                
                </typeAliases>
                
            </unity>
        
Listagem 12. Alterando o mapeamento do container para a nova classe

            <containers>

                <container name=”containerExtrator”>
                
                    <types>
                
                        <type type=”IExtrator” mapTo=”NetMagExtrator” />
                
                    </types>
                
                </container>
            </containers>
        

Se executarmos a aplicação e alterarmos o mapeamento apontado para a nova classe NetMagExtrator, constataremos que o Unity realizará a injeção de dependência em tempo de execução, uma vez que a interface IExtrator foi implementada por ambas as classes Extrator e NetMagExtrator. Pouco interessa ao método qual a implementação concreta, o que interessa é o contrato (interface) implementado.

Como gerenciar o tempo de vida do container (lifetime)

Podemos definir para um objeto do container alguns estilos de tempo de vida, ou lifetime. Como dito antes, o lifetime default é o transitório, ou seja, uma nova instância será criada em cada chamada do container. Através do gerenciador de lifetime poderíamos, por exemplo, definir uma instância do tipo singleton para um objeto, o que garantiria apenas uma instância do mesmo mantendo um ponto global de acesso a este objeto.

O Unity Application Block possui alguns gerenciadores de lifetime e permite a criação de outros tipos para implementar cenários específicos. Os gerenciados disponíveis são:

  • ContainerControlledLifetimeManager - Retorna a mesma instância do objeto ou tipo registrado a cada chamada do método Resolve ou ResolveAll implementando o padrão Singleton.
  • ExternallyControlledLifetimeManager - Permite registrar o mapeamento de objetos existentes mantendo uma referência fraca, caso o garbage collector identifique que outro código está mantendo uma referência forte ao objeto ele pode realizar o dispose da instância criada pelo gerenciador.
  • PerThreadLifetimeManager - Implementa um comportamento singleton através de threads permitindo a criação de um novo objeto caso não esteja armazenado em uma thread específica.

Para utilizarmos o gerenciador de lifetime precisamos fazer a seguinte alteração no código de registro de tipos do container mostrado na Listagem 6:


            IUnityContainer container = new UnityContainer();

            container.RegisterType<IExtrator, Extrator>(new
            ContainerControlledLifetimeManager());