Commencez avec un seul fichier
Un programme Go peut tenir entièrement dans un seul main.go, et les petits outils devraient s'en contenter. Ce programme complet analyse une entrée, fait son travail et affiche un rapport, dans un seul package :
Quand il grandit, découpez-le en plusieurs fichiers dans le même répertoire et le même package (parse.go, report.go, main.go). Les fichiers d'un même package partagent tous leurs identifiants, donc rien n'a besoin d'être exporté ni importé. C'est souvent toute la structure dont a besoin un projet de quelques milliers de lignes.
N'oubliez pas qu'un package main à plusieurs fichiers doit être lancé comme un package : go run ., pas go run main.go. Sinon les fichiers sont compilés seuls et vous obtenez des erreurs undefined pour les noms définis dans les autres fichiers.
Il n'existe pas d'organisation imposée
Go n'exige aucune structure de répertoires au-delà de « un package par répertoire ». La recommandation officielle est la page Organizing a Go module sur go.dev, et elle décrit quelques formes, pas des règles.
Le dépôt GitHub golang-standards/project-layout est largement copié et souvent pris à tort pour un standard. C'est une collection communautaire de conventions issues de gros projets, et Russ Cox, alors responsable technique de Go, y a ouvert une issue pour préciser que ce n'est pas un standard de Go. La plupart de ses répertoires (pkg/, api/, build/, deployments/) n'ont de sens que pour de grosses bases de code. Copier tout le squelette dans un nouveau projet crée des dossiers vides et des chemins d'import profonds sans aucun bénéfice.
Les règles imposées par l'outil sont courtes :
- Un répertoire est un package. Tous les fichiers
.goqu'il contient (sauf les fichiers_test.goqui utilisent le suffixe_test) partagent un nom de package. - Le chemin d'import est le chemin du module plus le chemin du répertoire.
- Un répertoire nommé
internalrestreint qui peut importer ce qu'il contient. - Les répertoires nommés
testdata, et ceux qui commencent par.ou_, sont ignorés par l'outil go.
Les formes courantes
Une seule commande
todo/
├── go.mod module github.com/you/todo
├── main.go
├── parse.go
├── report.go
└── parse_test.go
À plat, tout dans package main. go install github.com/you/todo@latest fonctionne et le binaire s'appelle todo.
Une bibliothèque
slug/
├── go.mod module github.com/you/slug
├── slug.go package slug
├── slug_test.go
├── example_test.go
└── internal/
└── table/ helpers the public package uses, hidden from users
Le package se trouve à la racine du module, donc les utilisateurs importent github.com/you/slug et appellent slug.Make(...). Tout ce que vous ne voulez pas maintenir comme API publique va sous internal/.
Un service ou plusieurs binaires
shop/
├── go.mod module github.com/you/shop
├── cmd/
│ ├── shop-api/
│ │ └── main.go package main: flags, config, wiring
│ └── shop-worker/
│ └── main.go
├── internal/
│ ├── order/ package order: domain types and logic
│ │ ├── order.go
│ │ └── order_test.go
│ ├── store/ package store: database access
│ └── httpapi/ package httpapi: handlers, routing
├── migrations/
└── README.md
cmd/<name>/main.go contient un répertoire par binaire, et le nom du répertoire devient le nom du binaire (go build ./cmd/shop-api produit shop-api). Chaque main reste mince : lire la configuration, construire les dépendances, démarrer le serveur. Le vrai code vit dans des packages sous internal/, où les deux binaires peuvent l'utiliser et aucun autre module ne le peut.
C'est l'organisation vers laquelle convergent la plupart des services Go. Adoptez-la quand vous avez un deuxième binaire ou une vraie raison de découper des packages, pas dès le premier jour.
internal/ est imposé par la commande go
Un package sous internal/ ne peut être importé que par du code de l'arborescence enracinée sous le parent de internal. Depuis un autre module, le build échoue :
package example.com/b
main.go:6:2: use of internal package example.com/a/internal/secret not allowed
Cela fait de internal/ l'outil pour garder votre surface d'API réduite. Le code qui s'y trouve peut changer librement, parce que vous savez que tous les appelants sont dans votre propre dépôt. Pour une application, mettre presque tout dans internal/ est raisonnable. Pour une bibliothèque, cela sépare ce que vous promettez de garder stable de ce que vous ne promettez pas.
internal fonctionne à n'importe quelle profondeur : shop/internal/order est visible par tout shop/, tandis que shop/internal/order/internal/pricing n'est visible qu'à l'intérieur de shop/internal/order/.
Nommer les packages
Le nom du package fait partie de chaque site d'appel, il compte donc plus que l'arborescence des répertoires.
- Court, en minuscules, un seul mot :
order,store,httpapi. Pas d'underscores ni de mixedCaps. - Nommez-le d'après ce qu'il fournit, pas d'après ce qu'il contient.
util,common,helpers,miscetmodelsne disent rien et deviennent des fourre-tout ; les recommandations de style de l'équipe Go les déconseillent explicitement. Mettez une fonction utilitaire dans le package qui l'utilise, ou dans un package nommé d'après son rôle (slug,retry). - Évitez les répétitions. Les appelants écrivent
order.Orderetorder.New, donc ne nommez pas les chosesorder.OrderServiceouorder.NewOrder. La bibliothèque standard le fait de façon cohérente :http.Server, pashttp.HTTPServer. - Le nom du répertoire et celui du package doivent correspondre, sauf pour
package maindans les répertoires de commandes. Quand ils diffèrent, les lecteurs doivent ouvrir un fichier pour savoir quel identifiant l'import introduit.
Découpez les packages par responsabilité, pas par nature. Un découpage models/, controllers/, services/ (courant dans d'autres écosystèmes) oblige chaque fonctionnalité à toucher trois packages et a tendance à créer des cycles d'import, que Go interdit. Des packages organisés autour d'un concept métier (order, payment, user) gardent chacun leurs types et leur logique ensemble.
Où vont les tests
Les tests vivent à côté du code, dans le même répertoire, dans des fichiers nommés *_test.go. Il n'y a pas d'arborescence tests/.
package orderdansorder_test.go: un test interne, qui peut utiliser les identifiants non exportés.package order_testdans le même répertoire : un test externe, qui ne voit que l'API exportée, comme un appelant. Utile pour les exemples et pour éviter les cycles d'import dans les tests.- Les fichiers de fixtures vont dans un répertoire
testdata/à côté des tests. L'outil go l'ignore, et les tests s'exécutent avec le répertoire du package comme répertoire de travail, donc des chemins relatifs commetestdata/big.jsonfonctionnent.
Les autres fichiers à la racine
| Fichier ou répertoire | Rôle |
|---|---|
go.mod, go.sum | définition du module et sommes de contrôle des dépendances, toujours à la racine |
README.md, LICENSE | comme dans tout projet |
Makefile ou Taskfile.yml | raccourcis de build facultatifs |
Dockerfile | généralement à la racine pour les services |
migrations/, web/, docs/ | ressources non Go, nommées d'après ce qu'elles contiennent |
tools.go | ancienne façon d'épingler les dépendances d'outils ; Go 1.24 la remplace par des lignes tool dans go.mod (go get -tool) |
go.work | un workspace pour développer plusieurs modules ensemble en local ; en général non committé pour les projets à un seul module |
Erreurs courantes
- Copier un gros modèle pour un petit projet. Commencez à plat et ajoutez des répertoires quand un deuxième binaire ou une vraie frontière apparaît.
- Des packages nommés
utilsoucommon. Nommez les packages d'après ce qu'ils font. - Un package par fichier ou par type. Les packages Go sont censés être des unités plus grandes que les classes Java. Un package de dix fichiers est normal.
- Des cycles d'import. Deux packages ne peuvent pas s'importer mutuellement. Cela signifie généralement qu'ils vont ensemble, qu'un type partagé doit descendre dans un package de plus bas niveau, ou qu'un côté devrait dépendre d'une petite interface au lieu de l'autre package.
pkg/par réflexe. Il ajoute un segment à chaque chemin d'import sans ajouter de sens.- Des tests dans un répertoire séparé. Ils ne peuvent pas y voir le code non exporté et l'outillage ne s'attend pas à les y trouver.
Questions fréquentes
Existe-t-il une organisation officielle des projets Go ?
Pas une organisation obligatoire. L'équipe Go publie des recommandations sur go.dev/doc/modules/layout, qui décrivent quelques formes courantes (un seul package, une commande, plusieurs commandes avec internal/). Le dépôt populaire golang-standards/project-layout est un projet communautaire, pas un standard de Go, et l'équipe Go l'a dit publiquement.
Qu'est-ce que le répertoire internal en Go ?
Un package dont le chemin d'import contient un élément internal ne peut être importé que par du code enraciné sous le parent de ce répertoire internal. example.com/app/internal/store peut être importé n'importe où sous example.com/app/, et la commande go refuse les imports depuis tout autre module avec use of internal package ... not allowed.
Faut-il utiliser un répertoire pkg en Go ?
C'est facultatif et cela ajoute un segment de chemin sans ajouter de sens. Certains gros projets utilisent pkg/ pour séparer le code de bibliothèque public du reste, mais la bibliothèque standard de Go et la plupart des projets modernes ne le font pas. Placez les packages importables à la racine du module ou dans des répertoires nommés, et tout ce qui est privé dans internal/.
Où vont les fichiers de test dans un projet Go ?
Dans le même répertoire que le code qu'ils testent, sous des noms *_test.go. Go n'a pas d'arborescence de tests séparée. Les fichiers de fixtures vont dans un répertoire testdata à côté des tests, que l'outil go ignore quand il cherche des packages.