Skip to main content


Organização de projetos em Go​


Essa foi uma das primeiras coisas que eu quis aprender em Go, como organizar os projetos e módulos, começar a aprender algo sem entender a estrutura princiapal é uma das coisas que sempre me deixa com aquela coceira na atrás da orelha.


Mas eu entendo que nem sempre precisamos começar pelo meio, mesmo que eu ache que qualquer livro ou documentação de Go devesse começar por aqui. Indo contra minhas vontades, deixei esse "módulo" mais para o meio da parte de introdução, espero que você não fique incomodado, assim como eu fiquei.


Em Go, a organização é feita principalmente com uso de packages e modules, que separam as responsabilidades e servem para reaproveitar código em diferentes partes do programa. É parecido com uma função, mas, em vez de executar uma tarefa específica, um package agrupa várias funcionalidades que podemos usar em outros lugares, fazemos o uso deles via import (desde que estejam exportadas, ou seja, que seus nomes comecem com letra maiúscula).


Neste capítulo, vamos estudar como os projetos Go são organizados, começando por packages e imports.



O que é um package?​


Um package é uma forma de agrupar arquivos Go que fazem parte de uma mesma responsabilidade. Em vez de colocar todas as funções e estruturas em um único arquivo, podemos distribuir o código em diferentes arquivos e packages.


Por exemplo, imagine um programa responsável por cadastrar usuários. Inicialmente, poderíamos colocar tudo no main.go.

package main

import "fmt"

type Usuario struct {
Nome string
Idade int
}

func cadastrarUsuario(nome string, idade int) Usuario {
return Usuario{
Nome: nome,
Idade: idade,
}
}

func main() {
usuario := cadastrarUsuario("Ana", 20)

fmt.Println(usuario)
}

Esse código funciona, mas imagine que o programa comece a ter funções para cadastrar, atualizar, remover e consultar usuários. Com o tempo, o arquivo pode ficar grande e difícil de manter.


Uma alternativa é colocar as funcionalidades relacionadas aos usuários em um package próprio. Antes de fazer isso, precisamos entender como os packages funcionam.

A declaração package​


Todo arquivo Go começa com uma declaração de package (desconsiderando comentários que possam aparecer antes), como em:

package main

Essa declaração identifica a qual package o arquivo pertence. O main é um package especial, usado para criar programas executáveis. Quando um programa Go é executado, a função main() do package main representa o ponto de entrada da aplicação.

package main

import "fmt"

func main() {
fmt.Println("Programa iniciado")
}

Aqui temos duas coisas diferentes:

  • package main: identifica que esse código pertence a um programa executável.

  • func main(): define a função que será executada quando o programa iniciar.


Os nomes são iguais, mas representam conceitos diferentes. Também podemos criar packages com outros nomes.

package usuarios

package produtos

package calculadora

Esses packages normalmente são usados para organizar funcionalidades que serão chamadas por outras partes do programa.



Como os packages se relacionam com os arquivos?​


Em Go, um package normalmente é formado por um ou mais arquivos .go que ficam no mesmo diretório, exemplo:

projeto/
│
├── main.go
├── usuario.go
└── validacao.go

Os três arquivos podem pertencer ao mesmo package, nesse caso, chamado main. Observe os códigos abaixo e perceba que todos usam package main, isso significa que fazem parte do mesmo package.

// main.go
package main

import "fmt"

func main() {
usuario := criarUsuario("Ana")

fmt.Println(usuario)
}

// usuario.go

package main

// Cria uma representação simples de usuário.
func criarUsuario(nome string) string {
return nome
}

// validacao.go

package main

// Verifica se um nome não está vazio.
func validarNome(nome string) bool {
return nome != ""
}

Por esse motivo, a função main() consegue acessar criarUsuario() diretamente, sem precisar importar outro package. Embora estejam em arquivos diferentes, as funções pertencem ao mesmo package e podem acessar umas às outras, outra vantagem é que não precisamos declarar as funções em uma ordem específica entre os arquivos.


Mas precisamos ter cuidado ao executar um arquivo em específico com go run main.go. O Go considerará somente o arquivo informado, então a função criarUsuario() não estará disponível. Para executar os arquivos do package, podemos usar:

go run .

Esse comando vai executar o package presente no diretório atual, considerando seus arquivos Go aplicáveis à compilação. Para esse exemplo, vamos considerar que o projeto já possui um módulo Go configurado, algo que veremos no próximo tópico.



Criando um package separado​


Agora vamos organizar nosso exemplo em dois diretórios.

projeto/
│
├── main.go
│
└── usuarios/
└── usuario.go

Nesse caso, teremos dois packages.

projeto/
│
├── main.go package main
│
└── usuarios/
└── usuario.go package usuarios

Essa é uma das forma mais comuns de organizar projetos em Go no dia a dia, principalmente para projetos pequenos. Aqui, o diretório usuarios será responsável pelas funcionalidades relacionadas aos usuários.


