Israel Aece

Software Developer

Transformação de Claims

Ao autenticar um usuário, nós podemos além de armazenar no token o seu nome, algumas outras propriedades que o descrevem, tais como: e-mail, papéis (roles), etc. Com isso, nós teremos diversas outras características dele além de apenas o nome e os papéis que ele possui dentro da aplicação.

Como sabemos, essas informações são expressadas através de claims. Ao autenticar, nós podemos criar uma coleção de claims contendo todas as informações sobre o respectivo usuário. Como as claims estão em todo lugar, o ASP.NET fornece um recurso específico que permite a transformação de claims, ou seja, além de utilizar informações que temos do lado do servidor para descrever o usuário, para complementar extraindo dados da requisição e incluir na coleção de claims.

Para customizar o tranformador, devemos implementar a interface IClaimsTransformer, e através do método TransformAsync podemos incrementar mais informações sobre o usuário e mais tarde utiliza-las para autorização de algum recurso específico. No exemplo abaixo, estamos extraindo a cultura (via header Accept-Language) da requisição e incluindo no ClaimsPrincipal gerado:

public class CultureToClaimTransformer : IClaimsTransformer
{
    public Task<ClaimsPrincipal> TransformAsync(ClaimsTransformationContext context)
    {
        var principal = context.Principal;

        if (principal.Identity.IsAuthenticated)
        {
            var culture = StringValues.Empty;

            if (context.Context.Request.Headers.TryGetValue("Accept-Language", out culture))
                ((ClaimsIdentity)principal.Identity).AddClaim(new Claim("Culture", culture.ToString()));
        }

        return Task.FromResult(principal);
    }
}

Só que a classe por si só não funciona. Precisamos incluir a mesma na execução, e para isso, recorremos ao método UseClaimsTransformation para indicar ao runtime do ASP.NET a classe que faz a transformação de claims. Depois do MVC devidamente configurado, estamos utilizando a autenticação baseada em cookies para o exemplo, indicamos a instância da classe CultureToClaimTransformer para a propriedade Transformer.

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddAuthentication();
        services.AddMvc();
    }

    
public void Configure(IApplicationBuilder app)
    {
        app.UseCookieAuthentication(new CookieAuthenticationOptions()
        {
            LoginPath = "/Aplicacao/Login",
            ReturnUrlParameter = "ReturnUrl",
            AutomaticAuthenticate = true,
            AutomaticChallenge = true
        });

        app.UseClaimsTransformation(
new ClaimsTransformationOptions()
        {
            Transformer = new CultureToClaimTransformer()
        });

        app.UseMvc(routes =>
        {
            routes.MapRoute(
                name: "default",
                template: "{controller=Aplicacao}/{action=Index}/{id?}");
        });
    }
}

Depois de toda a configuração realizada, nós vamos codificar o nosso controller. Ele possui apenas dois métodos: um que exibe informações e o outro que onde de fato realizamos o login. O método que exibe as informações (Index) está decorado com o atributo AuthorizeAttribute, que não permitirá usuários não autenticados acessá-lo. Já o segundo método serve para autenticar o usuário; em uma aplicação real, este método deve receber as informações postadas em um formulário para validar primeiramente se o usuário existe (em uma base de dados, por exemplo), e caso verdadeiro, aí sim devemos proceder com a autenticação.

public class
AplicacaoController : Controller
{
    [Authorize]
    public IActionResult Index()
    {
        return View();
    }

    
public IActionResult Login()
    {
        HttpContext.Authentication.SignInAsync(
            CookieAuthenticationDefaults.AuthenticationScheme,
            new ClaimsPrincipal(
                new ClaimsIdentity(new[] { new Claim(ClaimTypes.Name, "Israel Aece") }, 
                CookieAuthenticationDefaults.AuthenticationScheme))).Wait();

        
return Redirect(Request.Query["ReturnUrl"]);
    }
}

Por fim, ao rodar a aplicação e exibir a coleção de
claims do usuário logado, nós teremos duas: uma com o nome do usuário e a outra com a cultura que foi passada pelo navegador que o usuário está utilizando para acessar a aplicação:

  • http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name: Israel Aece
  • Culture: pt-BR

Explorando o EventStore - Overview

Recentemente em dois vídeos onde abordei a técnica de event sourcing, fiz uso de uma espécie de base de dados que armazena eventos que ocorrem na aplicação, e que é chamada de Event Store. Naqueles exemplos, a ideia era armazenar os eventos gerados pelo domínio e que eram, mais tarde, utilizando para reconstruir o estado atual do objeto em questão.

Como lá abordamos o Event Store de forma bem superficial, pois o foco era no conceito e não na ferramenta, neste artigo vamos aprofundar um pouco mais para explorar a API e os recursos que este repositório pode oferecer para armazenarmos e extrairmos os eventos para uso em cenários de event sourcing ou também em cenários que demandam um service bus, onde podemos publicar conteúdo e os assinantes possam ser notificados e/ou interrogar a base para saber se algo ocorreu e tomar alguma decisão sobre isso.

O primeiro passo é fazer o download através do site oficial. Este pacote trará o executável que é a "parte servidor", ou seja, a própria base onde os eventos serão armazenados. Além disso, há também uma console administrativa web, que é acessada através de um navegador e permitirá interagir com a base e realizar diversas configurações de funcionamento e monitoramento.

Para exemplificar o uso de alguns recursos o Event Store, vamos criar três aplicações: Loja, Nota Fiscal e Transportadora. Na primeira, o cliente irá postar o pedido de compra. A segunda, Nota Fiscal, irá interrogar periodicamente o servidor procurando por novos pedidos adicionados e, consequentemente, emitir a nota fiscal. Por fim, a Transportadora irá monitorar qualquer nova nota fiscal emitida, e irá imediatamente iniciar o processo de entrega do produto.



