Aula 5 • Projeto 9 • Sistemas de Informação
Gang of Four (GoF)

Padrões de projeto clássicos e sua aplicação em sistemas reais.

🧩 Padrões 🏗️ Arquitetura ♻️ Refatoração 📐 Boas práticas
Prof. Afonso Brandão • 2 horas • Fevereiro 2025
Agenda
  • História: A Gang of Four e o livro que mudou tudo (1994).
  • Por que padrões? Vocabulário comum e soluções testadas.
  • As 3 Categorias: Criacionais, Estruturais e Comportamentais.
  • Padrões Essenciais: Singleton, Factory, Observer, Strategy e mais.
  • Na Prática: Aplicando padrões no sistema de Blog/Analytics.
  • Anti-Patterns: O que evitar e por quê.
Gang of Four (GoF)
O livro que criou um vocabulário universal

"Design Patterns: Elements of Reusable Object-Oriented Software" (1994)

Erich Gamma

  • Eclipse IDE: Co-criador do Eclipse, a IDE Java mais famosa dos anos 2000
  • JUnit: Co-criador do framework de testes unitários com Kent Beck
  • Visual Studio Code: Líder técnico do VSCode na Microsoft
  • Jazz Platform: Arquiteto-chefe na IBM Rational

Richard Helm

  • IBM Research: Pesquisador em arquitetura de software
  • Ferramentas CASE: Trabalhou em ferramentas de engenharia de software assistida por computador
  • Refactoring Tools: Contribuiu para ferramentas automatizadas de refatoração
  • Academia: Influência em pesquisa de design orientado a objetos

Ralph Johnson

  • University of Illinois: Professor de Ciência da Computação
  • Smalltalk Community: Líder na comunidade Smalltalk e programação orientada a objetos
  • Refactoring: Pioneiro em técnicas de refatoração (influenciou Martin Fowler)
  • Frameworks: Pesquisa fundamental sobre design de frameworks reutilizáveis

John Vlissides (1961-2005)

  • IBM Research: Pesquisador sênior em Yorktown Heights
  • InterViews: Co-desenvolvedor do toolkit gráfico C++ InterViews
  • Unidraw: Framework para editores gráficos
  • Pattern Languages: Contribuições significativas para linguagens de padrões

💡 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.

Antes dos Padrões: O Caos
Por que padrões são necessários

❌ Sem Padrões

  • Cada dev resolve o mesmo problema de forma diferente.
  • Código difícil de entender e manter.
  • Acoplamento alto, coesão baixa.
  • Bugs recorrentes em mudanças simples.
  • Onboarding lento de novos devs.

✅ Com Padrões

  • Soluções padronizadas e reconhecíveis.
  • Código auto-documentado pelo padrão.
  • Baixo acoplamento, alta coesão.
  • Mudanças localizadas e seguras.
  • Onboarding rápido: "Ah, é um Observer!"

⚠️ Importante: Padrões NÃO são código pronto para copiar. São templates conceituais que você adapta ao seu contexto.

As 3 Categorias de Padrões
Criacionais, Estruturais e Comportamentais

🏗️ Criacionais

Foco: Como os objetos são criados.

Objetivo: Esconder a lógica de criação, tornando o sistema mais flexível.

  • Singleton
  • Factory Method
  • Abstract Factory
  • Builder
  • Prototype

🔗 Estruturais

Foco: Como os objetos se relacionam.

Objetivo: Organizar classes e objetos para formar estruturas maiores.

  • Adapter
  • Decorator
  • Facade
  • Proxy
  • Composite

⚡ Comportamentais

Foco: Como os objetos interagem.

Objetivo: Definir comunicação e responsabilidades entre objetos.

  • Observer
  • Strategy
  • Command
  • Template Method
  • State
Singleton (Criacional)
Uma única instância global

O que é?

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.

Quando usar?

  • Configurações globais
  • Pools de conexão
  • Loggers
  • Cache compartilhado

⚠️ 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!)
Factory Method (Criacional)
Delegando a criação de objetos

O que é?

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)!

