Interface IApplicationLifetime

Desde o ASP clássico nós temos um arquivo chamado Global.asa. Com o ASP.NET, este arquivo foi renomeado para Global.asax. A finalidade deste arquivo é fornecer uma forma de interceptar eventos que ocorrem no nível da aplicação. Entre estes eventos, nós temos um que é disparado indicando que a aplicação foi inicializada, outro que algum erro não tratado aconteceu, que uma nova requisição chegou, que a aplicação está sendo encerrada, etc.

Este arquivo já não existe mais no ASP.NET Core, porém a necessidade ainda existe em alguns tipos de aplicação. Para interceptar a inicialização da aplicação e o término dela, podemos recorrer à interface IApplicationLifetime. Esta interface fornece três propriedades que indica a inicialização da aplicação (ApplicationStarted), que ela está sendo encerrada (ApplicationStopping) e depois de encerrada (ApplicationStopped). Essas propriedades são do tipo CancellationToken, e através de um delegate do tipo Action, passamos para o método Register o código customizado que queremos executar quando estes eventos acontecerem.

Para exemplificar o uso e utilidade desta interface, considere o exemplo abaixo onde temos uma classe estática que gerencia o agendamento e execução de tarefas pelo Quartz.NET. Além do método Schedule, temos os métodos Start e Stop, que como já podemos perceber, queremos invocar cada um deles no início e no fim da aplicação.

public class TaskManager
{
    public static void Start()
    {
        Scheduler = StdSchedulerFactory.GetDefaultScheduler().Result;
        Scheduler.Start();
    }

    private static IScheduler Scheduler { get; set; }

    public async static Task Schedule<TJob>(IDictionary data) where TJob : IJob
    {
        await Scheduler.ScheduleJob(
            JobBuilder
                .Create<TJob>()
                .SetJobData(new JobDataMap(data))
                .Build(),
            TriggerBuilder
                .Create()
                .WithSimpleSchedule(x => x.WithRepeatCount(0))
                .Build());
    }

    public static void Shutdown()
    {
        Scheduler?.Shutdown(true);
    }
}

O método Start deverá ser chamado quando a aplicação estiver sendo inicializada. Já no momento, do encerramento, recorremos ao método Shutdown do Schedule, passando o parâmetro true, que indica que antes de encerrar o gerenciador de tarefas, é necessário aguardar o término de alguma que, eventualmente, ainda esteja em execução. A interface IApplicationLifetime, assim como diversas outras do ASP.NET Core, o próprio runtime cria a instância de uma implementação padrão e passa para o método Configure da nossa classe Startup. Com isso, basta associarmos os métodos aos respectivos eventos, conforme é possível visualizar abaixo:

public void Configure(
    IApplicationBuilder app, 
    IHostingEnvironment env, 
    ILoggerFactory log, 
    IApplicationLifetime lifetime)
{
    lifetime.ApplicationStarted.Register(() => TaskManager.Start());
    lifetime.ApplicationStopped.Register(() => TaskManager.Shutdown());

    //outras configurações
}

Por fim, a aplicação (os controllers) interagem com a classe TaskManager a partir do método Schedule<TJob>, indicando qual a tarefa que precisa ser executada e parâmetros contextuais que queremos passar para a implementação da mesma.

[HttpPost]
public async Task<IActionResult> AgendarExecucao()
{
    await TaskManager
        .Schedule<GeracaoDeDados>(
            Request.Form.ToDictionary(x => x.Key, x => x.Value.ToString()));

    return View("ExecucaoAgendada");
}

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.

NVarchar e NHibernate

Pode não ser novidade, mas as vezes podemos cair em algumas armadilhas que se não ficarmos atentos, podemos ter problemas de performance relacionados à campos do tipo de dado string. Apesar de focar o artigo no NHibernate, não é um problema somente dele, na verdade, acaba sendo mais um problema do banco de dados, pois ele pode acontecer independente se estiver ou não utilizando algum ORM na aplicação. Considere as consultas abaixo:

SELECT * FROM Duplicata WHERE PrefixoDoCnpj = N'111111111'
SELECT * FROM Duplicata WHERE PrefixoDoCnpj = '111111111'

O campo PrefixoDoCnpj é um campo do tipo varchar(11) na base de dados. Temos também um índice sobre este campo para otimizar as pesquisas por ele, afinal, é muito comum querer retornar todas as duplicatas de um determinado CNPJ. A única diferença que se nota em ambas as consultas é que a primeira tem o caractere “N” antes do valor do parâmetro. Isso serve para indicar ao SQL Server que o tipo de dado é nvarchar. Ao rodar as consultas, a primeira será muito mais lenta que a segunda versão, que utiliza o mesmo tipo de dado da coluna (varchar). Para entender melhor a diferença, vamos analisar os planos de execução:

nhnvarchar

Na primeira consulta, onde o parâmetro tem um tipo de dados diferente do tipo de dado da coluna, ele está fazendo um index scan, obrigando o SQL Server a tocar em cada uma das linhas do índice e fazer a conversão implícita de cada um dos valores (pode-se perceber isso na imagem acima), e sendo assim, o custo está diretamente associado à quantidade de linhas que a tabela tenha. Já a segunda consulta, o SQL Server opta por fazer uso do index seek, que recorre a forma de pesquisa onde ele extrai diretamente os registros que satisfazem o critério desejado.

Tudo o que vimos acima tem a ver exclusivamente com SQL Server. Se optarmos por utilizar o NHibernate para que ele faça a geração da base de dados a partir do nosso domínio, ao utilizar as configurações padrão, todos os campos strings serão mapeados como Unicode, ou seja, nvarchar. Além dos problemas de performance que vimos acima, por aceitar caracteres Unicode, precisamos de mais espaço de armazenamento, e dependendo do tipo de aplicação que está se desenvolvendo, isso não é necessário. E ainda, se criamos scripts manuais para a criação/manutenção de itens da base dados, optando pelo tipo de dado varchar e no NHibernate sermos omissos em relação à isso, o problema da degradação da performance acontecerá.

Para deixarmos explícito para ao NHibernate que ele deve utilizar varchar ao invés de nvarchar, podemos definir o tipo de dado da propriedade como AnsiString. Isso indicará ao SQL Server a tratar esta coluna da forma que desejamos (varchar), e além disso, podemos já definir o atributo sql-type para ser utilizado na geração do scripts de criação da base de dados se isso for necessário.

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="AppDeExemplo" namespace="AppDeExemplo">
  <class name="Sacado">
    <id name="PrefixoDoCnpj" length="11" type="AnsiString">
      <column name="PrefixoDoCnpj" sql-type="varchar(11)" length="11" />
      <generator class="assigned" />
    </id>
    <!-- Demais Configurações -->
    <bag name="Duplicatas">
      <key column="PrefixoDoCnpj" />
      <composite-element class="Duplicata">
        <!-- Demais Configurações -->
      </composite-element>
    </bag>
  </class>
</hibernate-mapping>

Segregando Requisições no ASP.NET

No ASP.NET Core temos o conceito de middlewares, que nada mais são que plugins que podemos acoplar à execução para que eles sejam executados a medida em que uma nova requisição chega para o serviço ou para o site. Além de vários que já foram criados pela Microsoft, a criação destes middlewares é extremamente simples, e é a principal forma que temos de interceptar o momento antes e/ou depois das requisições.

Durante a inicialização da aplicação, devemos realizar as configurações necessárias para que ela funcione, e é neste momento que montamos o pipeline por onde cada uma delas deverá passar, e isso quer dizer que faremos uso constante de middlewares, mesmo que não se esteja explicitamente claro no código que vamos escrever. A execução ocorre de forma linear, onde a requisição é entregue para o primeiro middleware e este é responsável por fazer alguma atividade e passar adiante a requisição (leia-se para o middleware interno). Abaixo temos um exemplo de um simples middleware; note que temos algumas convenções que precisam ser respeitadas, tais como: receber no construtor o próximo middleware, ter o método chamado Invoke que será disparado pelo ASP.NET no momento adequado. É dentro deste método que você define quando quer invocar o próximo middleware.

public class LoggingMiddleware
{
    private readonly RequestDelegate next;

    public LoggingMiddleware(RequestDelegate next)
    {
        this.next = next;
    }

    public async Task Invoke(HttpContext context)
    {
        //Ação Antes
        await next(context);
        //Ação Depois
    }
}

O problema aqui é que podemos hospedar na mesma aplicação dois conteúdos que demandam diferentes rotinas e características. É comum encontrarmos aplicações que expõe uma API web, e na mesma aplicação, fornece o acesso à um pequeno site com a documentação, tutoriais, central de suporte para a mesma. Do lado da API, é comum termos log de acesso, telemetria, controle de transações, etc., enquanto do “lado do UI” da aplicação, necessitamos de outros recursos, como a exposição de conteúdos estáticos e geração de HTML.

