Estrutura de projetos
Esta aula aborda a estrutura de projetos em Go, seguindo as convenções idiomáticas da comunidade. Você aprenderá sobre o layout padrão com pastas como cmd/ e internal/, além de boas práticas para organizar código de forma clara, testável e escalável.
Em Go, a organização do projeto é uma parte fundamental do desenvolvimento. Uma estrutura bem planejada não só facilita a navegação e a manutenção, mas também comunica intenções e promove boas práticas de engenharia de software. Nesta aula, vamos explorar o layout idiomático recomendado pela comunidade Go, entender a função das pastas especiais como cmd/ e internal/, e discutir boas práticas que vão além da simples organização de arquivos.
Diferente de outras linguagens que impõem estruturas rígidas (como Maven no Java), Go é minimalista e oferece liberdade. No entanto, a comunidade desenvolveu convenções que ajudam a manter consistência entre projetos, facilitando a colaboração e a adoção de ferramentas. Vamos mergulhar nessas convenções e ver como aplicá-las na prática.
Layout idiomático
O layout idiomático de um projeto Go é aquele que segue as convenções da comunidade, tornando o código previsível e fácil de entender. A documentação oficial e a maioria dos projetos open source populares (como Kubernetes, Docker, Hugo) seguem um padrão que inclui pastas como cmd, internal, pkg, configs, scripts, entre outras. A ideia central é separar claramente as responsabilidades: o código executável, a lógica de negócio, as bibliotecas reutilizáveis e os arquivos de configuração.
Um ponto importante é que o GOPATH foi substituído pelo module mode (introduzido no Go 1.11), permitindo que o projeto não precise estar dentro de um caminho específico. Com módulos, o projeto pode ser criado em qualquer diretório e o arquivo go.mod define o módulo e suas dependências. Isso trouxe mais flexibilidade, mas a estrutura interna continua relevante.
Vejamos um exemplo de estrutura típica para uma aplicação web:
meu-projeto/
├── cmd/
│ ├── api/
│ │ └── main.go
│ └── worker/
│ └── main.go
├── internal/
│ ├── handler/
│ │ └── user.go
│ ├── service/
│ │ └── user.go
│ └── repository/
│ └── user.go
├── pkg/
│ └── validator/
│ └── email.go
├── configs/
│ └── config.yaml
├── scripts/
│ └── build.sh
├── go.mod
└── go.sum
Cada pasta tem um propósito:
- cmd/: contém os pontos de entrada da aplicação (funções
main). Cada subpasta é um executável distinto. - internal/: código privado que não deve ser importado por outros módulos.
- pkg/: código que pode ser compartilhado publicamente, mas não obrigatório.
- configs/: arquivos de configuração.
- scripts/: scripts de build, deploy, etc.
Essa organização não é obrigatória, mas é amplamente adotada. Você pode adaptá-la conforme a necessidade, mas seguir o padrão facilita a vida de quem lê seu código.
internal/
A pasta internal/ é um recurso especial da linguagem Go: os pacotes dentro dela só podem ser importados por código que está no diretório pai de internal. Ou seja, se você tem um módulo meu-projeto e cria meu-projeto/internal/foo, apenas o código dentro de meu-projeto (incluindo subpastas) pode importar foo. Isso é uma forma de encapsulamento forçado pelo compilador.
Esse recurso é útil para proteger lógica interna que não deve ser usada por consumidores externos. Por exemplo, se você está desenvolvendo uma biblioteca, pode colocar implementações internas em internal/ para evitar que usuários dependam de APIs instáveis. Em aplicações, ajuda a manter a arquitetura limpa, garantindo que a camada de repositório não seja acessada diretamente pelo main, por exemplo.
Vamos criar um exemplo simples:
// internal/service/user.go
package service
import "meu-projeto/internal/repository"
type UserService struct {
repo *repository.UserRepository
}
func NewUserService(repo *repository.UserRepository) *UserService {
return &UserService{repo: repo}
}
Se um pacote externo tentar importar meu-projeto/internal/service, o compilador emitirá um erro: use of internal package not allowed. Isso é uma proteção em tempo de compilação, não apenas uma convenção.
É importante notar que o diretório internal/ pode estar em qualquer nível da árvore do módulo, mas a regra de visibilidade é sempre em relação ao diretório pai imediato. Por exemplo, se você tem meu-projeto/cmd/api/internal/foo, apenas o pacote api pode importar foo. Isso permite esconder detalhes de implementação de subcomandos.
cmd/
A pasta cmd/ é o local padrão para os executáveis do projeto. Cada subpasta dentro de cmd/ deve conter um pacote main que é compilado em um binário separado. Isso é especialmente útil em projetos que possuem múltiplos comandos, como uma API e um worker, ou uma CLI com vários subcomandos.
Por exemplo, em um projeto de microsserviços, você pode ter:
cmd/
├── api/
│ └── main.go
├── worker/
│ └── main.go
└── cli/
└── main.go
Cada main.go é um programa independente que pode ser construído com go build ./cmd/api. Isso separa a lógica de inicialização da lógica de negócio, que fica em internal/ ou pkg/.
Um exemplo de main.go para uma API:
package main
import (
"log"
"net/http"
"meu-projeto/internal/handler"
)
func main() {
http.HandleFunc("/users", handler.GetUsers)
log.Println("Servidor rodando na porta 8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
Perceba que o main é enxuto: ele apenas configura o servidor e delega para os handlers. Isso facilita testes e reutilização de código.
Se você tiver apenas um executável, pode usar cmd/meu-app ou até mesmo colocar o main.go na raiz, mas o padrão com cmd/ é mais escalável e consistente, especialmente se o projeto crescer.
Boas práticas
Além da estrutura de pastas, existem boas práticas que ajudam a manter o código Go limpo e eficiente. Vamos listar algumas:
- Evite o uso de
init()para efeitos colaterais complexos: prefira funções de inicialização explícitas chamadas nomain. - Nomeie pacotes com nomes curtos e descritivos: sem underscores e sem camelCase, como
handler,service. - Use
internal/para código privado: proteja sua lógica interna de uso externo. - Mantenha o
mainenxuto: delegue a lógica para pacotes testáveis. - Use módulos Go: sempre tenha um
go.mode use o módulo para gerenciar dependências. - Organize por responsabilidade, não por tipo de arquivo: agrupe handlers, services, repositories em pastas separadas, mas não crie pastas como
models/genéricas. - Prefira interfaces pequenas: defina interfaces no consumidor, não no produtor.
- Escreva testes para os pacotes: coloque os testes junto ao código, como
user_test.go.
Uma prática comum é usar o padrão de projeto clean architecture ou hexagonal, mas o Go permite uma implementação mais simples com as pastas internal, pkg e cmd. O importante é manter a coesão e o baixo acoplamento.
Outra dica: evite criar uma pasta src ou lib desnecessariamente. O Go já tem convenções claras; seguir o padrão evita confusão.
Exercícios
- Explique o propósito da pasta
internal/e cite um cenário onde ela é essencial. - Crie a estrutura de pastas para um projeto que possui dois executáveis: um servidor web e um worker de processamento em fila. Inclua também uma pasta para código compartilhado publicamente.
- Escreva um pequeno exemplo de código que demonstre a restrição imposta por
internal/. Mostre um pacote externo tentando importar um pacote interno e a mensagem de erro gerada. - Quais são as vantagens de colocar os
main.gona pastacmd/em vez de na raiz? - Descreva três boas práticas de organização de código em projetos Go que você aprendeu nesta aula.
internal/ serve para restringir a importação de pacotes apenas ao código dentro do diretório pai. Ela é essencial em bibliotecas públicas para esconder APIs internas instáveis ou em aplicações para proteger camadas internas (como repositório) de acesso externo, evitando dependências indevidas.
meu-projeto/
├── cmd/
│ ├── server/
│ │ └── main.go
│ └── worker/
│ └── main.go
├── internal/
│ ├── handler/
│ ├── service/
│ └── repository/
├── pkg/
│ └── queue/
│ └── client.go
├── go.mod
└── go.sum
meu-projeto com internal/foo. Um pacote externo outro-modulo tentando importar meu-projeto/internal/foo receberá o erro: use of internal package meu-projeto/internal/foo not allowed. O compilador impede a compilação.
go build ./cmd/..., além de manter a raiz limpa para arquivos de configuração e documentação.
internal/ para proteger código privado; 2) Manter o main enxuto e delegar lógica para pacotes testáveis; 3) Organizar por responsabilidade (handlers, services, repositories) em vez de por tipo de arquivo.
Referências
- Effective Go
- How to Write Go Code
- Standard Go Project Layout
- Go Modules Reference
- Command go - Documentation
- Using Go Modules
Observações Finais
Dominar a estrutura de projetos em Go é um diferencial para qualquer desenvolvedor. Além de seguir as convenções, lembre-se de que a organização deve refletir as necessidades do seu projeto. Não tenha medo de adaptar, mas sempre documente as decisões. Com o tempo, você desenvolverá um senso de organização que tornará seu código mais profissional e fácil de manter.