Quando usar?

  • Não sabe exatamente qual classe precisa criar
  • Quer delegar a decisão para subclasses
  • Precisa de flexibilidade para adicionar novos tipos

✅ 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!");
Observer (Comportamental)
O padrão por trás de eventos e notificações

O que é?

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!

Quando usar?

  • Sistemas de eventos (Event-Driven!)
  • Notificações em tempo real
  • UI reativa (quando dados mudam)
  • Pub/Sub patterns

💡 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!");
Strategy (Comportamental)
Trocar algoritmos em tempo de execução

O que é?

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.

Quando usar?

  • Múltiplas formas de fazer a mesma coisa
  • Evitar if/else ou switch gigantes
  • Algoritmos que mudam em runtime
  • Processamento de pagamento, ordenação, validação

✅ 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
Decorator (Estrutural)
Adicionando responsabilidades dinamicamente

O que é?

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!

Quando usar?

  • Adicionar funcionalidades opcionais
  • Evitar explosão de subclasses
  • Middleware, pipelines de processamento
  • Logging, caching, validação

💡 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"
Adapter (Estrutural)
Conectando interfaces incompatíveis

O que é?

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!

Quando usar?

  • Integração com APIs legadas
  • Bibliotecas de terceiros com interfaces diferentes
  • Migração gradual de sistemas
  • Abstrair dependências externas

💡 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
});
Chain of Responsibility (Comportamental)
Passando a batata quente até alguém resolver

O que é?

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!

Quando usar?

  • Pipeline de processamento de eventos
  • Validações em sequência
  • Middleware HTTP (ASP.NET Core!)
  • Sistema de suporte por níveis

💡 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!" });
📚 Recurso Essencial: Refactoring.Guru
O melhor site sobre Design Patterns e Refactoring

Refactoring.Guru

https://refactoring.guru

✨ Por que é incrível?

  • Visuais incríveis: Diagramas UML claros e ilustrações
  • Múltiplas linguagens: C#, Java, Python, TypeScript, Go, PHP...
  • Exemplos práticos: Código antes/depois lado a lado
  • Analogias do mundo real: Fácil de entender
  • Totalmente grátis! (tem versão paga com mais conteúdo)

🎯 Como usar?

  1. Acesse refactoring.guru
  2. Escolha "Design Patterns" no menu
  3. Selecione a categoria (Criacional, Estrutural, Comportamental)
  4. Clique no padrão que quer estudar
  5. Escolha sua linguagem favorita (C#!)

💡 Dica: Use para revisar TODOS os padrões que vimos hoje com exemplos visuais!

🚀 Links Diretos dos Padrões que Vimos:

Singleton Factory Method Observer Strategy Decorator Adapter Chain of Responsibility
Padrões no Sistema de Blog/Analytics
Aplicando GoF na prática

Como os padrões aparecem no nosso projeto?

Factory Method

Onde: Criação de notificações (Email, SMS, Push)

Permite adicionar novos tipos de notificação sem modificar código existente.

Observer

Onde: Sistema de eventos Kafka

Comentário criado → Analytics, Moderação e Dashboard são notificados.

Strategy

Onde: Análise de sentimento

Diferentes algoritmos de análise podem ser trocados dinamicamente.

Decorator

Onde: Pipeline de processamento de comentários

Filtro de spam, validação, logging - empilhados como decorators.

Singleton

Onde: Configuração global, Logger

Uma única instância de configuração compartilhada por toda a aplicação.

Adapter

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!

🚀 Padrões Modernos (2020+)
O que há de novo no mundo dos Design Patterns

A GoF ainda é relevante, mas o mundo evoluiu!

🆕 Novas Tendências

  • Functional Patterns: Imutabilidade, Pattern Matching
  • Event Sourcing & CQRS: Auditoria total, escalabilidade
  • Microservices Patterns: Resiliência distribuída
  • Cloud-Native: Serverless, containers
  • Modern C# Features: Records, init-only, pattern matching

🎯 Por que importa?

  • Sistemas distribuídos são a norma
  • Cloud-first architecture
  • Requisitos de escala massiva
  • Observabilidade e resiliência
  • Developer Experience (DX)

💡 Insight: GoF + Padrões Modernos = Arquitetura de ponta!

📚 Vamos explorar os 4 principais:

Modern C# Patterns
Event Sourcing & CQRS
Microservices Patterns
Functional Patterns
Modern C# Patterns (C# 9-12)
Pattern Matching, Records e mais

O que mudou?

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.

Principais Features:

  • Records: Imutabilidade fácil
  • Pattern Matching: Switch statements on steroids
  • Init-only properties: Imutabilidade após construção
  • Nullable Reference Types: Adeus NullReferenceException!
// ✅ 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());
        }
    }
}
📼 Event Sourcing - Introdução
A fonte da verdade são os eventos, não o estado

