Decorator em Rust

O padrao Decorator em Rust: wrappers com trait objects, middleware em web frameworks, funcionalidade em camadas e exemplos praticos de logging e compressao.

Introducao

O Decorator (Decorador) e um padrao estrutural que permite adicionar funcionalidades a um objeto sem alterar sua interface original. Em vez de usar heranca para estender comportamento, o Decorator envolve (wraps) o objeto original e delega chamadas, adicionando logica antes ou depois.

Em Rust, o Decorator e particularmente elegante porque traits permitem composicao flexivel sem heranca. O padrao de middleware amplamente usado em web frameworks como Actix e Axum e essencialmente o Decorator aplicado a handlers HTTP.


Problema

Voce tem um servico que processa requisicoes. Com o tempo, precisa adicionar varias funcionalidades transversais: logging, autenticacao, cache, rate limiting, compressao. Cada funcionalidade deve ser independente e combinavel livremente.

// PROBLEMA: Cada combinacao requer uma implementacao separada
fn processar_com_log_e_cache(req: &Request) -> Response { /* ... */ }
fn processar_com_log_e_auth(req: &Request) -> Response { /* ... */ }
fn processar_com_cache_e_auth(req: &Request) -> Response { /* ... */ }
fn processar_com_log_cache_e_auth(req: &Request) -> Response { /* ... */ }
// Explosao combinatoria! Impossivel de manter.

Solucao em Rust

Decorator com Trait Objects

use std::collections::HashMap;
use std::time::Instant;

/// Requisicao HTTP simplificada
#[derive(Debug, Clone)]
pub struct Request {
    pub metodo: String,
    pub caminho: String,
    pub headers: HashMap<String, String>,
    pub corpo: Option<String>,
}

/// Resposta HTTP simplificada
#[derive(Debug, Clone)]
pub struct Response {
    pub status: u16,
    pub corpo: String,
    pub headers: HashMap<String, String>,
}

impl Response {
    pub fn ok(corpo: impl Into<String>) -> Self {
        Self {
            status: 200,
            corpo: corpo.into(),
            headers: HashMap::new(),
        }
    }

    pub fn erro(status: u16, mensagem: impl Into<String>) -> Self {
        Self {
            status,
            corpo: mensagem.into(),
            headers: HashMap::new(),
        }
    }
}

/// Trait que define um handler de requisicao
pub trait Handler {
    fn processar(&self, req: &Request) -> Response;
}

// === Handler Base (o servico real) ===

pub struct ApiHandler;

