Aula 03 · 45–60 minutos
Módulos e pacotes em Go
Aula 3 — go.mod, a relação entre diretório e pacote, caminhos de import, o diretório internal, e o que Go não dita sobre estrutura de pastas.
Resultado e mapa da aula
Nas Lições 1 e 2 você viu a teoria: a Regra de Dependência, e como Go expressa isso via satisfação implícita de interfaces. O que ainda falta é o mecanismo mais básico de todos — como isso vira pastas e arquivos de verdade. Sem isso, não dá para montar o pequeno projeto real que a Lição 2 prometeu como próximo passo.
- Distinguimos módulo (o projeto inteiro) de pacote (cada diretório).
- Vemos como o caminho de import fisicamente impede uma violação da Regra de Dependência.
- Conhecemos o diretório especial
internal. - Separamos o que Go de fato dita do que é só convenção de comunidade — com um contraste prático de dois layouts.
Módulo: a unidade que o Go gerencia
Um módulo é uma coleção de pacotes versionada e distribuída em conjunto — na prática, é o seu projeto Go inteiro. Ele é definido por um único arquivo na raiz: go.mod.
module github.com/allangrds/aprovador-pedidos
go 1.22
Se você vem de Node (package.json) ou PHP (composer.json), a analogia é direta: go.mod cumpre o mesmo papel de declarar "isto é um projeto, e este é o seu nome/caminho canônico" — só que em Go esse caminho também funciona como prefixo de importação, como você vai ver a seguir.
Pacote: a unidade que corresponde a uma pasta
Dentro de um módulo, cada diretório é um pacote — não cada arquivo. Todos os arquivos .go num mesmo diretório declaram o mesmo package no topo, e compartilham tudo o que não começa com letra minúscula fora daquele diretório.
O caso mais simples é um módulo com um único pacote — tudo na raiz:
projeto/
go.mod
aprovador.go
aprovador_test.go
Isso é suficiente para um script pequeno. Mas o objetivo aqui é um projeto com camadas — domínio, use case, infra — separadas fisicamente. Para isso, o layout cresce para múltiplos pacotes, cada um num subdiretório:
projeto/
go.mod
main.go
usecase/
aprovador.go
postgres/
pedido_repository.go
Repare: usecase e postgres não são "pastas de organização" arbitrárias como você faria em Python ou JS — em Go, cada uma é um pacote próprio, com seu próprio namespace. É a mesma pasta usecase/ da Lição 2, agora no contexto do projeto inteiro.
Importando um pacote pelo caminho do módulo
Para usar um pacote de outro diretório, você importa pelo caminho completo: o nome do módulo (do go.mod) mais o caminho até a pasta.
// dentro de main.go, para usar o pacote em usecase/
import "github.com/allangrds/aprovador-pedidos/usecase"
Isso é o que fisicamente impede a violação da Regra de Dependência: se o pacote usecase nunca escreve import ".../postgres", ele literalmente não tem acesso a nenhum símbolo daquele pacote — não é uma convenção, é o compilador recusando a build. É a mesma lógica de "quem usa a interface a declara" (Lição 2), agora em escala de projeto inteiro.
O diretório especial internal
Go tem uma convenção reforçada pelo próprio compilador: qualquer pacote dentro de uma pasta chamada internal só pode ser importado por código dentro do mesmo módulo, a partir do diretório pai de internal. Nenhum outro módulo consegue importar isso, mesmo que seu repositório seja público.
projeto/
go.mod
main.go
internal/
usecase/
aprovador.go
postgres/
pedido_repository.go
Isso não é uma camada da Clean Architecture — é um mecanismo de visibilidade do Go, ortogonal às camadas. Um projeto real costuma colocar quase tudo dentro de internal/, porque a maioria dos projetos Go não é uma biblioteca pensada para ser importada por terceiros.
O que Go não dita
Um ponto que vale reforçar, porque é fácil confundir depois de ver diagramas de "estrutura de pastas para Clean Architecture em Go" na internet: a equipe do Go não recomenda pastas chamadas domain, usecase, adapter — isso é convenção da comunidade, sobreposta ao mecanismo simples de "um diretório = um pacote" que você acabou de ver. O documento oficial nem menciona essas camadas.
Para deixar concreto o que "crescer organicamente" significa, compare os dois caminhos para o mesmo projetinho pequeno — um serviço de aprovação de pedidos que ainda só tem usecase e postgres:
golang-standards/project-layout (copiado cedo demais)
projeto/
cmd/
aprovador/
main.go
internal/
usecase/
aprovador.go
postgres/
pedido_repository.go
pkg/ (vazio — nada aqui ainda é público)
api/ (vazio — sem spec ainda)
configs/ (vazio)
scripts/ (vazio)
build/ (vazio)
deployments/ (vazio)
test/ (vazio)
docs/ (vazio)
go.dev/doc/modules/layout (crescimento orgânico)
projeto/
go.mod
main.go
internal/
usecase/
aprovador.go
postgres/
pedido_repository.go
Os dois layouts funcionam — nenhuma pasta a mais quebra o compilador. O problema é outro: no primeiro, sete das oito pastas de topo estão vazias no dia 1, e cada uma delas obriga você a decidir algo que o projeto ainda não pediu — "isso é uma API pública? preciso de scripts de build? vou ter deployments distintos?". Isso consome atenção que deveria ir para o código. No segundo, cada pasta só aparece quando um pacote de verdade precisa dela — internal/ apareceu porque você já tem código que não quer exposto, não porque o template mandava. Se um dia o projeto ganhar uma API HTTP documentada, aí sim cria-se uma pasta para isso — quando ela tiver conteúdo, não antes.
Prática guiada: reorganize um projeto com dependência invertida
Raciocínio antes do código
A linha import "github.com/allangrds/aprovador-pedidos/postgres" dentro de usecase/aprovador.go significa que o pacote usecase agora tem acesso físico a todos os símbolos exportados de postgres, incluindo o tipo concreto PedidoRepositorySQL. Isso não é uma violação "conceitual" — é uma violação que aparece literalmente no topo do arquivo, em uma única linha de import. Qualquer mudança na assinatura pública de postgres.PedidoRepositorySQL agora força uma mudança em usecase, mesmo que a regra de negócio não tenha mudado nada.
Uma abordagem plausível, mas incorreta: renomear a pasta, sem tocar no import
projeto/
usecase/
aprovador.go // import ".../infra" ao invés de ".../postgres"
infra/
pedido_repository.go
Por que falha: renomear a pasta não muda a estrutura de dependência — usecase continua importando um pacote de infraestrutura concreta, só que com um nome mais genérico. Isso é um erro comum de quem confunde "nomear bem as pastas" com "aplicar a Regra de Dependência". O compilador continua permitindo que usecase use qualquer tipo concreto de infra diretamente — nada estrutural mudou.
A solução correta: interface em usecase, satisfeita implicitamente por postgres
// usecase/aprovador.go — não importa postgres em lugar nenhum
package usecase
type PedidoRepository interface {
Buscar(id string) (Pedido, error)
Salvar(p Pedido) error
}
type AprovarPedido struct {
repo PedidoRepository // interface, não tipo concreto
}
func NovoAprovarPedido(r PedidoRepository) *AprovarPedido {
return &AprovarPedido{repo: r}
}
// postgres/pedido_repository.go — importa usecase só para referenciar o tipo Pedido,
// nunca para "implementar" formalmente (satisfação implícita cuida disso)
package postgres
import "github.com/allangrds/aprovador-pedidos/usecase"
type PedidoRepositorySQL struct{ db *sql.DB }
func (r *PedidoRepositorySQL) Buscar(id string) (usecase.Pedido, error) { /* ... */ }
func (r *PedidoRepositorySQL) Salvar(p usecase.Pedido) error { /* ... */ }
// main.go — o único lugar que conhece os dois pacotes, e os conecta
package main
func main() {
repo := postgres.NovoRepositorio(db)
aprovador := usecase.NovoAprovarPedido(repo)
// ...
}
main importa usecase e postgres para montar a aplicação. postgres importa usecase apenas para referenciar o tipo Pedido. Não há nenhuma seta de usecase para postgres — essa ausência é o que garante fisicamente a Regra de Dependência.
Por que está correto: usecase/aprovador.go não tem mais nenhuma linha import ".../postgres" — a dependência de código-fonte desapareceu fisicamente. A direção da dependência se inverteu: agora é postgres quem importa usecase (só para referenciar o tipo Pedido), nunca o contrário. main.go é o único lugar que conhece os dois pacotes ao mesmo tempo — ele monta a aplicação, injetando o repositório concreto no Use Case. É a Injeção de Dependência da Lição 1, agora acontecendo no ponto de entrada do programa. Testar usecase.AprovarPedido não exige mais o pacote postgres compilado ou presente — um fake dentro do próprio pacote de teste basta.
Verificação: rode go list -deps ./usecase/... e confirme que postgres não aparece na lista de dependências do pacote usecase. Esse comando lista, de forma objetiva, tudo que um pacote importa direta ou transitivamente — se postgres sumir dessa lista, a extração está correta.
Perguntas de entrevista e de mercado
"Qual a diferença entre módulo e pacote em Go? Muita gente usa os termos como sinônimo." Distinga com precisão: módulo é a unidade de versionamento e distribuição — o projeto inteiro, declarado por um único go.mod na raiz. Pacote é a unidade de organização de código dentro do módulo — cada diretório com arquivos .go é um pacote. Um módulo normalmente contém múltiplos pacotes (um por diretório), mas um pacote nunca atravessa módulos. Cite o exemplo concreto: o módulo é github.com/allangrds/aprovador-pedidos; usecase e postgres, dentro dele, são pacotes distintos.
"Por que times Go costumam usar internal/ de forma tão agressiva, colocando quase todo o código lá dentro?" Explique o mecanismo primeiro (pacotes dentro de internal só são importáveis pelo código do mesmo módulo, a partir do diretório pai) e depois o motivo de mercado: a maioria dos projetos Go não é uma biblioteca pensada para reuso externo — é uma aplicação. Expor pacotes fora de internal cria uma API pública implícita que terceiros podem passar a depender, dificultando mudanças futuras sem quebrar compatibilidade. Colocar tudo em internal por padrão, e só promover algo para fora quando existe uma razão concreta de reuso externo, é a prática defensiva mais comum no ecossistema.
"Você entra num projeto Go novo e vê a estrutura completa do golang-standards/project-layout, com a maioria das pastas vazias. Isso é um sinal de bom ou mau design?" Essa pergunta testa julgamento sobre over-engineering estrutural. Não descarte o layout como "errado" — reconheça que ele pode ser apropriado para projetos grandes e maduros que já têm múltiplas CLIs, API pública documentada e pipelines de deploy distintos. O sinal de alerta é especificamente pastas vazias logo no início do projeto: isso sugere que a estrutura foi copiada como template antes de qualquer necessidade real. Recomende crescimento orgânico: comece simples, adicione uma pasta quando um pacote de verdade precisar dela.
"Como a organização física de pacotes em Go reforça (ou não) a Regra de Dependência da Clean Architecture?" Conecte as duas lições: a Regra de Dependência é uma regra sobre o que o código-fonte pode ou não conhecer. Em Go, "conhecer" se traduz literalmente em "importar" — se o pacote usecase nunca contém a linha import ".../postgres", ele fisicamente não tem acesso a nenhum símbolo daquele pacote, e o compilador barra qualquer tentativa de uso. Isso é diferente de linguagens onde a separação em camadas é só uma convenção de nomenclatura de pastas, sem qualquer imposição do compilador. Mencione que isso não é automático: nada impede alguém de escrever o import errado por engano — o compilador só garante que, se essa violação acontecer, ela fica visível no próprio código-fonte, nunca escondida.
Recupere da memória: 10 questões
Recupere da memória: 10 questões
Por que o pacote usecase nunca importar o pacote postgres garante a Regra de Dependência na prática?
Em Go, qual é a unidade que corresponde a um pacote?
Qual é o papel do arquivo go.mod na raiz de um projeto Go?
O que o diretório especial internal faz em Go?
A documentação oficial do Go recomenda pastas chamadas domain, usecase e adapter?
O repositório golang-standards/project-layout, muito citado em buscas sobre estrutura de projetos Go, é um padrão oficial da equipe do Go?
Qual é o problema prático de copiar um layout de pastas elaborado (cmd/, pkg/, api/, deployments/) antes de o projeto precisar de cada uma dessas pastas?
O que Ben Johnson (criador do BoltDB) argumenta em 'Standard Package Layout', citado nesta aula?
Como um pacote em usecase/ seria importado a partir de main.go, considerando um módulo declarado como module github.com/allangrds/aprovador-pedidos?
Qual comando Go permite verificar, de forma objetiva, se o pacote usecase depende (direta ou transitivamente) do pacote postgres?
Feche o modelo mental
Módulo é o projeto inteiro (go.mod); pacote é cada diretório dentro dele. O caminho de import transforma a Regra de Dependência de uma convenção em uma restrição física: sem a linha de import, não existe acesso possível, e o compilador barra qualquer tentativa. internal/ reforça isso na direção externa, impedindo que outros módulos importem o que você não quer expor. E a estrutura de pastas por camada (domain, usecase, adapter) é convenção da comunidade — útil, mas não imposta pela linguagem, o que significa que você precisa mantê-la por disciplina, não porque o compilador cobra.
Próxima aula planejada: Primeiro projeto Go real — domínio, use case, repositório fake para teste, implementação real e main.go.
Referências primárias para aprofundar
O guia oficial sobre módulos e pacotes está em Go Modules: Package and Module Layout. O contraste de mercado sobre estrutura de pastas vem do próprio README de golang-standards/project-layout e de Standard Package Layout, Ben Johnson, que chega a conclusões convergentes por um caminho independente.
Consulte também o glossário desta trilha.