Retour au cours

backend / go

Packages et modules (go.mod)

Leçon 161 exercice

Explication

Ce que vous allez apprendre

  • Comprendre la différence entre un module (go.mod) et un package (un dossier)
  • Ajouter et nettoyer des dépendances avec go get et go mod tidy
  • Organiser un projet réel avec internal/, pkg/ et cmd/
  • Contrôler la visibilité d'un identifiant grâce à la casse de son nom
  • Structurer plusieurs binaires dans un seul projet

Dans quel contexte ?

Une équipe backend fait grandir un projet Go qui tenait au départ dans un seul fichier main.go, jusqu'à devenir un service de plusieurs milliers de lignes. Sans convention d'organisation claire, ce fichier devient vite ingérable, et surtout, du code interne à l'entreprise se retrouve accidentellement importable par n'importe quel autre projet externe — un risque que la convention internal/ élimine structurellement.

D'abord, il faut distinguer deux notions souvent confondues

Un module Go correspond à un seul fichier go.mod, qui déclare son nom (souvent une URL de dépôt Git) et la version minimale de Go requise. Un package, lui, correspond simplement à un dossier : tous les fichiers .go d'un même dossier appartiennent obligatoirement au même package, déclaré en première ligne de chaque fichier.

Un module peut contenir de nombreux packages, mais un projet Go classique n'a qu'un seul go.mod à sa racine.

Une fois cette distinction claire, une convention de dossiers standard émerge

L'écosystème Go a convergé vers une organisation assez uniforme, reconnaissable d'un projet à l'autre.

DossierRôleVisibilité
internal/Code privé à l'organisationBloqué par le compilateur en dehors du module parent
pkg/Code réutilisable, pensé pour être importé ailleursPublic
cmd/Points d'entrée (un dossier par binaire)Chacun compile son propre exécutable

Prérequis

Il faut être à l'aise avec go mod init et la compilation (go build), vues dans la toute première leçon de ce cours.

Il reste une règle de visibilité étonnamment simple à retenir

Go n'a pas de mot-clé public/private : la visibilité d'une fonction, d'un type ou d'une variable dépend uniquement de la casse de la première lettre de son nom. Charger() (majuscule) est exportée, visible depuis n'importe quel autre package qui importe celui-ci. getEnv() (minuscule) reste strictement privée à son propre package.

Piège fréquent

Renommer accidentellement une fonction publique en minuscule (ou l'inverse) casse silencieusement la compilation de tous les packages qui l'importaient, avec un message d'erreur qui peut sembler déroutant au premier abord ("undefined: config.charger"). Vérifie toujours la casse en priorité face à une erreur d'import inattendue.

Ensuite, le dossier internal/ bénéficie d'une protection unique dans Go

Contrairement à pkg/ qui n'est qu'une convention de nommage sans effet technique, internal/ est reconnu spécifiquement par le compilateur Go : aucun package en dehors du module qui le contient (ou d'un sous-arbre précis) ne peut l'importer, même s'il le voulait. C'est une garantie d'encapsulation au niveau du langage, pas seulement une bonne pratique.

Bonne pratique

Exécute go mod tidy régulièrement, surtout après avoir ajouté ou retiré un import : cette commande synchronise go.sum avec les dépendances réellement utilisées dans le code, en retirant celles devenues inutiles et en complétant celles qui manquent.

Maintenant que ton projet est bien structuré, il devient indispensable de vérifier automatiquement que chaque package fonctionne comme prévu : place aux tests, avec le pattern le plus idiomatique de Go pour ça, les table-driven tests.

Commandes & code

Packages et modules (go.mod)

Organiser un projet Go réel : modules, packages internes, dépendances versionnées.

bash
# go.mod : declare le module racine et ses dependances
go mod init github.com/monorg/monprojet

# Ajouter une dependance externe
go get github.com/gin-gonic/gin@v1.9.1

# Nettoyer les dependances inutilisees et completer le go.sum
go mod tidy

# Voir l'arbre des dependances
go mod graph
bash
monprojet/
  go.mod
  go.sum
  main.go
  internal/          <- inaccessible depuis l'exterieur du module (convention Go)
    config/
      config.go
    service/
      user_service.go
  pkg/                <- code reutilisable, expose publiquement
    validator/
      validator.go
  cmd/                <- points d'entree (plusieurs binaires possibles)
    api/
      main.go
    worker/
      main.go
go
// fichier: internal/config/config.go
package config // le nom du package = nom du dossier, par convention

import (
	"os"
)

type Config struct {
	Port        string
	DatabaseURL string
}

// Charger : exportee (Majuscule) = visible hors du package
func Charger() Config {
	return Config{
		Port:        getEnv("PORT", "8080"),
		DatabaseURL: getEnv("DATABASE_URL", "postgres://localhost/app"),
	}
}

// getEnv : non exportee (minuscule) = privee au package
func getEnv(cle, defaut string) string {
	if v := os.Getenv(cle); v != "" {
		return v
	}
	return defaut
}
go
// fichier: main.go
package main

import (
	"fmt"

	"github.com/monorg/monprojet/internal/config"
)

func main() {
	cfg := config.Charger()
	fmt.Println("demarrage sur le port", cfg.Port)
}

Résumé

  • Un module = un go.mod ; un package = un dossier, tous les fichiers y déclarent package xxx.
  • La visibilité se fait par la casse : Exporte (public) vs nonExporte (privé au package).
  • internal/ est réservé par le compilateur : inaccessible depuis l'extérieur du module.
  • go mod tidy maintient go.sum cohérent avec les imports réellement utilisés.

Exercices pratiques

1 disponible
1

Mission : un partenaire externe importe du code censé rester interne

Objectif : Restructurer un projet Go monolithique pour protéger son code interne, corriger un import cassé par la casse, et distinguer module et package.

Contexte

L'équipe plateforme découvre qu'une startup partenaire importe directement github.com/monorg/monprojet/service dans son propre projet Go, un package qui contient la logique métier de facturation censée rester strictement interne à l'entreprise. Rien dans le code actuel ne l'empêche techniquement.

Résoudre l’exercice →