Se você se preocupa em desacoplar suas classes e camadas, com certeza já precisou descobrir uma maneira de passar um objeto construído, que vou chamar de objeto de serviço (OS), a um outro objeto que vai utilizá-lo, que vou chamar de objeto de aplicação (OA). Da mesma forma, se preocupou em gerar interfaces ou classes abstratas para o OS, gerando uma interface de serviço (IS) para permitir a inversão de controle, e facilitar tarefas como testes, além de facilitar o polimorfismo.

Essas preocupações trazem necessariamente a questão: como então passar um OS construído a um OA que depende de uma IS?

Falando em exemplos concretos. Você tem um controller de produtos (do ASP.Net MVC) que depende um repositório de produtos e de um repositório de categorias para obter e alterar os seus dados. Depende ainda de uma classe que faz o tratamento de erros. Como tratar as dependências do controlador com essas classes? Vejam o diagrama abaixo:

Dependências

Poderia fazer assim, com alto acoplamento, ainda que eu tivesse as interfaces (código do controlador, nosso OA):

        public ActionResult Index()
        {
            try
            {
                IProductRepository productRepository = new Models.ProductRepository();
                ViewData["products"] = productRepository.GetProducts();
                ICategoryRepository categoryRepository = new Models.CategoryRepository();
                ViewData["categories"] = categoryRepository.GetCategories();
                return View();
            }
            catch (Exception ex)
            {
                IExceptionManager exceptionManager = new Models.ExceptionManager();
                exceptionManager.HandleError(ex);
                return View("Error");
            }
        }

Qual o problema deste código? Ainda que eu utilize ISs, é o OA que está criando os OSs, ele os conhece fortemente. O controlador conhece o repositório concreto de produtos, o repositório concreto de categorias, e o tratador de erros concreto, além de suas interfaces. Com isso estou com acoplamento alto, além de coesão baixa, já que o método Index quebra o princípio da responsabilidade única (SRP) porque tem mais de um motivo para mudar (sabe obter produtos e categorias e sabe criar repositórios – nem vou entrar no mérito de saber tratar erro, apesar de ele existir).

Há duas opções que quero discutir: crio um service locator (SL), ou trabalho com injeção de dependência (Dependency Injection – DI).

Com SL fica assim:

        public ActionResult Index()
        {
            try
            {
                var registry = new Registry();
                IProductRepository productRepository = registry.Resolve<IProductRepository>();
                ViewData["products"] = productRepository.GetProducts();
                ICategoryRepository categoryRepository = registry.Resolve<ICategoryRepository>();
                ViewData["categories"] = categoryRepository.GetCategories();
                return View();
            }
            catch (Exception ex)
            {
                IExceptionManager exceptionManager = registry.Resolve<IExceptionManager>();
                exceptionManager.HandleError(ex);
                return View("Error");
            }
        }

O que mudou? Bom, o objeto Registry (que é aplicação do padrão Registry com alguns anabolizantes) é responsável por resolver as dependências. É ele quem faz o trabalho de obter uma instância dos meus objetos e me devolvê-la. Como ele faz isso é você quem decide, mas provavelmente um contêiner de DI, como o Unity, ou o Windsor, mas pode até ser hardcoded, no punho.

Nesse modelo, meu controlador está acoplado apenas ao meu registry, e às interfaces de serviço. Posso substituir tudo por mocks e testar com facilidade. É só substituir o registry com alguma herança, algo bem simples de fazer, e acabou. Este código teria que evoluir um pouco, porque da maneira com que está não é testável porque o controlador cria o registry. Um acesso via uma propriedade estática resolveria o problema.

A outra opção, trabalhando com DI:

    public class ProductsController : Controller
    {
        private IProductRepository _productRepository;
        private readonly ICategoryRepository _categoryRepository;
        private readonly IExceptionManager _exceptionManager;

        //construtor
        public ProductsController(
            IProductRepository productRepository, 
            ICategoryRepository categoryRepository, 
            IExceptionManager exceptionManager)
        {
            _productRepository = productRepository;
            _categoryRepository = categoryRepository;
            _exceptionManager = exceptionManager;
        }

        //Dependency injection
        public ActionResult Index()
        {
            try
            {
                ViewData["products"] = _productRepository.GetProducts();
                ViewData["categories"] = _categoryRepository.GetCategories();
                return View();
            }
            catch (Exception ex)
            {
                _exceptionManager.HandleError(ex);
                return View("Error");
            }
        }
    }

Nesse caso, as dependências (os três OS) são passados via construtor para a classe de controlador. Ela não sabe como criá-los. Isso fica melhor alinhado com o SRP e torna o OA efetivamente um cliente das ISs, sem ter o menor conhecimento dos OA concretos.

