MÓDULO 05 · 2 SEMANAS · ASPECT-DRIVEN & CROSS-CUTTING CONCERNS

Aspect-Driven & Cross-cutting Concerns

Logging, autenticação, retry, cache, telemetria, transação — tudo o que precisa atravessar o sistema sem se misturar ao domínio. Onde se decide se a base envelhece legível ou vira pântano.

Duração ~2 semanas
Conceitos 14 fundamentais
Projeto API com pipeline de aspects
Pré-requisito Módulos 00, 01, 02, 03 e 04
Manutenibilidade Confiabilidade Observabilidade

Quatorze conceitos para entender por que código de domínio bom raramente sobrevive sem uma camada lateral cuidadosamente desenhada — e como construir essa camada sem cair na armadilha oposta de magia escondida. Da teoria de AOP de Gregor Kiczales aos middlewares de hoje, dos decorators e proxies dinâmicos às policies de retry, do logging estruturado à instrumentação OpenTelemetry. O fio condutor é uma pergunta simples: como manter regras de negócio legíveis quando o sistema precisa também autenticar, registrar, medir, repetir, cachear e transacionar a cada chamada?

01

Cross-cutting concerns: taxonomia e o problema da dispersão

O que faz um concern ser transversal — logging, auth, retry, cache, transação, validação — e por que espalhá-los pelo código de domínio é a fonte mais comum de erosão arquitetural.

estudar →
02

AOP — a teoria de Kiczales

Gregor Kiczales no Xerox PARC (1997), AspectJ, o vocabulário (advice, pointcut, join point, aspect) e os tipos de weaving. O que sobreviveu e o que virou middleware.

estudar →
03

Decorator pattern e higher-order functions

O Decorator do GoF, @decorator em Python, func(http.Handler) http.Handler em Go, proxies em C#. A base funcional do AOP "sem framework".

estudar →
04

Middleware HTTP — pipeline com next

ASP.NET Core middleware, ASGI/WSGI, http.Handler chaining em Go. Ordem importa, short-circuit, onion model — e por que o pipeline é o lugar certo para a maioria dos cross-cutting concerns.

estudar →
05

Interceptors e filters

gRPC interceptors (unary/stream), .NET ActionFilters e Endpoint filters, FastAPI Depends, hooks orientados a método em vez de request — e quando preferir cada um.

estudar →
06

Proxy dinâmico e weaving

Castle DynamicProxy, .NET source generators / Roslyn, Spring AOP via JDK Proxy/CGLIB. Por que Go evita reflection-AOP e prefere geração de código.

estudar →
07

Logging estruturado como cross-cutting

Serilog, structlog, slog (Go 1.21+). Correlation IDs, contextual fields que propagam, log levels com critério — e por que printf espalhado é dívida técnica disfarçada.

estudar →
08

Observabilidade como aspect

OpenTelemetry SDK, instrumentação automática vs manual, traces e spans atravessando camadas, baggage e propagação de contexto entre serviços.

estudar →
09

Retry, timeout e circuit breaker como policies

Polly (.NET), tenacity (Python), sony/gobreaker (Go). Aplicar resiliência via decorator/policy externo ao domínio — e por que try/except retry espalhado é antipadrão.

estudar →
10

Cache como cross-cutting

Cache-aside via decorator, [OutputCache] em ASP.NET Core 7+, functools.lru_cache, wrappers em Go. Invalidação como problema separado — e por que ela quase sempre é onde o cache quebra.

estudar →
11

Auth/Authorization como aspect

Middleware de autenticação, [Authorize], FastAPI Depends(security), RBAC em Go middlewares. Separar autenticação de autorização — duas perguntas distintas que merecem aspects distintos.

estudar →
12

Transação e Unit of Work como aspect

TransactionScope, @transactional via decorator, escopo de Unit of Work por request. Riscos de ambient transaction, long-running transactions e por que aspect transacional precisa ser sóbrio.

estudar →
13

Validação cross-cutting

FluentValidation com pipeline, Pydantic na borda HTTP, validator tags em Go. A pergunta de fronteira: validar na borda, no aplicativo, ou no domínio? — e por que a resposta não é só "uma das três".

estudar →
14

Trade-offs e anti-padrões de AOP

Magia escondida, debug difícil, ordem implícita, "AOP como martelo". Quando preferir composição explícita, e a heurística do sênior para decidir o que vira aspect e o que continua chamada de função.

estudar →
princípio orientador

