código

8 mai, 2025

Arquitetura Hexagonal na prática com exemplos em Python

Publicidade

A arquitetura hexagonal, também conhecida como Ports and Adapters, é uma abordagem de design de software que separa a lógica central da aplicação (o “core”) das suas interfaces externas. Essa separação facilita a manutenção, testes automatizados e a evolução da aplicação ao longo do tempo.

Neste artigo, você vai entender:

  • O que é arquitetura hexagonal
  • Como ela se diferencia de outros padrões
  • Como aplicar o conceito em Python
  • Exemplos de código com repositório, serviços e controladores

✅ O que é arquitetura hexagonal?

Criada por Alistair Cockburn, a arquitetura hexagonal propõe que o sistema tenha uma lógica de negócio isolada, acessada por “portas” (interfaces). As dependências externas — como banco de dados, interfaces web, filas ou outros serviços — se conectam ao core por meio de “adaptadores”.

Benefícios principais:

  • Baixo acoplamento entre lógica de negócio e tecnologias
  • Testes mais fáceis (mock de adaptadores)
  • Flexibilidade para trocar implementações (por exemplo, SQLite → PostgreSQL)

🧠 Estrutura geral

         [Adaptador Web]   [CLI]   [Message Queue]
                 \           |           /
                  \          |          /
                [Portas de entrada - Application Services]
                           ↓
                [Core - Regras de Negócio]
                           ↑
                [Portas de saída - Interfaces]
                 /          |          \
        [DB adapter]   [API externa]   [Cache]

📦 Exemplo prático em Python

Vamos simular um sistema simples de cadastro de usuários, seguindo arquitetura hexagonal.


1. Entidade de domínio (core/domain/user.py)

from dataclasses import dataclass

@dataclass
class User:
    id: int
    name: str
    email: str

2. Porta de saída (core/ports/user_repository.py)

from abc import ABC, abstractmethod
from core.domain.user import User

class UserRepository(ABC):

    @abstractmethod
    def save(self, user: User) -> None:
        pass

    @abstractmethod
    def get_by_id(self, user_id: int) -> User:
        pass

3. Serviço de aplicação (core/services/user_service.py)

from core.domain.user import User
from core.ports.user_repository import UserRepository

class UserService:

    def __init__(self, repository: UserRepository):
        self.repository = repository

    def create_user(self, id: int, name: str, email: str):
        user = User(id=id, name=name, email=email)
        self.repository.save(user)

    def get_user(self, user_id: int) -> User:
        return self.repository.get_by_id(user_id)

4. Adaptador de saída com SQLite (infra/sqlite_user_repository.py)

import sqlite3
from core.domain.user import User
from core.ports.user_repository import UserRepository

class SQLiteUserRepository(UserRepository):

    def __init__(self, db_path: str = ":memory:"):
        self.conn = sqlite3.connect(db_path)
        self.conn.execute("CREATE TABLE IF NOT EXISTS users (id INTEGER, name TEXT, email TEXT)")
        self.conn.commit()

    def save(self, user: User) -> None:
        self.conn.execute("INSERT INTO users (id, name, email) VALUES (?, ?, ?)", 
                          (user.id, user.name, user.email))
        self.conn.commit()

    def get_by_id(self, user_id: int) -> User:
        cursor = self.conn.execute("SELECT id, name, email FROM users WHERE id = ?", (user_id,))
        row = cursor.fetchone()
        if row:
            return User(id=row[0], name=row[1], email=row[2])
        else:
            raise Exception("User not found")

5. Adaptador de entrada: interface via CLI (main.py)

from core.services.user_service import UserService
from infra.sqlite_user_repository import SQLiteUserRepository

if __name__ == "__main__":
    repo = SQLiteUserRepository()
    service = UserService(repository=repo)

    service.create_user(1, "Rafa", "rafa@example.com")
    user = service.get_user(1)
    print(user)

🧪 E os testes?

Graças à separação entre a lógica de negócio e os adaptadores, você pode testar o UserService usando mocks do UserRepository, sem necessidade de banco de dados real. Exemplo com unittest.mock:

from unittest.mock import MagicMock
from core.services.user_service import UserService
from core.domain.user import User

def test_create_user():
    mock_repo = MagicMock()
    service = UserService(repository=mock_repo)
    
    service.create_user(1, "Teste", "teste@example.com")
    
    mock_repo.save.assert_called_once_with(User(id=1, name="Teste", email="teste@example.com"))

 

🚀 Conclusão

A arquitetura hexagonal é uma excelente escolha para quem busca construir aplicações com alta coesão e baixo acoplamento. Ela promove uma separação clara entre as regras de negócio e as interfaces externas, permitindo que o desenvolvedor mantenha o foco no que realmente importa: a lógica central da aplicação.

Ao isolar o core da infraestrutura, fica mais fácil adaptar a aplicação a novas tecnologias — como trocar o banco de dados, mudar de framework web ou até adicionar uma API pública — sem precisar reescrever as regras de negócio. Isso significa menos retrabalho, mais testes automatizados e mais estabilidade em produção.

Além disso, essa abordagem encaixa perfeitamente com outras práticas modernas de engenharia de software, como TDD (Test-Driven Development), Clean Architecture e DevOps. Em contextos corporativos, a adoção da arquitetura hexagonal pode ajudar a lidar melhor com equipes grandes, rotatividade de devs e projetos de longa duração.

Se você ainda não aplica esse padrão, vale a pena começar com um projeto menor e experimentar na prática. Com o tempo, os benefícios em manutenção, testes e escalabilidade vão ficar evidentes.

Segue aqui o nosso Diagrama de Arquitetura Hexagonal

Hexagonal Architecture Diagram Template ...

Este diagrama mostra:

  • Domínio (Core): onde reside a lógica de negócio da aplicação.
  • Portas: interfaces que definem como o core se comunica com o mundo externo.
  • Adaptadores: implementações concretas que conectam as portas a sistemas externos, como bancos de dados, interfaces web, filas de mensagens, etc.

Você pode explorar e personalizar este modelo interativo no Visual Paradigm Online.

Até a próxima, pessoal!