Introdução ao MediatR

Ao trabalhar com CQRS, o primeiro passo é criar a infraestrutura de mensagens para suportar os comandos, consultas e eventos que a aplicação tenha. Somente a partir daí é que passamos a nos preocupar com o código referente as atividades quais nossa aplicação irá desempenhar. Para evitar a criação de toda estra estrutura, podemos recorrer à uma biblioteca chamada MediatR, que já disponibiliza esta estrutura para tráfego das mensagens, incluindo a possibilidade de notificações (eventos).

O vídeo abaixo explora essa biblioteca, com exemplos simples e práticos de sua utilização. O cenário é uma loja de comércio eletrônico extremamente simples, mas que faz uso de vários recursos que a biblioteca possui, passando pelas mensagens, tratadores, notificações e customização do pipeline de execução. O código usado no vídeo está disponível neste endereço.

Validação de Comandos

No artigo/vídeo anterior eu comentei como podemos expor comandos através do protocolo HTTP. O comando é serializado em JSON e enviado para o servidor que tenta encontrar a respectiva classe que representa o comando e, consequentemente, executa o seu respectivo handler para tratar a requisição. E como sabemos, os comandos são classes extremamente simples, contendo apenas poucas propriedades que parametrizam a tarefa a ser executada.

Só que os comandos podem chegar até o serviço sem as informações mínimas para a sua execução. Apesar de estarmos falando em comunicação entre sistemas, isso se aproxima muito do que temos em uma aplicação com interface com o usuário: apesar de termos uma validação sendo realizada no formulário, é uma boa prática reaplicar as validações quando ele é postado, já que do lado do cliente, a validação pode ser desabilitada, como é o caso de aplicações Web, que recorrem à JavaScript, que com algum esforço, podemos inativar no navegador e postar a mensagem sem devidas validações. A validação do lado do cliente serve para dar uma melhor experiência para o usuário, dando o feedback se alguma informação estiver inválida antes da requisição partir.

Isso é muitas vezes chamadas de validação superficial, e ela não substitui aquela que é feita pelo domínio, que por sua vez, é a qual garante a consistência da aplicação. Essa validação nos comandos devem garantir os parâmetros obrigatórios, que itens não sejam nulos ou iguais a zero, formatação de campos (e-mail, URLs, etc.), etc.

Agora, se a ideia é expor os comandos através de HTTP, temos também que tentar integrar as validações com os recursos que o protocolo fornece. Só que antes disso, precisamos incorporar nas classes dos comandos a estrutura da validação. Aqui existem diversas formas de se fazer, onde a mais comum, é declarar na classe base que representa o comando um método virtual que pode ser sobrescrito nas derivadas para customizar a validação de cada um deles.

public abstract class Command
{
    public virtual bool Validate() { }
}

A implementação do método pode ser feito de diversas formas, e uma delas sendo a forma “manual”, utilizando apenas recursos (tipos) da linguagem e criando classes que representam os erros. O método Validar retorna apenas um indicativo (bool) se a validação sucedeu ou não. Mas o que falhou? E vamos optar por retornar apenas quando a primeira falha for encontrada ou validaremos todo o objeto e retornaremos todos os problemas encontrados? Neste caso, ao invés do método retornar apenas um bool, teremos que customizar o tipo de retorno, com uma classe que totaliza todos os problemas encontrados.

public abstract class Command
{
    public virtual ValidationResult Validate()
    {
        return ValidationResult.Empty;
    }
}

public class ValidationResult
{
    public static ValidationResult Empty = new ValidationResult();
    private readonly IList<Error> errors = new List<Error>();

    public void Add(Error error) => this.errors.Add(error);

    public bool IsValid
    {
        get
        {
            return !this.errors.Any();
        }
    }

    public IEnumerable<Error> Errors
    {
        get
        {
            return this.errors;
        }
    }

    public class Error
    {
        public string Message { get; set; }
    }
}

