Introdução à Modularização Transverasl
A Programação Orientada a Aspectos (AOP) representa um paradigma focado na separação de preocupações transversais (cross-cutting concerns). Diferente das abordagens tradicionais, o AOP permite isolar funcionalidades que afetam múltiplos pontos do sistema, como logging, segurança e transações, sem poluir a lógica de negócio principal.
Evolução dos Paradigmas de Desenvolvimento
Para compreender a necessidade do AOP, é útil analisar a trajetória das metodologias de software:
Programação Orientada a Processos (POP)
Nesta abordagem, o foco reside na sequência lógica de execução. O desenvolvedor define passos específicos e os implementa através de funções chamadas ordenadamente. Embora eficiente para tarefas lineares e de baixo escopo, o POP tende a gerar código acoplado e de difícil manutenção quando a complexidade do sistema aumenta, pois a reutilização de módulos é limitada.
Programação Orientada a Objetos (OOP)
Com o surgimento de sistemas complexos, o OOP trouxe a abstração baseada em objetos, encapsulando estado e comportamento. Conceitos como herança e polimorifsmo permitem modelar domínios ricos. No entanto, o OOP opera principalmente numa dimensão vertical. Quando uma funcionalidade precisa ser aplicada horizontalmente em diversas classes (ex: validar permissão antes de cada método), o OOP exige repetição de código ou hierarquias excessivas.
O Papel do AOP
O AOP não substitui o OOP, mas complementa suas lacunas. Enquanto o OOP modela entidades (nomes), o AOP modela ações e processos (verbos) que cruzam essas entidades. Isso reduz o acoplamento e centraliza regras comuns, facilitando a evolução da arquitetura sem modificar o núcleo da aplicação.
Estratégias de Implementação em C#
Existem duas formas principais de aplicar aspectos: estaticamente, através de padrões de projeto, ou dinamicamente, utilizando proxies gerados em tempo de execução.
1. Proxy Estático (Padrão Decorator)
Nesta técnica, cria-se manualmente uma classe que envolve o objeto original. O decorador implementa a mesma interface e adiciona comportamento antes ou depois da chamada real.
// Definição do contrato de negócio
public interface IOrderHandler
{
Task SubmitOrder(Order order);
}
// Implementação core do serviço
public class OrderProcessingService : IOrderHandler
{
public Task SubmitOrder(Order order)
{
Console.WriteLine($"Processando pedido para {order.CustomerName}");
return Task.CompletedTask;
}
}
// Decorador para validação de segurança
public class SecurityOrderDecorator : IOrderHandler
{
private readonly IOrderHandler _innerService;
public SecurityOrderDecorator(IOrderHandler innerService)
{
_innerService = innerService;
}
public async Task SubmitOrder(Order order)
{
// Lógica prévia: Validação de Fraude
await ValidateFraudRisk(order);
// Chamada ao serviço real
await _innerService.SubmitOrder(order);
// Lógica posterior: Notificação
await SendConfirmation(order);
}
private Task ValidateFraudRisk(Order order)
{
Console.WriteLine("Verificando risco de fraude...");
return Task.CompletedTask;
}
private Task SendConfirmation(Order order)
{
Console.WriteLine("Enviando confirmação ao cliente...");
return Task.CompletedTask;
}
}
2. Proxy Dinâmico via Reflection
Frameworks utilizam reflexão para interceptar chamadas de método sem exigir wrappers manuais. Abaixo estão exemplos utilizando DispatchProxy (nativo .NET) e Castle.DynamicProxy.
Implementação com DispatchProxy
public class MonitoringProxy<T> : DispatchProxy where T : class
{
public T? Target { get; set; }
public Action? OnStart { get; set; }
public Action? OnComplete { get; set; }
public static T CreateProxy(T target, Action start, Action complete)
{
var proxy = Create<T, MonitoringProxy<T>>() as MonitoringProxy<T>;
proxy.Target = target ?? Activator.CreateInstance<T>();
proxy.OnStart = start;
proxy.OnComplete = complete;
return proxy!;
}
protected override object? Invoke(MethodInfo targetMethod, object?[] args)
{
OnStart?.Invoke();
var result = targetMethod.Invoke(Target, args);
OnComplete?.Invoke();
return result;
}
}
// Utilização
var order = new Order { CustomerName = "João Silva" };
var service = new OrderProcessingService();
var proxiedService = MonitoringProxy<IOrderHandler>.CreateProxy(
service,
() => Console.WriteLine("Início da monitoria"),
() => Console.WriteLine("Fim da monitoria")
);
proxiedService.SubmitOrder(order);
Implementação com Castle DynamicProxy
public class PerformanceInterceptor : IInterceptor
{
private static readonly ProxyGenerator _generator = new ProxyGenerator();
public Action? BeforeInvoke { get; set; }
public Action? AfterInvoke { get; set; }
public static T Intercept<T>(T target, Action before, Action after) where T : class
{
return _generator.CreateInterfaceProxyWithTarget(
target,
new PerformanceInterceptor { BeforeInvoke = before, AfterInvoke = after }
) as T ?? throw new InvalidOperationException("Falha na criação do proxy");
}
public void Intercept(IInvocation invocation)
{
try
{
BeforeInvoke?.Invoke();
invocation.Proceed();
AfterInvoke?.Invoke();
}
catch (TargetInvocationException ex)
{
throw ex.InnerException ?? ex;
}
}
}
// Utilização
var service = new OrderProcessingService();
var interceptedService = PerformanceInterceptor.Intercept<IOrderHandler>(
service,
() => Console.WriteLine("Calculando métricas..."),
() => Console.WriteLine("Métricas salvas.")
);
interceptedService.SubmitOrder(order);