O que é Event Sourcing?

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.

🎯 Analogia Lúdica: Conta Bancária

❌ Abordagem Tradicional (CRUD)

Conta { Id: 123, Saldo: 1500.00 }

😢 Perdemos o histórico! Como chegamos em R$ 1.500?

✅ Event Sourcing

+ ContaCriada { valor: 0 }
+ Deposito { valor: 2000 }
+ Saque { valor: 300 }
+ Saque { valor: 200 }
= Saldo: 1500

🎉 Auditoria completa! Sabemos tudo que aconteceu!

✅ Vantagens

  • Auditoria Total: Quem, quando, o quê
  • Time Travel: Estado em qualquer momento
  • Debugging: Replay de eventos
  • Event-Driven: Integração natural
  • Múltiplas Projeções: Diferentes visões

⚠️ Desvantagens

  • Complexidade: Curva de aprendizado
  • Performance: Replay pode ser lento
  • Storage: Mais espaço em disco
  • Queries: Não é direto como SQL
  • Eventual Consistency: Nem sempre imediato
📼 Event Sourcing - Implementação em C#
Código real de Event Sourcing para comentários de blog

1️⃣ Definir os Eventos

// 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;

2️⃣ Aggregate Root

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;
    }
}

3️⃣ Usando o Aggregate

// 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
📼 Event Store & Snapshots
Armazenamento e otimização de eventos

Event Store - Onde Salvar os Eventos?

🗄️ SQL Relacional

PostgreSQL, SQL Server

✅ Transações ACID
✅ Queries complexas
⚠️ Performance em escala

📦 Event Store DB

EventStoreDB (Greg Young)

✅ Otimizado pra eventos
✅ Projections nativas
✅ Subscriptions

☁️ Cloud Native

Azure Event Store, Kafka

✅ Escalabilidade
✅ Managed service
⚠️ Custo

⚡ Problema: E se tiver 10.000 eventos?

Replay de 10.000 eventos pra reconstruir o estado seria muito lento!

💡 Solução: Snapshots!

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!

Implementação com Snapshots

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()
            );
        }
    }
}

Estratégias de Snapshot

1️⃣ Por Quantidade

Snapshot a cada 100 eventos

2️⃣ Por Tempo

Snapshot diário (00:00)

3️⃣ Por Demanda

Se replay > 5 segundos, cria snapshot

4️⃣ Híbrido

Combina quantidade + tempo

🎯 Dica: Snapshots são opcionais! Comece sem eles e adicione só quando necessário.

📊 CQRS - Introdução
Command Query Responsibility Segregation

O que é CQRS?

Separar as operações de ESCRITA (Commands) das operações de LEITURA (Queries) usando modelos diferentes.

🎯 Analogia Lúdica: Restaurante

📝 COMMAND (Escrita)

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

🔍 QUERY (Leitura)

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

⚖️ Problema do Modelo Único (CRUD Tradicional)

// ❌ 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!

✅ Vantagens do CQRS

  • Performance: Queries otimizadas
  • Escalabilidade: Escalar leitura separado
  • Flexibilidade: Múltiplas projeções
  • Simplicidade: Modelos focados
  • Cache: Fácil cachear queries

⚠️ Desvantagens

  • Complexidade: Dois modelos pra manter
  • Eventual Consistency: Delay na sincronia
  • Duplicação: Mais código
  • Learning Curve: Paradigma diferente