Aspect-driven é a tentativa madura de separar o que o sistema faz do que ele precisa garantir enquanto faz. O domínio responde "qual é o pedido?", "qual é o saldo?", "qual é a regra?". A camada de aspects responde "isso está autenticado?", "isso foi medido?", "isso é repetível?", "isso entrou no log?". Quando essa separação é nítida, o código de negócio fica legível por anos. Quando não é — quando cada método de domínio começa com cinco linhas de boilerplate ou, pior, quando a magia é tanta que ninguém entende o que executa antes do método —, o sistema envelhece mal. O sênior aprende a sentir esse limite: usar aspect para o que é genuinamente transversal, e composição explícita para o que é decisão local.

Cada uma das decisões abaixo é tomada cedo num projeto e raramente revisitada — vira parte do "jeito que se faz aqui". Vale articular o trade-off enquanto ainda há liberdade para escolher.

Middleware no pipeline ou interceptor por método?

Middleware HTTP é o lugar natural para concerns que se aplicam a todo request: autenticação, logging de acesso, CORS, correlation ID, métricas de duração de request. Interceptor (ou filter, ou decorator de método) é melhor quando o concern depende de qual operação está sendo executada: cache de método específico, retry de chamada de domínio, autorização baseada em recurso. Misturar os dois confunde: uma equipe que coloca autorização em middleware e em filter no mesmo controlador acaba duplicando regra. Escolha por escopo: borda → middleware, operação → interceptor.

AOP via framework (Spring, Castle) ou composição explícita?

AOP via framework brilha quando o concern é genuinamente repetitivo em todas as classes/métodos de uma camada — caso clássico é transação por método de application service. Composição explícita (decorators, higher-order functions, middleware) ganha quando os aspects são poucos, bem nomeados, e quando a equipe valoriza poder ler o caminho da chamada sem rodar debugger. Em Go, a comunidade optou pela segunda via quase em peso — geração de código quando precisa, função wrapper quando dá. Em .NET e Java, frameworks ganharam tração porque os ecossistemas pagaram o custo de ferramental para tornar a magia inspecionável (Spring Tools, profiler). Heurística: se você não consegue desenhar no quadro o que vai executar, está errando o nível de magia.

Logging estruturado: campos fixos ou contexto livre?

Estruturado significa que cada log entry é um objeto com chaves tipadas, não uma string formatada. A pergunta seguinte é se o schema é fixo (um conjunto de campos definidos por contrato) ou livre (cada equipe adiciona o que faz sentido). A prática que envelhece bem é um core fixo — timestamp, level, service, trace_id, span_id, user_id quando aplicável — e uma área livre por contexto, validada por linter no CI. Sem o core fixo, ferramentas de busca (Grafana Loki, Elasticsearch) viram menos eficazes; sem a área livre, equipes começam a esconder informação dentro da mensagem.

Retry: na borda do cliente ou em policy global?

Retry é tentador colocar globalmente — "todo HTTP client retenta 3x" — e é exatamente assim que se constrói uma retry storm que derruba o serviço chamado. A regra: retry pertence à chamada que sabe se a operação é idempotente. GET de leitura pode retentar livremente; POST que cria recurso só retenta com idempotency key. Por isso vale ter policy nomeada (read-idempotent, write-idempotent, no-retry) e exigir que o autor da chamada escolha, em vez de aplicar default global. Polly em .NET e tenacity em Python suportam esse modelo; em Go, escreve-se policy à mão mesmo, e isso é uma feature, não bug.

Validação: na borda HTTP ou no domínio?

A resposta amadurecida é "ambos, com responsabilidades distintas". A borda valida forma: campos obrigatórios, tipos, faixas, formato de e-mail. Pydantic e FluentValidation fazem isso bem, e falham rápido com mensagens amigáveis para o cliente HTTP. O domínio valida invariantes: que o saldo não fica negativo, que a data de fim é depois da data de início, que a transição de estado é válida. Se você só valida na borda, qualquer caminho que não passa por HTTP (job, mensagem, teste) vira fonte de bug. Se só no domínio, a API responde 500 para input absurdo. As duas camadas existem porque protegem coisas diferentes.

O projeto força você a montar uma API com todos os cross-cutting concerns vivos ao mesmo tempo, comparando como cada ecossistema resolve a mesma demanda. O resultado é um pipeline legível onde cada aspect tem seu lugar — e onde retirar qualquer um deles quebra o sistema de uma forma diagnosticável.

PROJETO PRÁTICO

API com pipeline de cross-cutting concerns

Uma API REST de catálogo (produtos, estoques, pedidos) que serve para sentir cada conceito do módulo: cada request passa por um pipeline com autenticação, autorização, correlation ID, logging estruturado, métricas, retry policy nas chamadas externas, cache em endpoints de leitura, transação no escrita, e tracing OpenTelemetry. O domínio precisa ficar limpo dessas preocupações — se você ler só os handlers de domínio, deve ser possível entender o que o sistema faz, sem ruído.

