Menu

Estrutura de projeto em Golang: cmd, internal e organização de pacotes

Como organizar um projeto Go: comece plano, divida em pacotes quando houver motivo, use cmd/ para vários binários e internal/ para código que ninguém mais pode importar, dê bons nomes aos pacotes e mantenha os testes ao lado do código.

Esta página tem editores executáveis - edite, execute e veja a saída na hora.

Comece com um arquivo

Um programa Go pode viver inteiro em um único main.go, e ferramentas pequenas devem fazer isso. Este programa completo interpreta uma entrada, faz o seu trabalho e imprime um relatório, em um só pacote:

Quando ele crescer, divida-o em mais arquivos no mesmo diretório e no mesmo pacote (parse.go, report.go, main.go). Arquivos de um pacote compartilham todos os identificadores, então nada precisa ser exportado ou importado. Muitas vezes essa é toda a estrutura de que um projeto de alguns milhares de linhas precisa.

Lembre que um package main com vários arquivos precisa ser executado como pacote: go run ., e não go run main.go. Do contrário os arquivos são compilados sozinhos e você recebe erros undefined para nomes definidos nos outros arquivos.

Não existe um layout obrigatório

O Go não exige nenhuma estrutura de diretórios além de "um pacote por diretório". A orientação oficial é a página Organizing a Go module no go.dev, e ela descreve alguns formatos, não regras.

O repositório do GitHub golang-standards/project-layout é muito copiado e muito confundido com um padrão. Ele é uma coleção comunitária de convenções de projetos grandes, e Russ Cox, na época tech lead do Go, abriu uma issue lá dizendo que ele não é um padrão do Go. A maioria dos seus diretórios (pkg/, api/, build/, deployments/) só faz sentido em bases de código grandes. Copiar o esqueleto inteiro para um projeto novo cria pastas vazias e caminhos de import profundos sem benefício nenhum.

As regras impostas pela ferramenta são curtas:

  • Um diretório é um pacote. Todos os arquivos .go dele (exceto os arquivos _test.go que usam o sufixo _test) compartilham o nome do pacote.
  • O caminho de import é o caminho do módulo mais o caminho do diretório.
  • Um diretório chamado internal restringe quem pode importar o que está dentro dele.
  • Diretórios chamados testdata, e os que começam com . ou _, são ignorados pela ferramenta go.

Os formatos comuns

Um único comando

todo/
├── go.mod        module github.com/you/todo
├── main.go
├── parse.go
├── report.go
└── parse_test.go

Plano, tudo em package main. go install github.com/you/todo@latest funciona e o binário se chama todo.

Uma biblioteca

slug/
├── go.mod        module github.com/you/slug
├── slug.go       package slug
├── slug_test.go
├── example_test.go
└── internal/
    └── table/    helpers the public package uses, hidden from users

O pacote fica na raiz do módulo, então os usuários importam github.com/you/slug e chamam slug.Make(...). Tudo o que você não quer manter como API pública vai para internal/.

Um serviço ou vários binários

shop/
├── go.mod                module github.com/you/shop
├── cmd/
│   ├── shop-api/
│   │   └── main.go       package main: flags, config, wiring
│   └── shop-worker/
│       └── main.go
├── internal/
│   ├── order/            package order: domain types and logic
│   │   ├── order.go
│   │   └── order_test.go
│   ├── store/            package store: database access
│   └── httpapi/          package httpapi: handlers, routing
├── migrations/
└── README.md

cmd/<nome>/main.go tem um diretório por binário, e o nome do diretório vira o nome do binário (go build ./cmd/shop-api gera shop-api). Cada main fica enxuto: lê a configuração, monta as dependências e inicia o servidor. O código de verdade fica em pacotes sob internal/, onde os dois binários podem usá-lo e nenhum outro módulo pode.

É para esse layout que a maioria dos serviços Go converge. Recorra a ele quando tiver um segundo binário ou um motivo real para dividir pacotes, não no primeiro dia.

O internal/ é imposto pelo comando go

Um pacote sob internal/ só pode ser importado por código na árvore com raiz no pai de internal. A partir de outro módulo, o build falha:

package example.com/b
	main.go:6:2: use of internal package example.com/a/internal/secret not allowed

Isso faz do internal/ a ferramenta para manter pequena a sua superfície de API. O código ali pode mudar livremente, porque você sabe que todo código que o chama está no seu próprio repositório. Em uma aplicação, colocar quase tudo em internal/ é razoável. Em uma biblioteca, ele separa o que você promete manter estável do que não promete.

O internal funciona em qualquer profundidade: shop/internal/order é visível para todo o shop/, enquanto shop/internal/order/internal/pricing só é visível dentro de shop/internal/order/.

Dando nome aos pacotes

