Eu tinha ficado de falar de Domain Events por aqui, já que deixei um exemplo rápido no post de princípio aberto fechado. Chegou a hora.

Entrei em contato com a idéia no blog do Udi Dahan, que é um forte defensor do DDD. Lá ele fez uma série de três posts (um, dois, três). De lá pra cá, tive a chance de implementá-lo algumas vezes com alguns clientes, e os resultados foram muito interessantes. Em alguns casos os eventos eram parte do processo de atualização do domínio, em outros toda comunicação da camada de aplicação com o domínio se passava com eventos de domínio.

Os posts do Udi são muito bons, e recomendo que você os leia. Ainda assim, acho que eles ficaram muito extensos, então vou pegar mais leve, e mostrar um exemplo mais direto.

A idéia é que você tenha eventos que façam sentido para o domínio, os eventos são coisas interessantes que aconteceram no seu domínio. Vamos supor que você esteja fazendo um sistema de locadora, alguns eventos poderiam ser “filme locado”, “filme devolvido”, “cliente cadastrado”, e coisas do tipo.

O conceito de eventos de domínio ajuda a aplicar a idéia de Command and Query Separation (CQS). Com eventos de domínio, suas operações de escrita ficam todas encapsuladas em comandos, na forma de eventos (fugindo do conceito do Command Pattern, e entendendo um comando como uma mensagem).

E como funciona? Muito simples. Vou usar aqui Domain Events em conjunto com um contêiner de injeção de dependência (mais sobre DI neste blog aqui, quando discuto DI x Service Locator, e aqui, um webcast sobre o assunto). Mr. Dahan faz também com um modelo de registro, que é uma opção interessante e menos “mágica”.

O básico de tudo é uma classe abstrata/interface que abstraia a idéia de evento:

public interface IDomainEvent
{
}

A partir daí você cria classes que representam eventos reais. Aqui vocês vêem o evento de filme alugado:

public class FilmeAlugado : IDomainEvent
{
    public FilmeAlugado()
    {
        Quando = DateTime.Now;
    }
    public int IdFilme { get; set; }
    public int IdCliente { get; set; }
    public DateTime Quando { get; set; }
}

Estas classes são objetos de valor, são representadas pelos seus atributos, e não tem nenhum id. Poderiam ser structs numa boa.

Para lançar um evento, você utiliza uma classe estática que representa um ponto comum de lançamento de eventos:

public static class DomainEvents
{
    public static void Raise<T>(T evento) where T : IDomainEvent
    {
        var registry = ConfiguracaoRegistry.RegistryConfigurado;
        var enderecadoresDeEvento = registry.ResolverTodas<IEnderecadorDeEvento<T>>();

        foreach (var enderecadorDeEvento in enderecadoresDeEvento)
        {
            enderecadorDeEvento.EnderecarEvento(evento);
        }
    }
}

Essa classe chama os endereçadores de eventos interessados no evento lançado, e chama-os, um a um. Cada um faz o que precisar, de acordo com as necessidades da aplicação.

Notem que esta classe está puxando os endereçadores de eventos de um registry, que no meu caso utiliza um contêiner de DI – geralmente o Unity, para obter todos os endereçadores de eventos. Meu contêiner geralmente observa alguns assemblies, puxa todas as classes, descobre suas interfaces, e as registra automaticamente. Assim, basta criar uma classe que representa um endereçador de evento que tudo já funciona automaticamente, ligado automaticamente quando a aplicação inicia.

Tenho uma interface ou classe abstrata que representa um endereçador de eventos. Algo assim:

public interface IEnderecadorDeEvento<T> where T : IDomainEvent 
{
    void EnderecarEvento(T args);
}

Basta implementar então os endereçadores. Vamos supor que eu queira mandar um e-mail sempre que um filme é alugado, e também gravar essa locação no banco de dados. Basta implementar dois endereçadores:

public class GravaNoBancoQuandoUmFilmeEhAlugado : IEnderecadorDeEvento<FilmeAlugado>
{
    public void EnderecarEvento(FilmeAlugado filmeAlugado)
    {
        Console.WriteLine(
            "Aluguel gravado no banco para o filme id {0} e cliente id {1}!", 
            filmeAlugado.IdFilme, 
            filmeAlugado.IdCliente);
    }
}

public class MandaUmaMensagemParaOClienteAgradecendoAPreferencia : IEnderecadorDeEvento<FilmeAlugado>
{
    public void EnderecarEvento(FilmeAlugado filmeAlugado)
    {
        Console.WriteLine("Email enviado!");
    }
}

Eles vão ser pegos automaticamente no registro do contêiner de DI, e ao chamar o código da classe DomainEvents, eles vão ser executados. Eles poderiam ter dependências complexas, que seriam automaticamente resolvidas pelo Unity, tudo com inversão de controle, claro, pra facilitar um possível polimorfismo, principalmente na hora de testar.

Para usar você cria um evento, e manda ele pro ponto de contato comum, a classe DomainEvents. Aqui está um exemplo simples:

class Program
{
    static void Main(string[] args)
    {

        //setup app
        ConfiguracaoRegistry.RegistryConfigurado = new Registry();

        //preparando o evento
        const int idCliente = 1;
        const int idFilme = 1;
        var alugaramUmFilme = new FilmeAlugado
                                  {
                                      IdCliente = idCliente,
                                      IdFilme = idFilme,
                                      Quando = DateTime.Now
                                  };
        //lançando o evento
        DomainEvents.Raise(alugaramUmFilme);

        Console.ReadLine();
    }
}

O resultado é simples:

Pra testar isso tudo seria extremamente simples. A classe Domain Events não dá para mockar, já que é estática, mas como ela depende do meu IRegistry, tudo fica simples. Além disso, meu maior ponto de manutenção seriam os eventos e seus endereçadores, muito fáceis de testar, por serem simples, objetivos e pequenos. Nada como atender ao princípio da responsabilidade única, não é? Esse tipo de arquitetura é simples de testar e evoluir também por privilegiar o desacoplamento.

Uma aplicação muito comum para esse tipo de modelo é o log. Se você precisar incluir log em todas as operações da sua aplicação é só criar um endereçador de log para cada evento, dá pra fazer de maneira muito simples, com reflexão e um pouquinho de magia negra.

Outro que usa muito esse tipo de modelo é o Greg Young, MVP canadense. Ele grava diretamente os eventos no banco de dados, em um banco de dados de documentos, e condensa os dados de tempo em tempo para o contexto de leitura. Radical, mas muito interessante para cenários com muitas escritas, e alta necessidade de performance, onde você pode ter os dados eventualmente consistentes.

E é isso. Simples assim. É óbvio que no mundo real questões como eventos que devem ser tratados transacionalmente, processamento assíncrono dos eventos, entre outros, seriam tratadas. O maior ponto de alteração seria a classe DomainEvents, sendo que as outras teriam pequenas mudanças. Em dois clientes os eventos foram serializados com WCF e eram criados no Silverlight, em outra camada física. Funcionou perfeitamente.

Além disso, essa estrutura pode ser usada também para outros tipos de eventos, que não sejam de domínio, obviamente separada da estrutura de eventos de domínio.

Espero que gostem da idéia. Não é pra ser usada em todo lugar, mas sem dúvida é muito útil tê-la na caixa de ferramentas.

Giovanni Bassi

Arquiteto e desenvolvedor, agilista, escalador, provocador. É fundador e CSA da Lambda3. Programa porque gosta. Acredita que pessoas autogerenciadas funcionam melhor e por acreditar que heterarquia é mais eficiente que hierarquia. Foi reconhecido Microsoft MVP há mais de dez anos, dos mais de vinte que atua no mercado. Já palestrou sobre .NET, Rust, microsserviços, JavaScript, TypeScript, Ruby, Node.js, Frontend e Backend, Agile, etc, no Brasil, e no exterior. Liderou grupos de usuários em assuntos como arquitetura de software, Docker, e .NET.