O primeiro passo é iniciar o servidor que passará a receber as solicitações. Basicamente depois de extraído o pacote do download, basta executar o comando abaixo no prompt de comando e ele passará a receber as requisições. E depois do servidor inicializado, você pode ir até a console web administrativa através do seguinte endereço: http://localhost:2113, login "admin" e a senha "changeit".

C:\EventStore.ClusterNode.exe

Depois do servidor rodando, precisamos começar a codificar as aplicações, e para isso, o projeto também fornece diversas APIs para diferentes tecnologias, incluindo para .NET que é chamada de EventStore.Client. Utilize o comando abaixo para baixar e instalar ele através do Nuget:

PM> Install-Package EventStore.Client

Depois das três aplicações estarem com o pacote do cliente devidamente instalado, chega o momento de estabelecermos a conexão com o servidor para postar o evento de novo pedido criado na loja. A classe que serve para estabelecer a ligação com o servidor é a EventStoreConnection, e pode (e deve) ser mantida uma por aplicação para melhor reutilização dos recursos que a mesma utiliza para executar suas atividades. É importante notar que os métodos expostos são assíncronos, mas como estou utilizando aplicações console para o exemplo, forçarei a execução síncrona de todos eles.

A conexão se dá através do protocolo TCP, onde você pode utilizar o método estático Create para informar o endereço a partir de uma URI, e a porta padrão para o servidor é a 1113. Como dito acima, estou abrindo a conexão explicitamente e aguardando que a mesma seja concluída para dar sequência na utilização.

using (var conn = EventStoreConnection.Create(new Uri("tcp://localhost:1113")))
{
    conn.ConnectAsync().Wait();
}

Voltando ao exemplo de testes, a primeira aplicação se resume a postar um novo evento informando que um novo pedido foi criado. Note através do código abaixo como isso é definido. Não vou me preocupar em colocar a definição das classes aqui porque são muito simples e não há qualquer informação relevante dentro delas; são classes POCOs com as propriedades que refletem nosso negócio.

var
novoPedido = new NovoPedidoCriado(new Pedido()
{
    NomeDoCliente = "Israel Aece",
    ValorTotal = 1200
});

Depois do evento criado, precisamos postar o mesmo, mas antes precisamos entender um conceito do
Event Store, chamado de Stream. Como já suspeitamos, é a forma que temos de agrupar os eventos por qualquer regra ou seção da aplicação que desejamos. Para o nosso caso, vamos chamar o stream de "Ecommerce.Pedidos", que irá concentrar todos os eventos relacionados aos pedidos realizados em nosso loja.

A classe de conexão fornece um método chamado 
AppendToStreamAsync, e além de especificarmos o nome do stream onde queremos armazenar, temos que passar o objeto (evento) que deve ser persistido. Para encapsular e descrever o evento do ponto de vista da infraestrutura, o Event Store possui uma classe chamada EventData. Essa classe além de ter algumas características relacionadas a infraestrutura, recebe também em seu construtor o Id do evento, o nome e o objeto que representa o mesmo, e que no nosso caso está armazenado na variável "novoPedido".

A serialização pode ser binária, mas dependendo com quem vamos interagir/integrar, utilizar JSON pode ser uma opção bem mais interessante. E para isso, estou recorrendo ao
Json.NET (Newtonsoft) para serializar e deserializar os objetos.

conn.AppendToStreamAsync(
    "Ecommerce.Pedidos", 
    ExpectedVersion.Any, 
    GerarEvento(novoPedido)).Wait();

private static
EventData GerarEvento(Evento evento)
{
    return new EventData(
        evento.Id, 
        evento.GetType().Name,
        true
        Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(evento)), null);
}

Depois que o evento for postado, se acessarmos a console administrativa do
Event Store, veremos o stream e o evento criado, conforme é possível visualizar na imagem abaixo:



Na sequência vamos recorrer ao projeto de Nota Fiscal, que ficará interrogando periodicamente o servidor em busca de eventos do tipo
NovoPedidoCriado. Como os eventos são acumulativos do lado do servidor, nós, o cliente, devemos controlar os eventos que já foram processados pela aplicação, e por isso, estamos armazenando em uma variável o número do último evento, para que em uma próxima execução não corra o risco de gerar notas fiscais para os pedidos já gerados. E vale ressaltar que isso é um exemplo, e que em uma cenário real você precisa armazenar este contador em uma local seguro que resista a possíveis reinicializações da aplicação.

Da mesma forma que fazemos na criação do evento, para ler também precisamos criar a conexão com o servidor, e neste caso, vamos utilizar o método 
ReadStreamEventsForwardAsync, e como o próprio nome diz, lê os eventos na ordem em que eles foram postados.

var ultimoEventoProcessado = 0;

using
(var conn = EventStoreConnection.Create(new Uri("tcp://localhost:1113")))
{
    conn.ConnectAsync().Wait();

    
while ((Console.ReadLine() != null))
    {

        var itens = conn.ReadStreamEventsForwardAsync(
"Ecommerce.Pedidos", 
ultimoEventoProcessado + 1, 200, false).Result;

        if (itens.Events.Any())
        {
            foreach (var e in itens.Events)
                Processar(conn, e);
        }
        else
        {
            Log.Yellow("Não há eventos para processar.");
        }
    }
}

Os eventos extraídos são representados pela classe 
ResolvedEvent, que entre várias informações sobre o evento, temos o evento gerado e serializado pela nossa aplicação de novo pedido criado. Como podemos notar abaixo, o método Processar identifica que se trata de um evento deste tipo, deserializa o mesmo, o processa e depois disso, dispara um evento dizendo que a respectiva nota fiscal foi emitida, através de um novo evento.