📊 CQRS - Implementação em C#
Código real com MediatR e modelos separados

📝 WRITE SIDE (Commands)

// 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;
    }
}

🔍 READ SIDE (Queries)

// 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);
    }
}

🔄 Sincronização: Write Model → Read Model

// 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);
    }
}
🚀 CQRS + Event Sourcing Juntos
O casamento perfeito para sistemas complexos

💡 Por que combinar?

Event Sourcing cuida do WRITE (salva eventos).
CQRS cuida do READ (projeta eventos em views otimizadas).

🏗️ Arquitetura Completa

┌─────────────────── WRITE SIDE (Commands) ───────────────────┐
📝 Command → CommandHandler → Aggregate
Aggregate gera → DomainEvents
Events salvos em → EventStore
EventStore publica → Event Bus (Kafka/RabbitMQ)
└────────────────────────┬────────────────────────┘
┌────────────────────── READ SIDE (Queries) ──────────────────┐
Event BusProjections (Event Handlers)
Projections atualizam → Read Database (Views)
🔍 Query → QueryHandler → Read Database → DTO

📝 WRITE: Event Sourcing

// 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);

🔍 READ: Projections

// 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! 🚀

🎯 Auditoria Total

Todos os eventos salvos = histórico completo

⚡ Performance

Queries otimizadas sem JOINs complexos

📊 Múltiplas Views

Mesmos eventos, várias projeções diferentes

🎯 Quando usar esta arquitetura?

  • ✅ Sistemas financeiros (auditoria crítica)
  • ✅ E-commerce (histórico de pedidos/estoque)
  • ✅ Analytics em tempo real (dashboards)
  • ✅ Sistemas colaborativos (tracking de mudanças)
  • ❌ CRUD simples (overkill!)
  • ❌ Prototipagem rápida (muita complexidade)
🏗️ Microservices Patterns
Padrões essenciais para sistemas distribuídos resilientes

Por que Microservices Patterns?

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.

⚠️ Desafios de Sistemas Distribuídos

1️⃣ Falhas Parciais

Um serviço cai, mas outros continuam rodando. Como lidar?

2️⃣ Latência de Rede

Chamadas HTTP são 10.000x mais lentas que chamadas in-memory!

3️⃣ Dados Distribuídos

Cada serviço tem seu banco. Como manter consistência?

4️⃣ Debugging Complexo

Uma requisição passa por 10 serviços. Como rastrear erros?

🎯 Os 5 Padrões Essenciais

Circuit Breaker

Proteção contra falhas em cascata

🔄

Saga Pattern

Transações distribuídas

🚪

API Gateway

Entrada única

🌳

Strangler Fig

Migração gradual

🕸️

Service Mesh

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!

⚡ Circuit Breaker Pattern
Evitando falhas em cascata

O que é Circuit Breaker?

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.

🎯 Analogia Lúdica: Disjuntor de Casa

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!

🔄 Estados do Circuit Breaker

🟢

CLOSED

✅ Tudo funcionando
✅ Requisições passam
📊 Monitorando falhas

3+ falhas
🔴

OPEN

❌ Circuito aberto
⚡ Fail-fast imediato
⏱️ Aguardando timeout

↓ (após 30s) ↓
🟡

HALF-OPEN

🧪 Testando recuperação
✅ Se OK → CLOSED
❌ Se falhar → OPEN

Implementação com Polly (C#)

// 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
}

Com Fallback e Retry

// 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

🔄 Saga Pattern
Transações distribuídas com compensação

O que é Saga Pattern?

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.

🎯 Analogia Lúdica: Reserva de Viagem

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!

📊 Fluxo de uma Saga

✅ CENÁRIO DE SUCESSO

1️⃣ Criar Post no serviço de Blog
↓ ✅ Post criado (ID: 42)
↓ 📢 Evento: PostCriadoEvent
2️⃣ Enviar Notificações no serviço de Email
↓ ✅ Emails enviados
↓ 📢 Evento: NotificacoesEnviadasEvent
3️⃣ Indexar Busca no serviço de Search
↓ ✅ Post indexado no Elasticsearch
↓ 📢 Evento: PostIndexadoEvent
🎉 SAGA COMPLETA COM SUCESSO!

