WCF – Injeção de Dependências


Serviços WCF expõem operações, que dentro delas, executam algumas tarefas e retornam o resultado para o cliente que invocou a operação. Como sabemos, o serviço em si nada mais é que uma classe, que por sua vez, pode necessitar de recursos externos (outras classes), para desempenhar as suas respectivas tarefas.

Esses recursos externos são chamados de dependências da classe, que de alguma forma, precisam ser criados para que o serviço execute da forma correta. Só que depender diretamente de uma implementação é sempre complicado, por dificultar os testes, a reusabilidade e estensibilidade. O ideal é sempre depender de uma definição, que muitas vezes é representada por uma interface, pois isso nos permite acomodar uma implementação válida, ou até mesmo algo fake, justamente para simular alguma situação, sem precisar ter todo o “overhead” de uma implementação concreta, que em alguns cenários, não se faz necessário, como é o caso dos testes.

O ideia deste artigo é apresentar os pontos de estensibilidade do WCF, e que podemos utilizá-los para interceptar e injetar as dependências, suprindo todas as suas necessidades, antes de deixar a classe que representa o serviço pronta para ser utilizada. Para isso, vamos utilizar um container de injeção de dependência criado pela Microsoft, chamado Unity, que pode ser baixado isoladamente ou através do Enterprise Library.

Para exemplificar, considere o serviço WCF abaixo. Ele precisa de um logger e um repositório, que abstrai a persistência de usuários de um banco de dados qualquer. Essas duas dependências precisam ser informadas para o correto funcionamento das operações do serviço. Note que a ideia é abastecer essas dependências através do construtor do serviço.

public class ServicoDeUsuarios : IContrato
{
    private readonly IRepositorioDeUsuarios repositorio;
    private readonly ILogger logger;

    public ServicoDeUsuarios(IRepositorioDeUsuarios repositorio, ILogger logger)
    {
        this.repositorio = repositorio;
        this.logger = logger;
    }

    public void Cadastrar(Usuario usuario)
    {
        logger.Write(string.Format(“Cadastrando o usuário {0}”, usuario.Nome));
        this.repositorio.Adicionar(usuario);
    }

    public Usuario Buscar(string nome)
    {
        logger.Write(string.Format(“Buscando o usuário {0}”, nome));
        return this.repositorio.RecuperarTodos()
                   .Where(u => u.Nome == nome)
                   .SingleOrDefault();
    }

    public Usuario[] RecuperarTodos()
    {
        logger.Write(“Recuperando todos os usuários”);
        return this.repositorio.RecuperarTodos();
    }
}

A questão principal aqui é que, em princípio, nós não controlamos como a instância da classe que representa o serviço é construída. Se expormos o serviço acima diretamente, o runtime do WCF irá disparar uma exceção do tipo InvalidOperationException, informando que o tipo do serviço especificado no ServiceHost não possui um construtor sem parâmetros.

É justamente neste ponto que precisamos interceptar a criação desta classe, para abastecer as dependências exigidas pelo serviço. Para fazer essa interceptação, temos que customizar a classe responsável pela criação de instâncias da classe do serviço, e para isso, o WCF fornece uma interface chamada IInstanceProvider, que fornece dois métodos autoexplicativos: GetInstance e ReleaseInstance.

Aqui nós poderíamos criar a instância da classe ServicoDeUsuarios, passando as implementações concretas de cada dependência, mas vamos recorrer à algum container de injeção de dependências, para que ele resolva em runtime cada uma delas. Para executar essa tarefa, o provedor de instâncias do serviço precisará receber também o container pré-configurado com as dependências e um objeto do tipo Type, que representa a classe (serviço) a ser construída.

Agora podemos reparar na implementação do método GetInstance, que recorre ao método Resolve do container do Unity. Este método recebe o tipo a ser construído, que irá verificar todas as dependências do mesmo (incluindo o construtor), e irá procurar as implementações concretas dentro do container, e se localizadas, serão construídas e informadas no construtor da classe (serviço).

