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 eficientemente é fundamental para a manutenção a longo prazo, a colaboração em equipe e a escalabilidade. Ao contrário de frameworks que impõem layouts de diretórios rígidos, o 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.

árvore do projeto

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 impõe uma estrutura específica, confiando, em vez disso, nos desenvolvedores para tomar 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 corporativos.

O princípio fundamental é simplicidade primeiro, complexidade quando necessário. Muitos desenvolvedores caem na armadilha de superengenharia na 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 impõe essa restrição, tornando internal/ perfeito para lógica de aplicativo privada, regras de negócio e utilitários não destinados à reutilização fora do seu projeto.

O diretório pkg/, por outro lado, sinaliza que o código é destinado ao consumo externo. Coloque código aqui apenas se você estiver construindo uma biblioteca ou componentes reutilizáveis que deseja que outros importem. Muitos projetos não precisam de pkg/ em absoluto — se o 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 os genéricos do Go para criar componentes reutilizáveis e seguros quanto ao tipo.

O Layout Padrão de Projetos Go

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

meuprojeto/
├── 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 log compartilhado em internal/ (por exemplo, internal/logx) para que os binários em cmd/ configurem um logger na inicializaçã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 voltada à produção (linhas JSON, redação, campos de contexto e rastreamento e sinais que funcionam bem com pilhas de monitoramento), consulte Log Estruturado em Go com slog para Observabilidade e Alertas.

O Diretório cmd/

O diretório cmd/ contém os pontos de entrada do seu aplicativo. 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 o aplicativo. Toda a lógica substantiva pertence a pacotes que main.go importa.

// cmd/api/main.go
package main

import (
    "log"
    "meuprojeto/internal/server"
    "meuprojeto/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 reside seu código de aplicativo privado. 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ê está construindo uma pequena ferramenta ou protótipo, comece com:

meuprojeto/
├── 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 se multiplicarem. Deixe a estrutura emergir naturalmente em vez de impô-la antecipadamente.

Estruturas Planas vs. Aninhadas: Encontrando o Equilíbrio

Um dos erros mais comuns na estrutura de projetos Go é o aninhamento excessivo. O Go favorece hierarquias rasas — tipicamente um ou dois níveis de profundidade. Aninhamento profundo aumenta a carga cognitiva e torna as importações trabalhosas.

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 “code smells” (maus cheiros de código). Eles não transmitem funcionalidade específica e frequentemente se tornam 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 separação pobre de responsabilidades. Introduza interfaces ou extraia tipos compartilhados para um pacote separado.

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

Design Orientado a Domínio e Arquitetura Hexagonal

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

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 a 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 de negócios central, entidades, objetos de valor e interfaces de serviço de domínio. Esta camada não tem dependências de sistemas externos — sem importações de HTTP ou 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 por si só.

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, 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 de negócios central 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 do domínio. Se você adotar CQRS neste layout, Implementando CQRS em Go mostra como a camada application/ mapeia naturalmente para pacotes separados de manipuladores de comandos e consultas, mantendo o lado do comando estrito e o lado da consulta orientado a DTOs.

Padrões Práticos para Diferentes Tipos de Projetos

Ferramenta CLI Pequena

Para aplicativos de linha de comando, você desejará 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 construção de aplicativos CLI em Go com Cobra & Viper cobre este padrão em detalhes.

minhaferramenta/
├── 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 claramente as responsabilidades: 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 da biblioteca padrão, frameworks, autenticação, padrões de teste e melhores práticas prontas para produção, consulte nosso guia completo para construção de APIs REST em Go. Para registro JSON estruturado, correlação de solicitações e rastreamento, e formatos de log que suportam alertas, consulte Log Estruturado em Go com slog para Observabilidade e Alertas.

minhaapi/
├── 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

meuprojeto/
├── 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 do Go
└── README.md

Testes e Documentação

Coloque os arquivos de teste junto com o 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, facilitando sua localização e manutenção. Para testes de integração que envolvem múltiplos pacotes, crie um diretório test/ separado na raiz do projeto. Para orientação abrangente sobre a escrita de testes unitários eficazes, incluindo testes orientados a tabela, mocks, análise de cobertura e melhores práticas, consulte nosso guia de estrutura e melhores práticas de testes unitários em Go.

A documentação pertence a:

  • 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 de protobuf

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

Gerenciando Dependências com Go Modules

Todo projeto Go deve usar Go Modules para gerenciamento de dependências. Para uma referência abrangente sobre comandos Go e gerenciamento de módulos, verifique nossa Folha de Trapaça Go. Inicialize com:

go mod init github.com/seuusuario/meuprojeto

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

Atualize as 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 superengenharia 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 plana de pacotes do Go melhora a legibilidade.

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

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

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

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

  7. Deixe o Go guiá-lo: Siga os idiomáticos do Go em vez de importar padrões de outras linguagens. O 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.