Substituindo o Framework de AOP

Quando desenvolvemos um software, ou uma parte dele, além da parte relacionada ao negócio em si, nas maioria das vezes, precisamos recorrer a códigos de infraestrutura, que são responsáveis por gerenciar alguns recursos que estão indiretamente relacionados ao processo.

Geralmente mencionamos o termo cross-cutting concerns, que se referem a funcionalidades que podem se estender e afetar várias partes do sistema. Autenticação, Autorização, Logging e Transação, são algumas das funcionalidades que poderiam ser candidatas a serem encaradas e implementadas desta forma.

Apesar de possível, não é interessante misturar este tipo de funcionalidade com o código de negócio, pois facilmente você poderá agregar mais funcionalidade do que deveria. Além disso, a modularização é extremamente importante, pois facilita a manutenção, e eventuais alterações mais complexas. Como estas funcionalidades interceptam várias partes do sistema e em diversos níveis, uma das formas mais comuns de se acoplar isso à execução, é através da técnica de AOP, Aspect Oriented Programming.

Em .NET, existe um framework chamado PostSharp. Este é um framework fornece alguns atributos para decorar em elementos do nosso código, e onde o aplicamos, ele utilizará IL Weaving durante a compilação, reescrevendo o código para adicionar os comportamentos necessários, e durante a execução, serão executados de forma automática (e mágica?), sem que o nossos códigos de negócios mencionem diretamente funcionalidades de cross-cutting.

Apesar do framework fazer um grande trabalho, reescrevendo a IL para injetar o código customizado em torno do local onde o aplicamos, poderíamos trabalhar de forma semelhante, sem qualquer mágica envolvida. Tudo isso, utilizando apenas classes customizadas, onde você poderá ter o controle total, e tudo isso, sem a necessidade de envolver algum framework de terceiro.

Para exemplificar, podemos criar uma classe que representa um comando a ser executado. Basicamente, cada comando carrega as informações necessárias para que o mesmo possa ser executado, como por exemplo: criar, configurar e por fim, gravar um novo cliente na base de dados. Mas o comando por si só não funcionará. Isso quer dizer que vamos precisar de um tratador, ou seja, aquele que irá recepcionar o comando (que contém os dados), executando-o para que o cliente seja efetivamente gravado na base de dados.

Neste comando, poderíamos envolvê-lo em um bloco transacionado, efetuar o logging de algumas informações, mensurar o tempo de execução, etc. Mas como dito acima, colocar códigos de cross-cutting aqui, faria com que tivessemos algumas outras preocupações, e isso, voltaria a acontecer em todos os comandos que nossa aplicação venha a ter. Redundância e descentralização seriam alguns dos problemas que poderíamos enfrentar, sem contar a eventual necessidade de ter que injetar dependências indiretas ao comando (aquelas exigidas pela infraestrutura).

Abaixo temos o diagrama de classes que resume todos os tipos utilizados na solução. Classes sufixidas com a palavra “Handler”, correspondem aos tratadores dos comandos, e como eles estão implementados no formato de russian-dolls, podemos adicionar um dentro do outro, fazendo com que eles sejam executados antes ou depois do outro comando que você adicionou.

Repare que no código abaixo, LogingHandler, ele herda da classe abstrata CommandHandler, recebendo em seu construtor a instância de um próximo comando, qual será executado “entre este comando”.

public class LoggingHandler : CommandHandler
{
private readonly CommandHandler next;

public LoggingHandler(CommandHandler next)
{
this.next = next;
}

protected override void ExecuteInternal(Command command)
{
Console.WriteLine(“* Início da Execução do Comando.”);
this.next.ExecuteCommand(command);
Console.WriteLine(“* Fim da Execução do Comando.”);
}
}

Se evoluirmos um pouco mais o código, podemos criar métodos de extensão, para que cada um deles seja responsável por retornar um novo tratador, envolvendo nele o tratador atual, aquele onde está sendo aplicado a estensão. Neste caso, podemos nomear os métodos, de acordo com a funcionalidade que cada um deles agrega à execução:

public static class Estensoes
{
public static CommandHandler ComTransacao(this CommandHandler handler)
{
return new TransactionHandler(handler);
}

public static CommandHandler MedindoTempo(this CommandHandler handler)
{
return new TimerHandler(handler);
}

public static CommandHandler GerandoLog(this CommandHandler handler)
{
return new LoggingHandler(handler);
}
}

Como os métodos de estensão criados acima estão sendo aplicados ao tipo CommandHandler, podemos agregar ao mesmo qualquer uma das funcionalidades, de uma “forma fluente”. Com todos os tratadores do diagrama acima implementados, podemos escrever um código parecido com esse:

new CadastrarClienteHandler()
.ComTransacao()
.GerandoLog()
.MedindoTempo()
.ExecuteCommand(new CadastrarCliente() { Nome = “Israel” });

E, finalmente, o resultado depois da execução:

  • Início da Execução do Comando ‘CadastrarCliente’.
  • Início do Bloco Transacionado.
  • Cadastrando o Cliente ‘Israel’.
  • Commit da Transação.
  • Fim do Bloco Transacionado.
  • Fim da Execução do Comando ‘CadastrarCliente’.
  • Tempo Decorrido: 00:00:00.0042806.
Publicidade