REQUISITOS
  • Pipeline de middleware/interceptors com pelo menos: correlation-id, auth, structured-log, metrics, tracing, error-mapping
  • Autorização baseada em recurso aplicada por interceptor de método (não em middleware global)
  • Cache em endpoints de leitura via decorator/atributo, com invalidação documentada
  • Retry policy nomeada para chamadas externas (cliente HTTP de fornecedor de estoque), idempotente
  • Circuit breaker em torno do mesmo cliente externo, com half-open e métricas expostas
  • Transação por request em endpoints de escrita, via Unit of Work — não pulverizada nos handlers
  • Logs estruturados com trace_id/span_id propagados, validados em teste
  • Tracing OpenTelemetry exportando para Jaeger ou console
  • Domínio testável sem levantar pipeline (handlers puros)
  • Teste end-to-end que prova: removendo qualquer aspect, um cenário deixa de funcionar de forma observável
Manutenibilidade Confiabilidade Observabilidade
STACK SUGERIDA POR LINGUAGEM
STACK
.NET 10 + ASP.NET Core Minimal API + Serilog + OpenTelemetry .NET + Polly v8 + EF Core (SQLite) + MediatR + FluentValidation + xUnit + WebApplicationFactory
ESTRUTURA / NOTAS
  • Catalog.Domain/ (entities, value objects, invariantes)
  • Catalog.Application/ (handlers MediatR + behaviors transversais)
  • Catalog.Infrastructure/ (EF Core, HttpClient com Polly, OTel exporter)
  • Catalog.Api/ (Program.cs com pipeline de middleware + endpoint filters)
  • Pipeline behaviors do MediatR para logging, validation, transaction
  • OutputCache em endpoints de leitura com tag-based invalidation
  • Polly v8 com ResiliencePipeline nomeado por intent
STACK
Python 3.13 + FastAPI + Pydantic v2 + structlog + OpenTelemetry Python + tenacity + SQLAlchemy 2 + dependency_injector + pytest + httpx
ESTRUTURA / NOTAS
  • catalog/domain/ (dataclasses imutáveis + invariantes)
  • catalog/application/ (use cases puros, sem framework)
  • catalog/adapters/ (SQLAlchemy, httpx client com tenacity)
  • catalog/api/ (FastAPI middlewares + dependencies por endpoint)
  • structlog com processors para correlation ID e trace context
  • OTel auto-instrumentation FastAPI + SQLAlchemy + httpx
  • Decorators @cached e @transactional ao redor de use cases
STACK
Go 1.23 + chi router + slog + OpenTelemetry Go + sony/gobreaker + database/sql (SQLite via modernc) + golang-migrate + go-playground/validator + httptest
ESTRUTURA / NOTAS
  • internal/domain/ (structs + métodos de invariantes)
  • internal/usecase/ (puro, recebe interfaces de saída)
  • internal/adapter/ (sql, httpclient, breaker)
  • internal/transport/http/ (handlers + middlewares chi.Use(...))
  • Middlewares funcionais: correlationID, auth, slogContext, metrics, recoverer
  • Retry e breaker em wrapper do http client externo
  • UnitOfWork via tx passada explicitamente — sem ambient transaction
entregável

Repositório com README mostrando: o diagrama do pipeline (ordem dos aspects e por quê), exemplos de log estruturado de um request real atravessando todos os concerns com trace_id visível, screenshots do trace no Jaeger com spans de cada aspect, um ADR decidindo quais policies de retry existem e quem usa cada uma, e um teste de regressão que falha de propósito quando se remove um aspect — provando que cada um tem efeito observável. Bonus: um handler de domínio idêntico nas três linguagens, lado a lado, com todo o cross-cutting fora dele.

Em entrevista de sênior, AOP raramente aparece pelo nome — aparece nas perguntas sobre como manter o sistema legível, como instrumentar sem poluir, e como aplicar resiliência sem espalhar boilerplate.

Q.01

O que torna um concern "cross-cutting"? Dê três exemplos e mostre como cada um, mal organizado, vira fonte de bug ou erosão arquitetural.

Q.02

Diferença entre middleware HTTP e interceptor de método. Em um sistema com autorização baseada em recurso, qual dos dois você usaria — e por quê?

Q.03

Implemente retry com backoff exponencial em três cenários: GET idempotente, POST de criação, e chamada com efeito colateral não-idempotente. O que muda em cada um?

Q.04

Logging estruturado: por que printf-style não escala? O que precisa estar em todo log entry para que ferramentas de busca sejam úteis em produção? Como você propaga correlation ID entre serviços?

Q.05

Dê um exemplo concreto onde AOP é a escolha errada — onde composição explícita envelhece melhor. Qual heurística você usa para decidir entre os dois?