internal static void Processar(
IEventStoreConnection conexao, 
ResolvedEvent dadosDoEvento)
{
    if (dadosDoEvento.Event.EventType == "NovoPedidoCriado")
    {
        var novoPedido = Extrair<NovoPedidoCriado>(dadosDoEvento);

        Log.Green("NOVO PEDIDO");
        Log.Green("Id: " + novoPedido.Id, 4);
        Log.Green("Data: " + novoPedido.Data, 4);
        Log.Green("Cliente: " + novoPedido.NomeDoCliente, 4);
        Log.Green("Valor: " + novoPedido.ValorTotal.ToString("N2"), 4);
        Log.Yellow("Emitindo a Nota Fiscal do Pedido", 4);
        Log.NewLine();

        conexao.AppendToStreamAsync(
            "Ecommerce.Pedidos",
            ExpectedVersion.Any,
            GerarEvento(

new NotaFiscalEmitida(
novoPedido.NomeDoCliente, 
novoPedido.ValorTotal, 
"0001.0292.2999-2881-9918.11.9999/99"))).Wait();

        ultimoEventoProcessado = dadosDoEvento.Event.EventNumber;
    }
}

private static TEvento Extrair<TEvento>(ResolvedEvent dadosDoEvento) 
where TEvento : Evento
{
    return JsonConvert.DeserializeObject<TEvento>(

Encoding.UTF8.GetString(dadosDoEvento.Event.Data));
}

E, para finalizar o processo, temos agora a aplicação da transportadora, que ao invés de periodicamente procurar por um evento de nota fiscal emitida, assina o stream e tão logo quando ele for incluído, a transportadora será automaticamente notificada do evento gerado. E da mesma forma que antes, abrimos a conexão, e agora utilizamos o método 
SubscribeToStreamAsync, informando o nome do stream que desejamos monitorar e qualquer novo evento, disparamos o método Processar, conforme pode ser visualizado abaixo:

using (var conn = EventStoreConnection.Create(new Uri("tcp://localhost:1113")))
{
    conn.ConnectAsync().Wait();

    conn.SubscribeToStreamAsync(
"Ecommerce.Pedidos", 
false
(a, e) => Processar(e)).Wait();

    Console.ReadLine();
}

internal static void Processar(ResolvedEvent dadosDoEvento)
{
    if (dadosDoEvento.Event.EventType == "NotaFiscalEmitida")
    {
        var novoPedido = Extrair<NotaFiscalEmitida>(dadosDoEvento);

        Log.Green("NOVO PEDIDO - NOTA FISCAL EMITIDA");
        Log.Green("Id: " + novoPedido.Id, 4);
        Log.Green("Cliente: " + novoPedido.NomeDoCliente, 4);
        Log.Green("Valor: " + novoPedido.ValorTotal.ToString("N2"), 4);
        Log.Green("NF-e: " + novoPedido.ChaveDaNotaFiscalEletronica, 4);
        Log.NewLine();
    }
}

E agora, se colocarmos as três aplicações lado a lado, é possível visualizar o efeito do processamento nas três, onde uma gera o pedido, a segunda emite a nota fiscal e a terceira é notificada para iniciar o processo de transporte.



É claro que isso é um exemplo simplista, mas pode ser considerado algo parecido em uma escala maior, como por exemplo, utilizar este recurso para diálogo entre contextos do domínio, sincronização de bases de (escrita e leitura) em ambiente de CQRS ou para desempenhar qualquer outra tarefa em modo assíncrono.

Lazy Loading e Contextos de Domínio

Quando optamos por desenvolver uma aplicação orientada ao domínio (DDD), uma série de termos e conceitos devem ser entendidos para que sejam bem aproveitados e consigamos assim expressar em nosso código o que for necessário para atender a demanda daquela aplicação, serviço ou funcionalidade. O principal guia que agrupa todos esses termos é o livro do Eric Evans, chamado de Domain-Driven Design: Tackling Complexity in the Heart of Software.

Para persistência geralmente utilizamos repositórios (também descrito neste livro), que recorremos para inserir novos dados ou até mesmo para extrair os registros existentes, e mais tarde, persistir as eventuais alterações realizadas durante o processamento de alguma tarefa. E na maioria das implementações de repositórios, por trás (infraestrutura), sempre há um ORM que faz toda a mágica.

Na medida em que vamos desenvolvendo nosso domínio, vamos agregando às entidades diversas características e funcionalidades, tornando a classe cada vez mais próxima ao mundo real. E, como sabemos, os ORMs fornecem a possibilidade de habilitarmos um recurso chamado de lazy loading. Apenas para recordar, ele posterga a extração dos dados até que a mesma seja demandada, o que em outras palavras significa que a consulta será encaminhada à base de dados somente quando acessarmos a propriedade onde estão o(s) dado(s) custoso(s). É comumente relacionado à coleções, mas há situações onde se refere à outras classes ou até mesmo propriedades (como um array de bytes). Abaixo alguns exemplos (em negrito) do que poderia ser considerado como lazy loading:

public class Duplicata
{
    public AnaliseConfirmativa Confirmacao { get; private set; }

    public string Numero { get; private set; }

    public Sacado Sacado { get; }

    public IEnumerable<AcaoDeCobranca> AcoesDeCobranca
    {
        get
        {
            return acoesDeCobranca;
        }
    }

}

Com pouca configuração, os ORMs permitem postergar a carga das informações somente quando elas são solicitadas. Abaixo um pseudo-código que ilustra os vários momentos que vamos recorrer ao banco de dados; repare que somente quando acessamos as propriedades negritadas é que elas são extraídas, tornando um processo transparente para quem escreve o código.

var duplicata = repositorioDeTitulos.BuscarPorId(1);
//SELECT Numero, Sacado FROM Duplicata WHERE TituloId = 1

Console.WriteLine(duplicata.Confirmacao.Data);
//SELECT Data, Status, Confirmador FROM AnaliseConfirmativa WHERE Id = 3

foreach (var acao in duplicata.AcoesDeCobranca)
    //SELECT * FROM AcoesDeCobranca WHERE TituloId = 1
{
    //...
}