impl Handler for ApiHandler {
    fn processar(&self, req: &Request) -> Response {
        match req.caminho.as_str() {
            "/usuarios" => Response::ok(r#"[{"nome": "Maria"}, {"nome": "Joao"}]"#),
            "/status" => Response::ok(r#"{"status": "ok"}"#),
            _ => Response::erro(404, "Rota nao encontrada"),
        }
    }
}

// === Decorator: Logging ===

pub struct LoggingDecorator {
    inner: Box<dyn Handler>,
    nivel: String,
}

impl LoggingDecorator {
    pub fn new(inner: Box<dyn Handler>) -> Self {
        Self {
            inner,
            nivel: "INFO".to_string(),
        }
    }

    pub fn com_nivel(mut self, nivel: &str) -> Self {
        self.nivel = nivel.to_string();
        self
    }
}

impl Handler for LoggingDecorator {
    fn processar(&self, req: &Request) -> Response {
        let inicio = Instant::now();
        println!(
            "[{}] --> {} {}",
            self.nivel, req.metodo, req.caminho
        );

        // Delega para o handler interno
        let resposta = self.inner.processar(req);

        let duracao = inicio.elapsed();
        println!(
            "[{}] <-- {} {} ({:?})",
            self.nivel, resposta.status, req.caminho, duracao
        );

        resposta
    }
}

// === Decorator: Autenticacao ===

pub struct AuthDecorator {
    inner: Box<dyn Handler>,
    token_valido: String,
}

impl AuthDecorator {
    pub fn new(inner: Box<dyn Handler>, token: impl Into<String>) -> Self {
        Self {
            inner,
            token_valido: token.into(),
        }
    }
}

impl Handler for AuthDecorator {
    fn processar(&self, req: &Request) -> Response {
        // Verifica autenticacao ANTES de delegar
        match req.headers.get("Authorization") {
            Some(token) if token == &format!("Bearer {}", self.token_valido) => {
                println!("[AUTH] Token valido - acesso permitido");
                self.inner.processar(req)
            }
            Some(_) => {
                println!("[AUTH] Token invalido!");
                Response::erro(401, "Token de autenticacao invalido")
            }
            None => {
                println!("[AUTH] Header Authorization ausente!");
                Response::erro(401, "Autenticacao necessaria")
            }
        }
    }
}

// === Decorator: Rate Limiting ===

use std::sync::Mutex;

pub struct RateLimitDecorator {
    inner: Box<dyn Handler>,
    max_requisicoes: u32,
    contador: Mutex<u32>,
}

impl RateLimitDecorator {
    pub fn new(inner: Box<dyn Handler>, max: u32) -> Self {
        Self {
            inner,
            max_requisicoes: max,
            contador: Mutex::new(0),
        }
    }
}

impl Handler for RateLimitDecorator {
    fn processar(&self, req: &Request) -> Response {
        let mut contador = self.contador.lock().unwrap();

        if *contador >= self.max_requisicoes {
            println!("[RATE LIMIT] Limite excedido!");
            return Response::erro(429, "Muitas requisicoes. Tente novamente mais tarde.");
        }

        *contador += 1;
        let contagem_atual = *contador;
        drop(contador); // Libera o lock antes de processar

        println!(
            "[RATE LIMIT] Requisicao {}/{}",
            contagem_atual, self.max_requisicoes
        );
        self.inner.processar(req)
    }
}

// === Decorator: Cache ===

pub struct CacheDecorator {
    inner: Box<dyn Handler>,
    cache: Mutex<HashMap<String, Response>>,
}

impl CacheDecorator {
    pub fn new(inner: Box<dyn Handler>) -> Self {
        Self {
            inner,
            cache: Mutex::new(HashMap::new()),
        }
    }
}

impl Handler for CacheDecorator {
    fn processar(&self, req: &Request) -> Response {
        // So faz cache de GET
        if req.metodo != "GET" {
            return self.inner.processar(req);
        }

        let chave = req.caminho.clone();
        let cache = self.cache.lock().unwrap();

        if let Some(resposta_cache) = cache.get(&chave) {
            println!("[CACHE] HIT para {}", chave);
            return resposta_cache.clone();
        }
        drop(cache);

        // Cache MISS - processa e armazena
        println!("[CACHE] MISS para {}", chave);
        let resposta = self.inner.processar(req);

        if resposta.status == 200 {
            let mut cache = self.cache.lock().unwrap();
            cache.insert(chave, resposta.clone());
        }

        resposta
    }
}

fn main() {
    // Composicao de decorators: cada um envolve o anterior
    // A ordem importa! Externo -> Interno:
    // RateLimit -> Cache -> Auth -> Logging -> ApiHandler
    let handler: Box<dyn Handler> = Box::new(ApiHandler);
    let handler = Box::new(LoggingDecorator::new(handler));
    let handler = Box::new(AuthDecorator::new(handler, "meu-token-secreto"));
    let handler = Box::new(CacheDecorator::new(handler));
    let handler = Box::new(RateLimitDecorator::new(handler, 5));

    // Requisicao autenticada
    let req_auth = Request {
        metodo: "GET".to_string(),
        caminho: "/usuarios".to_string(),
        headers: HashMap::from([
            ("Authorization".to_string(), "Bearer meu-token-secreto".to_string()),
        ]),
        corpo: None,
    };

    println!("=== Primeira requisicao (cache miss) ===");
    let resp1 = handler.processar(&req_auth);
    println!("Resposta: {} - {}\n", resp1.status, resp1.corpo);

    println!("=== Segunda requisicao (cache hit) ===");
    let resp2 = handler.processar(&req_auth);
    println!("Resposta: {} - {}\n", resp2.status, resp2.corpo);

    // Requisicao sem autenticacao
    let req_sem_auth = Request {
        metodo: "GET".to_string(),
        caminho: "/usuarios".to_string(),
        headers: HashMap::new(),
        corpo: None,
    };

    println!("=== Requisicao sem auth ===");
    let resp3 = handler.processar(&req_sem_auth);
    println!("Resposta: {} - {}", resp3.status, resp3.corpo);
}

Diagrama

COMPOSICAO DE DECORATORS:

    Request
       |
       v
  +--------------------+
  | RateLimitDecorator  |  Verifica limite de requisicoes
  +----+---------------+
       |
       v
  +--------------------+
  |  CacheDecorator    |  Verifica/armazena cache
  +----+---------------+
       |
       v
  +--------------------+
  |   AuthDecorator    |  Verifica token de autenticacao
  +----+---------------+
       |
       v
  +--------------------+
  | LoggingDecorator   |  Registra tempo e status
  +----+---------------+
       |
       v
  +--------------------+
  |    ApiHandler      |  Handler real (logica de negocio)
  +--------------------+
       |
       v
    Response (sobe de volta pela pilha de decorators)


TODOS IMPLEMENTAM A MESMA TRAIT:

    trait Handler {
        fn processar(&self, req: &Request) -> Response;
    }

    LoggingDecorator: impl Handler  ---inner---> dyn Handler
    AuthDecorator:    impl Handler  ---inner---> dyn Handler
    CacheDecorator:   impl Handler  ---inner---> dyn Handler
    ApiHandler:       impl Handler  (handler base, sem inner)

Exemplo do Mundo Real

Decorator para a trait Write da biblioteca padrao, adicionando compressao, buffer e contagem:

use std::io::{self, Write};

/// Decorator que conta bytes escritos
pub struct ContadorWrite<W: Write> {
    inner: W,
    bytes_escritos: u64,
}

impl<W: Write> ContadorWrite<W> {
    pub fn new(inner: W) -> Self {
        Self {
            inner,
            bytes_escritos: 0,
        }
    }

    pub fn bytes_escritos(&self) -> u64 {
        self.bytes_escritos
    }

    /// Consome o decorator e retorna o writer interno
    pub fn into_inner(self) -> W {
        self.inner
    }
}

impl<W: Write> Write for ContadorWrite<W> {
    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
        let n = self.inner.write(buf)?;
        self.bytes_escritos += n as u64;
        Ok(n)
    }

    fn flush(&mut self) -> io::Result<()> {
        self.inner.flush()
    }
}

/// Decorator que adiciona prefixo a cada linha
pub struct PrefixoWrite<W: Write> {
    inner: W,
    prefixo: String,
    inicio_linha: bool,
}

impl<W: Write> PrefixoWrite<W> {
    pub fn new(inner: W, prefixo: impl Into<String>) -> Self {
        Self {
            inner,
            prefixo: prefixo.into(),
            inicio_linha: true,
        }
    }
}

impl<W: Write> Write for PrefixoWrite<W> {
    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
        let texto = String::from_utf8_lossy(buf);
        let mut total = 0;