A outra forma seria recorrer à algum framework de validação, como o FluentValidation, que já fornece diversos recursos para facilitar a validação. Depois disso, precisamos de alguma forma integrar com o HTTP, e a opção que temos é retornar o erro 400 (BadRequest), que serve justamente para representar alguma falha encontrada na postagem da requisição (onde o seu conteúdo não é válido). A resposta deve conter os dados da validação, para que o cliente consiga entender o problema e corrigir para as futuras requisições.

Por fim, ao receber a requisição, encontrar o respectivo comando e deserializar a mensagem, invocamos o método Validate e avaliamos a propriedade IsValid. Caso seja negativo, então configuramos o código de retorno do HTTP para 400 (BadRequest) e serializamos o resultado da validação  em JSON para ser devolvido ao cliente; caso o resultado da validação seja positivo, então a requisição é encaminhada para ser normalmente processada. O código na integra está disponível neste endereço.

var command = context.Request.ReadAsCommand(bus.Handlers[action]);
var validateResult = command.Validate();

if (!validateResult.IsValid)
{
    context.Response.StatusCode = 400;
    await context.Response.WriteAsync(validateResult.ToJson());
    return;
}

await bus.Send(command);

É importante dizer que isso que falamos aqui é uma validação superficial, e não deve ser utilizada sozinha, é só um complemento. O comando essencialmente não retorna resultado, mas a validação dele é algo que ocorre antes de sua execução. Agora se algo der errado durante a execução dele (pois está “tocando” o domínio, que pode estar protegido com o disparo de exceções), é muito provável que seja alguma regra de negócio que tenha sido violada, e para isso, há outras estratégias de notificação para reportar ao usuário ou à outras aplicações destas falhas, como o uso de eventos.

Web Commands

Quando falamos de CQRS, temos as consultas e os comandos. Esses comandos podem ser consumidos diretamente por aplicações, mas como expor publicamente para os clientes? Uma opção comum nos dias de hoje é utilizar o protocolo HTTP, facilitando assim o consumo pelas mais diversas linguagens e tecnologias. E quais as opções no .NET para fazermos esta exposição?

O ASP.NET é um candidato para isso, já que em sua versão core está modularizado e flexível, e assim podemos otimizar o pipeline de execução com somente aquilo que seja de fato necessário para executar os comandos. A finalidade do vídeo abaixo é exibir uma forma alternativa para incorporar os comandos do domínio no ASP.NET, utilizando o mínimo de recursos possível do hosting. Link para o código do exemplo.

Repositórios Plug-and-Play

No artigo anterior falamos sobre o uso de JOINs para extrair as informações que armazenamos em uma base de dados relacional. Comentamos também das dificuldades que temos quando precisamos manter os dados originais do momento da execução daquela tarefa (exemplo: o nome do cliente no momento da emissão da nota fiscal). No fim deste mesmo artigo, falamos em segregação da base de dados, a fim de ter estruturas distintas e otimizadas para leitura e gravação.

Dando continuidade no artigo, vamos falar sobre a base de dados de escrita. Quando trabalhamos com um domínio rico, baseado em DDD, é comum nossas classes possuírem diversas propriedades, que representam suas características, bem como funcionalidades e comportamentos que refletem, em geral, as mesmas funções do mundo real. Uma vez que este domínio está desenvolvido, chega o momento de decidir como vamos persisti-lo fisicamente. E a resposta na maioria das vezes, a resposta sempre é o uso de uma base de dados relacional, tais como: SQL Server, Oracle, MySQL, etc.

O problema entre o nosso domínio e uma base de dados relacional é a impedância. Isso quer dizer, o atrito que temos ao mapear classes e propriedades para tabelas, colunas e linhas. Trata-se de um trabalho árduo, e muitas vezes recorremos à mapeadores (ORM) para auxiliar nesta atividade, e que apesar de fazerem um grande trabalho por convenção, há muito o que se customizar em um ambiente mais complexo. Isso sem falar sobre evolução do domínio, onde a criação de novos tipos, adição e remoção de novas propriedades que precisam ser armazenadas, refletirá no mapeamento e que deverá ser ajustado.

