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.


Postado na(s) categoria(s) Arquitetura pelo Giovanni Bassi em 30 de novembro de 2009 às 00:57 | Tags: , , ,

Comentários


Brazil Alessandro
novembro 30. 2009 15:35
Alessandro
Giovanni,
excelente post. Eu estava aguardando por ele.

Valeu mesmo.

Só fiquei com uma dúvida nos testes. Como não dá pra mockar a DomainEvents.Rise, o meu Registry de teste vai ter que mockar o IEnderecadorDeEvento<T> e configurar a chamada EnderecarEvento() para retornar NULLO por exemplo, ou uma lambda para gravar o estado em uma var local para futura comparação?
Seria isto?

no site


Brazil Alessandro
novembro 30. 2009 15:39
Alessandro
Apenas um adendo ao comentário/dúvida anterior.
Neste caso estou querendo testar o método Main da class Program

no site


novembro 30. 2009 22:01
Giovanni Bassi
Alessandro, você pode ter um registry stub todo preparado para devolver mocks de endereçadores de eventos, a fim de testar o método Raise. Aí é só verificar se os mocks foram chamados no final do testes.

O método Main é só um exemplo, não teria porque testar ele.

http://unplugged.giggio.net/http://unplugged.giggio.net/


Brazil Marco Souza
dezembro 1. 2009 03:40
Marco Souza
Parabens Giovanni muito show esse post!
vai postar o código para o pessoal?

no site


Brazil Marco Souza
dezembro 1. 2009 03:40
Marco Souza
Parabens Giovanni muito show esse post!
vai postar o código para o pessoal?

no site


Brazil Marco Souza
dezembro 1. 2009 03:40
Marco Souza
Parabens Giovanni muito show esse post!
vai postar o código para o pessoal?

no site


dezembro 1. 2009 09:29
Israel Aece
Boas Giovanni,

Em alguns cenários que tenho (focado mais na infraestrutura do que no domínio), uso de uma forma um pouco diferente da qual você postou aqui. A diferença para o meu código é que crio geralmente uma espécie de "dispatcher", que é o responsável por registrar, e mais tarde, disparar os "eventos".

Mantenho o "dispatcher" como sendo o "repositório" dos meus eventos, onde posso registrar manualmente eventos, ou utilizar alguma library de DI para carregar dinamicamente.

public interface IEventDispatcher
{
    void Dispatch<TEventData>(TEventData args) where TEventData : IEventData;

    IAsyncResult[] DispatchAsync<TEventData>(TEventData args, AsyncCallback callback, object data) where TEventData : IEventData;
}

Com isso, a minha classe de DomainEvents (exemplo), tem apenas os métodos para disparar os eventos, que delegará isso para o dispatcher corrente.

public static class DomainEvents
{
    public static IEventDispatcher Dispatcher { get; set; }

    public static void Raise<TEventData>(TEventData args) where TEventData : IEventData
    {
        Dispatcher.Dispatch(args);
    }

    public static IAsyncResult[] RaiseAsync<TEventData>(TEventData args, AsyncCallback callback, object data) where TEventData : IEventData
    {
        return Dispatcher.DispatchAsync(args, callback, data);
    }
}

Para testar, faço:

ActionEventDispatcher aed = new ActionEventDispatcher();
aed.Register<FilmeAlugado>(GravaNoBancoQuandoUmFilmeEhAlugado);
aed.Register<FilmeAlugado>(MandaUmaMensagemParaOClienteAgradecendoAPreferencia);

Ou recorrendo a sua técnica, que é criar um endereçador de eventos através de uma interface, implementá-las nas classes que correspondem aos eventos e, finalmente, criar uma implementação de IEventDispatcher, que recorre à alguma library de DI para carregar as respectivas implementações.

Depois disso, tudo o que faço é:

DomainEvents.Dispatcher = aed;
DomainEvents.Raise(new FilmeAlugado() { Nome = "007" });

Ou de forma assíncrona:

IAsyncResult[] status =
    DomainEvents.RaiseAsync(
        new FilmeAlugado() { Nome = "007" },
        (r) => Console.WriteLine("Fim"),
        null);

http://www.israelaece.com/http://www.israelaece.com/


Brazil Alessandro
dezembro 1. 2009 10:14
Alessandro
Neste cenário, eu não entendo muito bem, como eu teria um registry stub.
Como fazer para que quando eu for rodar um teste em um SUT que chama o DomainEvents.Rise(...), como configurar este método (Rise) para retornar o registry stub quando ele chavar o registry.resolvertodos<...> e não o registry de produção? Tenho que ter um parametro no configurador do registry que devo mudar toda vez que for rodar o teste?

Teria algum exemplo de como testar classes státicas com mocks?

Muito grato...

no site


Brazil Michel
dezembro 10. 2009 22:26
Michel
Giovanni gostei bastante do artigo, tenho uma duvida:

Estou desenvolvendo uma aplicacao utilizando DDD , tenho um evento que é a Criação de Usuario, eu utilizo uma Factory para a construcao da instancia do usuario, persisto no banco e envio um email com a senha, a minha pergunta é se devo criar 3 endereçadores (construir,persistir,enviar email) para o evento "CriarUsuario" ou se  primeiro construo ele pela factory e notifico o evento contendo somente 2 endereçadores (persistir, enviar email)

Vlw

Michel

no site

Comentar


(Vai mostrar seu Gravatar)

  Country flag

biuquote
  • Comentário
  • Pré-visualização
Loading



Quem é Giovanni Bassi

Giovanni Bassi Sou uma pessoa apaixonada por tecnologia e especificamente por .Net. Sou consultor independente especialista em .Net, focado em arquitetura e melhores práticas. Tenho dezenas de artigos publicados na .Net Magazine, revista da qual sou editor técnico. Ministro palestras e cursos de vez em quando, e quando dá tempo eu respiro um pouco. Mais detalhes nesta página.

Busca

Selos

Eu vou ao TechEd Brasil 2010, e você?

MVP

MCPD

MCSD

.Net Magazine

Abaixo ao if!

Calendário

«  julho 2010  »
seteququsedo
2829301234
567891011
12131415161718
19202122232425
2627282930311
2345678
Ver detalhamento de posts no calendário

Blogs interessantes

    OPMLDownload OPML file

    Postagens recentes

    Comentários recentes

    Disclaimer / Aviso
    As opiniões colocadas neste blog são minhas e pessoais e não expressam necessariamente as opiniões de meus empregadores, pareceiros e amigos. Da mesma forma, os comentários feitos por leitores do blog não expressam a minha opinião.

    © Copyright 2010 .Net Unplugged
    Log in