O ASP.NET possibilita uma forma de segregar as requisições (branch), roteando cada uma delas de acordo com um critério e, que se atendido, podemos customizar um pipeline de middlewares exclusivo para aquele cenário. Isso evita ter um único pipeline com todos os passos e ter que dar by-pass de acordo com a requisição que estamos processando naquele momento exige. Para isso, podemos recorrer ao método Map da interface IApplicationBuilder. Em sua versão mais simplista, ele aceita uma string que permite especificarmos a URL que, se atendida, deverá ser executada utilizando a configuração que especificamos no segundo parâmetro deste mesmo método. Há uma variação deste método chamado MapWhen, que nos permite inspecionar outros itens da requisição (headers, por exemplo) para tomar a decisão se deve ou não ser processada por aquele pipeline.

No exemplo abaixo, se a requisição conter /commands na URL, então o middleware de logging será disparado, na sequência o de transações, e por fim, a ação do controller será executada. Já quando a requisição for para /help, estamos permitindo o acesso à arquivos estáticos (imagens, PDFs, etc.) e também o uso do MVC para renderizar HTML com o conteúdo da documentação da API:

public void Configure(IApplicationBuilder app)
{
    app.Map("/commands",
        config =>
            config
                .UseMiddleware<LoggingMiddleware>()
                .UseMiddleware<TransactionMiddleware>()
                .UseMvc());

    app.Map("/help", 
        config => 
            config
                .UseMvc()
                .UseStaticFiles());
}

Se desejar ter acesso ao código completo deste artigo, consulte este endereço.

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.

Além dos Tipos Primitivos

Ao criar tipos customizados para a aplicação, é comum utilizarmos os tipos primitivos da linguagem (inteiro, data, decimal, etc.) para especificar os campos que ele deverá conter, podendo trabalhar internamente ou externamente com estas informações. Se não nos atentarmos no momento da criação, é capaz de inflarmos o novo tipo com uma porção de propriedades, que muitas vezes podem estar relacionadas, e prejudicando assim a interface exposta para os consumidores.

Além disso, podemos perder parte do encapsulamento, pois se deixarmos para o consumidor combinar os campos para testar uma condição, pode ser que em algum momento ele se esqueça de avaliar de forma correta e, consequentemente, ter um resultado inesperado. Para exemplificar, considere a classe abaixo:

tipos1

Note que as propriedades estão divididas nas cores vermelha e azul. As propriedades em vermelho dizem a respeito do vencimento do boleto, enquanto as propriedade em azul se referem ao valor do título. O valor a ser pago pelo título é o valor bruto deduzindo o abatimento e o desconto (desde que esteja sendo pago antes da data limite para o mesmo). Já o vencimento agrupa tudo o que é necessário para indicar a real data de pagamento e/ou a quantidade de dias que falta para vencer ou que está vencido.

Se o consumidor quiser saber a data real de vencimento terá que fazer esta análise; se ele quiser saber o valor real do pagamento (deduzindo os descontos), terá que calcular. E o pior, isso deverá ser replicado para todos os pontos da aplicação que precisar disso. Uma solução seria criar propriedades de somente leitura na classe Boleto e já retornar os valores calculados. Só que fazendo isso, voltamos ao problema inicial: comprometer a interface pública do tipo. Mesmo que estivermos confortáveis em prejudicar a interface, pode ter outros tipos dentro do nosso domínio que precisa também lidar com isso (uma nota promissória, por exemplo, também tem vencimento), e tornamos a recriar tudo isso lá.

Para centralizar as regras, padronizar o consumo e reutilizar quando necessário, o ideal é abrir mão dos tipos primitivos e passar a criar tipos específicos para o nosso domínio, que agrupe os campos e forneça os dados já calculados, para que seja possível o consumo unificado das informações.

tipos2

Depois disso, a nossa classe Boleto fica bem mais enxuta, e a medida que vamos se aprofundando nas propriedades, chegamos nos mesmos dados, dando mais legibilidade e tendo a mesma riqueza de informações que tínhamos antes.

public class Boleto
{
    public Vencimento Vencimento { get; set; }
    public Valor Valor { get; set; }
}

var boleto = new Boleto()
{
    Vencimento = DateTime.Now.AddDays(5),
    Valor = 1250.29M
};

Note que se ajustarmos os operadores (overloading), conseguimos converter tipos primitivos em tipos customizados, conforme é possível ver acima, e tornar ainda mais sucinto o código.

Por fim, os ORMs que temos hoje permitem a serialização de todo o tipo ou de parte dele. Fica a nosso critério o que armazenar, ou seja, se optamos apenas pelos dados que originam o restante ou se armazenamos tudo o que temos, mas não é uma decisão fácil de tomar. Se optar por armazenar tudo, você ocupará mais espaço em disco; em contrapartida, se guardar apenas os dados que originam o restante e utilizar algum outro mecanismo para extração dos mesmo (DAL) sem passar pelo domínio, você não conseguirá reconstruir algumas informações sem replicar a regra na sua camada de leitura.

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.