Por essa flexibilidade na evolução e diversos outros pontos positivos, é que bases NoSQL estão cada vez mais ganhando espaço. E aqui, vamos tentar fazer a migração de uma aplicação que faz uso de NHibernate para RavenDB. Como estamos trabalhando orientado ao domínio, nós já temos interfaces em nosso código que definem a estrutura dos repositórios que a mesma utiliza. Por padrão, temos apenas a implementação para o NHibernate:

public class RepositorioDeClientes : IRepositorioDeClientes
{
    private readonly ISession session;

    public RepositorioDeClientes(ISession session)
    {
        this.session = session;
    }

    public void Salvar(Cliente entidade)
    {
        session.Save(entidade);
    }

    public Cliente BuscarPor(string cnpj)
    {
        return
            (
                from c in session.Query<Cliente>()
                where c.Empresa.Cnpj == cnpj
                select c
            ).SingleOrDefault();
    }
}

Só que o repositório ainda depende do arquivo de mapeamento (HBM) para fazer o de/para das classes para as tabelas. Vou omitir o mapeamento aqui. Ele estará disponível no código fonte do projeto que estará relacionado à este artigo. Como a interface define a estrutura, criamos uma implementação do repositório para o RavenDB. É possível notar que a API do ORM é bem semelhante à API do RavenDB.

public class RepositorioDeClientes : IRepositorioDeClientes
{
    private readonly IDocumentSession session;

    public RepositorioDeClientes(IDocumentSession session)
    {
        this.session = session;
    }

    public void Salvar(Cliente entidade)
    {
        session.Store(entidade);
        session.SaveChanges();
    }

    public Cliente BuscarPor(string cnpj)
    {
        return
            (
                from c in session.Query<Cliente>()
                where c.Empresa.Cnpj == cnpj
                select c
            ).SingleOrDefault();
    }
}

Se notarmos os códigos que criam um novo cliente utilizando os repositórios criados acima, eles serão idênticos, exceto pela criação, que cada um deve recorrer ao seu próprio modelo de construção e conexão. Se você injeta o repositório em outras classes, elas continuarão funcionando de forma transparente, já que toda a “complexidade” está embutida no repositório, e sua interface continua expondo os mesmos métodos.

public static class Repositorios
{
    public static void Executar()
    {
        ViaNHibernate();
        ViaRavenDB();
    }

    private static void ViaNHibernate()
    {
        using (var session = NHibernateContext.Factory.OpenSession())
        {
            var repositorio = new R.NHibernate.RepositorioDeClientes(session);
            var cliente = new Cliente(new Empresa() { RazaoSocial = "Nome Ltda", Cnpj = "123" });

            repositorio.Salvar(cliente);

            Console.WriteLine(repositorio.BuscarPor(cliente.Empresa.Cnpj).Empresa.RazaoSocial);
        }
    }

    private static void ViaRavenDB()
    {
        using (var session = RavenDBContext.Store.OpenSession())
        {
            var repositorio = new R.RavenDB.RepositorioDeClientes(session);
            var cliente = new Cliente(new Empresa() { RazaoSocial = "Nome Ltda", Cnpj = "123" });

            repositorio.Salvar(cliente);

            Console.WriteLine(repositorio.BuscarPor(cliente.Empresa.Cnpj).Empresa.RazaoSocial);
        }
    }
}

E como uma das principais características de bases NoSQL é não ter um schema predefinido, não há nada que se precise fazer para estruturar a base antes de receber os dados. E com isso, todos os arquivos HBM de mapeamento do NHibernate (ou se usar o Fluent NHibernate), podem ser completamente descartados. O método BuscarPor está sendo chamado aqui apenas para gerar o round-trip de testes.

Relacionamentos

Quando trabalhamos com base de dados relacional, utilizamos os relacionamentos para referenciar em uma linha o conteúdo de outra, ou seja, para complementar a informação, e evitando com isso, a redundância de dados. Para dar um exemplo, quando um pedido é realizado, ao invés de armazenar nos itens do mesmo toda a informação acerca do produto comprado, simplesmente referenciamos o produto através de seus Id como uma chave estrangeira, e através de JOINs, conseguimos remontar o pedido e saber quais produtos foram comprados.