❌ CENÁRIO DE FALHA COM COMPENSAÇÃO

1️⃣ Criar Post no serviço de Blog
↓ ✅ Post criado (ID: 42)
↓ 📢 Evento: PostCriadoEvent
2️⃣ Enviar Notificações no serviço de Email
↓ ✅ Emails enviados
↓ 📢 Evento: NotificacoesEnviadasEvent
3️⃣ Indexar Busca no serviço de Search
↓ ❌ FALHOU! Elasticsearch indisponível
↓ 📢 Evento: IndexacaoFalhouEvent
⚠️ INICIANDO COMPENSAÇÕES (ROLLBACK):
Compensar Notificações: Registrar falha no log
Compensar Post: Marcar post como "rascunho" ou deletar
🔄 SAGA REVERTIDA!

📋 Choreography Saga

Cada serviço escuta eventos e decide o que fazer.

Vantagens:
• Desacoplamento total
• Sem ponto único de falha
• Fácil adicionar novos serviços

Desvantagens:
• Difícil entender o fluxo completo
• Complexo debugar
• Risco de loops infinitos

🎭 Orchestration Saga

Um Orchestrator central coordena todos os passos.

Vantagens:
• Fluxo centralizado e claro
• Fácil debugar e monitorar
• Controle total sobre compensações

Desvantagens:
• Acoplamento ao orchestrator
• Ponto único de falha
• Orchestrator pode ficar complexo

Choreography: Event-Driven

// 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();
    }
}

Orchestration: Controlador Central

// 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);
        }
    }
}
🚪 API Gateway Pattern
Ponto único de entrada para microservices

O que é API Gateway?

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.

🎯 Analogia Lúdica: Recepcionista do Prédio

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!

🏗️ Arquitetura com API Gateway

📱💻🖥️
Clientes
(Mobile, Web, Desktop)
🚪 API GATEWAY
🔐 Auth • 🔀 Routing • ⚡ Rate Limit
📦 Cache • 🔄 Load Balance • 📊 Logging
📝
Blog Service
:5001
💬
Comment Service
:5002
👤
User Service
:5003
🔍
Search Service
:5004

🔀 Roteamento