Como já era de se esperar, o ORM honra a configuração de lazy loading, e recorre ao banco somente quando de fato precisamos dos dados. Mas vamos detalhar melhor o que acontece no código acima: primeiramente recorremos ao repositório para extrair a duplicata de identificador 1. A consulta que foi feita devolve apenas os dados básicos da duplicata (número e sacado), e é tudo o que queremos até então. Logo que precisamos da parte da confirmação, uma nova consulta é feita. Por fim, quando queremos iterar pelas ações de cobrança, uma terceira consulta é feita para extrair os registros filhos.

Podemos perceber que as "partes" da duplicata são carregadas sob demanda, mas como disse anteriormente, em certos contextos, poderíamos poupar trabalho e já carregar juntamente com os dados básicos, os dados complementares para executar uma determinada ação. Vamos supor que tivéssemos dois ambientes: confirmação e cobrança. No primeiro ambiente, gostaria de que em uma única consulta me retornasse os dados inerentes ao processo de confirmação da duplicata; já no segundo ambiente, gostaria que as ações de cobrança também fossem extraídas tão logo quando a classe Duplicata fosse materializada.

Mas a configuração do ORM é única, não me permitindo customizar isso caso a caso, ambiente por ambiente. Se a performance é um ponto crucial da aplicação, é capaz de termos que começar a poluir a interface do repositório com métodos que retorne - ainda - a duplicata, mas que contextualizem para qual ambiente queremos:

var duplicata = repositorioDeTitulos.BuscarDuplicataParaConfirmacao(1);
var duplicata = repositorioDeTitulos.BuscarDuplicataParaCobranca(1);

E no interior de cada um dos métodos, recorreríamos a API do ORM para fazer a carga antecipada das informações relativas aquele contexto. Apesar de funcionar, em pouco tempo, é provável que o repositório comece a ter diversos métodos que estão ali mais para tentar "burlar" o ORM/sistema, e induzi-los a extrair os dados necessários para executar a operação desejada pelo usuário dentro daquele contexto.

Note que com um pequeno exemplo é possível ver o tamanho do problema que podemos ter ao criar um grande domínio e sem nos preocuparmos com a separação em contextos. Eles precisam ser bem pensados para tentar mantermos as entidades com a estrutura necessária para atender aquele contexto específico, caso contrário, podemos degradar a performance e termos dificuldades na manutenção e evolução da aplicação.

Migrando de DAAB para Dapper

Um dos pilares do antigo Enterprise Library é o DAAB (Data Access Application Block), e como o próprio nome diz, é um componente que auxilia na extração de dados de uma base de dados relacional. Sua API fornece métodos que permitem executar consultas que retornam valore escalares e também materializar o resultado em objetos.

É importante salientar que ele não é um ORM, e sendo assim, não rastreia mudanças em objetos depois que eles são materializados; compete ao cliente que faz uso desta biblioteca gerenciar tais mudanças que ocorrem nos mesmos, para depois devolver as alterações para a base de dados executando comandos de UPDATE, DELETE ou INSERT.

Abaixo um exemplo simples de como materializar o resultado para um objeto. Além da consulta SQL, temos que ter um gerador de parâmetros que os cria e anexa ao comando e, por fim, um builder, que permite customizar o mapeamento entre o resultado e as propriedades expostas pela classe mencionada.

var cliente =
database.CreateSqlStringAccessor<Cliente>
(
@"
SELECT
 c.ClienteId As 'Id'
, e.RazaoSocial
, e.Cnpj
FROM Cliente c
INNER JOIN Empresa e ON e.EmpresaId = c.EmpresaId
WHERE
c.ClienteId = @p0",
new ParameterMapper(),
MapBuilder<Cliente>
.MapNoProperties()
.MapByName(c => c.Id)
.MapByName(c => c.RazaoSocial)
.MapByName(c => c.Cnpj)
.Build()
).Execute(clienteId).SingleOrDefault();

O método 
CreateSqlStringAccessor<TResult> possui uma sobrecarga que permite apenas passar a consulta, e o mapeamento é feito de forma automática se ele encontrar as propriedades com os mesmos nomes das colunas. Além destes facilitadores para converter o resultado em objetos conhecidos pela aplicação, o DAAB gerencia a conexão para nós, ou seja, ele abre o mais tarde e fecha o mais cedo possível.

Isso é válido, pois evita precisamos gerenciar manualmente a conexão, pois algum descuido pode deixá-la aberta consumindo recursos desnecessários ou esgotar a quantidade máxima de conexões que o banco de dados possui. Só que em alguns casos o que queremos é manter a conexão aberta para executar mais de uma consulta, pois o fechamento e a abertura logo em seguida pode ser mais oneroso do que mante-la aberta por um curto período de tempo. E quando isso é necessário, temos novamente que voltar a lidar com as classes já tão conhecidas para trabalhar com dados no .NET (Connection, Command, DataReader, Parameter, etc.).

Infelizmente já faz algum tempo que esta biblioteca não sofre atualização (2013). Como uma - boa - alternativa, um Micro-ORM chamado Dapper foi criado por
 Sam Saffron e Marc Gravell para ser usado pelo site StackOverflow (um grande case de performance) e hoje é open-source e pode ser utilizado por nossas aplicações. Ele foi criado com foco em performance, otimizando e reutilizando internamente objetos para tornar as consultas recorrentes cada vez mais veloz.

A sua API é extremamente simples, e faz uso de métodos de extensão na sobre a interface IDbConnection para materializar o resultado em objetos. Através de um método genérico chamado Query<T>, nós informamos a consulta SQL, e a parametrização é bem mais simples de se configurar, pois basta informar os parâmetros através de objetos anônimos, que o Dapper irá mapear as propriedades do objeto em parâmetros na consulta SQL.

using (var conn = new SqlConnection("Data Source=.;Initial Catalog=Dados;Integrated Security=True"))

