Implementando um notificador de eventos

Olá, pessoal

No artigo de hoje, veremos na prática a implementação de um notificador de eventos (o famoso padrão “Observer”) fazendo uso de um container de DI. A solução proposta é flexível, utilizando boas práticas de design e arquitetura de software.
____________________________

Nota: este é um artigo avançado, pois utiliza-se de padrões como Observer e Ambient Context, conceitos como Dependency Injection, CompositionRoot e Princípio da Inversão de Dependência e práticas de teste como mocks. Portanto, esses conceitos são necessários para um melhor entendimento do “porquê” das coisas. O artigo fornece diversas referências para todos eles.
____________________________

Vamos lá!

NOTIFICANDO EVENTOS

Um notificador de eventos é uma variação do conhecido design pattern Publish/Subscribe (ou Observer). Seguindo a definição do padrão à risca, o “publisher” (nosso Notificador) notifica todos os interessados (os “observers”) sempre que aquele muda seu estado.

É uma boa solução quando temos uma relação de um para muitos entre objetos e não queremos acoplá-los por se tratarem de objetos de outras camadas e para deixar o notificador mais flexível em relação aos seus “observers”.

Obs. 1: No nosso caso, o notificador não notificará mudança de estado – veremos que ele nem sequer possui estado – e sim um evento ocorrido em nossa aplicação.

Obs. 2: O evento, no caso deste artigo, será um evento de domínio, ou seja, uma situação ocorrida em nossa lógica de negócio e que precisa ser comunicada a outros contextos (que podem ser outras aplicações). No entanto, a mesma ideia mostrada no artigo pode ser utilizada para notificar outros tipos de eventos (de outras camadas).

IMPLEMENTANDO UM NOTIFICADOR BASEADO EM UM CONTAINER DE DI

Vendo a definição formal do padrão Observer no link mais acima, vocês podem notar que o “publisher/subject” expõe métodos para adicionar e remover os “observers” que deverão ser notificados. Em outras palavras, o notificador mantém a lista de “observers”.

No exemplo deste artigo, a implementação adotada faz uso de um container de Dependency Injection para localizar seus “observers” e poder notificá-los, dispensando assim que o notificador conheça a lista de seus “observers”.

Vejamos como fica o código:

// o "publisher/subject"
public class NotificadorDeEventosDeDominio
{
    public static void NotificarSobre<T>(T eventoDeDominio) where T : IEventoDeDominio
    {
         foreach (var enderecador in this.container.GetAllInstances<IEnderecadorDeEvento<T>>())
             enderecador.Enderecar(eventoDeDominio);
    }
}

// interface de marcação para os eventos de domínio
public interface IEventoDeDominio { }

// o "subscriber/observer"
public interface IEnderecadorDeEvento<T> where T : IEventoDeDominio
{
    void Enderecar(T eventoDeDominio);
}

Algumas observações sobre o código:

  • O notificador não conhece seus “observers”. Ao invés disso, ele pede ao container de DI para localizá-los, ou seja, estamos usando o container como um Service Locator. Muita cautela com esse tipo de prática. Não espalhe o container por toda sua aplicação, pois além da dependência concreta em relação ao container, você estará escondendo todas as dependências que suas classes possuem.
  • O container de DI é injetado via construtor e armazenado em um field usado no método acima (this.container).
  • Qualquer container de DI de sua preferência pode ser utilizado.
  • IEventoDeDominio é apenas uma interface de marcação para restringirmos o parâmetro do método e os “observers” para aceitarem somente objetos que sejam eventos de domínio.
  • Tanto o notificador como as interfaces usadas estão na camada de domínio, pois estão diretamente relacionados.

Um exemplo de uso seria o seguinte:

public class Despesa
{
    public void Baixar()
    {
        // baixa da despesa ....

        // notificando evento
        NotificadorDeEventosDeDominio
            .NotificarSobre(new DespesaPaga(idDespesa, valorPago));
    }
}

No exemplo acima, uma entidade do domínio, após realizar sua operação de baixa, notifica aos interessados a ocorrência do evento DespesaPaga. Um “observer” que receberia essa notificação seria qualquer objeto que implemente IEnderecadorDeDominio<DespesaPaga>.

 ALGUNS PROBLEMAS

A solução mostrada apresenta alguns problemas:

1. Como dito antes, o NotificadorDeEventosDeDominio encontra-se na camada de domínio, juntamente com a classe Despesa e demais classes que modelam o negócio. Como usamos o container de DI na implementação do notificador, somos obrigados a manter uma referência do container no domínio, ou seja, o domínio conhece um detalhe técnico (o container).

De um ponto de vista arquitetural, devemos deixar o domínio o mais ignorante possível de detalhes técnicos (mais aqui).

Portanto, precisamos remover essa dependência do domínio para o container de DI!

2. Testabilidade! Conseguimos garantir que o estado do objeto Despesa foi alterado na baixa mas não conseguimos testar que a notificação realmente foi chamada, uma vez que fazemos isso por meio de um método estático do notificador.

VERSÃO MELHORADA

Para sanar os problemas acima, precisamos abstrair o notificador de eventos, aplicando o Princípio da Inversão de Dependência.

No entanto, quero continuar usando o notificador como um ambient context, ou seja, não quero poluir a interface das minhas entidades de domínio injetando o notificador via construtor, método ou propriedade.

Vejamos como fica a nova versão, começando pela abstração:

public abstract class NotificadorDeEventosDeDominio
{
    private static NotificadorDeEventosDeDominio _notificadorCorrente;

