Estrutura de Projetos Go: Práticas e Padrões

Estruture seus projetos Go para escalabilidade e clareza

Conteúdo da página

Estruturar um projeto Go de forma eficaz é fundamental para a manutenção a longo prazo, a colaboração em equipe e a escalabilidade. Diferente de frameworks que impõem layouts de diretórios rígidos, Go abraça a flexibilidade, mas com essa liberdade vem a responsabilidade de escolher padrões que atendam às necessidades específicas do seu projeto.

project tree

Compreendendo a Filosofia do Go sobre Estrutura de Projetos

A filosofia de design minimalista do Go estende-se à organização de projetos. A linguagem não exige uma estrutura específica, confiando, em vez disso, que os desenvolvedores tomem decisões informadas. Essa abordagem levou a comunidade a desenvolver vários padrões comprovados, desde layouts planos simples para pequenos projetos até arquiteturas sofisticadas para sistemas empresariais.

O princípio fundamental é simplicidade primeiro, complexidade apenas quando necessário. Muitos desenvolvedores caem na armadilha de superengenheirizar sua estrutura inicial, criando diretórios profundamente aninhados e abstrações prematuras. Comece com o que você precisa hoje e refatore conforme seu projeto cresce.

Quando devo usar o diretório internal/ versus pkg/?

O diretório internal/ serve a um propósito específico no design do Go: contém pacotes que não podem ser importados por projetos externos. O compilador do Go aplica essa restrição, tornando o internal/ perfeito para lógica de aplicação privada, regras de negócios e utilitários que não devem ser reutilizados fora do seu projeto.

Por outro lado, o diretório pkg/ sinaliza que o código é destinado ao consumo externo. Coloque código aqui apenas se estiver construindo uma biblioteca ou componentes reutilizáveis que você deseja que outros importem. Muitos projetos não precisam do pkg/ de forma alguma; se seu código não estiver sendo consumido externamente, mantê-lo na raiz ou em internal/ é mais limpo. Ao construir bibliotecas reutilizáveis, considere aproveitar generics do Go para criar componentes reutilizáveis e seguros em tipos.

O Layout Padrão de Projetos Go

O padrão mais amplamente reconhecido é o golang-standards/project-layout, embora seja importante notar que este não é um padrão oficial. Veja como uma estrutura típica se parece:

myproject/
├── cmd/
│   ├── api/
│   │   └── main.go
│   └── worker/
│       └── main.go
├── internal/
│   ├── auth/
│   ├── storage/
│   └── transport/
├── pkg/
│   ├── logger/
│   └── crypto/
├── api/
│   └── openapi.yaml
├── config/
│   └── config.yaml
├── scripts/
│   └── deploy.sh
├── go.mod
├── go.sum
└── README.md

Muitas equipes mantêm um pequeno pacote de logging compartilhado sob internal/ (por exemplo, internal/logx) para que os binários em cmd/ conectem um logger na inicialização; o pkg/logger/ no esboço acima é apropriado apenas quando esse código é destinado a ser importado por outros módulos. Para uma configuração de log/slog orientada à produção (linhas JSON, redação, campos de contexto e rastreamento e sinais que funcionam bem com pilhas de monitoramento), veja Logging Estruturado em Go com slog para Observabilidade e Alertas.

O Diretório cmd/

O diretório cmd/ contém seus pontos de entrada da aplicação. Cada subdiretório representa um binário executável separado. Por exemplo, cmd/api/main.go compila seu servidor de API, enquanto cmd/worker/main.go pode compilar um processador de jobs em segundo plano.

Melhor prática: Mantenha seus arquivos main.go mínimos — apenas o suficiente para conectar dependências, carregar a configuração e iniciar a aplicação. Toda a lógica substantiva pertence a pacotes que main.go importa.

// cmd/api/main.go
package main

import (
    "log"
    "myproject/internal/server"
    "myproject/internal/config"
)

func main() {
    cfg, err := config.Load()
    if err != nil {
        log.Fatal(err)
    }
    
    srv := server.New(cfg)
    if err := srv.Start(); err != nil {
        log.Fatal(err)
    }
}

O Diretório internal/

É aqui que o código privado da sua aplicação reside. O compilador Go impede que qualquer projeto externo importe pacotes dentro de internal/, tornando-o ideal para:

  • Lógica de negócios e modelos de domínio
  • Serviços de aplicação
  • APIs e interfaces internas
  • Repositórios de banco de dados (para escolher o ORM certo, veja nossa comparação de ORMs Go para PostgreSQL)
  • Lógica de autenticação e autorização

Organize internal/ por recurso ou domínio, não por camada técnica. Em vez de internal/handlers/, internal/services/, internal/repositories/, prefira internal/user/, internal/order/, internal/payment/, onde cada pacote contém seus manipuladores, serviços e repositórios.