        for (i, linha) in texto.split('\n').enumerate() {
            if i > 0 {
                self.inner.write_all(b"\n")?;
                total += 1;
                self.inicio_linha = true;
            }

            if !linha.is_empty() {
                if self.inicio_linha {
                    self.inner.write_all(self.prefixo.as_bytes())?;
                    self.inicio_linha = false;
                }
                let n = self.inner.write(linha.as_bytes())?;
                total += n;
            }
        }

        Ok(total)
    }

    fn flush(&mut self) -> io::Result<()> {
        self.inner.flush()
    }
}

/// Decorator que transforma tudo para maiusculas
pub struct MaiusculasWrite<W: Write> {
    inner: W,
}

impl<W: Write> MaiusculasWrite<W> {
    pub fn new(inner: W) -> Self {
        Self { inner }
    }
}

impl<W: Write> Write for MaiusculasWrite<W> {
    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
        let texto = String::from_utf8_lossy(buf);
        let maiusculas = texto.to_uppercase();
        self.inner.write_all(maiusculas.as_bytes())?;
        Ok(buf.len())
    }

    fn flush(&mut self) -> io::Result<()> {
        self.inner.flush()
    }
}

/// Trait de extensao para composicao fluente de decorators
pub trait WriteExt: Write + Sized {
    fn com_contador(self) -> ContadorWrite<Self> {
        ContadorWrite::new(self)
    }