Como mencionei no artigo anterior, além do custo de executar um JOIN, muitas vezes a redundância é benéfica. E vale ressaltar novamente aqui: o custo de armazenamento, via de regra, é mais barato que o custo de processamento. Ao mudar para uma base de dados NoSQL, os relacionamentos são tratados de forma um pouco diferente aos quais conhecemos no mundo relacional. Abaixo vou mostrar alguns experimentos, e entender o comportamento de cada um deles para podemos escolher o qual melhor se encaixa com a nossa necessidade.

Para o exemplo, vamos considerar um exemplo bastante trivial: uma classe chamada Pedido, que possui os itens que foram comprados. Para representar cada item, teremos a classe Item, que, em princípio, referenciará o produto (classe Produto) que foi adquirido. Essa classe irá ser modificada para exemplificar cada um dos cenários. Por fim, a classe Pedido irá expor uma propriedade chamada Itens que retorna todos os itens associados à ele.

Flat

A primeira opção é conhecida como flat, ou seja, as referências serão armazenadas como parte do documento principal, embutindo todas as propriedades e seus respectivos valores. Para este primeiro cenário, a estrutura da classe Item do pedido será a seguinte:

public class Item
{
    public Item(Produto produto, int quantidade)
    {
        this.Produto = produto;
        this.Quantidade = quantidade;
        this.Total = produto.Valor * quantidade;
    }

    public Produto Produto { get; private set; }

    public int Quantidade { get; private set; }

    public decimal Total { get; private set; }
}

Note que a instância da classe produto é passada como parâmetro no construtor da classe Item e armazenado em uma propriedade. O RavenDB irá embutir todas as propriedades do produto no interior do documento pedido. Como um paralelo, o NHibernate permite configurar a propriedade cascade para “save-update” e fazer com que a classe Produto seja salva em uma tabela exclusiva para o mesmo; mais tarde, se este produto for novamente comprado, então ela reutilizaria o mesmo registro. Abaixo o código que utilizamos para inserir o pedido e o JSON que foi armazenado na base:

var repositorioDePedidos = new RepositorioDePedidos(RavenDBContext.Store.OpenSession());

var produto = new Produto() { Descricao = "Mouse Microsoft", Valor = 120M };
var pedido = new Pedido();

pedido.AdicionarItem(new Pedido.Item(produto, 2));

repositorioDePedidos.Salvar(pedido);
{
    "Data": "2017-01-17T16:59:58.2372992",
    "Total": 240,
    "Itens": [
        {
            "Produto": {
                "Descricao": "Mouse Microsoft",
                "Valor": 120,
                "Id": null
            },
            "Quantidade": 2,
            "Total": 240
        }
    ]
}

O grande ponto negativo deste modelo, é que se precisarmos alterar qualquer informação no produto, teremos que varrer todos os pedidos existentes e fazer a tal alteração.

PorId

A opção por id faz com que a referência para o produto seja armazenada, ou seja, o item passa agora a referenciar o id do produto que foi comprado que, por sua vez, e acaba sendo armazenado em uma coleção a parte dos pedidos. Com isso, os dados do produto não serão duplicados, mas você terá que saltar para outro documento para ter acesso às informações sobre o produto que foi comprado. A classe Item sofre uma pequena alteração para armazenar apenas o Id do produto:

public class Item
{
    public Item(Produto produto, int quantidade)
    {
        this.ProdutoId = produto.Id;
        this.Quantidade = quantidade;
        this.Total = produto.Valor * quantidade;
    }

    public string ProdutoId { get; private set; }

    public int Quantidade { get; private set; }

    public decimal Total { get; private set; }
}

Agora o produto deve ser armazenado separadamente em nossa base, afinal, é muito provável que ele seja um agregado na perspectiva do domínio, o que o confere direito de ter um repositório. Obviamente que antes de comprarmos algo, ele deve estar previamente cadastrado, e é esta instância deste produto já cadastrado que é passada para o item do pedido, que o reutilizará:

using (var session = RavenDBContext.Store.OpenSession())
{
    var repositorioDePedidos = new RepositorioDePedidos(session);
    var repositorioDeProdutos = new RepositorioDeProdutos(session);

    var produto = new Produto() { Descricao = "Mouse Microsoft", Valor = 120M };

    repositorioDeProdutos.Salvar(produto);

    var pedido = new Pedido();

    pedido.AdicionarItem(new Pedido.Item(produto, 2));

    repositorioDePedidos.Salvar(pedido);
}
{
    "Data": "2017-01-17T17:11:49.7687856",
    "Total": 240,
    "Itens": [
        {
            "ProdutoId": "produtos/1",
            "Quantidade": 2,
            "Total": 240
        }
    ]
}

Redundante

Por fim, esta opção nos permite criar uma classe e copiar somente os campos necessários do objeto de origem. A ideia é trazer para o item tudo o que é necessário para realizar a compra, e mesmo que no futuro as informações mudem, o pedido atual terá armazenado as informações originais do momento da compra. Quando, por exemplo, a descrição do produto for alterada, não mais refletirá nos pedidos já realizados.

Para isso, a classe Item passa a armazenar também a descrição do produto, além do valor que já vinha fazendo. Não há mais qualquer referência com a classe Produto.

public class Item
{
    public Item(Produto produto, int quantidade)
    {
        this.Descricao = produto.Descricao;
        this.Quantidade = quantidade;
        this.Total = produto.Valor * quantidade;
    }

    public string Descricao { get; private set; }

    public int Quantidade { get; private set; }

    public decimal Total { get; private set; }
}

O código que armazena o pedido nada muda em relação ao que vimos no primeiro cenário, porém se inspecionarmos o JSON da base de dados, veremos que ele armazena apenas as propriedades da classe Item, sem qualquer menção ao Produto, muito menos ao seu Id.

using (var session = RavenDBContext.Store.OpenSession())
{
    var repositorioDePedidos = new RepositorioDePedidos(session);

    var produto = new Produto() { Descricao = "Mouse Microsoft", Valor = 120M };
    var pedido = new Pedido();

    pedido.AdicionarItem(new Pedido.Item(produto, 2));

    repositorioDePedidos.Salvar(pedido);
}
{
    "Data": "2017-01-17T17:21:39.6605485",
    "Total": 240,
    "Itens": [
        {
            "Descricao": "Mouse Microsoft",
            "Quantidade": 2,
            "Total": 240
        }
    ]
}

A utilização depende de cada situação que temos. Em muito casos quando trabalhamos com base de dados relacional, não nos atentamos para algumas situações, que podem gerar problemas mais sérios em termos de negócios, como por exemplo, não armazenar o nome ou o endereço do cliente no momento da realização da compra. No caso do RavenDB a redundância é natural, mas, como disse, nem sempre ela é ruim.

O código fonte dos exemplos utilizados no decorrer este artigo está disponível neste endereço.

O custo do JOIN

Considere o exemplo exibido na imagem a seguir. Trata-se das notas fiscais emitidas para um determinado cliente, e por questões de normalização, o cliente (e todos os seus dados) é armazenado em uma tabela separada da nota fiscal, para assim evitar duplicidade de informações. A transportadora será discutida mais adiante.

join01

Podemos identificar na imagem acima que nossa aplicação é responsável por emitir notas fiscais contra um determinado cliente e apresenta-las na tela. Vamos pensar que esta aplicação nada mais é que um simples CRUD. Quando uma nota fiscal é emitida para o cliente “Israel Aece Ltda”, uma linha é adicionada na tabela NotaFiscal. Para exibirmos as notas fiscais emitidas na aplicação, basta utilizarmos a seguinte consulta:

SELECT
      nf.Data
    , nf.Total
    , c.Nome As Cliente
    , t.Nome As Transportadora
FROM NotaFiscal nf
INNER JOIN Cliente c ON c.ClienteId = nf.ClienteId
INNER JOIN Transportadora t ON t.TransportadoraId = nf.TransportadoraId

