Friday, July 6, 2012

Padrão Service Locator

Conforme prometido no post anterior (http://levyfialho.blogspot.com.br/2012/07/inversao-de-controle-dependency.html), este artigo pretende demonstrar como funciona o padrão Service Locator, suas vantagens e desvantagens.

Vamos supor um cenário onde temos uma loja que recebe pedidos de seus clientes, valida os dados do pedido e solicita a entrega.

Temos uma classe Loja no projeto. Para manter a coesão da classe iremos isolar as regra de negócios para validar o pedido e também a regra responsável pela entrega do pedido.

Iremos criar dois serviços diferentes:

ValidatorService : Responsável por fazer toda a validação do pedido.

ShippingService : Responsável por fazer a entrega do pedido.

Repare que esta estrutura faz com que existam duas dependências na nossa classe loja, conforme mostra a figura abaixo:




Esta situação tem os seguintes problemas:


  • Para substituir ou atualizar as dependências  você precisa alterar o código da classe Loja.
  • As implementações das dependências tem que estar disponíveis em tempo de compilação.
  • Suas classes são difíceis de testar de forma isolada, porque eles têm referências diretas a dependências que não podem ser mockadas.
  • Suas classes contem código repetitivo para criar, localizar e gerenciar suas dependências.




Para resolver a situação acima podemos aplicar o pattern Service Locator. Entre os principais motivos para aplicarmos este pattern, podemos citar:


  • Você quer separar a classe Loja de suas dependências para que essas dependências possam ser substituídas ou atualizadas com alterações mínimas no código.
  • A implementação concreta das dependências não é conhecida em tempo de compilação.
  • Você quer ser capaz de testar as suas classes de forma isolada.
  • Você não quer que a classe Loja seja responsável por localizar e gerir as dependências.



Aplicando o Service Locator..



Este padrão consiste na criação de uma classe chamada Locator que funciona como um repositório de serviços. Quando a classe Loja precisa solicitar um serviço, ela acessa um método da classe Locator que inicializa o serviço caso seja necessário e retorna um objeto que implementa a interface necessária. É comum termos Service Locators que inicializam serviços em tempo de execução, de acordo com os parâmetros passados pelo solicitante, ou através de arquivos de configuração.

Segue abaixo um UML de exemplo:



 Seguindo o nosso exemplo, temos agora a classe Loja requisitando os serviços de validação e entrega do pedido através do service locator. Os itens do diagrama estão descritos abaixo.

IValidatorService e IShippingService: 
Estas interfaces definem o contrato das classes de serviços que as implementam. As classe Loja  usa estas interfaces para reduzir o acoplamento. Com isso você pode mockar objetos que implementam essas interfaces para gerar seus casos de teste.

ValidatorService e ShippingService: 
Estas duas classes são implementações concretas das  interfaces. Estes são os tipos de objetos que serão fornecidos pelo Locator.

Locator:
A classe Locator é responsável por armazenar referências aos serviços instanciados pelo Starter e fornecer estes serviços quando forem requisitados pela classe Loja que acessa os métodos GetValidator e GetShipper. A classe tem outros membros que implementam o padrão de projeto singleton para garantir que exista sempre apenas um objeto instanciado.

ServiceStarter:
Esta classe é responsável por inicializar os objetos de serviço e armazená-los no localizador de serviços.

Loja:
A classe Loja é um consumidor das classes de serviço. Quando ela precisa acessar uma de suas dependências, recorre ao localizador de serviços.


Abaixo segue o código C# para implementação:




O exemplo acima demonstra um Service Locator estático. É possível também construir um locator que permita que você armazene qualquer tipo de serviço que você precise e faça as suas escolhas em tempo de execução. Neste caso, o Locator usa um mapa ao invés de campos para cada um dos serviços, e disponibiliza métodos genéricos para startar e retornar os serviços para o consumidor.




Service Locator vs Dependency Injection


Ambas as implementações fornecem desaclopamento da aplicação pois em ambos os casos o código da aplicação é independente da implementação concreta da interface do serviço. Como escolher entre os dois padões?

A diferença importante entre os dois padrões é como que o serviço é fornecido para a classe solicitante. Com o Service Locator a classe requisita explicitamente o serviço através de uma mensagem para o Locator. Com a injeção de dependência não existe requisição, o serviço simplesmente "aparece" na classe - daí a inversão de controle.

Inversão de controle é uma característica comum dos frameworks, mas é algo difícil de entender, principalmente em grandes projetos ou quando temos muitos desenvolvedores, e leva a problemas quando você está tentando debugar.

A principal diferença é que, com o Service Locator cada consumidor depende do localizador. O localizador pode esconder dependências para outras implementações, mas você precisa ver o localizador. Sendo assim, a decisão entre Service Locator e Dependency injection vai depender se esta dependência é um problema.

Geralmente, os desenvolvedores preferem usar injeção de dependência pois este padrão facilita o debug e os testes da aplicação, com injeção de dependência é mais fácil ver quais são as dependências do seu componente, principalmente nos casos de Contructor Injection.

Service Locator é um problema , se você está desenvolvendo um componente que será usado em aplicação de terceiros. O problema com o Service Locator é que ele esconde as dependências de uma classe, provocando erros em tempo de execução, em vez de em tempo de compilação.

 O padrão Service Locator torna o código mais difícil de manter porque você nunca sabe ao certo o nível de complexidade das alterações que você está fazendo(já que ele esconde as dependências). O compilador pode oferecer muita ajuda  aos desenvolvedores  quando usamos injeção de dependência, mas praticamente nenhuma assistência está disponível para APIs que dependem de um Service Locator.

Fontes:

http://martinfowler.com
http://blog.ploeh.dk
http://msdn.microsoft.com
http://www.blackwasp.co.uk




2 comments:

  1. Parabéns pelo artigo!
    Levy, então dificilmente vamos ver Service Locator
    em projetos maiores já que ele me parece ser mais complexo?

    ReplyDelete
    Replies
    1. Tem gente que considera o service locator um anti-pattern. O maior problema é que ele esconde as dependências, então quanto maior o projeto e a equipe de desenvolvimento pior fica para você garantir que todos conheçam as dependências sem precisar tomar um erro em runtime e ajustar o código.

      Delete