Sumário
- Visão geral e objetivos da aula
- Gang of Four e a origem dos padrões
- Antes dos padrões: o problema que eles resolvem
- Categorias de padrões: criacionais, estruturais e comportamentais
- Singleton
- Factory Method
- Observer
- Strategy
- Decorator
- Adapter
- Chain of Responsibility
- Padrões no sistema Blog/Analytics
- Tendências modernas (2020+)
- Modern C# patterns
- Event Sourcing
- CQRS
- CQRS + Event Sourcing
- Microservices patterns essenciais
- Circuit Breaker
- Saga
- API Gateway
- Strangler Fig
- Service Mesh e observabilidade
- Padrões funcionais
- Anti-patterns
- Quando nao usar padrões
- Cheat sheet + roteiro de estudo
1) Visão Geral e Objetivos
O objetivo central desta aula é transformar "padrões" de um tema teórico em uma ferramenta prática de projeto. Você deve sair sabendo responder: que padrão usar, quando usar, por que usar e quando evitar.
- Compreender o valor histórico e técnico do catálogo GoF.
- Relacionar problemas reais de software a padrões adequados.
- Aplicar os padrões em um sistema de dados e eventos (blog/analytics).
- Conectar padrões clássicos com CQRS, Event Sourcing e arquitetura distribuída.
2) Gang of Four (GoF): por que isso importa até hoje
Em 1994, Erich Gamma, Richard Helm, Ralph Johnson e John Vlissides organizaram um vocabulário comum para problemas recorrentes em software orientado a objetos. O valor principal não é decorar os 23 padrões, e sim aprender a reconhecer formas recorrentes de problema e de solução.
Quando alguém diz "isso parece Strategy" ou "vamos usar Adapter para integrar legado", a equipe já compartilha uma estrutura mental, acelera decisões e reduz divergências de implementação.
3) Antes dos Padrões: o Caos
Sem padrões, projetos crescem por acumulação de decisões locais: duplicação, condicionais gigantes, acoplamento alto e baixa previsibilidade. O custo aparece depois: bugs frequentes, refatorações caras e dificuldade para onboarding de novos desenvolvedores.
4) As 3 Categorias de Padrões
- Criacionais: organizam criação de objetos (ex.: Singleton, Factory, Builder).
- Estruturais: organizam composição e integração (ex.: Adapter, Decorator, Facade).
- Comportamentais: organizam colaboração entre objetos (ex.: Observer, Strategy, Chain).
Uma boa heurística: se o problema envolve "quem cria", pense em criacionais; se envolve "quem conversa com quem", pense em estruturais/comportamentais.
5) Singleton
Definição: garante uma única instância e um ponto global de acesso.
- Estado global controlado (configuração imutável, log central, cache compartilhado).
- Recursos que nao podem ter multiplas instâncias concorrentes.
Acoplamento global e teste difícil. Em C#, priorize DI com lifetime Singleton no container em vez de instância estática manual.
// via DI (recomendado)
services.AddSingleton<IClock, SystemClock>();
public sealed class SystemClock : IClock
{
public DateTime UtcNow() => DateTime.UtcNow;
}6) Factory Method
Definição: encapsula criação e permite escolher a implementação concreta sem acoplar o cliente.
- Muitos `if/else` para instanciar classes diferentes.
- Necessidade frequente de incluir novos tipos sem mexer no cliente.
public interface INotifier { Task SendAsync(string msg); }
public interface INotifierFactory
{
INotifier Create(string channel);
}7) Observer
Quando um sujeito muda de estado, vários observadores recebem notificação. É a base de sistemas orientados a eventos.
- Uso típico: notificações de domínio, integração assíncrona, UI reativa.
- No ecossistema .NET: eventos, `IObservable`, SignalR e brokers como Kafka.
8) Strategy
Encapsula algoritmos intercambiáveis. O cliente mantém o mesmo contrato e troca comportamento por configuração, perfil de usuário, feature flag ou tipo de dado.
public interface ISentimentStrategy
{
Sentiment Analyze(string text);
}
// runtime: escolhe estratégia por idioma, domínio ou custo
var strategy = strategyResolver.Resolve(context.Language);
var result = strategy.Analyze(comment);9) Decorator
Adiciona responsabilidades sem alterar a classe base e sem explosão de herança.
- Muito útil para cross-cutting concerns: logging, cache, métricas, autorização.
- No ASP.NET Core, middlewares são um caso clássico de composição em cadeia.
10) Adapter
Converte uma interface em outra esperada pelo cliente. É essencial para integrar legado e terceiros sem contaminar o domínio.
Dependência externa sempre entra por adapter. Assim, se a API externa mudar, você altera uma borda, não o sistema inteiro.
11) Chain of Responsibility
Requisições percorrem uma cadeia de handlers. Cada etapa processa, enriquece ou repassa.
- Casos comuns: validação em etapas, aprovação hierárquica, pipeline de processamento de eventos.
- Benefício: adicionar/remover regras sem alterar o núcleo do fluxo.
var pipeline = new ValidateHandler(
new EnrichHandler(
new PersistHandler(
new NotifyHandler(null))));12) Padrões no Sistema de Blog/Analytics
| Padrão | Onde aparece | Problema resolvido | Resultado esperado |
|---|---|---|---|
| Factory Method | Criação de notificações Email/SMS/Push | Criação acoplada ao cliente | Extensão sem quebrar fluxos |
| Observer | Eventos de publicação e analytics | Propagação manual de atualizações | Reatividade e desacoplamento |
| Strategy | Análise de sentimento e ranking | Algoritmo único para cenários distintos | Troca dinâmica por contexto |
| Decorator | Pipeline de enriquecimento de comentário | Cross-cutting espalhado | Composição limpa de responsabilidades |
| Adapter | Integrações externas de dados | Incompatibilidade de interfaces | Domínio protegido de mudanças externas |
13) Tendências Modernas (2020+)
- Event Sourcing + CQRS: histórico completo de mudanças e leitura otimizada.
- Microservices patterns: resiliência e consistência em ambiente distribuído.
- Cloud-native: automação operacional, observabilidade e escalabilidade elástica.
- C# moderno: records, pattern matching, null safety e código mais expressivo.
14) Modern C# Patterns (C# 9-12)
- Records: valor, imutabilidade e comparação sem boilerplate.
- Pattern Matching: regras declarativas e seguras em `switch`.
- Init-only: proteção contra mutação indevida após construção.
- Nullable refs: prevenção de falhas de nulidade em design-time.
public record CommentCreated(Guid CommentId, Guid PostId, string Author, string Text);
string Severity(Comment c) => c switch
{
{ IsSpam: true } => "High",
{ Likes: > 100 } => "Medium",
_ => "Low"
};15) Event Sourcing
Em vez de salvar somente o estado final, salvamos os eventos de mudança. O estado passa a ser derivado por replay.
- Auditoria total (quem fez o que, quando e em qual ordem).
- Time travel e reprocessamento.
- Base natural para integração event-driven.
- Curva de aprendizado maior.
- Rebuild de estado pode ficar caro sem estratégia de snapshot.
- Exige disciplina de versionamento de eventos.
public interface IDomainEvent { DateTime OccurredAt { get; } }
public sealed record MoneyDeposited(decimal Amount, DateTime OccurredAt) : IDomainEvent;
public sealed class AccountAggregate
{
private readonly List<IDomainEvent> _changes = new();
public decimal Balance { get; private set; }
public void Deposit(decimal amount)
{
var evt = new MoneyDeposited(amount, DateTime.UtcNow);
Apply(evt);
_changes.Add(evt);
}
public void Apply(IDomainEvent evt)
{
if (evt is MoneyDeposited d) Balance += d.Amount;
}
}16) Event Store e Snapshots
Event store guarda eventos por stream/aggregate. Snapshot guarda foto periódica do estado para acelerar reconstrução.
// carga otimizada var snapshot = snapshotRepo.Get(streamId); var aggregate = snapshot is null ? new AccountAggregate() : AccountAggregate.FromSnapshot(snapshot); var events = eventStore.LoadAfter(streamId, snapshot?.Version ?? 0); foreach (var evt in events) aggregate.Apply(evt);
17) CQRS (Command Query Responsibility Segregation)
Separa escrita e leitura com modelos diferentes. Write side foca consistência de domínio. Read side foca performance e experiência de consulta.
- Command: expressa intenção de mudar estado.
- Query: obtém projeções otimizadas para tela/API.
- Muita assimetria entre volume de leitura e escrita.
- Consultas complexas e múltiplas visões do mesmo domínio.
- Sistema pequeno com CRUD simples e baixa escala.
18) CQRS + Event Sourcing juntos
Combinação natural: Event Sourcing alimenta write model; projeções assíncronas alimentam read model.
Command -> Aggregate -> Events -> Event Store -> Projectors -> Read DB -> Query API
19) Microservices Patterns: por que precisamos deles
Em sistemas distribuídos, falha é comportamento normal, não exceção. Padrões de microserviços existem para conter falhas, coordenar transações distribuídas e reduzir acoplamento operacional.
- Latência variável e timeout.
- Falha parcial entre serviços.
- Consistência distribuída e reprocessamento.
- Observabilidade ponta a ponta.
20) Circuit Breaker
Protege o sistema quando uma dependência degrada. Estados usuais: Closed, Open, Half-Open.
- Closed: opera normalmente e mede falhas.
- Open: bloqueia chamadas por janela de resfriamento.
- Half-Open: testa recuperação com poucas requisições.
// Polly (conceito) var policy = Policy .Handle<HttpRequestException>() .CircuitBreakerAsync(handledEventsAllowedBeforeBreaking: 5, durationOfBreak: TimeSpan.FromSeconds(30));
21) Saga Pattern
Coordena transações locais entre serviços com compensações quando algo falha. Em vez de rollback global (2PC), cada passo tem ação compensatória.
- Choreography: serviços reagem a eventos; baixo acoplamento, observabilidade mais difícil.
- Orchestration: coordenador central; maior controle, risco de ponto central de complexidade.
22) API Gateway
Ponto único de entrada para clientes. Roteia, autentica, aplica limite de taxa e pode agregar respostas.
- Benefícios: simplificação para cliente, políticas centralizadas, melhor governança.
- Riscos: gateway gordo, latência adicional e novo ponto de falha se mal projetado.
// cenário comum Cliente -> Gateway -> BlogService + CommentService + UserService -> resposta agregada
23) Strangler Fig Pattern
Migração incremental de monolito para serviços. O gateway/proxy roteia partes do tráfego para o novo serviço, enquanto o legado continua em produção.
- Escolher um bounded context.
- Extrair capacidade mínima para novo serviço.
- Rotear parcela pequena de tráfego.
- Medir, estabilizar e ampliar rollout.
- Remover trecho equivalente do monolito.
24) Service Mesh e Observabilidade
Service mesh move preocupações de comunicação para infraestrutura (retry, TLS mútuo, roteamento, telemetria), normalmente com sidecars/proxies.
- Métricas: taxa de erro, latência p95/p99, throughput.
- Tracing distribuído: identifica gargalos entre serviços.
- Políticas de segurança e tráfego sem mudar código de negócio.
25) Functional Programming Patterns
Padrões funcionais aumentam previsibilidade: imutabilidade, funções puras e modelagem explícita de sucesso/erro.
- Option/Maybe: evita null como protocolo implícito.
- Result: torna erros parte do tipo de retorno.
- Composição: pipelines de transformação testáveis.
public abstract record Result<T>;
public sealed record Success<T>(T Value) : Result<T>;
public sealed record Failure<T>(string Error) : Result<T>;
Result<Comment> Save(string text) =>
string.IsNullOrWhiteSpace(text)
? new Failure<Comment>("Texto vazio")
: new Success<Comment>(new Comment(text));26) Anti-Patterns: o que evitar
- God Object: classe concentra responsabilidades demais.
- Spaghetti Code: fluxo ilegível com condicionais aninhadas.
- Copy-Paste Programming: duplicação que espalha bugs.
- Golden Hammer: aplicar sempre o mesmo padrão.
- Premature Optimization: complexidade sem métrica.
- Magic Numbers/Strings: semântica escondida em literais.
27) Quando nao usar padrões
- Quando um fluxo simples resolve com clareza.
- Quando o sistema ainda está em descoberta e mudará muito.
- Quando o custo de abstração supera o ganho esperado.
Princípios norteadores: KISS, YAGNI e refatoração incremental.
28) Cheat Sheet de Decisão Rápida
| Se o problema for... | Padrão sugerido | Pergunta de validação |
|---|---|---|
| Criação de objetos variáveis | Factory Method | Novos tipos surgem com frequência? |
| Troca de algoritmo em runtime | Strategy | Hoje existe `if/else` por tipo de regra? |
| Notificações para vários interessados | Observer | Precisa desacoplar produtor e consumidores? |
| Adicionar capacidades sem herança | Decorator | Você quer compor comportamentos por camada? |
| Integrar interface incompatível | Adapter | A dependência externa pode mudar? |
| Histórico completo e auditoria | Event Sourcing | Eventos de domínio são ativos estratégicos? |
| Leitura e escrita com perfis distintos | CQRS | Há gargalo de query no modelo atual? |
| Resiliência a dependência instável | Circuit Breaker | Falha externa está causando cascata? |
29) Atividade Guiada (pós-aula)
- Escolha um fluxo do projeto (publicar post, analisar sentimento, gerar dashboard).
- Mapeie 2 problemas atuais de acoplamento ou legibilidade.
- Proponha 2 padrões e justifique por que eles resolvem o problema.
- Liste trade-offs e como você medirá se a mudança deu certo.
Entrega ideal: diagrama simples + explicação de decisão arquitetural (1 a 2 páginas).
Referências
- Design Patterns (Gamma, Helm, Johnson, Vlissides)
- Head First Design Patterns (Freeman & Freeman)
- Refactoring (Martin Fowler)
- Clean Code (Robert C. Martin)
- Refactoring.Guru (revisão visual dos padrões)