{
    var cliente = conn.Query<Cliente>

(@"
SELECT
              c.ClienteId As 'Id'
    , e.RazaoSocial
    , e.Cnpj
FROM Cliente c
INNER JOIN Empresa e ON e.EmpresaId = c.EmpresaId
WHERE
    c.ClienteId = @id", new { id = 2688 }
).SingleOrDefault();

    Console.WriteLine(cliente.RazaoSocial);
}

Aqui, como podemos notar, somos nós que temos que gerenciar a conexão, mas ao contrário do que ocorre com o DAAB, a forma de se escrever é bem mais simples. E como é possível notar no próprio site do projeto, o uso desta biblioteca é extremamente eficiente.

Event Sourcing - Parte 2

Dando continuidade ao post anterior, por questões de simplicidade,  armazenávamos os eventos gerados para uma entidade em memória, algo que não é útil em ambiente de produção.

Como alternativa a isso, podemos recorrer à algum repositório que persista fisicamente as informações. O uso de uma base de dados relacional pode ser útil, porém é necessário que você utilize colunas que possam armazenar o objeto serializado (varbinary, varchar(max), etc.).

Isso é necessário porque é difícil prever o schema. Podem haver muitos eventos, novos eventos podem acontecer, novas informações podem ser propagadas; será difícil evoluir o schema na mesma velocidade.

Uma alternativa aqui é utilizar uma base no-sql. Apesar de alguns nomes já virem a cabeça, existe uma chamada GetEventStore. Ela foi desenhada para dar  suporte à cenários de event sourcing, e sua API também dispõe de métodos e facilitadores que permitem gravar e ler eventos sem dificuldades.

O GetEventStore também permite o acesso a leitura e gravação através de HTTP, possibilitando assim que outras tecnologias, incluindo JavaScript, possam também usufruir dela.

Por fim, ele também permite a gestão da base de eventos através de um interface web, onde podemos interagir, diagnosticar e monitorar os recursos que são necessários para o seu funcionamento.

O vídeo abaixo altera o projeto criado anteriormente para fazer uso do GetEventStore. E tudo o que precisamos alterar é a classe RepositorioDeEventos; o resto da aplicação se comporta da mesma forma, porém com os dados sendo persistidos fisicamente. Se desejar, pode baixar o projeto clicando aqui.

Event Sourcing - Parte 1

Todos temos uma conta bancária. Além das informações cadastrais, muito provavelmente ela também possui o saldo atual, que representa o quanto temos naquele exato momento, podendo ser positivo, negativo ou simplesmente zero.

O saldo, subindo ou descendo, queremos, obrigatoriamente, saber como ele chegou naquele valor. É importante saber o saldo que tínhamos em uma determinada data (retroativa) e o que aconteceu para ele atingir o saldo atual.

Felizmente todos os bancos dão aos seus correntistas o extrato de sua respectiva conta. É com ele que vamos conseguir enxergar o aumento ou diminuição do nosso dinheiro. Posicionado em uma data, identificando o saldo daquele dia, se viermos somando os valores (lembrando que pode ser positivo ou negativo), chegaremos ao saldo atual da conta. Claro, se nada estiver errado.

Tecnicamente falando, é provável que no banco de dados tenhamos uma estrutura de duas tabelas, onde uma teria os dados cadastrais da conta e a outra os lançamentos. A tabela pai provavelmente também irá armazenar o saldo atual da conta e outras características.



A medida em que os lançamentos são inseridos na segunda tabela, a coluna saldo da primeira vai sendo sensibilizado para refletir o saldo atual da conta. O valor atual do saldo vai sendo sobrescrito com o novo valor.

Já está claro que o importante para o dia a dia é o valor que temos disponível, ou seja, o saldo atual. Os lançamentos são necessários, até então, apenas para histórico; na maioria das vezes, eles são dispensáveis para operar a conta corrente (pagar contas, realizar saques, sacar dinheiro, etc.). Agora, considere o exemplo abaixo:



Repare que o saldo atual que temos não corresponde a soma dos lançamentos da segunda tabela. É provável que nosso sistema possua um bug, que afeta diretamente a atualização do saldo atual.

Apesar de, teoricamente, os lançamentos estarem corretos, é difícil diagnosticar o problema, já que não é possível, de uma forma fácil, reproduzir os mesmos eventos, exatamente da forma em que eles aconteceram, para só assim identificar onde e quando o problema de fato ocorreu.

Eis que entra em cena o Event Sourcing. Ao invés de armazenar o estado atual da entidade em colunas e linhas, o uso desta técnica determina que se armazene uma sequência (stream) de eventos, onde cada um deles informa a ação que foi executada sobre a entidade. Quando esses eventos são recarregados, eles são reaplicados contra a entidade, reconstruindo a mesma. Depois de todos eventos processados, a entidade terá, novamente, o seu estado (versão) atual. Neste modelo não fazemos a persistência física das propriedades na base de dados.

Versionamento

Uma das preocupações neste modelo é com o versionamento da entidade. É através de versão que vamos controlar o processamento e aplicação dos eventos. A versão irá garantir que um evento não seja aplicado à entidade depois que outro evento tenha se antecipado e alterado o estado da mesma em relação ao momento em que ela foi construída, ou melhor, carregada.

Isso é conhecido como concorrência otimista. É algo que também já fazemos com o banco de dados relacional, comparando o valor anterior com o atual que está na tabela antes de proceder com o comando de UPDATE ou de DELETE. O controle pode ser feito de várias maneiras, mas para ser simplista, podemos nos ater ao uso de um número inteiro, que vai sendo incrementado a medida em que um novo evento é aplicado.

Snapshots

Ao longo do tempo, mais e mais eventos vão sendo adicionados e, eventualmente, a performance pode ser degradada. Neste caso, uma possível solução é a criação de snapshots, que depois de carregado os eventos, reaplicados na entidade, ela pode disponibilizar métodos para expor o seu estado atual.