Os dados são retornados com sucesso e uma listagem é apresentada para o usuário. Daqui seis meses o nome do cliente muda para “Israel Aece Ltda em Recuperação Judicial” e precisamos novamente ter acesso às notas fiscais emitidas para ele. Ao retornar os dados, o nome que será exibido na nota fiscal já não coincide mais com a razão social da empresa da época da emissão; isso pode piorar ainda mais se outras informações mudarem, por exemplo, o endereço, algo que é comum.

Para resolver isso, podemos rastrear as alterações na tabela de clientes, criando um log de alterações para armazenar cada mudança que ocorreu no registro. Além de ser uma tarefa complicada, o JOIN ficará muito mais verboso, já que terá que contemplar outras tabelas, podendo a performance ser diretamente impactada. Armazenar na tabela NotaFiscal a razão social do cliente no momento da emissão também é uma opção, mas podemos interpretar isso de outra forma, ou seja, no mundo real, o que a nota fiscal possui é um cliente? Ou seria um destinatário?

O que vimos acima é uma aplicação no estilo CRUD, onde nossas tarefas são encaradas como simples ações (DML) a serem executadas na base de dados. A estrutura de dados é o nosso principal guia, fazendo com que a nossa aplicação tenha uma grande afinidade com ele, e tarefas triviais são difíceis de serem implementadas, como foi o exemplo que vimos acima. Pra agravar, percebemos que um mesmo conjunto de dados é compartilhado entre a escrita (emissão da nota) e leitura (exibição em tela).

Muitas vezes a emissão de uma nota fiscal é muito mais do que um simples INSERT. Se mal analisado, a aplicação que antes era só lógica de acesso à dados, começa a ser poluída com regras de negócios e o código que atendia inicialmente, começa a ficar frágil e de difícil manutenção, pois inevitavelmente vamos acabar misturando as responsabilidades, já que não haverá divisão lógica/física da arquitetura da aplicação.

Se começarmos a pensar separadamente no que precisamos fazer para atender a regra de negócio (emissão da nota fiscal) do que precisamos fazer para exibir na tela (listagem de notas emitidas), o resultado vai sair muito melhor, ou, no mínimo, vai provocar discussões que certamente ajudarão na modelagem da arquitetura. Considere o código a seguir:

public class Cliente
{
    public string Nome { get; set; }
}

public class NotaFiscal
{
    public NotaFiscal(Cliente cliente)
    {
        this.Destinatario = new DadosDoDestinatario()
        {
            Nome = cliente.Nome
        };
    }

    public DadosDoDestinatario Destinatario { get; private set; }

    public class DadosDoDestinatario
    {
        public string Nome { get; set; }
    }
}

Deixando o modelo CRUD em detrimento ao modelo orientado ao domínio (DDD), os elementos ficam muito mais evidentes, como podemos perceber acima. Note que a nota fiscal não possui referência direta com um cliente, mas sim um destinatário da mercadoria, e para facilitar, criamos um construtor que aceita o cliente como parâmetro e copia os dados necessários (o nome, para este exemplo) para emissão da nota fiscal. A transportadora, que até então não falamos dela, está associada à nota fiscal e é um item importante ao exibir na tela quando formos apresentar as notas fiscais.

Ao persistir as classes acima em uma base de dados relacional, o custo do JOIN para extrair o respectivo cliente não existirá mais, pois o destinatário, que passa a ser uma coluna na tabela NotaFiscal, terá a informação. O JOIN ainda será necessário para chegar até a transportadora. Isso pode aumentar ainda mais, por exemplo, para exibir os itens comprados, pois precisaremos fazer JOIN com a tabela de produtos. Nada disso é tão ruim, porém se o banco de dados começar a ser o principal gargalo da aplicação, vai ser difícil conseguir ter uma estrutura performática para atender o lado da escrita e o lado da leitura.

