Padrões de projeto clássicos e sua aplicação em sistemas reais.
💡 Impacto: Catalogou 23 padrões que se tornaram a base da engenharia de software moderna. Até hoje, esses padrões são referência obrigatória. O livro vendeu mais de 500.000 cópias e foi traduzido para mais de 13 idiomas.
⚠️ Importante: Padrões NÃO são código pronto para copiar. São templates conceituais que você adapta ao seu contexto.
Foco: Como os objetos são criados.
Objetivo: Esconder a lógica de criação, tornando o sistema mais flexível.
Foco: Como os objetos se relacionam.
Objetivo: Organizar classes e objetos para formar estruturas maiores.
Foco: Como os objetos interagem.
Objetivo: Definir comunicação e responsabilidades entre objetos.
Garante que uma classe tenha apenas uma instância e fornece um ponto de acesso global a ela.
🎯 Exemplo Lúdico: Como o presidente de um país - só pode ter UM ao mesmo tempo! Todo mundo acessa o mesmo presidente, não criam novos presidentes toda hora.
⚠️ Cuidado: Singleton pode dificultar testes e criar acoplamento global. Use com moderação!
// Singleton Thread-Safe em C#
public sealed class DatabaseConnection
{
private static DatabaseConnection _instance = null;
private static readonly object _lock = new object();
// Construtor privado impede new externo
private DatabaseConnection()
{
Console.WriteLine("Conexão criada!");
}
public static DatabaseConnection Instance
{
get
{
lock (_lock)
{
if (_instance == null)
{
_instance = new DatabaseConnection();
}
return _instance;
}
}
}
public void Query(string sql)
{
Console.WriteLine($"Executando: {sql}");
}
}
// Uso
var db1 = DatabaseConnection.Instance;
var db2 = DatabaseConnection.Instance;
// db1 == db2 (mesma instância!)
Define uma interface para criar objetos, mas deixa as subclasses decidirem qual classe instanciar.
🎯 Exemplo Lúdico: Como pedir "uma pizza" na pizzaria. Você não faz a pizza, só pede! A pizzaria decide se faz margherita, calabresa ou portuguesa. A fábrica (pizzaria) cria o objeto (pizza)!
✅ Vantagem: Open/Closed Principle - aberto para extensão, fechado para modificação.
// Interface comum
public interface INotificacao
{
void Enviar(string mensagem);
}
// Implementações concretas
public class EmailNotificacao : INotificacao
{
public void Enviar(string msg) =>
Console.WriteLine($"📧 Email: {msg}");
}
public class SmsNotificacao : INotificacao
{
public void Enviar(string msg) =>
Console.WriteLine($"📱 SMS: {msg}");
}
// Factory Method
public abstract class NotificacaoFactory
{
public abstract INotificacao CriarNotificacao();
}
public class EmailFactory : NotificacaoFactory
{
public override INotificacao CriarNotificacao()
=> new EmailNotificacao();
}
public class SmsFactory : NotificacaoFactory
{
public override INotificacao CriarNotificacao()
=> new SmsNotificacao();
}
// Uso
NotificacaoFactory factory = new EmailFactory();
INotificacao notif = factory.CriarNotificacao();
notif.Enviar("Novo comentário!");
Define uma dependência um-para-muitos: quando um objeto muda de estado, todos os seus observadores são notificados automaticamente.
🎯 Exemplo Lúdico: Como inscrever no canal do YouTube! Quando o youtuber posta vídeo novo, TODOS os inscritos recebem notificação. Ele não precisa avisar cada um individualmente!
💡 Conexão: Kafka, SignalR e eventos do Blazor usam Observer internamente!
// Subject (Observável)
public class Blog
{
private List<IObserver> _observers = new();
public void AdicionarObserver(IObserver observer)
{
_observers.Add(observer);
}
public void NovoComentario(string texto)
{
Console.WriteLine($"Comentário criado: {texto}");
// Notifica todos os observers
foreach (var obs in _observers)
{
obs.Atualizar(texto);
}
}
}
// Interface Observer
public interface IObserver
{
void Atualizar(string mensagem);
}
// Observers concretos
public class AnalyticsService : IObserver
{
public void Atualizar(string msg)
{
Console.WriteLine($"📊 Analytics: registrando '{msg}'");
}
}
public class ModeracaoService : IObserver
{
public void Atualizar(string msg)
{
Console.WriteLine($"🛡️ Moderação: analisando '{msg}'");
}
}
// Uso
var blog = new Blog();
blog.AdicionarObserver(new AnalyticsService());
blog.AdicionarObserver(new ModeracaoService());
blog.NovoComentario("Ótimo post!");
Define uma família de algoritmos, encapsula cada um e os torna intercambiáveis. Strategy permite que o algoritmo varie independentemente dos clientes que o usam.
🎯 Exemplo Lúdico: Como escolher transporte para ir trabalhar: carro, ônibus, bicicleta ou Uber. O objetivo é o mesmo (chegar no trabalho), mas a estratégia muda! Você pode trocar a qualquer momento.
✅ Vantagem: Elimina condicionais complexas e facilita adicionar novos algoritmos.
// Interface Strategy
public interface IAnaliseStrategy
{
double CalcularScore(string texto);
}
// Strategies concretas
public class SentimentoPositivo : IAnaliseStrategy
{
public double CalcularScore(string texto)
{
return texto.Contains("ótimo") ? 0.9 : 0.5;
}
}
public class SentimentoNegativo : IAnaliseStrategy
{
public double CalcularScore(string texto)
{
return texto.Contains("péssimo") ? 0.1 : 0.5;
}
}
// Context (usa a strategy)
public class AnalisadorComentario
{
private IAnaliseStrategy _strategy;
public AnalisadorComentario(IAnaliseStrategy strategy)
{
_strategy = strategy;
}
public void MudarStrategy(IAnaliseStrategy novaStrategy)
{
_strategy = novaStrategy;
}
public double Analisar(string comentario)
{
return _strategy.CalcularScore(comentario);
}
}
// Uso
var analisador = new AnalisadorComentario(new SentimentoPositivo());
Console.WriteLine(analisador.Analisar("ótimo post!")); // 0.9
analisador.MudarStrategy(new SentimentoNegativo());
Console.WriteLine(analisador.Analisar("péssimo")); // 0.1
Permite adicionar comportamentos a objetos individuais, dinamicamente, sem afetar outros objetos da mesma classe.
🎯 Exemplo Lúdico: Como adicionar coberturas numa pizza! Pizza base → +queijo extra → +borda recheada → +catupiry. Cada "decorator" adiciona uma camada sem mudar a pizza original!
💡 Exemplo real: ASP.NET Core middleware é uma implementação de Decorator!
// Interface base
public interface IComentario
{
string ObterTexto();
}
// Componente concreto
public class ComentarioSimples : IComentario
{
private string _texto;
public ComentarioSimples(string texto)
{
_texto = texto;
}
public string ObterTexto() => _texto;
}
// Decorator base
public abstract class ComentarioDecorator : IComentario
{
protected IComentario _comentario;
public ComentarioDecorator(IComentario comentario)
{
_comentario = comentario;
}
public virtual string ObterTexto()
=> _comentario.ObterTexto();
}
// Decorators concretos
public class FiltroSpam : ComentarioDecorator
{
public FiltroSpam(IComentario c) : base(c) { }
public override string ObterTexto()
{
var texto = base.ObterTexto();
return texto.Replace("SPAM", "***");
}
}
public class LogDecorator : ComentarioDecorator
{
public LogDecorator(IComentario c) : base(c) { }
public override string ObterTexto()
{
Console.WriteLine("📝 Log: lendo comentário");
return base.ObterTexto();
}
}
// Uso - empilhando decorators!
IComentario c = new ComentarioSimples("SPAM aqui");
c = new FiltroSpam(c);
c = new LogDecorator(c);
Console.WriteLine(c.ObterTexto()); // "*** aqui"
Converte a interface de uma classe em outra interface que o cliente espera. Permite que classes incompatíveis trabalhem juntas.
🎯 Exemplo Lúdico: Adaptador de tomada! Seu celular americano (110V tomada de 2 pinos) precisa funcionar no Brasil (220V tomada de 3 pinos). O adaptador converte uma interface na outra!
💡 Conexão: Essencial para integrar sistemas antigos com novos!
// Sistema legado (não podemos mudar)
public class SistemaLegadoAnalytics
{
public void EnviarDadosAntigos(string data, int valor)
{
Console.WriteLine($"Sistema Legado: {data} = {valor}");
}
}
// Nova interface que queremos usar
public interface IAnalyticsModerno
{
void Registrar(MetricaModerna metrica);
}
public class MetricaModerna
{
public DateTime Timestamp { get; set; }
public int Valor { get; set; }
}
// Adapter - faz a ponte!
public class AnalyticsAdapter : IAnalyticsModerno
{
private SistemaLegadoAnalytics _legado = new();
public void Registrar(MetricaModerna metrica)
{
// Converte formato moderno para formato legado
string dataLegada = metrica.Timestamp.ToString("yyyy-MM-dd");
_legado.EnviarDadosAntigos(dataLegada, metrica.Valor);
}
}
// Uso - cliente usa interface moderna
IAnalyticsModerno analytics = new AnalyticsAdapter();
analytics.Registrar(new MetricaModerna
{
Timestamp = DateTime.Now,
Valor = 42
});
Passa uma requisição por uma cadeia de handlers. Cada handler decide se processa ou passa para o próximo da fila.
🎯 Exemplo Lúdico: Você pede aumento pro seu chefe → ele passa pro gerente → gerente passa pro diretor → diretor passa pro CEO. Cada um pode aprovar OU passar pra frente!
💡 Conexão: Perfeito para processar eventos! Cada handler trata uma parte do evento.
// Handler abstrato
public abstract class ComentarioHandler
{
protected ComentarioHandler _proximo;
public void SetProximo(ComentarioHandler handler)
{
_proximo = handler;
}
public abstract void Processar(Comentario comentario);
}
// Handlers concretos
public class ValidadorSpam : ComentarioHandler
{
public override void Processar(Comentario c)
{
if (c.Texto.Contains("SPAM"))
{
Console.WriteLine("🛡️ SPAM bloqueado!");
return; // Para aqui, não passa adiante
}
_proximo?.Processar(c); // Passa pra frente
}
}
public class ValidadorPalavrao : ComentarioHandler
{
public override void Processar(Comentario c)
{
if (c.Texto.Contains("palavrão"))
{
c.Texto = c.Texto.Replace("palavrão", "***");
}
_proximo?.Processar(c);
}
}
public class SalvadorBanco : ComentarioHandler
{
public override void Processar(Comentario c)
{
Console.WriteLine("💾 Salvando no banco...");
_proximo?.Processar(c);
}
}
// Montando a corrente
var spam = new ValidadorSpam();
var palavrao = new ValidadorPalavrao();
var salvar = new SalvadorBanco();
spam.SetProximo(palavrao);
palavrao.SetProximo(salvar);
// Inicia a cadeia!
spam.Processar(new Comentario { Texto = "Ótimo post!" });
💡 Dica: Use para revisar TODOS os padrões que vimos hoje com exemplos visuais!
Onde: Criação de notificações (Email, SMS, Push)
Permite adicionar novos tipos de notificação sem modificar código existente.
Onde: Sistema de eventos Kafka
Comentário criado → Analytics, Moderação e Dashboard são notificados.
Onde: Análise de sentimento
Diferentes algoritmos de análise podem ser trocados dinamicamente.
Onde: Pipeline de processamento de comentários
Filtro de spam, validação, logging - empilhados como decorators.
Onde: Configuração global, Logger
Uma única instância de configuração compartilhada por toda a aplicação.
Onde: Integração com APIs externas
Adaptar API de terceiros para nossa interface interna.
💡 Insight: Padrões não são usados isoladamente. Na prática, você combina vários padrões para resolver problemas complexos!
💡 Insight: GoF + Padrões Modernos = Arquitetura de ponta!
C# moderno trouxe features que mudam como escrevemos padrões!
🎯 Exemplo Lúdico: Antes você precisava de 50 linhas pra fazer algo que agora faz em 5! É como trocar uma bicicleta por um carro.
// ✅ ANTES (C# 7): Muito código!
public class Comentario
{
public int Id { get; set; }
public string Texto { get; set; }
public DateTime DataCriacao { get; set; }
public override bool Equals(object obj) { /* 20 linhas */ }
public override int GetHashCode() { /* 10 linhas */ }
}
// ✅ AGORA (C# 12): Records!
public record Comentario(int Id, string Texto, DateTime DataCriacao);
// Imutável, com Equals/GetHashCode automáticos!
// ✅ Pattern Matching avançado
string AnalisarComentario(Comentario c) => c switch
{
{ Texto.Length: > 500 } => "Muito longo!",
{ Texto: var t } when t.Contains("spam") => "SPAM detectado",
{ DataCriacao: var d } when d > DateTime.Now.AddDays(-1)
=> "Comentário recente",
_ => "Comentário normal"
};
// ✅ Null Safety
public class Blog
{
public required string Titulo { get; init; } // Obrigatório!
public string? Subtitulo { get; init; } // Pode ser null
public void Processar()
{
// Compiler avisa se não checar null!
if (Subtitulo is not null)
{
Console.WriteLine(Subtitulo.ToUpper());
}
}
}
Ao invés de salvar o estado atual das entidades no banco de dados, salvamos a sequência de eventos que levaram a esse estado.
😢 Perdemos o histórico! Como chegamos em R$ 1.500?
🎉 Auditoria completa! Sabemos tudo que aconteceu!
// Base para todos os eventos
public abstract record DomainEvent
{
public Guid Id { get; init; } = Guid.NewGuid();
public DateTime Timestamp { get; init; } = DateTime.UtcNow;
public string Usuario { get; init; }
}
// Eventos específicos
public record ComentarioCriadoEvent(
int ComentarioId,
int PostId,
string Texto,
string Usuario
) : DomainEvent;
public record ComentarioEditadoEvent(
int ComentarioId,
string NovoTexto,
string Usuario
) : DomainEvent;
public record ComentarioExcluidoEvent(
int ComentarioId,
string Motivo,
string Usuario
) : DomainEvent;
public record ComentarioCurtidoEvent(
int ComentarioId,
string Usuario
) : DomainEvent;
public class Comentario
{
// Estado atual (calculado dos eventos)
public int Id { get; private set; }
public string Texto { get; private set; }
public bool Excluido { get; private set; }
public int Curtidas { get; private set; }
// Lista de eventos não commitados
private List<DomainEvent> _eventos = new();
public IReadOnlyList<DomainEvent> EventosNaoCommitados
=> _eventos.AsReadOnly();
// Construtor para criar novo comentário
public static Comentario Criar(int id, int postId,
string texto, string usuario)
{
var comentario = new Comentario();
comentario.Apply(new ComentarioCriadoEvent(
id, postId, texto, usuario));
return comentario;
}
// Aplicar evento (muda o estado)
public void Apply(DomainEvent evento)
{
When(evento); // Muda o estado
_eventos.Add(evento); // Guarda evento
}
// Lógica de como cada evento muda o estado
private void When(DomainEvent evento)
{
switch (evento)
{
case ComentarioCriadoEvent e:
Id = e.ComentarioId;
Texto = e.Texto;
break;
case ComentarioEditadoEvent e:
Texto = e.NovoTexto;
break;
case ComentarioExcluidoEvent:
Excluido = true;
break;
case ComentarioCurtidoEvent:
Curtidas++;
break;
}
}
// Reconstruir do histórico de eventos
public static Comentario FromEvents(
IEnumerable<DomainEvent> eventos)
{
var comentario = new Comentario();
foreach (var evento in eventos)
{
comentario.When(evento); // SEM adicionar à lista
}
return comentario;
}
}
// Criar novo comentário
var comentario = Comentario.Criar(1, postId: 42, "Ótimo artigo!", "joao@email.com");
await eventStore.Save(comentario); // Salva eventos no Event Store
// Editar comentário
var eventos = await eventStore.GetEvents(comentarioId: 1);
var comentario = Comentario.FromEvents(eventos); // Reconstrói do histórico
comentario.Apply(new ComentarioEditadoEvent(1, "Artigo excelente!", "joao@email.com"));
await eventStore.Save(comentario);
// Ver histórico completo
var todosEventos = await eventStore.GetEvents(comentarioId: 1);
// ComentarioCriadoEvent -> ComentarioEditadoEvent
PostgreSQL, SQL Server
EventStoreDB (Greg Young)
Azure Event Store, Kafka
Replay de 10.000 eventos pra reconstruir o estado seria muito lento!
Snapshot = Foto do estado em um momento específico.
Ao invés de replay de 10.000 eventos, carrega o snapshot #9000 e só processa os últimos 1000!
public class ComentarioSnapshot
{
public int Id { get; set; }
public string Texto { get; set; }
public bool Excluido { get; set; }
public int Curtidas { get; set; }
public int VersaoEvento { get; set; } // Evento #9000
}
// Repository com Snapshots
public class ComentarioRepository
{
public async Task<Comentario> GetById(int id)
{
// 1. Buscar último snapshot
var snapshot = await _snapshots.GetLatest(id);
// 2. Buscar eventos APÓS o snapshot
var eventos = await _eventStore.GetEvents(
id,
fromVersion: snapshot?.VersaoEvento ?? 0
);
// 3. Reconstruir
var comentario = snapshot != null
? Comentario.FromSnapshot(snapshot)
: new Comentario();
foreach (var evento in eventos)
comentario.When(evento);
return comentario;
}
public async Task Save(Comentario comentario)
{
await _eventStore.SaveEvents(
comentario.EventosNaoCommitados
);
// Criar snapshot a cada 100 eventos
if (comentario.Versao % 100 == 0)
{
await _snapshots.Save(
comentario.ToSnapshot()
);
}
}
}
Snapshot a cada 100 eventos
Snapshot diário (00:00)
Se replay > 5 segundos, cria snapshot
Combina quantidade + tempo
🎯 Dica: Snapshots são opcionais! Comece sem eles e adicione só quando necessário.
Separar as operações de ESCRITA (Commands) das operações de LEITURA (Queries) usando modelos diferentes.
Garçom anota seu pedido
• Validação: ingredientes disponíveis?
• Processamento: envia pra cozinha
• Complexo: várias validações
• Lento: precisa processar tudo
CriarPedidoCommand → Validar → Processar → Salvar
Menu já está pronto
• Sem validação: só consulta
• Otimizado: dados já preparados
• Simples: retorna informação
• Rápido: sem processamento
GetMenuQuery → Cache → Retorna JSON
// ❌ Mesmo modelo pra TUDO = Conflito!
public class Comentario
{
public int Id { get; set; }
public string Texto { get; set; } // Leitura precisa?
public int PostId { get; set; } // Leitura precisa?
public int AutorId { get; set; } // ❌ Leitura quer o NOME, não o ID!
public DateTime DataCriacao { get; set; }
// Navegação pra queries (overhead na escrita!)
public virtual Post Post { get; set; } // ❌ Lazy loading = N+1 queries
public virtual Usuario Autor { get; set; } // ❌ JOINs pesados
public virtual ICollection<Curtida> Curtidas { get; set; } // ❌ Contagem lenta
}
// Queries ficam lentas e complexas
var comentarios = await _context.Comentarios
.Include(c => c.Autor) // JOIN 1
.Include(c => c.Post) // JOIN 2
.Include(c => c.Curtidas) // JOIN 3 + COUNT
.Where(c => c.PostId == postId)
.ToListAsync(); // 😱 LENTO!
// Modelo de ESCRITA (domain model)
public class Comentario
{
public int Id { get; private set; }
public string Texto { get; private set; }
public int PostId { get; private set; }
public int AutorId { get; private set; }
// Métodos de negócio
public void Editar(string novoTexto)
{
if (string.IsNullOrWhiteSpace(novoTexto))
throw new ArgumentException("Texto inválido");
Texto = novoTexto;
}
}
// Command (intenção de mudar o estado)
public record CriarComentarioCommand(
int PostId,
string Texto,
int AutorId
) : IRequest<int>;
// Handler do Command
public class CriarComentarioHandler
: IRequestHandler<CriarComentarioCommand, int>
{
private readonly DbContext _db;
public async Task<int> Handle(
CriarComentarioCommand cmd,
CancellationToken ct)
{
// Validações de negócio
var post = await _db.Posts
.FindAsync(cmd.PostId);
if (post == null)
throw new NotFoundException();
if (!post.AceitaComentarios)
throw new BusinessException(
"Post não aceita comentários"
);
// Criar entidade
var comentario = new Comentario
{
PostId = cmd.PostId,
Texto = cmd.Texto,
AutorId = cmd.AutorId
};
_db.Comentarios.Add(comentario);
await _db.SaveChangesAsync(ct);
// Publicar evento para atualizar read model
await _bus.Publish(
new ComentarioCriadoEvent(comentario.Id)
);
return comentario.Id;
}
}
// Modelo de LEITURA (view model) - OTIMIZADO!
public class ComentarioListItemDto
{
public int Id { get; set; }
public string TextoResumo { get; set; } // Só 100 chars
public string AutorNome { get; set; } // JÁ TEM O NOME!
public string AutorAvatar { get; set; } // URL do avatar
public DateTime DataCriacao { get; set; }
public int TotalCurtidas { get; set; } // JÁ CALCULADO!
public bool CurtidoPorMim { get; set; } // Pro usuário atual
}
// Query (pergunta, não muda nada)
public record GetComentariosQuery(
int PostId,
int UsuarioAtualId
) : IRequest<List<ComentarioListItemDto>>;
// Handler da Query
public class GetComentariosHandler
: IRequestHandler<GetComentariosQuery,
List<ComentarioListItemDto>>
{
private readonly IReadDbContext _readDb;
public async Task<List<ComentarioListItemDto>> Handle(
GetComentariosQuery query,
CancellationToken ct)
{
// Consulta OTIMIZADA no read model
// (que já está desnormalizado!)
return await _readDb.ComentariosView
.Where(c => c.PostId == query.PostId)
.OrderByDescending(c => c.DataCriacao)
.ToListAsync(ct);
// SEM JOINS! SEM INCLUDE! Tudo pronto! 🚀
}
}
// Uso no Controller
[ApiController]
[Route("api/posts/{postId}/comentarios")]
public class ComentariosController : ControllerBase
{
private readonly IMediator _mediator;
[HttpGet]
public async Task<IActionResult> Get(int postId)
{
var query = new GetComentariosQuery(
postId,
User.GetUserId()
);
var result = await _mediator.Send(query);
return Ok(result);
}
[HttpPost]
public async Task<IActionResult> Post(
int postId,
[FromBody] CriarComentarioDto dto)
{
var command = new CriarComentarioCommand(
postId,
dto.Texto,
User.GetUserId()
);
var id = await _mediator.Send(command);
return CreatedAtAction(nameof(Get), new { id }, null);
}
}
// Event Handler que atualiza o Read Model
public class ComentarioCriadoEventHandler : INotificationHandler<ComentarioCriadoEvent>
{
private readonly IReadDbContext _readDb;
public async Task Handle(ComentarioCriadoEvent evt, CancellationToken ct)
{
// Buscar dados completos do write model
var comentario = await _writeDb.Comentarios
.Include(c => c.Autor)
.Include(c => c.Post)
.FirstAsync(c => c.Id == evt.ComentarioId, ct);
// Criar registro no read model (desnormalizado)
var viewModel = new ComentarioListItemDto
{
Id = comentario.Id,
TextoResumo = comentario.Texto.Substring(0, Math.Min(100, comentario.Texto.Length)),
AutorNome = comentario.Autor.Nome, // ✅ Desnormalizado!
AutorAvatar = comentario.Autor.AvatarUrl,
DataCriacao = comentario.DataCriacao,
TotalCurtidas = 0
};
await _readDb.ComentariosView.AddAsync(viewModel, ct);
await _readDb.SaveChangesAsync(ct);
}
}
Event Sourcing cuida do WRITE (salva eventos).
CQRS cuida do READ (projeta eventos em views otimizadas).
// 1. Command
var command = new CriarComentarioCommand(
postId: 42,
texto: "Ótimo!"
);
// 2. Aggregate processa
var comentario = Comentario.Criar(command);
// 3. Eventos gerados
// → ComentarioCriadoEvent
// 4. Salvos no EventStore
await eventStore.Save(comentario.Events);
// 5. Publicados no bus
await bus.Publish(comentario.Events);
// 1. Escuta eventos do bus
public class ComentarioProjection
{
public async Task On(ComentarioCriadoEvent e)
{
// 2. Atualiza view otimizada
await _readDb.Execute(@"
INSERT INTO ComentariosView
(Id, Texto, AutorNome, PostTitulo, Curtidas)
VALUES
(@Id, @Texto,
(SELECT Nome FROM Usuarios WHERE Id = @AutorId),
(SELECT Titulo FROM Posts WHERE Id = @PostId),
0
)", e);
}
}
// 3. Query super rápida!
var comentarios = await _readDb
.ComentariosView
.Where(c => c.PostId == 42)
.ToListAsync(); // Sem JOINs! 🚀
Todos os eventos salvos = histórico completo
Queries otimizadas sem JOINs complexos
Mesmos eventos, várias projeções diferentes
Em arquiteturas distribuídas, cada serviço pode falhar independentemente. Precisamos de padrões para lidar com falhas, latência, dados distribuídos e comunicação.
Um serviço cai, mas outros continuam rodando. Como lidar?
Chamadas HTTP são 10.000x mais lentas que chamadas in-memory!
Cada serviço tem seu banco. Como manter consistência?
Uma requisição passa por 10 serviços. Como rastrear erros?
Proteção contra falhas em cascata
Transações distribuídas
Entrada única
Migração gradual
Observabilidade
💡 Dica Importante: Esses padrões não são exclusivos! Na prática, você usa combinações deles para construir sistemas robustos. Vamos explorar cada um em detalhes!
Quando um serviço está falhando, o Circuit Breaker "desliga o circuito" temporariamente, evitando chamadas desnecessárias e dando tempo para o serviço se recuperar.
Quando você liga muitos aparelhos ao mesmo tempo, o disjuntor desarma automaticamente pra não queimar a fiação. Depois de alguns minutos, você pode tentar religar. Se ainda tiver sobrecarga, desarma de novo!
✅ Tudo funcionando
✅ Requisições passam
📊 Monitorando falhas
❌ Circuito aberto
⚡ Fail-fast imediato
⏱️ Aguardando timeout
🧪 Testando recuperação
✅ Se OK → CLOSED
❌ Se falhar → OPEN
// 1. Configurar Circuit Breaker
var circuitBreakerPolicy = Policy
.Handle<HttpRequestException>()
.Or<TimeoutException>()
.CircuitBreakerAsync(
handledEventsAllowedBeforeBreaking: 3, // 🔴 3 falhas = OPEN
durationOfBreak: TimeSpan.FromSeconds(30) // ⏱️ 30s = HALF-OPEN
);
// 2. Usar o Circuit Breaker
try
{
var comentarios = await circuitBreakerPolicy
.ExecuteAsync(async () =>
{
return await _httpClient
.GetFromJsonAsync<List<Comentario>>(
"https://api-comentarios/api/posts/42/comentarios"
);
});
return comentarios;
}
catch (BrokenCircuitException ex)
{
// ⚡ Circuito OPEN - fail-fast!
_logger.LogWarning("Circuit breaker is OPEN. Using fallback.");
return GetComentariosFromCache(); // Fallback
}
// Política completa: Retry + Circuit Breaker + Fallback
var retryPolicy = Policy
.Handle<HttpRequestException>()
.WaitAndRetryAsync(2, i => TimeSpan.FromSeconds(i));
var circuitBreaker = Policy
.Handle<HttpRequestException>()
.CircuitBreakerAsync(3, TimeSpan.FromSeconds(30));
var fallbackPolicy = Policy<List<Comentario>>
.Handle<Exception>()
.FallbackAsync(GetComentariosFromCache());
// Combinar políticas (ordem importa!)
var policy = Policy.WrapAsync(
fallbackPolicy, // 3️⃣ Se tudo falhar, usa cache
circuitBreaker, // 2️⃣ Protege contra falhas repetidas
retryPolicy // 1️⃣ Tenta 2x antes de desistir
);
var comentarios = await policy.ExecuteAsync(
() => _httpClient.GetFromJsonAsync<List<Comentario>>(url)
);
✅ Benefícios:
• Evita sobrecarga no serviço falhando
• Resposta rápida (fail-fast)
• Auto-recuperação (half-open testing)
• Fallback para cache/dados default
Uma Saga é uma sequência de transações locais, onde cada transação atualiza um serviço e publica um evento. Se uma transação falha, transações compensatórias desfazem as mudanças anteriores.
Você reserva voo + hotel + carro. Se o hotel falhar, o sistema automaticamente cancela o voo e o carro que já foram reservados. Ninguém fica com reserva incompleta!
Cada serviço escuta eventos e decide o que fazer.
Um Orchestrator central coordena todos os passos.
// Serviço de Blog
public class BlogService
{
public async Task CriarPost(CriarPostCommand cmd)
{
var post = new Post { Titulo = cmd.Titulo };
await _db.Posts.AddAsync(post);
await _db.SaveChangesAsync();
// Publica evento
await _bus.Publish(new PostCriadoEvent(post.Id));
}
}
// Serviço de Email (escuta eventos)
public class EmailSagaHandler : IHandleMessages<PostCriadoEvent>
{
public async Task Handle(PostCriadoEvent evt)
{
try
{
await _emailService.EnviarNotificacoes(evt.PostId);
await _bus.Publish(new NotificacoesEnviadasEvent(evt.PostId));
}
catch
{
// Falhou! Publica evento de falha
await _bus.Publish(new NotificacoesFalharamEvent(evt.PostId));
}
}
}
// Serviço de Blog (escuta falhas para compensar)
public class BlogCompensationHandler : IHandleMessages<IndexacaoFalhouEvent>
{
public async Task Handle(IndexacaoFalhouEvent evt)
{
// COMPENSAÇÃO: Marca post como rascunho
var post = await _db.Posts.FindAsync(evt.PostId);
post.Status = PostStatus.Rascunho;
await _db.SaveChangesAsync();
}
}
// Orchestrator coordena TUDO
public class CriarPostSagaOrchestrator
{
private readonly IBlogService _blog;
private readonly IEmailService _email;
private readonly ISearchService _search;
public async Task ExecuteSaga(CriarPostCommand cmd)
{
int? postId = null;
bool emailEnviado = false;
try
{
// Passo 1: Criar post
postId = await _blog.CriarPost(cmd);
// Passo 2: Enviar notificações
await _email.EnviarNotificacoes(postId.Value);
emailEnviado = true;
// Passo 3: Indexar
await _search.IndexarPost(postId.Value);
// ✅ SUCESSO!
}
catch (Exception ex)
{
// ❌ FALHA! Compensar tudo
_logger.LogError(ex, "Saga falhou. Compensando...");
if (emailEnviado)
{
// Não dá pra "cancelar" email, só logar
_logger.LogWarning("Emails já enviados");
}
if (postId.HasValue)
{
// Compensar: deletar ou marcar como rascunho
await _blog.MarcarComoRascunho(postId.Value);
}
throw new SagaFailedException("Saga falhou", ex);
}
}
}
Um API Gateway é um servidor que atua como ponto único de entrada para todos os clientes. Ele roteia requisições para os microservices apropriados e pode agregar respostas de múltiplos serviços.
Você chega no prédio e fala com a recepcionista. Ela te direciona pro departamento certo (RH, TI, Vendas), verifica sua identidade, registra sua visita no log, e até combina informações de vários departamentos se necessário!
/api/posts/* → Blog Service
/api/comments/* → Comment Service
/api/users/* → User Service
Valida JWT uma vez no gateway, não em cada serviço
100 req/min por usuário, proteção contra DDoS
// appsettings.json
{
"ReverseProxy": {
"Routes": {
"blog-route": {
"ClusterId": "blog-cluster",
"Match": {
"Path": "/api/posts/{**catch-all}"
},
"Transforms": [
{ "PathPattern": "/api/posts/{**catch-all}" }
]
},
"comments-route": {
"ClusterId": "comments-cluster",
"Match": {
"Path": "/api/comments/{**catch-all}"
}
}
},
"Clusters": {
"blog-cluster": {
"Destinations": {
"blog1": { "Address": "https://localhost:5001" },
"blog2": { "Address": "https://localhost:5002" }
},
"LoadBalancingPolicy": "RoundRobin"
},
"comments-cluster": {
"Destinations": {
"comments1": { "Address": "https://localhost:5003" }
}
}
}
}
}
// Program.cs
builder.Services.AddReverseProxy()
.LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"));
app.MapReverseProxy();
// Endpoint de agregação no Gateway
[HttpGet("api/posts/{postId}/full")]
public async Task<PostFullDto> GetPostFull(int postId)
{
// Chama 3 serviços em PARALELO
var postTask = _blogClient
.GetAsync($"api/posts/{postId}");
var commentsTask = _commentsClient
.GetAsync($"api/comments/post/{postId}");
var authorTask = _usersClient
.GetAsync($"api/users/{post.AuthorId}");
await Task.WhenAll(postTask, commentsTask, authorTask);
// Agrega as respostas
return new PostFullDto
{
Post = await postTask.Result.Content
.ReadFromJsonAsync<Post>(),
Comments = await commentsTask.Result.Content
.ReadFromJsonAsync<List<Comment>>(),
Author = await authorTask.Result.Content
.ReadFromJsonAsync<User>()
};
}
// Cliente recebe TUDO em 1 requisição! 🚀
✅ Benefícios:
• Menos chamadas do cliente
• Reduz latência (paralelo)
• Simplifica lógica do frontend
• Backend for Frontend (BFF) pattern
Padrão para migrar gradualmente de um monolito para microservices, substituindo funcionalidades uma de cada vez, sem reescrever tudo de uma vez.
A figueira-estranguladora (strangler fig) é uma planta que cresce ao redor de uma árvore hospedeira. Com o tempo, ela vai crescendo e eventualmente substitui completamente a árvore original, sem derrubar tudo de uma vez!
// StranglerFigMiddleware.cs
public class StranglerFigMiddleware
{
private readonly RequestDelegate _next;
private readonly IHttpClientFactory _httpClient;
public async Task InvokeAsync(HttpContext context)
{
var path = context.Request.Path.Value;
// Feature Flag: está migrado?
if (path.StartsWith("/api/comments") &&
await _featureFlags.IsEnabled("CommentsV2"))
{
// ✅ ROTA PARA O NOVO MICROSERVICE
await ProxyToService(
context,
"https://comments-service:5002"
);
}
else
{
// 🏛️ ROTA PARA O MONOLITO
await ProxyToService(
context,
"https://monolito:5000"
);
}
}
private async Task ProxyToService(
HttpContext context,
string targetUrl)
{
var client = _httpClient.CreateClient();
var targetUri = new Uri(targetUrl + context.Request.Path);
var proxyRequest = new HttpRequestMessage
{
Method = new HttpMethod(context.Request.Method),
RequestUri = targetUri
};
// Copia headers e body
foreach (var header in context.Request.Headers)
proxyRequest.Headers.TryAddWithoutValidation(
header.Key, header.Value.ToArray());
var response = await client.SendAsync(proxyRequest);
// Retorna resposta pro cliente
context.Response.StatusCode = (int)response.StatusCode;
await response.Content.CopyToAsync(
context.Response.Body
);
}
}
✅ Vantagens:
• Migração sem big bang
• Rollback fácil (só mudar proxy)
• Risco controlado
• Negócio continua operando
💡 Ferramentas:
• YARP (Yet Another Reverse Proxy)
• Nginx com Lua scripts
• Feature Flags (LaunchDarkly, Split)
• Canary deployments
Uma camada de infraestrutura que gerencia comunicação entre microservices de forma transparente, sem mudar código da aplicação. Adiciona retry, circuit breaker, observability, security automaticamente!
Imagine que cada serviço é uma casa. O Service Mesh é a rede de encanamento que conecta todas as casas. Ela monitora o fluxo de água, detecta vazamentos, regula pressão, e até redireciona água se um cano quebrar - tudo sem as casas saberem!
O QUE: Números agregados
Exemplos:
• Taxa de requisições/s
• Latência P50/P95/P99
• Taxa de erro
• CPU/Memory usage
Ferramentas: Prometheus, Grafana
O QUE: Eventos discretos
Exemplos:
• "User 42 created post 123"
• "Payment failed: timeout"
• Stack traces de erros
Ferramentas: ELK Stack, Loki, Seq
O QUE: Jornada completa
Exemplos:
• Requisição passa por:
Gateway (5ms) →
Blog (20ms) →
User (120ms) ← LENTO!
Ferramentas: Jaeger, Zipkin, OpenTelemetry
# VirtualService: Roteamento inteligente
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: comments-route
spec:
hosts:
- comments-service
http:
- match:
- headers:
user-type:
exact: "beta-tester"
route:
- destination:
host: comments-service
subset: v2 # ✅ Beta testers → v2
weight: 100
- route:
- destination:
host: comments-service
subset: v1 # Resto → v1
weight: 90
- destination:
host: comments-service
subset: v2 # 10% canary deployment
weight: 10
---
# DestinationRule: Circuit Breaker automático!
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
name: comments-circuit-breaker
spec:
host: comments-service
trafficPolicy:
connectionPool:
tcp:
maxConnections: 100
http:
http1MaxPendingRequests: 50
maxRequestsPerConnection: 2
outlierDetection:
consecutiveErrors: 3
interval: 30s
baseEjectionTime: 30s
// Program.cs - ASP.NET Core
builder.Services.AddOpenTelemetry()
.WithTracing(tracing =>
{
tracing
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddSqlClientInstrumentation()
.AddJaegerExporter(options =>
{
options.Endpoint = new Uri("http://jaeger:14268");
});
})
.WithMetrics(metrics =>
{
metrics
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddPrometheusExporter();
});
// Uso automático! Só configurar e pronto.
// Istio injeta headers de tracing automaticamente:
// x-request-id, x-b3-traceid, x-b3-spanid
// Resultado no Jaeger:
//
// GET /api/posts/42/full [200ms total]
// ├─ Gateway [5ms]
// ├─ Blog Service [20ms]
// │ └─ SQL Query [15ms]
// ├─ Comment Service [30ms]
// │ └─ SQL Query [25ms]
// └─ User Service [120ms] ← GARGALO!
// └─ External API [115ms] ← Problema aqui!
✅ Benefícios do Service Mesh:
• Zero mudança no código da app
• Circuit breaker/retry automático
• mTLS entre serviços
• Canary deployment fácil
• Observability completa
Programação funcional traz padrões que evitam bugs e facilitam testes.
🎯 Analogia: Como receita de bolo - mesmos ingredientes, sempre o mesmo resultado! Sem surpresas.
💡 C# + FP: C# 12 suporta FP muito bem! LINQ, Records, Pattern Matching...
// ✅ Option/Maybe Pattern (sem null!)
public record Option<T>
{
public static Option<T> Some(T value) => new Some<T>(value);
public static Option<T> None() => new None<T>();
}
public record Some<T>(T Value) : Option<T>;
public record None<T> : Option<T>;
// Uso
Option<Comentario> BuscarComentario(int id) =>
comentarios.ContainsKey(id)
? Option<Comentario>.Some(comentarios[id])
: Option<Comentario>.None();
// Pattern matching FTW!
var resultado = BuscarComentario(42) switch
{
Some<Comentario>(var c) => $"Encontrado: {c.Texto}",
None<Comentario> => "Não encontrado!"
};
// ✅ Railway Oriented Programming
public record Result<T>
{
public static Result<T> Success(T value) => new Success<T>(value);
public static Result<T> Failure(string error) => new Failure<T>(error);
}
public record Success<T>(T Value) : Result<T>;
public record Failure<T>(string Error) : Result<T>;
// Encadeamento de operações que podem falhar
var resultado = ValidarComentario(texto)
.Bind(SalvarNoBanco)
.Bind(EnviarNotificacao)
.Match(
success => $"Sucesso: {success}",
failure => $"Erro: {failure}"
);
Uma classe que faz TUDO. Sabe demais, controla demais.
// ❌ Anti-Pattern
class Sistema {
void CriarUsuario() { }
void EnviarEmail() { }
void ProcessarPagamento() { }
void GerarRelatorio() { }
void ValidarDados() { }
// ... 50 métodos mais
}
Código sem estrutura, cheio de gotos, ifs aninhados, sem separação de responsabilidades.
// ❌ Anti-Pattern
if (tipo == "A") {
if (status == 1) {
if (ativo) {
// 50 linhas aqui
if (validado) {
// mais 30 linhas
}
}
}
}
Duplicar código ao invés de criar abstrações reutilizáveis.
Problema: Bug em um lugar = bug em 10 lugares.
"Quando você só tem um martelo, tudo parece um prego."
Exemplo: Usar Singleton para TUDO, mesmo quando não faz sentido.
"A raiz de todo mal" - Donald Knuth
Otimizar antes de medir. Código complexo sem ganho real.
Valores literais sem contexto espalhados pelo código.
// ❌ Anti-Pattern
if (status == 3) { }
// ✅ Correto
if (status == Status.Aprovado) { }
"Padrões são ferramentas, não dogmas. Use-os quando resolvem um problema real, não para mostrar que você conhece padrões. O código mais simples que funciona sempre é melhor do que o código complexo que impressiona."
| Padrão | Categoria | Problema que Resolve | Quando Usar |
|---|---|---|---|
| Singleton | Criacional | Múltiplas instâncias indesejadas | Config, Logger, Pool |
| Factory Method | Criacional | Criação acoplada ao código cliente | Tipos variados de objetos |
| Builder | Criacional | Construtores com muitos parâmetros | Objetos complexos |
| Adapter | Estrutural | Interfaces incompatíveis | Integração com legado/3rd party |
| Decorator | Estrutural | Adicionar comportamento sem herança | Middleware, logging, caching |
| Facade | Estrutural | Sistema complexo difícil de usar | Simplificar API complexa |
| Observer | Comportamental | Notificar mudanças para múltiplos objetos | Eventos, Pub/Sub, UI reativa |
| Strategy | Comportamental | Algoritmos variáveis | Trocar comportamento em runtime |
| Command | Comportamental | Encapsular requisições | Undo/Redo, filas de comandos |
Aplicando orientação a objetos na estrutura do sistema.