Os snapshots também são armazenados e quando a entidade for recarregada, primeiramente devemos avaliar a existência dele antes de reaplicar os eventos. Se houver um snapshot, restauramos o estado da entidade e reaplicaremos os eventos que aconteceram do momento do snapshot para frente, não havendo a necessidade de percorrer toda a sequência de eventos, ganhando assim em performance. Tão importante quanto o estado atual, o snapshot também deve armazenar a versão atual da entidade, pois esta informação é essencial para o correto funcionamento dos snapshots. Essa rotina de criação de snapshots pode ser feita em background, em uma janela de baixa atividade do sistema, podendo ser implementado através de uma tarefa do Windows que roda periodicamente ou até mesmo com um Windows Service. Para a criação de snapshots, podemos recorrer ao padrão Memento, que nos permite expor e restaurar o estado sem violar o encapsulamento do objeto.

Naturalmente o cenário de conta corrente que vimos acima quase se encaixa perfeitamente neste modelo, exceto pelo fato de termos o saldo atual sumarizado e armazenado fisicamente. Vamos então transformar uma simples classe com uma coleção de lançamentos em um modelo que suporte a técnica descrita pelo Event Sourcing. Vale lembrar que isso é "apenas" mais uma forma de persistência. Você pode ter situações que talvez esse modelo seja útil e necessário, já para outros ambientes, pode ser considerado overkill.

MessageQueue e HTTP

Quando trabalhamos com o Microsoft Message Queue, serviço de mensagens fornecido pelo Windows, ele nos permite expor as filas através do protocolo HTTP. Isso permitirá apenas o envio de mensagens através deste protocolo (podendo também ser via HTTPS), e com isso, clientes que estiverem além dos limites da empresa, poderão enfileirar mensagens para o processamento interno de alguma rotina que rodará periodicamente.

Apenas o envio é permitido via HTTP, pois somente a porta 80 é necessária para executar esta ação; para recepcionar a mensagem, é exigido que outras portas também estejam abertas no firewall, e devido a isso, a Microsoft optou por não permitir o recebimento de mensagens sobre estes protocolos.

Para fazer uso deste recurso, ao instalar o Message Queue no computador que receberá as mensagens, você deve marcar a opção de suporte ao protocolo HTTP, conforme é mostrado na imagem abaixo. Vale lembrar que esta opção exige que o IIS (Internet Information Services) também esteja instalado nesta mesma máquina.


Depois de instalado, o Message Queue cria uma diretório virtual dentro do IIS, que servirá o ponto de acesso (HTTP/S) para as filas criadas nesta máquina. Abaixo vemos a fila chamada "Exemplo" criada e o diretório virtual chamado "MSMQ".


Por fim, ao fazer uso da API System.Messaging, podemos apontar para o endereço HTTP, combinando o diretório virtual criado com o nome da fila para qual queremos enviar a mensagem.

using (var mq = new MessageQueue("FormatName:DIRECT=http://localhost/msmq/private$/exemplo"))
    mq.Send("Algo Aqui", MessageQueueTransactionType.Single);

Apesar disso ser possível, considere o uso do WCF com MessageQueue.

Desenvolvimento de UIs

Por mais simples e fina que ela seja, uma das partes mais importantes de um sistema deskop, web ou mobile, é a interface gráfica. Por mais complexo e tecnológico que seja as camadas mais baixas da aplicação, independentemente dos padrões de arquitetura e projeto que utilize, se é uma base de dados relacional ou No-SQL, etc., é a interface com o usuário que receberá a maior parte das interações e é ela que inicialmente é apresentada quando se propõe ou vendo uma solução.

Além da interface ser a principal responsável por capturar informações e devolver respostas (de sucesso ou falha), é ela também quem exibe os dados para que os usuários realizem a análise gerencial e operacional da empresa para qual o sistema foi desenvolvido.

Apesar de existir uma porção de artigos e documentações que descrevem boas práticas para a construção de interfaces, o bom entendimento do negócio garantirá aos desenvolvedores a criação e otimização de telas para que em pouco espaço, sejamos capazes de apresentar poucos dados, mas com muitas informações.

A ideia é repensar a forma como as construímos, tendo em mente a atividade (ou tarefa) que o usuário irá desempenhar naquela tela. Entender a essência do problema é crucial para o sucesso da criação das telas, e o usuário que é o responsável por executar a tal atividade dentro da empresa é a melhor pessoa com quem se pode falar para extrair as dificuldades/exigências. Em seguida, temos que tentar abstrair tudo de importante que foi colhido, e por fim, implementar.

Abaixo estão algumas técnicas que utilizamos na construção de aplicações para a empresa em que trabalho. A ideia aqui é exibir alguns conceitos, dificuldades e soluções que foram encontradas e implementadas. Apesar dos exemplos serem aplicações desktop (WPF) e web, eles não se limitam a estes cenários; nada impede de utilizar estes conceitos na construção de um aplicativo móvel.

Observação: clique nas imagens para visualiza-las em um tamanho maior.

Dashboards

Todos os funcionários da empresa têm atividades diárias. Algumas pessoas estão condicionadas a executar sempre as mesmas tarefas, analisar os mesmos dados. Se conseguirmos mapear tudo o que ele deve olhar e monitorar, podemos criar um painel que concentra todas as informações que coordenarão o seu dia.

Bem-vindo aos dashboards. Assim como o painel de um carro, que centraliza todas as informações relevantes para a condução e monitoramento do mesmo, os dashboards em uma aplica��ão tem o mesmo sentido. Se começar a setorizar a sua empresa, irá perceber que cada departamento terá ações distintas (muitas vezes, sobre os mesmos dados), e antes dele sair analisando um a um para saber se há problemas, porque já não apontar as divergências para que ele resolva?

