Conheça: seu código isolado do mundo externo
\n\nVocê já reescreveu a lógica de negócios só porque trocou o banco de dados de PostgreSQL para MongoDB? Ou acordou suando frio ao pensar que precisava adicionar um novo endpoint HTTP, e toda a aplicação é um gigantesco main.go de 3000 linhas?
Se sim — bem-vindo ao clube. Se não — ou você programa em Go há pouco tempo, ou já usa a Arquitetura Hexagonal (também conhecida como Ports & Adapters).
\n\nNeste artigo, vamos analisar como construir uma aplicação em Go que não tem medo de mudanças. Nem a troca de banco de dados, nem a migração de REST para gRPC, nem um novo framework farão você reescrever o núcleo do sistema. Apenas código. Apenas Go. Sem enrolação.
\n\nO que é Arquitetura Hexagonal em palavras simples
\n\nImagine que sua aplicação é uma nave espacial. No centro está a sala de comando (lógica de negócios). Ao redor, portas de acoplamento. Através das portas, você conecta diferentes módulos: motores (banco de dados), sensores (requisições HTTP), comunicação com a Terra (filas de mensagens).
\n\nSe você decidir trocar os motores — basta desacoplar os antigos e acoplar os novos. A sala de comando nem perceberá a substituição. Isso é a Arquitetura Hexagonal.
\n\nConceitos principais:
\n- \n
- Port (Porta) — interface que define como o mundo externo pode interagir com o núcleo. Por exemplo,
UserRepositorycom métodosSaveeFindByID. \n - Adapter (Adaptador) — implementação concreta da porta. Uma porta pode ter vários adaptadores: PostgreSQLAdapter, InMemoryAdapter, MockAdapter para testes. \n
- Core (Núcleo) — lógica de negócios que não sabe nada sobre o mundo externo. Ela funciona apenas através das portas. \n
Essa abordagem oferece três superpoderes: testabilidade (do núcleo sem BD), flexibilidade (substituição de qualquer componente) e limites claros de responsabilidade.
\n\nPor que Go é ideal para Hexagonal
\n\nGo não impõe POO, mas fornece interfaces — a única coisa necessária para Ports & Adapters. As interfaces em Go são implícitas: uma struct satisfaz automaticamente a interface se implementar seus métodos. Isso significa que você pode substituir o adaptador sem alterar uma única linha no núcleo.
\n\nAlém disso, Go compila para binário, o que torna a arquitetura especialmente útil para microsserviços. E os testes e benchmarks integrados permitem verificar imediatamente o isolamento dos componentes.
\n\nEstrutura do projeto: como organizar as pastas
\n\nNão existe um padrão único, mas aqui está um esquema comprovado usado em projetos de produção:
\n\nmyapp/\n├── cmd/\n│ └── app/\n│ └── main.go # Ponto de entrada, montagem de dependências\n├── internal/\n│ ├── core/\n│ │ ├── domain/ # Entidades, value objects\n│ │ ├── ports/ # Interfaces (portas)\n│ │ └── services/ # Lógica de negócios (casos de uso)\n│ └── adapters/\n│ ├── api/ # Adaptadores HTTP/gRPC\n│ ├── db/ # Implementações de repositórios\n│ └── mq/ # Filas de mensagens\n└── go.mod\n\n\nImportante: a pasta internal proíbe importação externa — ideal para proteger o núcleo. Os adaptadores ficam separados e podem ser facilmente substituídos.
Escrevendo código: das interfaces à implementação
\n\nVamos criar um serviço simples para trabalhar com usuários. Começamos pelo núcleo.
\n\nPasso 1. Domínio e portas
\n\n// internal/core/domain/user.go\npackage domain\n\nimport "time"\n\ntype User struct {\n ID string\n Email string\n Name string\n CreatedAt time.Time\n}\n\n// internal/core/ports/repositories.go\npackage ports\n\nimport "myapp/internal/core/domain"\n\ntype UserRepository interface {\n Save(user domain.User) error\n FindByID(id string) (domain.User, error)\n FindByEmail(email string) (domain.User, error)\