<h2>O que é Clean Architecture</h2>
<p>Clean Architecture é um conjunto de princípios de design que visa criar sistemas de software independentes de frameworks, testáveis, independentes de interface de usuário, independentes de banco de dados e independentes de qualquer agente externo. Proposto por Robert C. Martin (Uncle Bob), o padrão organiza o código em camadas concêntricas, onde as dependências sempre apontam para dentro. Cada camada tem responsabilidades bem definidas, e a camada mais interna (domínio) nunca conhece a camada mais externa (frameworks e drivers).</p>
<p>Em Go, a implementação de Clean Architecture ganha características próprias da linguagem, como a preferência por interfaces implícitas e composição sobre herança. O objetivo prático é criar projetos que crescem sem dificuldade, onde modificar uma dependência externa (como trocar de banco de dados) não quebra toda a lógica de negócio. Isso economiza tempo de manutenção e facilita testes automatizados significativamente.</p>
<h3>Por que Clean Architecture importa em Go</h3>
<p>Go é uma linguagem excelente para backend, mas projetos sem estrutura clara viram um "spaghetti code" rapidamente. Clean Architecture força decisões de design que Go naturalmente incentiva: simplicidade, clareza e separação de responsabilidades. Um projeto bem estruturado em Go é altamente manutenível porque a linguagem tem pouca "magia" — tudo é explícito.</p>
<h2>As Camadas da Clean Architecture</h2>
<p>A Clean Architecture divide-se em 4 camadas principais, cada uma com responsabilidades específicas. O fluxo de dependências sempre aponta para dentro, nunca sai da esfera central.</p>
<h3>Camada de Domínio (Entities)</h3>
<p>Esta é a camada mais interna e contém as entidades do negócio — a lógica que não mudaria nem se você trocar de web framework ou banco de dados. São estruturas simples de dados e funções que implementam regras de negócio fundamentais. Essa camada não deve importar nada de fora dela.</p>
<pre><code class="language-go">// domain/user.go
package domain
import "errors"
// User representa a entidade de usuário
type User struct {
ID string
Name string
Email string
Age int
}
// NewUser cria um novo usuário com validações de negócio
func NewUser(id, name, email string, age int) (*User, error) {
if name == "" {
return nil, errors.New("nome é obrigatório")
}
if age < 18 {
return nil, errors.New("usuário deve ser maior de 18 anos")
}
if !isValidEmail(email) {
return nil, errors.New("email inválido")
}
return &User{
ID: id,
Name: name,
Email: email,
Age: age,
}, nil
}
func isValidEmail(email string) bool {
// Validação simples
return len(email) > 5 && len(email) < 254
}</code></pre>
<h3>Camada de Casos de Uso (Use Cases)</h3>
<p>Aqui vivem os interatores (use cases) que orquestram a lógica de negócio. Um caso de uso representa uma ação específica do sistema — por exemplo, "registrar novo usuário" ou "atualizar perfil". Essa camada conhece a camada de domínio, mas não conhece detalhes de implementação como HTTP ou banco de dados.</p>
<pre><code class="language-go">// usecase/register_user.go
package usecase
import (
"context"
"github.com/seu-usuario/seu-projeto/domain"
)
// UserRepository define o contrato para persistência
type UserRepository interface {
Save(ctx context.Context, user *domain.User) error
FindByEmail(ctx context.Context, email string) (*domain.User, error)
}
// RegisterUserUseCase implementa o caso de uso de registro
type RegisterUserUseCase struct {
userRepo UserRepository
}
func NewRegisterUserUseCase(repo UserRepository) *RegisterUserUseCase {
return &RegisterUserUseCase{userRepo: repo}
}
// Execute executa o caso de uso
func (u *RegisterUserUseCase) Execute(ctx context.Context,
id, name, email string, age int) (*domain.User, error) {
// Verifica se usuário já existe
existing, _ := u.userRepo.FindByEmail(ctx, email)
if existing != nil {
return nil, ErrUserAlreadyExists
}
// Cria a entidade (validações de domínio acontecem aqui)
user, err := domain.NewUser(id, name, email, age)
if err != nil {
return nil, err
}
// Persiste o usuário
if err := u.userRepo.Save(ctx, user); err != nil {
return nil, err
}
return user, nil
}
var ErrUserAlreadyExists = domain.NewDomainError("usuário com este email já existe")</code></pre>
<h3>Camada de Interface de Adaptadores (Adapters)</h3>
<p>Essa camada contém os adaptadores que fazem a conversão entre o mundo externo (HTTP, gRPC, filas) e os casos de uso. Aqui vivem os controllers, presenters, gateways e implementações de repositórios. A camada conhece casos de uso mas não é conhecida por eles (inversão de controle).</p>
<pre><code class="language-go">// adapter/http/handler.go
package http
import (
"encoding/json"
"net/http"
"github.com/seu-usuario/seu-projeto/usecase"
)
type RegisterUserHandler struct {
registerUseCase *usecase.RegisterUserUseCase
}
func NewRegisterUserHandler(useCase usecase.RegisterUserUseCase) RegisterUserHandler {
return &RegisterUserHandler{registerUseCase: useCase}
}
type RegisterUserRequest struct {
ID string json:"id"
Name string json:"name"
Email string json:"email"
Age int json:"age"
}
type UserResponse struct {
ID string json:"id"
Name string json:"name"
Email string json:"email"
Age int json:"age"
}
func (h RegisterUserHandler) Handle(w http.ResponseWriter, r http.Request) {
var req RegisterUserRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
user, err := h.registerUseCase.Execute(r.Context(),
req.ID, req.Name, req.Email, req.Age)
if err != nil {
w.WriteHeader(http.StatusUnprocessableEntity)
json.NewEncoder(w).Encode(map[string]string{"error": err.Error()})
return
}
response := UserResponse{
ID: user.ID,
Name: user.Name,
Email: user.Email,
Age: user.Age,
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(response)
}</code></pre>
<h3>Camada de Frameworks e Drivers (Frameworks & DB)</h3>
<p>A camada mais externa contém implementações específicas de banco de dados, frameworks web, logging, e outras dependências externas. Essa é a camada que muda mais frequentemente e deve ser a mais isolada possível do resto do código.</p>
<pre><code class="language-go">// adapter/storage/postgres_user_repository.go
package storage
import (
"context"
"database/sql"
"github.com/seu-usuario/seu-projeto/domain"
_ "github.com/lib/pq"
)
type PostgresUserRepository struct {
db *sql.DB
}
func NewPostgresUserRepository(db sql.DB) PostgresUserRepository {
return &PostgresUserRepository{db: db}
}
func (r PostgresUserRepository) Save(ctx context.Context, user domain.User) error {
query := INSERT INTO users (id, name, email, age) VALUES ($1, $2, $3, $4)
_, err := r.db.ExecContext(ctx, query,
user.ID, user.Name, user.Email, user.Age)
return err
}
func (r *PostgresUserRepository) FindByEmail(ctx context.Context,
email string) (*domain.User, error) {
query := SELECT id, name, email, age FROM users WHERE email = $1
var user domain.User
err := r.db.QueryRowContext(ctx, query, email).Scan(
&user.ID, &user.Name, &user.Email, &user.Age)
if err == sql.ErrNoRows {
return nil, nil
}
if err != nil {
return nil, err
}
return &user, nil
}</code></pre>
<h2>Estrutura de Diretórios e Wiring</h2>
<p>A organização física do projeto reflete as camadas lógicas. Um projeto bem estruturado em Go segue um padrão claro de diretórios, facilitando navegação e manutenção.</p>
<h3>Estrutura de Pastas Recomendada</h3>
<pre><code>seu-projeto/
├── cmd/
│ └── main.go # Entry point da aplicação
├── domain/ # Entidades e lógica pura
│ ├── user.go
│ └── error.go
├── usecase/ # Casos de uso / Interatores
│ ├── register_user.go
│ └── get_user.go
├── adapter/
│ ├── http/ # Controllers HTTP
│ │ ├── handler.go
│ │ └── middleware.go
│ └── storage/ # Implementações de repositórios
│ ├── postgres_user_repository.go
│ └── memory_user_repository.go
├── infra/ # Configuração de DI e setup
│ └── wire.go
├── go.mod
└── go.sum</code></pre>
<h3>Inversão de Controle com Wire</h3>
<p>Go não possui containers de DI nativos. A biblioteca <code>wire</code> do Google resolve isso gerando código de injeção de dependência em tempo de compilação.</p>
<pre><code class="language-go">// infra/wire.go
// +build wireinject
package infra
import (
"database/sql"
"github.com/google/wire"
"github.com/seu-usuario/seu-projeto/adapter/http"
"github.com/seu-usuario/seu-projeto/adapter/storage"
"github.com/seu-usuario/seu-projeto/usecase"
)
func InitializeHandler(db sql.DB) http.RegisterUserHandler {
wire.Build(
storage.NewPostgresUserRepository,
usecase.NewRegisterUserUseCase,
http.NewRegisterUserHandler,
)
return nil // Wire gera a implementação real
}</code></pre>
<p>Execute <code>wire</code> no diretório para gerar o código: <code>wire ./infra/...</code></p>
<h2>Testabilidade e Benefícios Práticos</h2>
<p>Uma das maiores vantagens da Clean Architecture é a facilidade de testes. Como cada camada tem responsabilidades claras e usa interfaces, criar mocks é trivial.</p>
<h3>Testando Casos de Uso</h3>
<pre><code class="language-go">// usecase/register_user_test.go
package usecase
import (
"context"
"testing"
"github.com/seu-usuario/seu-projeto/domain"
)
// MockUserRepository implementa UserRepository para testes
type MockUserRepository struct {
SaveCalled bool
Users map[string]*domain.User
}
func (m MockUserRepository) Save(ctx context.Context, user domain.User) error {
m.SaveCalled = true
m.Users[user.Email] = user
return nil
}
func (m MockUserRepository) FindByEmail(ctx context.Context, email string) (domain.User, error) {
return m.Users[email], nil
}
func TestRegisterUserUseCase_Success(t *testing.T) {
mockRepo := &MockUserRepository{Users: make(map[string]*domain.User)}
useCase := NewRegisterUserUseCase(mockRepo)
user, err := useCase.Execute(context.Background(),
"123", "João", "joao@example.com", 25)
if err != nil {
t.Fatalf("esperava sucesso, got %v", err)
}
if user.Name != "João" {
t.Errorf("esperava João, got %s", user.Name)
}
if !mockRepo.SaveCalled {
t.Error("esperava que Save fosse chamado")
}
}
func TestRegisterUserUseCase_DuplicateEmail(t *testing.T) {
mockRepo := &MockUserRepository{
Users: map[string]*domain.User{
"joao@example.com": {ID: "456", Name: "João Antigo"},
},
}
useCase := NewRegisterUserUseCase(mockRepo)
_, err := useCase.Execute(context.Background(),
"123", "João Novo", "joao@example.com", 25)
if err != ErrUserAlreadyExists {
t.Errorf("esperava ErrUserAlreadyExists, got %v", err)
}
}</code></pre>
<h3>Trocar de Banco de Dados é Trivial</h3>
<p>Se precisar trocar PostgreSQL por MongoDB, você só muda a implementação do repositório. Os casos de uso não sabem disso e nem o código HTTP.</p>
<pre><code class="language-go">// adapter/storage/mongo_user_repository.go
package storage
import (
"context"
"go.mongodb.org/mongo-driver/mongo"
"github.com/seu-usuario/seu-projeto/domain"
)
type MongoUserRepository struct {
collection *mongo.Collection
}
func NewMongoUserRepository(collection mongo.Collection) MongoUserRepository {
return &MongoUserRepository{collection: collection}
}
func (r MongoUserRepository) Save(ctx context.Context, user domain.User) error {
_, err := r.collection.InsertOne(ctx, user)
return err
}
func (r MongoUserRepository) FindByEmail(ctx context.Context, email string) (domain.User, error) {
var user domain.User
err := r.collection.FindOne(ctx, map[string]string{"email": email}).Decode(&user)
if err == mongo.ErrNoDocuments {
return nil, nil
}
return &user, err
}</code></pre>
<p>Agora na injeção de dependência, você só troca qual implementação usar. O resto do código não muda.</p>
<h2>Conclusão</h2>
<p>Clean Architecture em Go fornece uma estrutura sólida para projetos que precisam crescer. Os três pontos principais aprendidos são: (1) A separação em camadas concêntricas com dependências apontando para dentro garante que a lógica de negócio é independente de detalhes técnicos; (2) O uso de interfaces e inversão de controle torna testes automatizados simples e rápidos, porque substituir implementações reais por mocks é natural; (3) A estrutura de diretórios clara reflete as responsabilidades, facilitando onboarding de novos desenvolvedores e reduzindo tempo de manutenção quando mudanças externas (como trocar banco de dados) são necessárias.</p>
<h2>Referências</h2>
<ul>
<li><a href="https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html" target="_blank" rel="noopener noreferrer">The Clean Architecture - Robert C. Martin</a></li>
<li><a href="https://github.com/google/wire" target="_blank" rel="noopener noreferrer">Google Wire - Dependency Injection for Go</a></li>
<li><a href="https://github.com/marcusoldham/go-clean-architecture" target="_blank" rel="noopener noreferrer">Clean Architecture in Go - Marcus Olsson</a></li>
<li><a href="https://www.golang-book.com/" target="_blank" rel="noopener noreferrer">Domain-Driven Design in Go - Matthew Boyle</a></li>
<li><a href="https://golang.org/doc/effective_go" target="_blank" rel="noopener noreferrer">Go Code Review Comments - Effective Go</a></li>
</ul>
<p><!-- FIM --></p>