Pesquisa «estrutura de projeto Go» e, em dois cliques, vais parar a golang-standards/project-layout, um repositório do GitHub com mais de 50.000 estrelas e uma árvore de diretórios cheia de cmd/, pkg/, internal/, api/ e build/. Parece ter autoridade, e a maioria dos principiantes copia-o por inteiro para um programa de cinco ficheiros.
Só que não é um padrão oficial, e a equipa do Go já o disse publicamente. A orientação oficial é mais simples: começa com uma estrutura plana e só acrescenta pastas quando o código precisar delas. As secções seguintes acompanham essa orientação uma pasta de cada vez, usando uma CLI do tempo e uma API de livraria como exemplos ao longo do texto.
Qual é a estrutura padrão de um projeto Go?
Não há uma árvore obrigatória. O Go não exige src/ nem cmd/, nem qualquer pasta além de um ficheiro go.mod na raiz do módulo. O guia oficial, «Organizing a Go module», faz a estrutura depender do tamanho do projeto. Mostra quatro estruturas, cada uma um passo acima da anterior: um pacote básico, um comando básico, um pacote que ganha pacotes de apoio e um projeto de «servidor» com vários binários. (fonte)
Por isso, a pergunta útil é de quanta estrutura o teu projeto precisa neste momento, e para a maioria dos programas a resposta é muito pouca. O resto deste artigo percorre as quatro estruturas da mais simples à mais complexa. Encontra a que corresponde ao teu projeto e fica por aí.
Começa com uma estrutura plana: um só pacote é idiomático
As duas primeiras estruturas da documentação oficial são um único pacote e um único comando, ambos diretamente na raiz do módulo, sem subdiretórios. Para uma pequena ferramenta de linha de comandos que lê uma cidade e mostra o estado do tempo, o projeto completo é este:
example.texttextweather/ ├── go.mod ├── main.go ├── weather.go └── weather_test.go
go.mod declara o caminho do módulo (por exemplo, module github.com/you/weather). main.go contém package main e o ponto de entrada. weather.go contém a lógica, no mesmo pacote. weather_test.go contém os testes, mesmo ao lado do código que cobrem, que é onde o Go espera encontrá-los. Podes distribuir isto tal como está. A documentação oficial chama-lhe um «comando básico», e é a forma certa para a maioria das ferramentas que vais escrever no teu primeiro ano.
Há uma regra que explica a maior parte das decisões de estrutura que vêm a seguir: um diretório é um pacote. Todos os ficheiros de um diretório declaram o mesmo pacote, e o nome do pacote é o último elemento do caminho de importação. Se adicionares um diretório weather/store/, todos os ficheiros lá dentro dizem package store. Importas o pacote como github.com/you/weather/store e chamas as funções como store.Get.
Não adiciones pastas por enquanto. Um diretório utils/ com uma única função, ou um diretório models/ com uma única struct, acrescenta um caminho de importação e uma fronteira de pacote e não te dá nada em troca. Mantém o projeto plano até essa estrutura plana começar a causar problemas reais.
Quando deves adicionar um diretório internal/?
A primeira boa razão para acrescentar estrutura é a privacidade. O Go tem dois níveis de visibilidade para identificadores: os nomes com inicial maiúscula são exportados e os nomes com inicial minúscula são privados do pacote. Isso funciona dentro de um pacote. Mas, quando o teu módulo passa a ter vários pacotes, muitas vezes queres código que todos os pacotes dentro do módulo possam usar e que nada fora dele possa importar. É isso que o internal/ te dá, e o compilador garante-o.
Isto faz parte da linguagem, não é uma convenção de nomes. Um pacote dentro de um diretório chamado internal/ só pode ser importado por código que esteja dentro do diretório pai desse internal/, e uma importação a partir de outro módulo não compila. A documentação oficial recomenda manter os pacotes em internal «tanto quanto possível». O código externo não os pode importar, por isso não pode passar a depender deles, e nunca acabas a manter uma API que não pretendias publicar. (fonte)
Imagina que a CLI do tempo se transforma numa pequena API HTTP para uma livraria. Agora tem autenticação de pedidos e uma camada de acesso a dados, e nenhuma delas pertence a uma API pública:
example.texttextbookstore/ ├── go.mod ├── main.go └── internal/ ├── auth/ │ ├── auth.go │ └── auth_test.go └── store/ ├── store.go └── store_test.go
O main.go está dentro do módulo bookstore, por isso pode importar github.com/you/bookstore/internal/auth e github.com/you/bookstore/internal/store à vontade. O código de qualquer outro módulo não pode. Se alguém executar go get no teu módulo e tentar importar esses pacotes, o compilador rejeita a importação. Como ninguém fora do teu módulo pode depender de auth ou store, podes mudar o nome das suas funções, alterar as suas assinaturas ou reorganizá-los sempre que quiseres, sem partir o código de mais ninguém.
Quando deves adicionar um diretório cmd/?
O cmd/ serve para repositórios que compilam mais do que um binário, ou que distribuem um binário e uma biblioteca importável ao mesmo tempo. Se só tiveres um binário, não precisas dele, e ter o main.go na raiz fica mais limpo.
Dentro de cmd/, cada binário tem o seu próprio subdiretório, e o nome do diretório passa a ser o nome do programa. A estrutura oficial de «projeto de servidor» faz isto: cada comando fica em cmd/<name>/, com um main.go pequeno que liga os pacotes de internal/. (fonte) Imagina que a livraria passa agora a distribuir um servidor de API e uma ferramenta de migração da base de dados:
example.texttextbookstore/ ├── go.mod ├── cmd/ │ ├── api/ │ │ └── main.go │ └── migrate/ │ └── main.go └── internal/ ├── auth/ └── store/
go build ./cmd/api produz um binário chamado api, e go build ./cmd/migrate produz migrate. Os dois ficheiros main.go mantêm-se curtos e importam o trabalho real a partir de internal/.
Eis toda a progressão numa tabela. Escolhe a linha que corresponde ao teu projeto tal como está hoje, e não ao projeto em que imaginas que se vai tornar.
| Tipo de projeto | Estrutura recomendada | Porquê |
|---|---|---|
| Ferramenta pequena ou script | Plana: main.go + ficheiros de lógica na raiz | Um só pacote é idiomático e completo |
| Um binário com código interno reutilizável | Adiciona internal/ para os pacotes privados | Privacidade garantida pelo compilador, sem API prematura |
| Dois ou mais binários | Adiciona cmd/<name>/ por cada binário | Cada main tem o seu próprio diretório |
| Repositório com vários serviços (binários + código partilhado) | cmd/ + internal/, divididos por domínio | A estrutura oficial de «projeto de servidor» |
O mito do golang-standards/project-layout
O repositório golang-standards/project-layout tem «standards» no nome da sua organização do GitHub, mais de 50.000 estrelas e uma árvore grande com api/, build/, configs/, deployments/, pkg/ e test/. É fácil assumir que a equipa do Go o aprovou, mas não aprovou. O README do repositório inclui agora um aviso a dizer que não é um padrão oficial, e a objeção veio da própria liderança do Go.
Na issue #117, Russ Cox, líder técnico do Go, escreveu que esta «não é uma estrutura padrão de projeto Go» e que a maioria dos repositórios Go é «muito mais simples» e nem sequer usa um diretório pkg/. A discussão chegou à primeira página do Hacker News, onde a maioria dos programadores Go experientes lhe deu razão. A estrutura pode fazer sentido num monorepo grande partilhado por várias equipas, mas como modelo de partida para um projeto normal é prejudicial. Se a copiares para um programa pequeno, os teus cinco ficheiros acabam debaixo de oito pastas quase vazias.
Daqui resultam algumas regras, que coincidem com o que profissionais de Go como Eli Bendersky e Alex Edwards costumam aconselhar:
- Não uses um diretório
src/. Esse hábito vem do Java e de projetos Node mais antigos. O código Go fica na raiz do módulo, e nenhuma estrutura oficial temsrc/. - Não cries pastas aninhadas antes de teres código para lá pôr. Pastas
api/,build/edeployments/vazias não fazem nada. Cria uma pasta no dia em que tiveres alguma coisa para ela. - Dá nome aos pacotes pelo que oferecem, não pelo papel que têm. Um pacote chamado
utils,commonouhelpersnão diz nada a quem lê e acaba por se tornar um saco de gatos. Chama-lhestore,auth,weather,retryouslugify. O conselho oficial do Go sobre nomes de pacotes é mantê-los curtos e claros e fazer com que se leiam bem no ponto de chamada, por issostore.Geté melhor do queutils.GetFromStore. (fonte)
Para erros que aparecem no código e não nas pastas, vê o nosso guia de erros comuns em Go a evitar.
Regra prática para principiantes na estrutura de um projeto Go
A progressão resume-se a algumas perguntas sobre o teu projeto. Percorre-as a partir do topo e só adiciona um diretório quando uma resposta o pedir.
flowchart TD A["New Go project"] --> B["Is it one package<br/>of related code?"] B -->|Yes| C["Keep it flat:<br/>main.go + logic in the root"] B -->|"Grew past that"| D["Need to hide code<br/>from outside importers?"] D -->|Yes| E["Add internal/<br/>put private packages there"] E --> F["More than one<br/>binary to build?"] F -->|No| G["Done. main.go in root,<br/>logic in internal/"] F -->|Yes| H["Add cmd/name/<br/>one dir per binary"] H --> I["Many services sharing code?"] I -->|Yes| J["Server layout:<br/>cmd/ + internal/ by domain"]
Cada passo responde a uma necessidade concreta: uma fronteira de pacote, um segundo binário ou código que queres manter privado. Se não conseguires apontar uma delas, provavelmente não precisas da pasta. Começa com uma estrutura plana, adiciona internal/ quando quiseres que o compilador garanta a privacidade, adiciona cmd/ quando tiveres um segundo binário e só passa para a estrutura completa de servidor quando o repositório tiver mesmo vários serviços.
Perguntas frequentes
Existe uma estrutura oficial para projetos Go?
Não, não existe uma estrutura de pastas única e obrigatória. O documento oficial, «Organizing a Go module», descreve uma estrutura que cresce com o tamanho do projeto, em vez de uma estrutura fixa. Vai de um pacote plano para internal/, depois cmd/ e depois uma estrutura completa de servidor. Usa a estrutura mais pequena que sirva ao teu projeto. (fonte)
Devo usar o diretório pkg/?
Normalmente não. Russ Cox, líder técnico do Go, observou que a maioria dos repositórios Go não usa um diretório pkg/ e que estes «tendem a ser muito mais simples». Põe o código privado em internal/, onde o compilador garante a privacidade, e expõe pacotes na raiz do módulo só quando planeares mantê-los como API pública. (fonte)
O que vai para internal/ e o que vai para cmd/?
internal/ contém código de biblioteca importável que só o teu módulo pode usar, e o compilador garante isso. cmd/<name>/ contém o pacote main, curto, de cada binário que compilas, que liga o código de internal/. A lógica vai para internal/, e os pontos de entrada vão para cmd/. (fonte)
Um projeto Go real pode ser um único ficheiro ou pacote?
Sim. O primeiro exemplo do guia oficial de estrutura é um único pacote na raiz do módulo, e muitas ferramentas em produção são distribuídas assim. Um pacote com um go.mod, um main.go e os respetivos testes é idiomático e completo. Acrescenta estrutura quando o código deixar de caber nesse formato. (fonte)
Em que é que um projeto Go difere de um projeto Java ou Node?
O Go não tem diretório src/. O código fica na raiz do módulo, cada diretório é um pacote e o nome do pacote é o último elemento do caminho. Nenhuma ferramenta de build impõe uma árvore como o Maven ou um bundler fazem, por isso as estruturas Go mantêm-se mais planas e só crescem quando é preciso. (fonte)
Por onde continuar
A estrutura é uma das partes mais fáceis do Go. Distribuis um programa com uma estrutura plana e depois acrescentas internal/ e cmd/ quando o código precisar deles. Se não consegues dizer porque é que uma pasta existe, provavelmente podes apagá-la.
A melhor forma de ganhares sensibilidade para isto é construir um par de programas reais, por exemplo uma CLI e um serviço HTTP, e deixar a estrutura crescer à medida que avanças. O percurso Clean Go Code do LevelUpGo aborda o design idiomático de pacotes e a estrutura de projetos através de exercícios que escreves e executas no navegador, e o Go Fundamentals começa pelo teu primeiro go.mod e pela toolchain do Go. Também podes explorar o roteiro interativo completo.