Devo começar com uma estrutura de diretórios complexa?

Absolutamente não. Se você estiver construindo uma pequena ferramenta ou protótipo, comece com:

myproject/
├── main.go
├── go.mod
└── go.sum

Conforme seu projeto cresce e você identifica agrupamentos lógicos, introduza diretórios. Você pode adicionar um pacote db/ quando a lógica do banco de dados se tornar substancial, ou um pacote api/ quando os manipuladores HTTP aumentarem. Deixe que a estrutura surja naturalmente em vez de impor antecipadamente.

Estruturas Planas vs. Aninhadas: Encontrando o Equilíbrio

Um dos erros mais comuns na estrutura de projetos Go é o excesso de aninhamento. Go favorece hierarquias rasas — tipicamente um ou dois níveis de profundidade. O aninhamento profundo aumenta a carga cognitiva e torna as imports trabalhosos.

Quais são os erros mais comuns?

Aninhamento excessivo de diretórios: Evite estruturas como internal/services/user/handlers/http/v1/. Isso cria complexidade de navegação desnecessária. Em vez disso, use internal/user/handler.go.

Nomes de pacotes genéricos: Nomes como utils, helpers, common ou base são cheiros de código (code smells). Eles não transmitem funcionalidade específica e muitas vezes tornam-se depósitos para código não relacionado. Use nomes descritivos: validator, auth, storage, cache.

Dependências circulares: Quando o pacote A importa o pacote B, e B importa A, você tem uma dependência circular — um erro de compilação no Go. Isso geralmente sinaliza uma má separação de preocupações. Introduza interfaces ou extraia tipos compartilhados para um pacote separado.

Mistura de preocupações: Mantenha os manipuladores HTTP focados em preocupações HTTP, repositórios de banco de dados no acesso a dados e lógica de negócios em pacotes de serviço. Colocar regras de negócios em manipuladores torna os testes difíceis e acopla sua lógica de domínio ao HTTP.

Design Orientado a Domínio e Arquitetura Hexagonal

Para aplicações maiores, especialmente microsserviços, o Design Orientado a Domínio (DDD) com Arquitetura Hexagonal fornece uma separação clara de preocupações.

Como estruturar um microsserviço seguindo o Design Orientado a Domínio?

A Arquitetura Hexagonal organiza o código em camadas concêntricas com dependências fluindo para dentro:

internal/
├── domain/
│   └── user/
│       ├── entity.go        # Modelos de domínio e objetos de valor
│       ├── repository.go    # Interface do repositório (porta)
│       └── service.go       # Serviços de domínio
├── application/
│   └── user/
│       ├── create_user.go   # Caso de uso: criar usuário
│       ├── get_user.go      # Caso de uso: recuperar usuário
│       └── service.go       # Orquestração de serviço de aplicação
├── adapter/
│   ├── http/
│   │   └── user_handler.go  # Adaptador HTTP (endpoints REST)
│   ├── postgres/
│   │   └── user_repo.go     # Adaptador de banco de dados (implementa porta do repositório)
│   └── redis/
│       └── cache.go         # Adaptador de cache
└── api/
    └── http/
        └── router.go        # Configuração de rotas e middleware

Camada de Domínio (domain/): Lógica central de negócios, entidades, objetos de valor e interfaces de serviço de domínio. Esta camada não tem dependências de sistemas externos — sem HTTP, sem imports de banco de dados. Ela define interfaces de repositório (portas) que os adaptadores implementam.

Camada de Aplicação (application/): Casos de uso que orquestram objetos de domínio. Cada caso de uso (por exemplo, “criar usuário”, “processar pagamento”) é um arquivo ou pacote separado. Esta camada coordena objetos de domínio, mas não contém regras de negócios em si.

Camada de Adaptador (adapter/): Implementa interfaces definidas pelas camadas internas. Manipuladores HTTP convertem solicitações em objetos de domínio, repositórios de banco de dados implementam persistência e filas de mensagens lidam com comunicação assíncrona. Esta camada contém todo o código específico de framework e infraestrutura.

Camada de API (api/): Rotas, middleware, DTOs (Objetos de Transferência de Dados), versionamento de API e especificações OpenAPI.

Esta estrutura garante que sua lógica central de negócios permaneça testável e independente de frameworks, bancos de dados ou serviços externos. Você pode substituir PostgreSQL por MongoDB, ou REST por gRPC, sem tocar no código de domínio.

Padrões Práticos para Diferentes Tipos de Projetos

Ferramenta CLI Pequena