Esse é o modelo que eu prefiro. Trabalho com DI na maioria dos casos, usando SL só onde não é possível. É bem mais fácil de testar, porque não preciso mockar o registry, e com um bom contêiner de injeção de dependência, como o Unity, fica ridículo de trabalhar as dependências, que, depois de pouco código, se resolvem sozinhas. (Nota mental: postar um exemplo de autoresolução de dependência com Unity. Nota para vocês, leitores: se eu não postar me cobrem!)

Esse cenário é plenamente suportado no ASP.Net MVC, que trabalha com uma Factory para controladores (depois eu mostro isso aqui também, mas tem um exemplo no código da palestra de MVC em N camadas se quiserem ver). Já no ASP.Net Webforms isso não existe, quando um WF é criado pela infraestrutura do ASP.Net ele já foi criado e nenhuma dependência foi injetada. Isso significa que você está preso ao SL, mas isso não chega a ser um problema. Aplicações Windows Forms são triviais também no uso de DI, porque controlamos o lifeline dos objetos do começo ao fim.

Por fim, quero deixar aqui uma recomendação minha: nunca exponha o conteiner de injeção de dependência, como o UnityContainer, diretamente, como se ele fosse o SL. Abstraia-o, porque você pode precisar trocar a tecnologia, além de ter dificuldades nos testes. Uma classe Registry com um método "Resolve" é geralmente suficiente e resolve o problema.

Sugiro também a leitura das considerações do Martin Fowler, que fala no site dele que prefere SL com Registries, mas em seu livro PoEAA, ele diz que só usa Registries em última opção.

E você, o que usa para desacoplar suas classes? Como resolve o problema de testabilidade e substituição de dependências? Prefere DI ou SL?


Postado na(s) categoria(s) Arquitetura , Polêmicas pelo giovanni bassi em 14 de abril de 2009 às 10:10 | Tags: ,

Comentários


Brazil André
abril 14. 2009 11:11
André
Ótimo post, Giovanni... Como sempre.

Gosto das duas soluções (SL e DI). SL eu já uso a algum tempo, e DI é muito legal, pricipalmente com esses novos containers que vem surgindo.

Fiquei com uma coisa na cabeça, que me parece ser uma solução interessante também, que na verdade usa as duas "coisas". Não pensei muito no assunto, por isso queria ouvir o que os colegas acham disso...

Na solução com DI, poderíamos "injetar" o SL no construtor do ProductsController, ao invés de injetar os OS's. Assim, caso o ProductsController precise de mais algum OS, esse já estaria "disponível", não precisaríamos de alterar o seu construtor, ficaria testável e desacoplado.

O que acha?

Forte abraço.

no site


abril 14. 2009 15:55
Giovanni Bassi
André,
É possível, já pensei nisso. Mas isso deixaria as dependência não explícitas, e sinto que isso não seria bom.

http://unplugged.giggio.net/http://unplugged.giggio.net/


abril 14. 2009 18:21
Rodrigo
Legal o post Giovanni.

Eu acho que o uso de DI é mais interessante porque também é uma forma de documentar as dependências da classe, a gente bate o olho no construtor e sabe de cara de quais componentes ela depende. Isso ajuda na compreensão de classes com as quais não estamos familiarizados. Sem contar com o benefício de potencialmente dispensar mocks, como vc mencionou.

http://rodbv.com/bloghttp://rodbv.com/blog

Comentar


(Vai mostrar seu Gravatar)

  Country flag

biuquote
  • Comentário
  • Pré-visualização
Loading



Quem é Giovanni Bassi

Giovanni Bassi Sou uma pessoa apaixonada por tecnologia e especificamente por .Net. Sou consultor independente especialista em .Net, focado em arquitetura e melhores práticas. Tenho dezenas de artigos publicados na .Net Magazine, revista da qual sou editor técnico. Ministro palestras e cursos de vez em quando, e quando dá tempo eu respiro um pouco. Mais detalhes nesta página.

Busca

Selos

Eu vou ao TechEd Brasil 2010, e você?

MVP

MCPD

MCSD

.Net Magazine

Abaixo ao if!

Calendário

«  julho 2010  »
seteququsedo
2829301234
567891011
12131415161718
19202122232425
2627282930311
2345678
Ver detalhamento de posts no calendário

Blogs interessantes

    OPMLDownload OPML file

    Postagens recentes

    Comentários recentes

    Disclaimer / Aviso
    As opiniões colocadas neste blog são minhas e pessoais e não expressam necessariamente as opiniões de meus empregadores, pareceiros e amigos. Da mesma forma, os comentários feitos por leitores do blog não expressam a minha opinião.

    © Copyright 2010 .Net Unplugged
    Log in