/api/posts/* → Blog Service
/api/comments/* → Comment Service
/api/users/* → User Service

🔐 Autenticação

Valida JWT uma vez no gateway, não em cada serviço

⚡ Rate Limiting

100 req/min por usuário, proteção contra DDoS

Implementação com YARP (C#)

// 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();

Agregação de Respostas

// 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

🌳 Strangler Fig Pattern
Migração gradual de monolito para microservices

O que é Strangler Fig Pattern?

Padrão para migrar gradualmente de um monolito para microservices, substituindo funcionalidades uma de cada vez, sem reescrever tudo de uma vez.

🎯 Analogia Lúdica: Figueira-Estranguladora

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!

📊 Processo de Migração Gradual

FASE 1: Início

🏛️ MONOLITO
100% do tráfego

• Posts
• Comments
• Users
• Search
Tudo no monolito!

FASE 2: Migração Parcial

🎯 API Gateway
Roteamento inteligente
✅ Comments Service
20% tráfego
🏛️ Monolito
80% tráfego

• Posts
• Users
• Search

FASE 3: Completo

🎯 API Gateway
✅ Posts
✅ Comments
✅ Users
✅ Search
🏛️ Monolito desligado!

Implementação do Proxy

// 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
        );
    }
}

Estratégia de Migração

📋 Passo a Passo

  1. Identificar bounded context a migrar
  2. Criar novo microservice
  3. Implementar funcionalidade no novo serviço
  4. Configurar proxy/gateway para rotear
  5. Testar com % pequeno do tráfego
  6. Aumentar gradualmente até 100%
  7. Remover código do monolito
  8. Repetir para próxima feature!

✅ 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

🕸️ Service Mesh & Observability
Infraestrutura dedicada para comunicação entre serviços

O que é Service Mesh?

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!

🎯 Analogia Lúdica: Rede de Encanamento

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!

🏗️ Arquitetura do Service Mesh

📝 Blog Service
🔷 Sidecar Proxy
(Envoy)
💬 Comment Service
🔷 Sidecar Proxy
(Envoy)
👤 User Service
🔷 Sidecar Proxy
(Envoy)
↕️ ↕️ ↕️
Comunicação via proxies (encrypted, monitored, resilient)
🎛️ CONTROL PLANE
Istio / Linkerd / Consul
📊 Telemetria • 🔐 Policies • 🔧 Configuração
🚦 Traffic Management • 🔒 mTLS • 📈 Métricas

🔭 Os 3 Pilares da Observability

📊 Metrics

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

📝 Logs

O QUE: Eventos discretos
Exemplos:
• "User 42 created post 123"
• "Payment failed: timeout"
• Stack traces de erros
Ferramentas: ELK Stack, Loki, Seq

🔍 Traces

O QUE: Jornada completa
Exemplos:
• Requisição passa por:
Gateway (5ms) →
Blog (20ms) →
User (120ms) ← LENTO!
Ferramentas: Jaeger, Zipkin, OpenTelemetry

Istio: Traffic Management

# 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

Distributed Tracing com OpenTelemetry

// 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

Functional Programming Patterns
Imutabilidade, composição e pureza

O que é FP?

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.

Principais Conceitos:

  • Imutabilidade: Dados nunca mudam
  • Pure Functions: Sem side effects
  • Option/Maybe: Adeus null!
  • Railway Oriented: Tratamento de erros elegante

💡 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}"
    );
Anti-Patterns: O que EVITAR
Padrões ruins que você precisa reconhecer

God Object (Objeto Deus)

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
}

Spaghetti Code

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
      }
    }
  }
}

Copy-Paste Programming

Duplicar código ao invés de criar abstrações reutilizáveis.

Problema: Bug em um lugar = bug em 10 lugares.

Golden Hammer

"Quando você só tem um martelo, tudo parece um prego."

Exemplo: Usar Singleton para TUDO, mesmo quando não faz sentido.

Premature Optimization

"A raiz de todo mal" - Donald Knuth

Otimizar antes de medir. Código complexo sem ganho real.

Magic Numbers/Strings

Valores literais sem contexto espalhados pelo código.

// ❌ Anti-Pattern
if (status == 3) { }
// ✅ Correto
if (status == Status.Aprovado) { }
Quando NÃO usar Padrões
Padrões não são bala de prata

⚠️ Cuidados

  • Over-engineering: Não use padrão só por usar. YAGNI (You Aren't Gonna Need It).
  • Complexidade desnecessária: Se um if/else simples resolve, não precisa de Strategy.
  • Código pequeno: Padrões têm overhead. Em scripts pequenos, KISS (Keep It Simple).
  • Performance crítica: Alguns padrões adicionam camadas que podem impactar performance.

✅ Boas Práticas

  • Refatore gradualmente: Não force padrões no início. Deixe emergir.
  • Contexto importa: Padrão X pode ser ótimo aqui, péssimo ali.
  • Combine com SOLID: Padrões + princípios SOLID = código limpo.
  • Documente a intenção: Comente POR QUE escolheu aquele padrão.

💭 Filosofia

"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."

Cheat Sheet: Padrões Essenciais
Referência rápida
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
Referências & Próximos Passos

Leitura Essencial:

  • "Design Patterns: Elements of Reusable Object-Oriented Software" - Gang of Four (Gamma, Helm, Johnson, Vlissides)
  • "Head First Design Patterns" - Freeman & Freeman (mais didático para iniciantes)
  • "Refactoring: Improving the Design of Existing Code" - Martin Fowler
  • "Clean Code" - Robert C. Martin

Recursos Online:

  • Refactoring.Guru - Excelente site visual sobre padrões
  • SourceMaking - Padrões + Anti-Patterns
  • DoFactory - Exemplos em C#

Próxima Aula: Classes, Instâncias e Herança (MVC)

Aplicando orientação a objetos na estrutura do sistema.

Atividade de sala