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