Além disso, os dashboards muitas vezes exibem informações e índices sobre a saúde de algum recurso da empresa, sobre dados financeiros, etc., para que pessoas de nível gerencial possam acompanhar e tomar as decisões cabíveis para cada situação. Reportar periodicamente tais informações garante que ele possa reagir assim que detecta algo suspeito.



Análises

Além dos dashboards que dão uma visão mais panorâmica sobre certos aspectos, há telas que podem concentrar informações mais detalhadas a respeito de algo ou alguém. Por exemplo, qual o risco atual do cliente XYZ? Qual a posição do meu estoque?

Quando colocamos várias informações lado a lado, temos que de alguma forma evidenciar algumas delas, separando muitas vezes por severidade. Existem índices que servem como histórico, enquanto outros indicam a quantidade e total de títulos vencidos que a empresa possui. Saber diferenciar entre essas duas informações garantirá o sucesso da tela, pois é com essa segmentação do que é ou não crítico que vamos utilizar para desenhar e destinar os espaços necessários para cada seção.

O tamanho da fonte é um importante aliado no momento em que precisamos destacar certos valores. A parte superior da tela sempre me pareceu o primeiro ponto em que olhamos quando a mesma é aberta. E aproveitando este instinto, podemos concentrar em uma linha horizontal as informações mais críticas do cliente, enquanto o restante da tela exibe informações de menor severidade, mas não tão menos importantes.



É claro que isso não é uma regra. Você pode optar por utilizar o canto direito, canto esquerdo, etc., para concentrar informações de análise acerca do que está olhando naquele momento. Abaixo está um outro cenário, agora web, que apresenta a situação atual de uma empresa na mesma tela em que exibe os seus títulos em aberto e algumas outras informações de histórico.



Por fim, é importante dizer que nada é estático. A maioria dos índices podem ser (e na maioria das vezes serão) clicáveis. O que quero dizer é que ao clicar no total de vencidos, será apresentado uma tela contendo os títulos que compõem aquele valor.

Repare que essa forma muda como o usuário pensa. Algumas vezes, tudo o que o usuário quer saber é quanto a empresa XYZ está devendo; não importa quantos títulos são. Em geral, o que se apresenta é uma tela contendo vários tipos de filtros, onde um deles é o código/CNPJ da empresa em questão, que ao clicar em filtrar, os 4822 títulos são listados.

Neste outro modelo proposto, basta ele chegar até a empresa e já terá uma sumarização, as pendências não concluídas, sendo possível apontar os títulos que estão vencidos (que ele precisa atuar).

Cores

O tamanho da fonte não é o seu único aliado no momento de destacar informações. As cores também podem representar muita coisa. Além do verde (positivo), vermelho (negativo), azul (indiferente) e amarelo (alerta), podemos abusar das mais diversas cores para indicar algum comportamento, evolução ou tendência de alguma informação.

As cores não se restringem apenas aos índices. Você pode optar por colorir diversas partes do sistema, como por exemplo, o contorno de uma janela que exibe a nota daquele cliente. Repare que a nota, o número propriamente dito, começa a ser irrelevante, já que todo o contexto gráfico indica que aquele é um cliente ruim, independente da escala utilizada (1 a 5, 1 a 10, 10 a 100, etc.).





Gráficos

Gráficos são recursos que cabem na maioria das aplicações. O importante é sempre ter um bom componente à mão para que seja fácil a criação, já que gráficos podem ser criados aos montes, cruzando diversos tipos de informações.



O maior desafio não é a parte técnica, mas sim as informações que compõem o gráfico. Com o conhecimento do negócio é possível que você sugira algumas opções predefinidas, mas é o usuário, com o conhecimento e experiência, que pode indicar quais os melhores gráficos para enriquecer a aplicação.

Sempre há uma discussão em torno disso, que é: criar manualmente cada gráfico ou disponibilizar os dados para que alguma ferramenta externa (Excel, Power BI, etc.)? É uma decisão difícil, pois a construção de gráficos é estática, ou seja, se quiser um novo gráfico, precisamos criar uma nova tela, extrair os dados e exibir, compilar e redistribuir, ao contrário dessas ferramentas específicas, que recebem um conjunto de dados e você molda como preferir.

Ao meu ver, a vantagem de ter o gráfico dentro da aplicação é a possibilidade de inserir o mesmo em um contexto específico, como por exemplo, colocar lado a lado com outras informações que estão sendo analisadas no momento da aprovação de alguma operação.



Contexto

Informações de contexto são sempre bem-vindas quando está analisando ou tomando alguma decisão. São informações que estão conectadas de alguma forma com o que está sendo analisado, e não estou me referindo à banco de dados aqui. Situações onde você está avaliando o crédito para uma empresa e vê que ela está devendo R$ 200.000,00 no mercado, e com apenas um clique, você consegue chegar rapidamente a lista de credores e seus respectivos valores.

Elas podem estar na mesma tela de análise, mas mesmo que haja espaço suficiente para ela, sacar a informação de algum outro lugar pode ser mais interessante, pois evita o risco de poluir demasiadamente a tela. Temos que ponderar entre a discrição e a facilidade de acesso à informação. Considere o exemplo abaixo, onde há uma operação de crédito sendo analisada, qual já possui diversos parâmetros, mas se o usuário desejar, pode acessar as últimas operações realizadas para definir a taxa que será aplicada à operação corrente. A janela que aparece é apenas uma tooltip.



Interação

Nem sempre as aplicações têm uma área de ajuda, pois não é algo fácil de fazer e, principalmente, de manter. Muitas vezes ela nunca é utilizada, pois é mais fácil ligar para alguém e perguntar sobre a dificuldade em fazer alguma atividade dentro do sistema.

Ao invés de criar uma área de ajuda, que concentra 100% das informações e detalhamento de como utilizar seções do sistema, a forma mais simples e intuitiva seria espalhar pelas telas da aplicação informações que ajudem o usuário a esclarecer possíveis dúvidas que ele tenha no momento da realização de uma determinada tarefa.