    fn com_prefixo(self, prefixo: impl Into<String>) -> PrefixoWrite<Self> {
        PrefixoWrite::new(self, prefixo)
    }

    fn em_maiusculas(self) -> MaiusculasWrite<Self> {
        MaiusculasWrite::new(self)
    }
}

// Implementa para TODOS os tipos que implementam Write
impl<W: Write> WriteExt for W {}

fn main() {
    // Composicao fluente de decorators
    let buffer = Vec::new();
    let mut writer = buffer
        .com_contador()
        .com_prefixo("[LOG] ")
        .em_maiusculas();

    writeln!(writer, "Iniciando aplicacao").unwrap();
    writeln!(writer, "Conectando ao banco de dados").unwrap();
    writeln!(writer, "Servidor pronto na porta 8080").unwrap();

    // Saida gerada pelo pipeline de decorators:
    println!("=== Saida decorada ===");

    // Para demonstrar, usamos stdout diretamente
    let mut stdout_writer = io::stdout()
        .com_prefixo("[APP] ");

    writeln!(stdout_writer, "Mensagem com prefixo").unwrap();
    writeln!(stdout_writer, "Outra mensagem").unwrap();
}

Quando Usar

  • Funcionalidades transversais (logging, cache, auth) que se aplicam a multiplos handlers
  • Combinacao flexivel de comportamentos sem explosao de subclasses
  • Middleware em web frameworks, pipelines de processamento
  • Extensao de tipos da std (Write, Read, Iterator) com funcionalidade adicional
  • Monitoramento e instrumentacao sem alterar o codigo de negocio

Quando NAO Usar

  • Muitos decorators aninhados tornam o debug dificil (stack traces confusos)
  • Quando a ordem dos decorators importa demais - pode causar bugs sutis
  • Performance critica - cada camada adiciona um nivel de indirection
  • Funcionalidade simples - nao crie decorator para algo que uma funcao resolve
// SIMPLES DEMAIS para Decorator - use uma funcao:
fn com_log(resultado: String) -> String {
    println!("Resultado: {}", resultado);
    resultado
}

Variacoes em Rust

1. Decorator com genericos (sem alocacao)

// Genericos evitam Box<dyn Trait> - sem custo de indirection
pub struct Log<H: Handler> {
    inner: H,
}

impl<H: Handler> Handler for Log<H> {
    fn processar(&self, req: &Request) -> Response {
        println!("LOG: {}", req.caminho);
        self.inner.processar(req)
    }
}

2. Decorator com closures

/// Decorator funcional usando closures
pub fn com_logging(
    handler: impl Fn(&Request) -> Response,
) -> impl Fn(&Request) -> Response {
    move |req| {
        println!("Antes: {}", req.caminho);
        let resp = handler(req);
        println!("Depois: {}", resp.status);
        resp
    }
}

3. Decorator via Iterator (padrao da std)

// A std ja usa Decorator extensivamente em iteradores!
let resultado: Vec<i32> = (1..100)
    .filter(|n| n % 2 == 0)   // FilterDecorator
    .map(|n| n * n)             // MapDecorator
    .take(5)                    // TakeDecorator
    .collect();
// Cada adaptador de iterador e um Decorator!

Padroes Relacionados

  • Adapter - Adapter muda a interface; Decorator adiciona funcionalidade mantendo a interface
  • Composite - Ambos usam composicao recursiva, mas com propositos diferentes
  • Strategy - Strategy troca o algoritmo inteiro; Decorator adiciona camadas ao existente
  • Facade - Facade simplifica; Decorator enriquece

Conclusao

O Decorator e um dos padroes mais poderosos e amplamente usados em Rust. A composicao via traits se encaixa perfeitamente na filosofia de Rust de “composicao sobre heranca”. Os iteradores da biblioteca padrao sao o exemplo mais evidente: map, filter, take sao todos decorators que envolvem o iterador anterior. Ao projetar suas APIs com traits e wrappers, voce cria sistemas modulares, testaveis e extensiveis, onde funcionalidades podem ser adicionadas ou removidas como camadas de uma cebola, sem alterar o codigo existente.