Imagine que implementamos uma struct e uma função dentro do package usuarios (dentro do arqivo usuario.go). Para que possamos usar as funções e tipos do package usuarios em outros packages, precisamos que seus nomes comecem com letra maiúscula, isso mostra ao Go que eles devem ser exportados. Por exemplo:

Usuario
CriarUsuario

Isso permite que essas funcionalidades sejam acessadas de outros packages por meio do import.

O nome do package continua em minúsculo (usuarios). A regra da letra maiúscula se aplica aos identificadores que queremos exportar, como funções, tipos, variáveis e campos de structs.



Entendendo o import​


O import permite importar funcionalidades de outros packages. Já usamos esse recurso em vários exemplos aqui, vamos importar o package fmt:

import "fmt"

O fmt é um package da biblioteca padrão do Go que oferece recursos para formatação de textos e entrada e saída de dados, exemplo:

fmt.Println("Olá")

O fmt.Println é uma função exportada pelo package fmt. Podemos entender essa estrutura assim:

fmt.Println
│ │
│ └── Função exportada
│
└── Package

Quando precisarmos importar vários packages, podemos agrupar eles:

import (
"fmt"
"strings"
)


Módulos com go mod​


Até aqui, entendemos que os packages permitem organizar funções, structs e outras funcionalidades em diferentes arquivos e diretórios.


Quando começamos a separar o código em vários packages, precisamos de uma forma de identificar o projeto como um todo e permitir que esses packages sejam importados corretamente, é aqui que entram os modules.


Um module é uma forma de organizar um projeto que pode conter um ou vários packages. Ele define um caminho que identifica o projeto e permite que o Go gerencie suas dependências.


Enquanto um package agrupa funcionalidades relacionadas, um module agrupa packages que fazem parte do mesmo projeto, exemplo:

projeto/                       ← Module
│
├── main.go ← Package main
│
├── usuarios/
│ └── usuario.go ← Package usuarios
│
└── produtos/
└── produto.go ← Package produtos

Nesse exemplo, temos um projeto com três packages, todos pertencentes ao mesmo module. O module também permite usar packages externos, como bibliotecas criadas por outros desenvolvedores, mantendo o controle das dependências e suas versões.


Uma dependência é um módulo externo que nosso projeto precisa para funcionar, exemplo, podemos usar uma biblioteca para conectar a um banco de dados, sem precisar desenvolver toda essa funcionalidade.


Os modules ajudam a identificar de qual projeto um ou vários packages pertencem. Além disso, um projeto pode usar bibliotecas externas, que precisam ser baixadas e mantidas em versões compatíveis. Os modules resolvem essas necessidades, permitindo que o Go identifique o projeto, localize seus packages e gerencie as dependências utilizadas.


Para isso, o Go usa principalmente o arquivo go.mod, que fica na raiz do módulo.



Entendendo o go.mod​


O go.mod é um arquivo que identifica o module e registra informações necessárias para gerenciar suas dependências, exemplo:

module exemplo.com/projeto

go 1.25

Aqui temos duas informações.

  • module exemplo.com/projeto: define o caminho que identifica nosso módulo, esse caminho será usado como base para importar os packages que pertencem ao projeto.

  • go 1.25: indica a versão mínima da linguagem Go exigida pelo módulo e influencia o comportamento das ferramentas do Go.


O endereço exemplo.com/projeto é apenas um identificador usado neste exemplo. O arquivo go.mod também pode conter informações sobre dependências externas conforme elas forem adicionadas ao projeto.



Criando um módulo​


Não precisamos criar o arquivo go.mod manualmente, para isso, podemos usar o comando go mod init, que inicializa um novo módulo.


Primeiro, vamos criar um diretório para nosso projeto:

mkdir projeto
cd projeto

Depois, executamos:

go mod init exemplo.com/projeto

Isso deve ser feito sempre, independente se vamos ter um ou vários packages.


O Go criará um arquivo go.mod dentro do diretório, nossa estrutura deve ficar assim:

projeto/
│
└── go.mod

O nome informado no comando será usado como caminho do módulo.

go mod init exemplo.com/projeto

Nesse caso, escolhemos exemplo.com/projeto, mas poderíamos usar outro caminho adequado ao projeto. Em projetos publicados no GitHub, é comum usar caminhos como:

github.com/usuario/projeto

Isso permite que outros projetos identifiquem e importem os packages publicados nesse módulo.



Importando packages do próprio módulo​


Agora que entendemos o que é um module, podemos voltar ao exemplo do package usuarios. Vamos usar esta estrutura:

projeto/
│
├── go.mod
├── main.go
│
└── usuarios/
└── usuario.go

O arquivo go.mod identifica nosso módulo:

module exemplo.com/projeto

go 1.25

Dentro do diretório usuarios, criaremos um package responsável pelas funcionalidades relacionadas aos usuários:

// usuarios/usuario.go

package usuarios

// Usuario representa os dados de um usuário.
type Usuario struct {
Nome string
Idade int
}

