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 no main.
  • 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 main enxuto: delegue a lógica para pacotes testáveis.
  • Use módulos Go: sempre tenha um go.mod e 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

  1. Explique o propósito da pasta internal/ e cite um cenário onde ela é essencial.
  2. ✓ Resposta: A pasta 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.
  3. 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.
  4. ✓ Resposta:
    meu-projeto/
    ├── cmd/
    │   ├── server/
    │   │   └── main.go
    │   └── worker/
    │       └── main.go
    ├── internal/
    │   ├── handler/
    │   ├── service/
    │   └── repository/
    ├── pkg/
    │   └── queue/
    │       └── client.go
    ├── go.mod
    └── go.sum
    
  5. 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.
  6. ✓ Resposta: Suponha um módulo 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.
  7. Quais são as vantagens de colocar os main.go na pasta cmd/ em vez de na raiz?
  8. ✓ Resposta: As vantagens incluem: separação clara de múltiplos executáveis, manutenção mais fácil (cada main é pequeno), e a possibilidade de compilar binários individuais com go build ./cmd/..., além de manter a raiz limpa para arquivos de configuração e documentação.
  9. Descreva três boas práticas de organização de código em projetos Go que você aprendeu nesta aula.
  10. ✓ Resposta: Três boas práticas: 1) Usar 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

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.