public class UnityInstanceProvider : IInstanceProvider
{
    private readonly Type serviceType;
    private readonly IUnityContainer container;

    public UnityInstanceProvider(Type serviceType, IUnityContainer container)
    {
        this.serviceType = serviceType;
        this.container = container;
    }

    public object GetInstance(InstanceContext instanceContext)
    {
        return GetInstance(instanceContext, null);
    }

    public object GetInstance(InstanceContext instanceContext, Message message)
    {
        return this.container.Resolve(this.serviceType);
    }

    public void ReleaseInstance(InstanceContext instanceContext, object instance)
    {
        this.container.Teardown(instance);
    }
}

Só que a criação desta classe não é o suficiente. Precisamos acoplá-la à execução, e para isso, devemos recorrer à um behavior de serviço, que permitirá ter acesso aos dispatchers, onde poderemos informar que o criador da instância da classe que representa o serviço será realizada pelo Unity.

Para colocar isso em prática, vamos recorrer a interface IServiceBehavior, que nos permite acessar o objeto DispatchRuntime, que expõe propriedades para customizar a execução do serviço. Uma dessas propriedades é a InstanceProvider, que recebe a instância de uma classe que implementa a interface IInstanceProvider, e que no nosso cenário, será o Unity o responsável pela criação e resolução de dependências do mesmo.

public class UnityServiceBehavior : IServiceBehavior
{
    private readonly IUnityContainer container;

    public UnityServiceBehavior(IUnityContainer container)
    {
        this.container = container;
    }

    public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
    {
        foreach (ChannelDispatcher cd in serviceHostBase.ChannelDispatchers)
            foreach (EndpointDispatcher ed in cd.Endpoints)
                ed.DispatchRuntime.InstanceProvider = 
                    new UnityInstanceProvider(serviceDescription.ServiceType, container);
    }

    //Outros do Métodos Ocultados
}

Note que estamos instanciando a classe UnityInstanceProvider que criamos previamente, e no seu construtor informamos o tipo (Type) do serviço a ser construído e o container, que também é passado através do construtor para este behavior e é armazenado internamente no membro chamado container.

Depois do behavior criado, chega o momento de criarmos o host e, principalmente, o container do Unity, registrando os tipos para que ele consiga resolvê-los em runtime. Criamos um método chamado ConfigureContainer, que para o exemplo, registra os tipos envolvidos estaticamente no container utilizando a sua interface fluente, mas em um ambiente real, pode ser mais flexível configurar o container e suas dependências através do arquivo de configuração, já que o Unity dá suporte a isso.

static void Main(string[] args)
{
    using (ServiceHost host = 
        new ServiceHost(typeof(ServicoDeUsuarios), new Uri(“net.tcp://localhost:8282”)))
    {
        host.AddServiceEndpoint(typeof(IContrato), new NetTcpBinding(), “srv”);

        using (IUnityContainer container = ConfigureContainer())
        {
            host.Description.Behaviors.Add(new UnityServiceBehavior(container));
            host.Open();

            Console.WriteLine(“[ Serviço no Ar ]”);
            Console.ReadLine();
        }
    }
}

private static IUnityContainer ConfigureContainer()
{
    return new UnityContainer()
        .RegisterType(typeof(ILogger), typeof(TextLogger))
        .RegisterType(typeof(IRepositorioDeUsuarios), typeof(RepositorioDeUsuarios));
}

Para acoplar a execução, pegamos o container recém criado e devidamente configurado, e o informamos no construtor do behavior UnityServiceBehavior, que dali em diante já configura o Unity para criar e resolver as dependências da classe que representa o serviço. Feito todas essas customizações, o serviço já é capaz de ser executado, utilizando as implementações que foram supridas pelo Unity.

Conclusão: A injeção de dependência não é algo que está disponível somente com WCF. Tão importante quanto o container que você usa e de sua automatização na resolução das dependências, é a forma como você escreve o seu código, visando o baixo acoplamento entre as dependências envolvidas.

WCF – InjecaoDeDependencia.zip (12.91 kb)

Anúncios

Deixe uma resposta

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s