Para aplicações de linha de comando, você quererá uma estrutura que suporte múltiplos comandos e subcomandos. Considere usar frameworks como Cobra para estrutura de comandos e Viper para gerenciamento de configuração. Nosso guia sobre construir aplicações CLI em Go com Cobra & Viper cobre este padrão em detalhes.

mytool/
├── main.go
├── command/
│   ├── root.go
│   └── version.go
├── go.mod
└── README.md

Serviço de API REST

Ao construir APIs REST em Go, esta estrutura separa preocupações de forma limpa: manipuladores lidam com preocupações HTTP, serviços contêm a lógica de negócios e repositórios gerenciam o acesso a dados. Para um guia abrangente que cobre abordagens de biblioteca padrão, frameworks, autenticação, padrões de teste e melhores práticas prontas para produção, veja nosso guia completo para construir APIs REST em Go. Para logging estruturado em JSON, correlação de solicitações e rastreamento e formatos de log que suportam alertas, veja Logging Estruturado em Go com slog para Observabilidade e Alertas.

myapi/
├── cmd/
│   └── api/
│       └── main.go
├── internal/
│   ├── config/
│   ├── middleware/
│   ├── user/
│   │   ├── handler.go
│   │   ├── service.go
│   │   └── repository.go
│   └── product/
│       ├── handler.go
│       ├── service.go
│       └── repository.go
├── pkg/
│   └── httputil/
├── go.mod
└── README.md

Monorepo com Múltiplos Serviços

myproject/
├── cmd/
│   ├── api/
│   ├── worker/
│   └── scheduler/
├── internal/
│   ├── shared/        # Pacotes internos compartilhados
│   ├── api/          # Código específico da API
│   ├── worker/       # Código específico do worker
│   └── scheduler/    # Código específico do scheduler
├── pkg/              # Bibliotecas compartilhadas
├── go.work           # Arquivo de workspace Go
└── README.md

Testes e Documentação

Coloque arquivos de teste ao lado do código que eles testam usando o sufixo _test.go:

internal/
└── user/
    ├── service.go
    ├── service_test.go
    ├── repository.go
    └── repository_test.go

Esta convenção mantém os testes próximos à implementação, tornando-os fáceis de encontrar e manter. Para testes de integração que tocam múltiplos pacotes, crie um diretório test/ separado na raiz do projeto. Para orientação abrangente sobre como escrever testes unitários eficazes, incluindo testes orientados a tabela, mocks, análise de cobertura e melhores práticas, veja nosso guia de estrutura e melhores práticas de testes unitários em Go.

A documentação pertence em:

  • README.md: Visão geral do projeto, instruções de configuração, uso básico
  • docs/: Documentação detalhada, decisões de arquitetura, referências de API
  • api/: Especificações OpenAPI/Swagger, definições protobuf

Para APIs REST, gerar e servir documentação OpenAPI com Swagger é essencial para descoberta de API e experiência do desenvolvedor. Nosso guia sobre adicionar Swagger à sua API Go cobre integração com frameworks populares e melhores práticas.

Gerenciando Dependências com Módulos Go

Todo projeto Go deve usar Módulos Go para gerenciamento de dependências. Para uma referência abrangente sobre comandos Go e gerenciamento de módulos, consulte nossa Lista de Atalhos Go. Inicialize com:

go mod init github.com/yourusername/myproject

Isso cria go.mod (dependências e versões) e go.sum (checksums para verificação). Mantenha esses arquivos em controle de versão para builds reproduzíveis.

Atualize dependências regularmente:

go get -u ./...          # Atualizar todas as dependências
go mod tidy              # Remover dependências não utilizadas
go mod verify            # Verificar checksums

Principais Pontos

  1. Comece simples, evolua naturalmente: Não superengenheire sua estrutura inicial. Adicione diretórios e pacotes quando a complexidade exigir.

  2. Prefira hierarquias planas: Limite o aninhamento a um ou dois níveis. A estrutura de pacote plana do Go melhora a legibilidade.

  3. Use nomes de pacotes descritivos: Evite nomes genéricos como utils. Nomeie pacotes pelo que eles fazem: auth, storage, validator.

  4. Separe preocupações claramente: Mantenha manipuladores focados no HTTP, repositórios no acesso a dados e lógica de negócios em pacotes de serviço.

  5. Aproveite internal/ para privacidade: Use para código que não deve ser importado externamente. A maioria do código da aplicação pertence aqui.

  6. Aplique padrões de arquitetura quando necessário: Para sistemas complexos, a Arquitetura Hexagonal e DDD fornecem limites claros e testabilidade.

  7. Deixe o Go guiá-lo: Siga os idiomas do Go em vez de importar padrões de outras linguagens. Go tem sua própria filosofia sobre simplicidade e organização.

Outros Artigos Relacionados

Assinar

Receba novos artigos sobre sistemas, infraestrutura e engenharia de IA.