
Un module regroupe des packages versionnés ; un package regroupe des fichiers d'un même dossier. Ce guide vous apprend à structurer un vrai projet Go : créer un module, comprendre la visibilité par la casse, isoler du code privé avec internal/, et adopter la disposition cmd/ + internal/ des outils sérieux. C'est ce qui transforme un main.go fourre-tout en projet maintenable. Public visé : vous avez déjà écrit quelques programmes Go. Exemples testés sur Go 1.26.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Créer un module avec
go mod initet comprendrego.sum. - Exporter ou masquer un identifiant par la casse.
- Isoler du code privé dans le dossier
internal/. - Structurer un projet avec
cmd/etinternal/.
Un module : go mod init, go.mod, go.sum
Section intitulée « Un module : go mod init, go.mod, go.sum »Un module est l'unité de distribution et de versionnement en Go. On le crée avec go mod init en lui donnant un chemin d'import (souvent l'URL du dépôt).
go mod init exemple/monoutilCette commande crée un fichier go.mod qui déclare le nom du module et la version de Go :
module exemple/monoutil
go 1.26Quand vous ajoutez une dépendance avec go get, Go l'inscrit dans go.mod et enregistre son empreinte cryptographique dans go.sum. Ce second fichier verrouille le contenu exact de chaque dépendance : il garantit que tout le monde compile avec les mêmes octets. Les deux fichiers se versionnent dans Git.
Packages et visibilité : la majuscule exporte
Section intitulée « Packages et visibilité : la majuscule exporte »Chaque dossier de code est un package, nommé par la première ligne package X. La règle de visibilité de Go est d'une simplicité radicale : un identifiant qui commence par une majuscule est exporté (visible des autres packages), une minuscule le garde privé au package.
package greeter
// Bonjour est exportée (majuscule) : utilisable ailleurs.func Bonjour(nom string) string { return "Bonjour, " + nom + " !" }
// secret est privée (minuscule) : invisible hors du package.func secret() string { return "invisible dehors" }Pas de mots-clés public/private : la casse fait tout. C'est concis et cela rend l'API d'un package immédiatement lisible. On expose le strict nécessaire et on garde le reste minuscule.
Le dossier internal : du code vraiment privé
Section intitulée « Le dossier internal : du code vraiment privé »La visibilité par la casse s'arrête au package. Pour empêcher d'autres modules d'importer votre code, Go réserve un nom de dossier spécial : internal/. Un package sous internal/ n'est importable que par le code du même module.
monoutil/├── go.mod├── cmd/│ └── app/│ └── main.go # importe internal/greeter (OK, même module)└── internal/ └── greeter/ └── greeter.gopackage main
import ( "fmt"
"exemple/monoutil/internal/greeter")
func main() { fmt.Println(greeter.Bonjour("Go")) // Bonjour, Go !}Depuis cmd/app, l'import de internal/greeter fonctionne car c'est le même module. Un projet extérieur qui tenterait d'importer exemple/monoutil/internal/greeter serait rejeté à la compilation par l'outillage Go. C'est le moyen de garder une frontière nette entre votre API publique et vos détails d'implémentation.
La disposition cmd/ + internal/
Section intitulée « La disposition cmd/ + internal/ »Cette structure est la convention des outils Go de production. cmd/ contient les points d'entrée (un dossier par binaire), internal/ la logique métier privée.
cmd/<binaire>/main.go: le minimum, juste l'assemblage et l'appel du code interne.internal/<domaine>/: la vraie logique, découpée par responsabilité.
Garder les main.go fins et pousser la logique dans internal/ rend le code testable (on teste les packages internes, pas le main) et permet plusieurs binaires partageant le même cœur.
Exercice : découper un programme en package
Section intitulée « Exercice : découper un programme en package »Un exercice de structure. Réfléchissez à l'organisation avant d'ouvrir la réponse.
Vous avez un main.go unique qui calcule et affiche une salutation. Réorganisez-le en un module propre :
- Créez le module
exemple/monoutilavecgo mod init. - Placez la fonction de salutation dans un package
internal/greeter, avec une fonction exportéeBonjour(nom string) string. - Le point d'entrée va dans
cmd/app/main.goet se contente d'appelergreeter.Bonjour.
Indice : le chemin d'import complet est exemple/monoutil/internal/greeter.
Structure finale et commandes :
monoutil/├── go.mod├── cmd/app/main.go└── internal/greeter/greeter.gogo mod init exemple/monoutilpackage greeter
func Bonjour(nom string) string { return "Bonjour, " + nom + " !"}package main
import ( "fmt"
"exemple/monoutil/internal/greeter")
func main() { fmt.Println(greeter.Bonjour("Go"))}On compile et exécute avec :
go run ./cmd/app# Bonjour, Go !Le main ne fait qu'assembler : toute la logique est dans internal/greeter, testable et réutilisable par un futur second binaire. C'est exactement la structure des CLI Go réelles.
À retenir
Section intitulée « À retenir »- Un module (
go mod init) est l'unité de versionnement ;go.modle déclare,go.sumverrouille les dépendances. - Un package est un dossier de code ; un identifiant exporté commence par une majuscule, sinon il est privé.
- Le dossier
internal/rend un package importable uniquement par le même module. - La disposition
cmd/+internal/garde lesmainfins et la logique métier testable. - On expose le strict nécessaire : une petite API publique, des détails en minuscule et sous
internal/.