// CriarUsuario cria um usuário com os dados recebidos.
func CriarUsuario(nome string, idade int) Usuario {
return Usuario{
Nome: nome,
Idade: idade,
}
}

Agora podemos usar esse package dentro do main.go:

// main.go

package main

import (
"fmt"

"exemplo.com/projeto/usuarios"
)

func main() {
// Chama a função exportada do package usuarios.
usuario := usuarios.CriarUsuario("Ana", 20)

fmt.Println(usuario.Nome)
fmt.Println(usuario.Idade)
}

Para executar o programa, podemos usar:

go run .

Observe que o caminho é formado pelo nome do módulo (definido no go.mod), seguido do diretório que contém o package:

exemplo.com/projeto/usuarios
└────────┬────────┘└───┬───┘
│ │
Nome do módulo Diretório
do package

Assim, o Go consegue identificar que estamos usando o package usuarios, que pertence ao nosso próprio módulo. Depois de importar o package, conseguimos acessar suas funcionalidades exportadas.



Usando módulos de outros desenvolvedores​


Além de organizar os packages do projeto, os modules permitem gerenciar dependências externas. Quando usamos bibliotecas de outros desenvolvedores, o Go precisa identificar quais módulos são necessários e quais versões devem ser usadas.


Isso é parecido com o Python, quando usamos o pip para instalar uma biblioteca e depois o import para usar ela no nosso código.


No Python seria assim:

pip install requests

Depois importamos no nosso código:

import requests

Em Go, o processo é meio parecido, mas usamos o comando go get para adicionar uma dependência ao nosso projeto.



Adicionando uma dependência com go get​


Vamos usar como exemplo a biblioteca github.com/google/uuid, que permite gerar identificadores únicos (conhecidos como UUIDs). Primeiro, precisamos estar dentro de um projeto que já tenha um módulo inicializado.

projeto/
│
├── go.mod
└── main.go

Depois, executamos:

go get github.com/google/uuid

Esse comando adiciona a biblioteca como dependência do nosso módulo, baixa os arquivos necessários e atualiza o go.mod. O arquivo passará a registrar a dependência, com a versão selecionada pelo Go.

module exemplo.com/projeto

go 1.25

require github.com/google/uuid v1.6.0

Nesse exemplo, v1.6.0 representa a versão da biblioteca baixada. O Go também pode criar ou atualizar o arquivo go.sum, que registra hashes usados para verificar a integridade dos módulos e conteúdos baixados.



Importando a biblioteca​


Depois de adicionar a dependência, podemos importar o package no nosso código, assim como fazemos com os packages da biblioteca padrão.

// main.go

package main

import (
"fmt"

"github.com/google/uuid"
)

func main() {
// Gera um identificador UUID aleatório.
id := uuid.NewString()

fmt.Println(id)
}

A função uuid.NewString() gera um UUID aleatório e retorna seu valor como string. Observe que usamos o caminho completo para importar o package: import "github.com/google/uuid".



Gerenciando dependências com go mod tidy​


Também podemos adicionar dependências sem executar diretamente o go get. Por exemplo, podemos escrever o import no código:

import "github.com/google/uuid"

Depois, executamos:

go mod tidy

O Go analisa os imports do projeto, identifica os módulos necessários e ajusta os arquivos de dependências. Esse comando pode:

  • Adicionar dependências necessárias que ainda não estão registradas.
  • Remover dependências que não são mais necessárias.
  • Atualizar o arquivo go.sum com informações de verificação das dependências (quando necessário).

Portanto, podemos usar go get quando queremos adicionar ou atualizar uma dependência específica, enquanto o go mod tidy mantém as dependências registradas de acordo com o código usado.



Utilizando uma versão específica​


Também podemos escolher qual versão de uma biblioteca queremos usar:

go get github.com/google/uuid@v1.6.0

Nesse caso, estamos solicitando a versão v1.6.0. Se quisermos solicitar a versão mais recente disponível, podemos usar:

go get github.com/google/uuid@latest

O Go registra a versão selecionada no go.mod, permitindo manter o controle das dependências utilizadas pelo projeto.



Removendo uma dependência​


Imagine que adicionamos uma biblioteca externa ao projeto e depois removemos todas as partes do código que ela usava. Para remover uma dependência que não é mais necessária, basta remover seus imports e executar:

go mod tidy

O Go analisa novamente os packages utilizados e remove do go.mod as dependências que não são mais necessárias. Isso não significa que os arquivos baixados serão apagados do computador, porque o Go mantém um cache de módulos que pode ser reutilizado por outros projetos.



Onde encontrar bibliotecas para Go?​


Podemos encontrar packages e consultar suas documentações no site oficial, nele, conseguimos pesquisar bibliotecas, consultar suas funções, verificar os caminhos de importação e encontrar exemplos de utilização.


É parecido com o PyPI do Python, mas o pkg.go.dev funciona principalmente como um catálogo de packages e suas documentações.