Em Go, a organização do projeto segue convenções que facilitam a legibilidade, o build e o reuso. Diferente de outras linguagens, Go não impõe uma estrutura rígida, mas a comunidade adotou padrões que se provaram eficientes. Nesta aula, vamos explorar o layout idiomático, os diretórios especiais internal/ e cmd/, além de boas práticas para estruturar seus projetos.

Entender esses padrões é essencial para trabalhar em equipe, publicar pacotes e manter a sanidade mental em projetos maiores. Vamos começar com o layout geral.

Layout idiomático

O layout idiomático de um projeto Go geralmente segue a estrutura abaixo, recomendada pela comunidade e adotada em muitos projetos open source:

meu-projeto/
├── cmd/
│   └── meu-app/
│       └── main.go
├── internal/
│   └── meu-pacote/
│       └── arquivo.go
├── pkg/
│   └── publico/
│       └── util.go
├── api/
│   └── swagger.yaml
├── web/
│   └── templates/
├── configs/
│   └── config.yaml
├── scripts/
│   └── build.sh
├── test/
│   └── integration/
├── go.mod
├── go.sum
├── Makefile
└── README.md

Esse layout separa claramente as responsabilidades: cmd/ contém os pontos de entrada (main), internal/ para código privado, pkg/ para código público reutilizável, e api/, web/, configs/ para recursos auxiliares. Nem todo projeto precisa de todos esses diretórios; o importante é manter consistência.

internal/

O diretório internal/ é especial no Go: ele define um limite de visibilidade. Pacotes dentro de internal/ só podem ser importados por código que esteja na árvore pai do diretório internal. Por exemplo, se seu projeto tem a estrutura:

projeto/
├── internal/
│   └── db/
│       └── conexao.go
└── cmd/
    └── server/
        └── main.go

O pacote projeto/internal/db pode ser importado por projeto/cmd/server/main.go, mas não por um pacote externo como github.com/outro/projeto. Isso é útil para encapsular detalhes de implementação que não devem ser expostos publicamente. Exemplo de código:

// internal/db/conexao.go
package db

import "database/sql"

var db *sql.DB

func Init(connStr string) error {
    var err error
    db, err = sql.Open("postgres", connStr)
    return err
}

func GetDB() *sql.DB {
    return db
}

Assim, apenas o código dentro do mesmo módulo (ou subdiretórios) pode acessar esses pacotes, promovendo encapsulamento.

cmd/

O diretório cmd/ agrupa os pontos de entrada do seu projeto. Cada subdiretório dentro de cmd/ representa um binário diferente. Por exemplo, um projeto pode ter cmd/server/main.go e cmd/cli/main.go. Cada um terá seu próprio package main e função main(). Exemplo:

// cmd/server/main.go
package main

import (
    "fmt"
    "meu-projeto/internal/server"
)

func main() {
    srv := server.New()
    fmt.Println("Servidor iniciado na porta 8080")
    srv.Run()
}

Essa separação permite compilar cada binário independentemente com go build ./cmd/server ou go build ./cmd/cli. É uma prática recomendada ter um único main.go por subdiretório, e manter a lógica de negócio fora de cmd/.

Boas práticas

Aqui estão algumas boas práticas para estruturar projetos Go:

  • Evite grandes pacotes: quebre em pacotes menores com responsabilidades únicas.
  • Use nomes descritivos: o nome do pacote deve ser o mesmo do diretório.
  • Não use src/: Go não requer um diretório src; coloque o código na raiz do módulo.
  • Prefira internal/ a pkg/: só exponha o necessário em pkg/.
  • Mantenha cmd/ enxuto: a função main deve apenas inicializar e chamar o código de aplicação.
  • Use go.mod com o nome do módulo adequado: geralmente o repositório (ex: github.com/usuario/projeto).
  • Documente: coloque um README.md e, se possível, exemplos de uso.

Exemplo de um go.mod bem estruturado:

module github.com/meu-usuario/meu-projeto

go 1.21

require (
    github.com/gorilla/mux v1.8.0
)

Essas práticas facilitam a colaboração e a manutenção do código a longo prazo.

Referências

Exercícios

  1. Crie a estrutura de diretórios para um projeto Go que possui dois binários: um servidor HTTP e uma ferramenta CLI. Inclua um pacote interno para lógica de banco de dados e um pacote público para utilitários.
  2. ✓ Resposta:
    meu-projeto/
    ├── cmd/
    │   ├── server/
    │   │   └── main.go
    │   └── cli/
    │       └── main.go
    ├── internal/
    │   └── db/
    │       └── conexao.go
    ├── pkg/
    │   └── util/
    │       └── strings.go
    ├── go.mod
    └── README.md
  3. Explique por que o código dentro de internal/ não pode ser importado por pacotes externos. Dê um exemplo.
  4. ✓ Resposta:O Go impõe uma restrição de visibilidade: pacotes dentro de internal/ só podem ser importados por código que esteja na árvore do módulo que contém o diretório internal. Por exemplo, se o módulo é github.com/meu/modulo, e há internal/foo, apenas pacotes dentro de github.com/meu/modulo podem importá-lo. Um pacote externo como github.com/outro/modulo receberia um erro de compilação. Isso permite encapsular implementações internas.
  5. No diretório cmd/, quantos pacotes main podem existir e como compilar cada um?
  6. ✓ Resposta:Podem existir vários pacotes main, um para cada subdiretório dentro de cmd/. Para compilar um específico, use go build ./cmd/server ou go build ./cmd/cli. O comando go build ./cmd/... compila todos.
  7. Qual a diferença entre colocar código em internal/ e em pkg/? Quando usar cada um?
  8. ✓ Resposta:internal/ é para código privado do projeto, que não deve ser importado por outros módulos. pkg/ é para código público que pode ser reutilizado por outros projetos. Use internal/ para detalhes de implementação e pkg/ para bibliotecas que você quer disponibilizar.
  9. Crie um go.mod para um projeto que depende do pacote github.com/gorilla/mux e tem nome de módulo github.com/meuapp/meuprojeto.
  10. ✓ Resposta:
    module github.com/meuapp/meuprojeto
    
    go 1.21
    
    require github.com/gorilla/mux v1.8.0