Qu'est-ce qu'un monorepo et pourquoi en a-t-on besoin en Go ?
Un monorepo (monorepository) est une approche de gestion de code où tous les projets, bibliothèques et services sont stockés dans un seul dépôt. Dans l'écosystème Go, cette approche devient particulièrement populaire grâce au support intégré des modules et des workspaces.
Pour les développeurs débutants, un monorepo offre plusieurs avantages clés :
- Version unique du code — toutes les dépendances sont synchronisées
- Refactorisation simplifiée — les modifications sont immédiatement visibles dans tous les projets
- Standardisation — paramètres identiques pour les linters, CI/CD et tests
- Transparence — il est facile de comprendre comment les différentes parties du système sont liées
Dans cet article, nous allons voir comment organiser correctement un monorepo en Go, en commençant par la structure de base et en terminant par des exemples pratiques.
Structure de base d'un monorepo en Go
Une bonne organisation du code est la base d'un monorepo réussi. Examinons une structure typique :
my-monorepo/├── apps/│ ├── web-api/ # Application principale│ │ ├── main.go│ │ ├── go.mod│ │ └── internal/│ └── worker/ # Gestionnaire en arrière-plan│ ├── main.go│ └── go.mod├── libs/│ ├── config/ # Configuration commune│ │ ├── config.go│ │ └── go.mod│ └── logger/ # Journalisation│ ├── logger.go│ └── go.mod├── scripts/ # Scripts auxiliaires├── docs/ # Documentation├── go.work # Go workspace└── README.mdPrincipes clés de cette structure :
- apps/ — applications exécutables (binaires)
- libs/ — bibliothèques réutilisables
- scripts/ — automatisation et utilitaires
- docs/ — documentation du projet
Configuration du Go Workspace pour un monorepo
Depuis Go 1.18, le support officiel des workspaces est disponible via le fichier go.work. Cela permet de travailler avec plusieurs modules simultanément sans avoir à les publier dans un dépôt distant.
Créons un workspace pour notre monorepo :
// go.workgo 1.22
use ( ./apps/web-api ./apps/worker ./libs/config ./libs/logger)Après la création de go.work, tous les modules de la liste use seront accessibles localement les uns aux autres. C'est particulièrement pratique lors du développement — les modifications dans une bibliothèque sont immédiatement visibles dans les applications.
Initialisation des modules
Pour chaque composant, il faut créer son propre go.mod. Exemple pour la bibliothèque de configuration :
// libs/config/go.modmodule github.com/your-org/libs/config
go 1.22Et pour l'application web-api :
// apps/web-api/go.modmodule github.com/your-org/apps/web-api
go 1.22
require github.com/your-org/libs/config v0.0.0
replace github.com/your-org/libs/config => ../../libs/configNotez le replace — il indique à Go d'utiliser la version locale de la bibliothèque, plutôt que de la chercher sur Internet.
Exemple de code : bibliothèque et application
Créons une bibliothèque simple pour la configuration et une application qui l'utilise.
Bibliothèque libs/config
// libs/config/config.gopackage config
import ( "fmt" "os")
type Config struct { Port string Env string}
func Load() (*Config, error) { port := os.Getenv("PORT") if port == "" { port = "8080" }
env := os.Getenv("ENV") if env == "" { env = "development" }
return &Config{ Port: port, Env: env, }, nil}
func (c *Config) String() string { return fmt.Sprintf("Config{