O nome do pacote aparece em todo ponto de chamada, então ele importa mais que a árvore de diretórios.

  • Curto, em minúsculas, uma palavra: order, store, httpapi. Sem underscores e sem mixedCaps.
  • Dê o nome do que ele oferece, não do que ele contém. util, common, helpers, misc e models não dizem nada e viram depósitos; o guia de estilo do time do Go os desencoraja explicitamente. Coloque uma função auxiliar no pacote que a usa, ou em um pacote com o nome do propósito dela (slug, retry).
  • Evite repetição. Quem chama escreve order.Order e order.New, então não crie nomes como order.OrderService ou order.NewOrder. A biblioteca padrão faz isso de forma consistente: http.Server, e não http.HTTPServer.
  • O nome do diretório e o nome do pacote devem coincidir, com exceção do package main nos diretórios de comandos. Quando diferem, quem lê precisa abrir um arquivo para saber qual identificador o import introduz.

Divida os pacotes por responsabilidade, não por tipo. Uma divisão models/, controllers/, services/ (comum em outros ecossistemas) obriga cada funcionalidade a mexer em três pacotes e tende a criar ciclos de import, que o Go proíbe. Pacotes organizados em torno de um conceito de domínio (order, payment, user) mantêm juntos os seus tipos e a sua lógica.

Onde ficam os testes

Os testes ficam ao lado do código, no mesmo diretório, em arquivos chamados *_test.go. Não existe uma árvore tests/.

  • package order em order_test.go: um teste interno, que pode usar identificadores não exportados.
  • package order_test no mesmo diretório: um teste externo, que só enxerga a API exportada, como quem chama enxergaria. Útil para exemplos e para evitar ciclos de import nos testes.
  • Arquivos de fixture ficam em um diretório testdata/ ao lado dos testes. A ferramenta go o ignora, e os testes executam com o diretório do pacote como diretório de trabalho, então caminhos relativos como testdata/big.json funcionam.

Outros arquivos na raiz

Arquivo ou diretórioPropósito
go.mod, go.sumdefinição do módulo e checksums das dependências, sempre na raiz
README.md, LICENSEcomo em qualquer projeto
Makefile ou Taskfile.ymlatalhos de build opcionais
Dockerfilenormalmente na raiz, em serviços
migrations/, web/, docs/arquivos que não são Go, com o nome do que guardam
tools.goforma antiga de fixar dependências de ferramentas; o Go 1.24 a substitui por linhas tool no go.mod (go get -tool)
go.workum workspace para desenvolver vários módulos juntos localmente; normalmente não é commitado em projetos de um só módulo

Erros comuns

  • Copiar um template grande para um projeto pequeno. Comece plano e acrescente diretórios quando aparecer um segundo binário ou uma fronteira real.
  • Pacotes chamados utils ou common. Dê aos pacotes o nome do que eles fazem.
  • Um pacote por arquivo ou por tipo. Pacotes Go são pensados como unidades maiores que classes Java. Um pacote com dez arquivos é normal.
  • Ciclos de import. Dois pacotes não podem importar um ao outro. Isso normalmente significa que eles andam juntos, que um tipo compartilhado deveria ir para um pacote de nível mais baixo ou que um dos lados deveria depender de uma pequena interface em vez do outro pacote.
  • pkg/ por reflexo. Acrescenta um segmento a todo caminho de import sem acrescentar significado.
  • Testes em um diretório separado. Ali eles não enxergam o código não exportado, e as ferramentas não esperam por eles.

Perguntas frequentes

Existe um layout oficial de projeto Go?

Não um obrigatório. O time do Go publica orientações em go.dev/doc/modules/layout, que descrevem alguns formatos comuns (um único pacote, um comando, vários comandos com internal/). O repositório popular golang-standards/project-layout é um projeto da comunidade, não um padrão do Go, e o time do Go já disse isso publicamente.

O que é o diretório internal em Go?

Um pacote cujo caminho de import contém um elemento internal só pode ser importado por código com raiz no diretório pai desse internal. example.com/app/internal/store pode ser importado em qualquer lugar sob example.com/app/, e o comando go rejeita imports de qualquer outro módulo com use of internal package ... not allowed.

Devo usar um diretório pkg em Go?

É opcional e acrescenta um segmento ao caminho sem acrescentar significado. Alguns projetos grandes usam pkg/ para separar o código de biblioteca público do resto, mas a biblioteca padrão do Go e a maioria dos projetos modernos não usam. Coloque os pacotes importáveis na raiz do módulo ou em diretórios com nome, e tudo o que for privado em internal/.

Onde ficam os arquivos de teste em um projeto Go?

No mesmo diretório do código que testam, com nomes *_test.go. Go não tem uma árvore de testes separada. Arquivos de fixture ficam em um diretório testdata ao lado dos testes, que a ferramenta go ignora ao procurar pacotes.

Coddy programming languages illustration

Aprenda a programar com o Coddy

COMEÇAR