    public static NotificadorDeEventosDeDominio NotificadorCorrente
    {
        get { return _notificadorCorrente; }
        set
        {
            if (value == null)
               throw new ArgumentNullException("value");

            _notificadorCorrente = value;
        }
    }

    public abstract void NotificarSobre<T>(T eventoDeDominio) where T : IEventoDeDominio;
}

O notificador concreto é praticamente o anterior, porém herdando da abstração acima:

public class NotificadorBaseadoEmContainerDeDi : NotificadorDeEventosDeDominio
{
    // construtor com injecao do container omitido

    public override void NotificarSobre<T>(T eventoDeDominio)
    {
        foreach (var enderecador in this.container.GetAllInstances<IEnderecadorDeEvento<T>>())
           enderecador.Enderecar(eventoDeDominio);
    }
}

Agora, os objetos de domínio passam a depender da abstração e não mais do notificador concreto:

public class Despesa
{
    public void Baixar()
    {
        // baixa da despesa ...

        // notificando evento
        NotificadorDeEventosDeDominio
             .NotificadorCorrente.NotificarSobre(new DespesaPaga(idDespesa, valorPago));
    }
}

Sendo assim, podemos mover o notificador concreto do domínio para o assembly que faz uso do container de DI para subir a aplicação. Com isso, o domínio passa a não conhecer mais o container de DI.

GARANTINDO A TESTABILIDADE

Com a nova versão, podemos testar melhor os objetos de domínio que fazem uso do notificador.

Vejamos como fica o teste da entidade Despesa, que verifica se o notificador foi realmente chamado (e com o evento correto):

[Test]
public void Deve_notificar_sobre_baixa_da_despesa()
{
    var notificadorDeEventosDeDominioMock = new Mock<NotificadorDeEventosDeDominio>();
    NotificadorDeEventosDeDominio.NotificadorCorrente = notificadorDeEventosDeDominioMock.Object;

    var despesa = new Despesa();

    despesa.Baixar();

    var despesaPaga = (new DespesaPaga(100, 100.50)).AsSource().OfLikeness<DespesaPaga>().CreateProxy();
    notificadorDeEventosDeDominioMock.Verify(mock => mock.NotificarSobre(despesaPaga), Times.Once);
}

Vejam que, para fins de teste, configuramos o NotificadorCorrente com um mock ao invés de usarmos o notificador concreto e fazemos a asserção do teste verificando que o método NotificarSobre() foi realmente chamado com o evento correto e somente uma vez.

(Obs.: Neste teste, utilizei a lib SemanticComparison para criar o objeto esperado como parâmetro do método. Para saber mais sobre ela, leia meu post anterior sobre comparação semântica.)

USANDO O NOTIFICADOR CONCRETO

Ok, usamos um mock como NotificadorCorrente nos testes de unidade. E no código de produção? Onde usamos o notificador concreto?

A resposta é simples: configuramos o NotificadorCorrente com o notificador concreto no start da aplicação (*) e nunca mais mexemos com ele:

var container = ConfigurarContainer();
NotificadorDeEventosDeDominio.NotificadorCorrente =
           new NotificadorBaseadoEmContainerDeDi(container);

(*) Esse local é chamado de CompositionRoot e falei sobre ele na minha série sobre Dependency Injection.

Relembrando que o notificador concreto fica junto do container no mesmo assembly, que pode ser o assembly referente à camada de apresentação ou um específico para o CompositionRoot (o que eu acho mais adequado em projetos reais).

E OS OBSERVERS CONCRETOS?

Os observers concretos – aqueles que implementam IEnderecadorDeEvento<T> – ficam na camada correspondente ao seu propósito. Se o observer, por exemplo, informa alguma coisa para a view ele ficará na camada de apresentação. Outro observer que trata o evento comunicando-se com algum serviço externo (e-mail, API REST, messaging) certamente ficará na camada de infra-estrutura. E assim por diante.

CONCLUINDO

Vimos, passo-a-passo, como criar um notificador de eventos (padrão Observer) usando um container de DI em sua implementação.

A solução mostrada apresenta baixo acoplamento entre os módulos, o que garante testabilidade dos clientes do notificador e atende a regra de dependências entre camadas (sempre da mais externa para a mais interna).

Foi um artigo avançado, que exigiu conhecimento de diversos padrões de design e arquitetura de software, mas espero ter conseguido passar a ideia.

Para reforçar o entendimento, disponibilizei no meu github o código do artigo juntamente com um pequeno exemplo funcional de uma app console, que faz uso de tudo que foi visto aqui.

Espero que tenham gostado. Comentários, dúvidas, críticas? Só escrever!

[]s e até a próxima!

Anúncios

4 comentários em “Implementando um notificador de eventos

  1. Parabéns pelo post, Robson! Eu gosto muito do Domain Events. Geralmente eu coloco a implemntação dele num Shared Kernel e Interfaceio o container DI. Vou experimentar essa abordagem no próximo projeto.

    1. Olá, Yan
      Obrigado.
      Então, já fiz isso mas eliminei essa interface pelo seguinte:
      Não acho que faça sentido uma interface pra um container de DI (dado que vc so utilizará um e normalmente em um só local da app – o CompositionRoot). Além disso, voce teria que coloca-la no dominio (que faz ainda menos sentido pra mim) ou numa camada acima (e aí o dominio teria que fazer referencia a alguem mais acima, mesmo problema de depender do container, embora não seja o concreto).
      Por esses motivos, matei a interface e uso o container concreto (apenas no CompositionRoot, em duas ou tres classes).
      IMHO!
      []s

      (AH..deletei seu outro comentário que era praticamente uma cópia desse :)).

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