Separar as bases de dados pode ser uma opção, permitindo com que a estrutura do lado da escrita seja uma base mais normalizada, enquanto do lado da leitura, queremos otimizar para termos uma performance extraordinária. Em geral, o custo do armazenamento é mais barato do que o custo de processamento, então a redundância de informações será benéfica e o custo será baixo. Com isso, o lado da leitura passa a ter a relação de todas as notas fiscais emitidas sem a necessidade de realizar JOINs para complementar informações. É importante dizer que nada impede de um lado utilizar uma base de dados relacional e da outra uma base de dados orientada à documentos; independente disso, a dificuldade aqui é em como fazer a sincronização destas bases de dados. Continua.

Middlewares para Comandos

Quando optamos por separar a leitura da escrita em nossa aplicação, é comum adotarmos o padrão CQRS. Entre os vários conceitos que temos, um deles é a criação de comandos, que nada mais são do que classes que expressam a intenção do usuário em alterar, criar ou excluir alguma informação. O comando é definido através de uma classe, que traz informações (dados) sobre a ação a ser executada. Ele é passado para o que chamamos de command handler, que é o tratador deste comando, ou seja, ele coordena todas as ações sobre aquela entidade. E, da mesma forma, também é implementada através de uma classe (apesar de haver algumas variações). Em geral, a relação um tratador para cada comando.

O código abaixo exibe um exemplo simples disso. A classe Command não traz qualquer informação adicional, é apenas uma convenção; já a interface IHandler<T> define a estrutura que todo tratador de comando deverá ter, ou seja, o método Handle. É no interior deste método que recorremos ao repositório para adicionar/remover a entidade, manipular, etc.

public class CadastrarUsuario : Command
{
    public string Nome { get; set; }
}

public class CadastrarUsuarioHandler : IHandler<CadastrarUsuario>
{
    public CadastrarUsuarioHandler(IRepositorioDeUsuarios repositorio) { }

    public void Handle(CadastrarUsuario message)
    {
        Console.WriteLine("Cadastrando o usuário " + message.Nome);
    }
}

Para manter a simplicidade, este código está apenas escrevendo o nome do usuário na tela. Em um ambiente real, é provável termos que incrementar isso. Para citar alguns exemplos, é comum ter a necessidade de realizar logs, mensurar o tempo gasto na operação, proteger as ações através de uma transação, etc. A partir do momento que começarmos a incorporar essas atividades em nosso tratador, ele começará a ficar poluído, e o pior, começa a ter mais de uma responsabilidade além daquela para qual ele foi criado. Note o mesmo tratador que temos acima, contendo estes incrementos:

public class CadastrarUsuarioHandler : IHandler<CadastrarUsuario>
{
    public CadastrarUsuarioHandler(
        ILogger logger, 
        ITimer timer, 
        IUnitOfWork transaction, 
        IRepositorioDeUsuarios repositorio) { }

    public void Handle(CadastrarUsuario message)
    {
        this.timer.Start();
        this.logger.Info("Início");

        using (this.transaction)
        {
            this.repositorio.Salvar(message.Nome);
            Console.WriteLine("Cadastrando o usuário " + message.Nome);
        }

        this.logger.Info("Fim");
        this.timer.Stop();
    }
}

Mesmo que estamos recebendo as implementações do mundo exterior, a classe CadastrarUsuarioHandler está manipulando mais coisas do que deveria. Quando criarmos um segundo comando, teremos que repetir todos estes códigos, ficando sujeito a erros e com pouca reusabilidade.

O melhor a fazermos aqui é remover todo este código de cross-cutting, deixando o tratador do comando responsável apenas por criar e armazenar o usuário na base de dados através do repositório. Todas essas outras – importantes – atividades podem ser delegadas para classes que circundam todos os tratadores da aplicação, não ficando restrito a apenas um, e a partir daí, tendo um ponto central para isso, fica fácil a manutenção e eventual ajuste na interface dos componentes, já que não precisamos sair refatorando em diversos lugares da aplicação.

