Padrões de DI: Ambient Context

Olá, pessoal

Enfim chegamos ao último artigo da série “Padrões de DI”, trazendo o padrão conhecido por Ambient Context.

De todos os padrões mostrados, este é o mais específico e o único que ainda não implementei em um projeto real, embora já o considere principalmente pela questão da testabilidade, mostrada ao final do artigo.

Boa leitura!

1. O QUE É

Ambient Context é um padrão de DI que consiste em definir um contexto que esteja disponível para todos os módulos da aplicação.

É um padrão que se difere bastante dos outros apresentados nesta série porque o contexto é uma dependência usada diretamente pelos seus clientes, sem que a mesma seja injetada.

(Existem dezenas de exemplos de implementação desse padrão no mundo .Net, como as classes CultureInfo, HttpContext e tantas outras que terminem com a palavra “Context”.)

2. QUANDO USAR

Esse padrão é mais adequado quando desejamos disponibilizar uma informação para várias camadas da aplicação sem que tenhamos que poluir toda a API passando a dependência por parâmetro.

Estamos falando aqui de uma dependência que seja um cross-cutting concern ou “aspecto transversal”, ou seja, aquele que atravessa diversas camadas da aplicação. Entre alguns aspectos transversais estão: data/hora atuais, globalização e tratamento de erros.

3. COMO IMPLEMENTAR

Para esclarecer melhor o padrão, vamos mostrar um exemplo de implementação de um contexto que irá fornecer a data/hora atuais. Este exemplo foi retirado do livro “Dependency Injection in .Net”.

public abstract class TimeProvider
{
    private static TimeProvider current;

    static TimeProvider()
    {
        TimeProvider.current = new DefaultTimeProvider();
    }

    public abstract DateTime UtcNow { get; }

    public static TimeProvider Current
    {
        get { return TimeProvider.current; }
        set
        {
            if (value == null)
                throw new ArgumentNullException("value");
            TimeProvider.current = value;
        }
    }

    public static void ResetToDefault()
    {
        TimeProvider.current = new DefaultTimeProvider();
    }
}

public class DefaultTimeProvider : TimeProvider
{
    public override DateTime UtcNow
    {
        get { return DateTime.UtcNow; }
    }
}

Vamos a alguns detalhes do código:

– O contexto (TimeProvider) é uma abstração para algo que possa nos fornecer a data atual.
–  A propriedade Current é a propriedade responsável por devolver o TimeProvider atual e por setar o provider que desejamos utilizar.
– DefaultTimeProvider é uma implementação de TimeProvider que é utilizada neste como um local default para que Current não seja null. Notem que ela simplesmente implementa TimeProvider com os métodos de data disponíveis na classe DateTime.

Devemos agora, sempre que precisarmos da data atual, chamar TimeProvider.Current.UtcNow (poderíamos implementar também o .Now e o .Today, por exemplo).

_____________________________________________________
Nota: caso o aspecto transversal não seja uma query, ou seja, não retorna nenhum valor (como, por exemplo, logar erros), é uma boa ideia utilizar interception.
_____________________________________________________

4. UM POUCO SOBRE TESTABILIDADE

O baixo acoplamento da solução acima nos permite a fácil substituição do provider por algum outro (como um serviço web que forneça data/hora). Mesmo que isso não seja uma necessidade em um código de produção, ainda é algo bastante útil para garantirmos testabilidade, uma das vantagens  de DI mencionadas na introdução desta série.

Imagine que precisamos testar uma classe que possui uma regra que depende de uma data específica (como o primeiro dia do mês):

public class ProcessadorDePedido
{
    private readonly INotificadorDeEventos _notificador;

    public ProcessadorDePedido(INotificadorDeEventos notificador)
    {
        if (notificador == null) throw new ArgumentNullException("notificador");
        _notificador = notificador;
    }

    public void Processar()
    {
        // processando pedido...
        // so deve notificar no dia primeiro de cada mes
        if (TimeProvider.Current.Today.Day == 1)
            _notificador.Notificar();
    }
}

O teste seria algo como abaixo:

[Test]
public void Deve_notificar_somente_no_dia_primeiro()
{
    var notificadorMock = new Mock<INotificadorDeEventos>();
    var processador = new ProcessadorDePedido(notificadorMock.Object);

    processador.Processar();

    notificadorMock.Verify(n => n.Notificar());
}

Percebam que o teste não é determinístico, uma vez que DateTime.Today (usado na implementação default de TimeProvider) é uma dependência instável. Isso quer dizer que esse teste só irá passar no dia primeiro de cada mês, a menos que você altere a data do seu computador todo dia para o dia primeiro! Problema!

O teste corrigido ficaria assim:

[Test]
public void Deve_notificar_somente_no_dia_primeiro()
{
    var primeiroDiaDoMes = new DateTime(2014, 10, 1);
    var timeProviderStub = new Mock<TimeProvider>();
    timeProviderStub.Setup(t => t.Today).Returns(primeiroDiaDoMes);
    // aqui substituímos o provider pelo nosso stub
    TimeProvider.Current = timeProviderStub.Object;

    var notificadorMock = new Mock<INotificadorDeEventos>();
    var processador = new ProcessadorDePedido(notificadorMock.Object);

    processador.Processar();

    notificadorMock.Verify(n => n.Notificar());
}

[TearDown]
public void TearDown()
{
    // retornando à implementação default (DefaultTimeProvider)
    // após a execução de cada teste
    TimeProvider.ResetToDefault();
}

Vejam que agora substituímos a implementação default por um stub. Com isso, “forçamos” que a data “atual” seja a data que quisermos (no caso, 01/10/2014), independente da data atual verdadeira, fazendo com que nosso teste agora seja determinístico.

Notem também que chamamos TimeProvider.ResetToDefault() ao final de cada teste, voltando assim ao DefaultTimeProvider.

5. CONCLUINDO

Neste artigo, vimos o último padrão de DI da série, conhecido por Ambient Context. Bem mais específico que os outros padrões, ele é mais adequado para resolvermos aspectos transversais da aplicação, em especial, aqueles dos quais o consumidor precisa obter algum valor.

Com isso, finalizamos esta série, apresentando Dependency Injection e seus principais conceitos com relação à composição de objetos.

O resultado das práticas de DI é o baixo acoplamento, que nos oferece várias vantagens como late binding, extensibilidade, manutenibilidade e testabilidade.

Esta série não cobriu outros dois aspectos importantes de Dependency Injection: interception e object lifetime (ciclo de vida dos objetos). Se você quiser saber mais sobre esses conceitos e também aprofundar mais os conceitos apresentados nesta série, recomendo fortemente a leitura do ótimo livro “Dependency Injection in .Net” do Mark Seemann.

Espero que tenham gostado.
Aguardo o feedback de vocês!

Até a próxima!

obs.: os links de todos os artigos da série estão disponíveis <aqui>.

Anúncios

Participe! Vamos trocar uma ideia sobre desenvolvimento de software!

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