Repare que com poucas palavras é possível esclarecer um termo que à primeira vista parece ser complexo e confunde o usuário na escolha. A mesma regra pode se aplicada na descrição de um link que ele pode estar na dúvida em qual deles escolher:



Feedback

Reportar falha de execução de alguma atividade (seja de infraestrutura ou de negócio) é fácil, pois basta indicar o motivo do erro e pedir para o usuário tentar novamente. Mas o grande desafio é notificar, de forma simples e objetiva, a conclusão da mesma.

Não estou falando aqui de "cadastro realizado com sucesso", "operação enviada para análise", etc., mas sim de exibir detalhes precisos da atividade que o usuário acabou de realizar. Quando você dá entrada em algum órgão, é comum eles darem um protocolo indicando o número, a data, o prazo de conclusão, o nome, etc. Poderíamos utilizar a mesma regra aqui, ou seja, quando a atividade for concluída, podemos exibir na tela detalhes que o deixem seguro de que a mesma foi executada com sucesso.



É claro que isso nem sempre é possível ou viável. Processos simples também demandam mensagens mais simples. Mas considere mesmo estes casos simples para ser mais esclarecedor com o usuário, indicando com mais ênfase o que acabou de ser realizado. Opte por "A empresa teve a sua razão social alterada de Israel Aece-ME para Israel Aece Ltda." ao invés de "Alteração realizada com sucesso.".

Foco em Tarefas

No nível mais baixo, sabemos que tudo o que um sistema faz em uma base de dados é inserir, atualizar, excluir e ler os dados(CRUD). Só que se ficarmos preso a este modelo de trabalho, vamos construir telas que simplesmente farão estas quatro operações básicas, orientando elas a serem voltadas para a estrutura do nosso banco de dados.

Só que as telas podem dizer muita coisa sobre o comportamento e as atividades que devemos fazer com os dados, que nem sempre se referem à apenas uma única entidade. Muitas vezes a atividade é composta de diversas outras subatividades, que contribuem para a totalidade de uma tarefa a ser executada.

Imagine que você tem um cliente e ele está bloqueado de operar. A implementação mais trivial possível no banco de dados é ter uma tabela chamada "Cliente" com um flag chamado "Bloqueado". Como seria a construção da tela que desbloqueia o cliente?

Geralmente temos um formulário que você edita todos os dados, e nele está incluído um checkbox que está associado à coluna "Bloqueado". Ao clicar no botão atualizar, os dados são persistidos na base de dados e o cliente está apto a operar novamente. Mas essencialmente será que o desbloqueio seria apenas isso? Será que o desbloqueio não seria algo muito maior, que depende que alguém solicite e outra pessoa seja responsável por analisar o motivo e autorizar o não o desbloqueio? Será que não precisamos de histórico de alteração? Qual a justificativa para o desbloqueio? Qual a razão de aceitar o desbloqueio?



Isso é um exemplo de uma tarefa que você executa contra o seu cliente, ou seja, solicitar o seu desbloqueio. Não é um simples formulário de edição. Se você pensar orientando à tarefas que podem ser executadas naquela entidade, é provável que nem precise de um formulário para isso. Aos poucos você começa a perceber que as telas da aplicação desencadeiam ações.



Suas telas deixam de serem formulários baseados na base de dados. Elas possam a ser desenhadas de acordo com a atividade que aquele usuário deve desempenhar. Se ele é responsável por aprovar a alteração de vencimento de boletos, então podemos relacionar os que estão atualmente na sua alçada e permitir com que ele possa selecionar os que deseja aprovar e escolher a opção de conclusão.



Relatórios

É uma tentação por parte dos usuários. Sempre quando eles querem visualizar alguma massa de dados, eles falam em "relatórios". Para mim, relatório é o que se imprime, ou seja, aquilo que se manda para a impressora. Mesmo que relatórios sejam indispensáveis para aplicações, não quer dizer que eles devem ser utilizados como forma de exibir um conjunto de dados em tela.

Algumas aplicações, por questões de simplicidade, utilizam relatórios para exibição, por exemplo: listar todos os títulos vencidos do Israel, clientes com divergências cadastrais, empresas localizadas em São Paulo, etc. Apesar de isso ser possível, torna a relação estática, ou seja, não é possível clicar no cliente que está sem endereço e ajustar. Teria que imprimir, abrir a tela de cadastro de clientes, localizar o mesmo, e em seguida, fazer a alteração.

Como provavelmente existe uma pessoa responsável pelo cadastro dos clientes, então no dashboard dela poderia existir um índice que indica a quantidade de clientes com divergências, e ao clicar, uma tela seria apresentada com esta relação; bastaria dar um clique e já ajustar o endereço e ao sair, a relação já seria sensibilizada e o cliente recém alterado iria desaparecer. As telas tem um grande poder, pois pequenas sumarizações servem também de filtro para uma listagem, sem a necessidade de ter um formulário de pesquisa para isso.



Outro ponto importante a ser considerado é segurança. A impressão de relatórios permite com que algum funcionário mal-intencionado imprima e entregue a relação de clientes para um concorrente. Não que com a tela seja impossível de acontecer, mas chama mais atenção alguém fotografando a tela do computador.

Os relatórios precisam ser pensados com cautela, pois como disse acima, é uma tentação para os usuários. É importante entender a essência do problema, focando na atividade que vem após a impressão. Isso nos dará a oportunidade de construir uma tela que atenda a necessidade deles sem a impressão física da relação de dados.

E claro, não há como escapar de desenvolver relatórios. E sem querer se contraditório, eles são sempre bem-vindos.



A ideia deste artigo foi apenas mostrar algumas técnicas que utilizamos no desenvolvimento das aplicações para empresa onde trabalho. Não há nenhuma regra que deve ser assegurada aqui. São apenas experiências que foram vivenciadas e que a escolha, no meu ambiente, se fez válida. Use como quiser.