Rust e Arquitetura Hexagonal: código limpo sem compromissos

Treinador Online de Python para Iniciantes

Aprenda Python facilmente sem sobrecarga teórica. Resolva tarefas práticas com verificação automática e escreva código diretamente no navegador.

Start Course

Rust e Arquitetura Hexagonal: escrevendo código que não assusta mudar

\n\n

Todo desenvolvedor já enfrentou pelo menos uma vez a situação em que uma pequena mudança no banco de dados ou a troca de um framework HTTP se transformava em uma refatoração infernal. A lógica de negócios se mistura com detalhes de implementação, e o código vira um "espaguete". Para Rust, com seu foco em segurança e desempenho, essa mistura é um verdadeiro crime.

\n\n

A solução para esse problema é a Arquitetura Hexagonal (também conhecida como Ports and Adapters). Neste artigo, vamos analisar como aplicar esse padrão em Rust, usando o poder do sistema de tipos e traits. Você aprenderá como isolar a lógica de domínio do mundo externo, escrever código testável e trocar facilmente drivers de banco de dados ou servidores web. Esta é uma habilidade obrigatória para quem deseja construir sistemas complexos e sustentáveis em Rust.

\n\n

O que é Arquitetura Hexagonal? (Muito resumidamente)

\n\n

Imagine um hexágono. No centro está sua lógica de negócios (domínio). Nas bordas estão as "portas" (interfaces), através das quais o domínio se comunica com o mundo externo. E do lado de fora estão os "adaptadores" (implementações concretas), que se conectam às portas.

\n\n

Ideia chave: o domínio não sabe nada sobre como os dados são salvos ou como as requisições são enviadas. Ele conhece apenas as portas — traits com métodos. Os adaptadores (por exemplo, um repositório PostgreSQL ou um controlador HTTP) implementam esses traits.

\n\n

Em Rust, esse conceito se encaixa perfeitamente graças aos traits (análogo a interfaces) e genéricos. O compilador verifica todas as conexões em tempo de compilação, não em tempo de execução.

\n\n

Estrutura do projeto em Rust

\n\n

Para começar, vamos criar uma estrutura de pastas que separe visualmente o domínio da infraestrutura:

\n\n
src/\n├── domain/          # Lógica de negócios pura (sem dependências!)\n│   ├── mod.rs\n│   ├── models.rs    # Entidades e Value Objects\n│   └── services.rs  # Serviços que trabalham com portas\n├── ports/           # Traits (interfaces) para o mundo externo\n│   ├── mod.rs\n│   └── repositories.rs\n├── adapters/        # Implementações das portas (bancos, HTTP, etc.)\n│   ├── mod.rs\n│   ├── postgres_repo.rs\n│   └── in_memory_repo.rs  # Para testes\n├── main.rs          # Montagem da aplicação (Wiring)\n└── lib.rs           # API pública da biblioteca
\n\n

Passo 1: Domínio — o coração do sistema

\n\n

Vamos começar com um modelo simples de usuário. No domínio, não há nenhuma dependência de bibliotecas externas (nem mesmo de serde, se não for exigido pela lógica de negócios).

\n\n
// src/domain/models.rs\npub struct User {\n    pub id: String,\n    pub name: String,\n    pub email: String,\n}\n\nimpl User {\n    pub fn new(id: String, name: String, email: String) -> Self {\n        // Aqui pode haver validação\n        Self { id, name, email }\n    }\n}
\n\n

Agora vamos definir o serviço de domínio. Ele trabalhará com usuários, mas não saberá de onde eles vêm.

\n\n
// src/domain/services.rs\nuse crate::ports::repositories::UserRepository;\n\npub struct UserService {\n    repo: Box<dyn UserRepository>,\n}\n\nimpl UserService {\n    pub fn new(repo: Box<dyn UserRepository>) -> Self {\n        Self { repo }\n    }\n\n    pub fn create_user(&mut self, name: String, email: String) -> Result<User, String> {\n        let id = uuid::Uuid::new_v4().to_string(); // Já é uma dependência? Sim, mas é um detalhe\n        let user = User::new(id, name, email);\n        self.repo.save(&user)?;\n        Ok(user)\n    }\n\n    pub fn get_user(&self, id: &str) -> Result<Option<User>, String> {\n        self.repo.find_by_id(id)\n    }\n}
\n\n

Importante: Usamos Box<dyn UserRepository> para despacho dinâmico. Em Rust, isso é normal para aplicações complexas. Para casos simples, podemos usar genéricos.

\n\n

Passo 2: Portas — contratos para o externo

Blogs

Book Recommendations