Uma das técnicas para isso, é criar um pipeline de funcionalidades, onde o item mais baixo de ações é a execução do tratador do comando. Até ele chegar no tratador propriamente dito, ele passaria pelas funcionalidades de log, timer e transação, deixando o ambiente todo preparado para a execução da atividade principal. A ideia é criar middlewares, próximo ao que temos no ASP.NET Core, para que possamos acoplar ou desacoplar alguma funcionalidade quando necessário. Além da extensibilidade, temos uma separação bem definida do que cada middleware deve fazer, sem um afetar no trabalho doutro. A imagem abaixo ilustra o exemplo:

commandhandlermiddleware1

O primeiro passo é a criação da interface que irá definir a estrutura de um middleware. Basicamente ele fornecerá um método chamado Execute, que além da instância do comando a ser executado, ela também recebe o método a ser executado na sequência. Opcionalmente, você pode optar por receber o delegate da próxima ação no construtor do middleware.

public interface IMiddleware
{
    void Execute(Command command, Action<Command> next);
}

A partir daí podemos ir criando os middlewares necessários para a nossa aplicação. Abaixo temos o exemplo do TimerMiddleware, que utiliza um Stopwatch para mensurar o tempo gasto na execução do comando. É importante notar que podemos ter códigos sendo executado antes e depois da próxima ação.

public class TimerMiddleware : IMiddleware
{
    public void Execute(Command command, Action<Command> next)
    {
        var timer = Stopwatch.StartNew();
        Console.WriteLine("[INFO] - Início - {0:HH:mm:ss}", DateTime.Now);

        try
        {
            next(command);
        }
        finally
        {
            timer.Stop();
        }

        Console.WriteLine("[INFO] - Fim - {0:HH:mm:ss} - Tempo Decorrido: {1}", 
            DateTime.Now, timer.Elapsed);
    }
}

Só que este middleware por si só não funciona. Temos agora que criar uma estrutura que consiga receber os middlewares e, consequentemente, montar uma cadeia de chamadas na ordem desejada para execução. A ideia é receber todos os middlewares necessários através do construtor do pipeline, e com isso, já criar uma sequência de execução, para que ela já esteja pronta quando chegar para o pipeline um novo comando. É importante notar aqui que neste cenário, o command bus (mencionado em arquitetura CQRS) é implementado como um middleware.

public class Pipeline
{
    private Action<Command> middlewareChain;

    public Pipeline(params IMiddleware[] middlewares)
    {
        this.middlewareChain = BuildChain(middlewares.Reverse().ToArray());
    }

    private static Action<Command> BuildChain(IMiddleware[] middlewares)
    {
        Action<Command> chain = command => { };

        foreach (var middleware in middlewares)
        {
            var temp = chain;

            chain = command => middleware.Execute(command, temp);
        }

        return chain;
    }

    public virtual void Handle<T>(T command) where T : Command
    {
        this.middlewareChain(command);
    }
}

Se quiser postergar a criação da cadeia de chamada para a primeira execução, podemos declarar o campo middlewareChain como Lazy<T>. E, por fim, basta instanciarmos o pipeline de acordo com a nossa estrutura de middlewares necessária para a aplicação, e a partir daí, passar os comandos que estão sendo requisitados pelos usuários. Novamente, note que o middleware CommandHandlerMiddleware é o último da lista:

var pipeline = 
    new Pipeline(
        new TimerMiddleware(), 
        new LoggingMiddleware(), 
        new TransactionMiddleware(), 
        new CommandHandlerMiddleware());

pipeline.Handle(new CadastrarUsuario() { Nome = "Israel" });

O código na íntegra está disponível neste link.

Rescrevendo Controllers

Codificar as ações (métodos) dentro de um controller não é uma tarefa difícil. A implementação varia muito, e todas elas podem ser consideradas corretas se atingirem o objetivo esperado. A finalidade deste vídeo é apresentar uma forma alternativa para a escrita de controllers e ações, mantendo o mesmo comportamento, porém organizando, estruturando e reaproveitando melhor os códigos que serão executados.

A ideia é mostrar uma outra forma para escrever controllers do ASP.NET MVC ou Web API, mas o conceito pode ser estendido para outros tipos de aplicação que estão além do mundo web.