Structure de projet Go : Pratiques et Modèles

Structurez vos projets Go pour une évolutivité et une clarté optimales

Sommaire

Structurer efficacement un projet Go est fondamental pour la maintenabilité à long terme, la collaboration en équipe et l’évolutivité. Contrairement aux frameworks qui imposent des arborescences de répertoires rigides, Go privilégie la flexibilité — mais avec cette liberté vient la responsabilité de choisir des modèles qui répondent aux besoins spécifiques de votre projet.

project tree

Comprendre la philosophie de Go sur la structure des projets

La philosophie de design minimaliste de Go s’étend à l’organisation des projets. Le langage n’impose pas de structure spécifique, faisant confiance aux développeurs pour prendre des décisions éclairées. Cette approche a conduit la communauté à développer plusieurs modèles éprouvés, allant de dispositions plates simples pour les petits projets à des architectures sophistiquées pour les systèmes d’entreprise.

Le principe clé est la simplicité d’abord, la complexité si nécessaire. De nombreux développeurs tombent dans le piège de l’over-engineering de leur structure initiale, créant des répertoires profondément imbriqués et des abstractions prématurées. Commencez par ce dont vous avez besoin aujourd’hui, et refactorisez à mesure que votre projet évolue.

Quand utiliser le répertoire internal/ par rapport à pkg/ ?

Le répertoire internal/ sert un but spécifique dans la conception de Go : il contient des packages qui ne peuvent pas être importés par des projets externes. Le compilateur Go impose cette restriction, rendant internal/ parfait pour la logique d’application privée, les règles métier et les utilitaires non destinés à être réutilisés en dehors de votre projet.

Le répertoire pkg/, en revanche, indique que le code est destiné à une consommation externe. N’y placez du code que si vous construisez une bibliothèque ou des composants réutilisables que vous souhaitez que d’autres importent. De nombreux projets n’ont pas besoin de pkg/ du tout — si votre code n’est pas consommé externes, le garder à la racine ou dans internal/ est plus propre. Lors de la création de bibliothèques réutilisables, envisagez d’utiliser les généériques Go pour créer des composants réutilisables et sûrs de type.

La mise en page standard d’un projet Go

Le modèle le plus largement reconnu est golang-standards/project-layout, bien qu’il soit important de noter qu’il ne s’agit pas d’une norme officielle. Voici à quoi ressemble une structure typique :

myproject/
├── cmd/
│   ├── api/
│   │   └── main.go
│   └── worker/
│       └── main.go
├── internal/
│   ├── auth/
│   ├── storage/
│   └── transport/
├── pkg/
│   ├── logger/
│   └── crypto/
├── api/
│   └── openapi.yaml
├── config/
│   └── config.yaml
├── scripts/
│   └── deploy.sh
├── go.mod
├── go.sum
└── README.md

De nombreuses équipes conservent un petit package de journalisation partagé sous internal/ (par exemple internal/logx) afin que les binaires dans cmd/ connectent un journal au démarrage ; pkg/logger/ dans le croquis ci-dessus n’est approprié que lorsque ce code est destiné à être importé par d’autres modules. Pour une configuration log/slog orientée production (lignes JSON, redaction, champs de contexte et de traçage, et signaux qui fonctionnent bien avec les piles de surveillance), consultez Journalisation structurée en Go avec slog pour l’observabilité et l’alerte.

Le répertoire cmd/

Le répertoire cmd/ contient les points d’entrée de votre application. Chaque sous-répertoire représente un binaire exécutable séparé. Par exemple, cmd/api/main.go compile votre serveur API, tandis que cmd/worker/main.go pourrait compiler un processeur de tâches en arrière-plan.

Meilleure pratique : Gardez vos fichiers main.go minimaux — juste assez pour connecter les dépendances, charger la configuration et démarrer l’application. Toute la logique substantielle appartient aux packages que main.go importe.

// cmd/api/main.go
package main

import (
    "log"
    "myproject/internal/server"
    "myproject/internal/config"
)

func main() {
    cfg, err := config.Load()
    if err != nil {
        log.Fatal(err)
    }
    
    srv := server.New(cfg)
    if err := srv.Start(); err != nil {
        log.Fatal(err)
    }
}

Le répertoire internal/

C’est là que réside votre code d’application privé. Le compilateur Go empêche tout projet externe d’importer des packages situés dans internal/, ce qui le rend idéal pour :

  • Logique métier et modèles de domaine
  • Services d’application
  • API et interfaces internes
  • Repositaires de base de données (pour choisir le bon ORM, consultez notre comparaison des ORM Go pour PostgreSQL)
  • Logique d’authentification et d’autorisation

Organisez internal/ par fonctionnalité ou domaine, et non par couche technique. Au lieu de internal/handlers/, internal/services/, internal/repositories/, privilégiez internal/user/, internal/order/, internal/payment/ où chaque package contient ses gestionnaires, services et repositaires.

Dois-je commencer par une structure de répertoires complexe ?

Absolument pas. Si vous construisez un petit outil ou un prototype, commencez par :

myproject/
├── main.go
├── go.mod
└── go.sum

À mesure que votre projet se développe et que vous identifiez des regroupements logiques, introduisez des répertoires. Vous pourriez ajouter un package db/ lorsque la logique de base de données devient substantielle, ou un package api/ lorsque les gestionnaires HTTP se multiplient. Laissez la structure émerger naturellement plutôt que de l’imposer dès le départ.

Structures plates vs imbriquées : trouver l’équilibre

L’une des erreurs les plus courantes dans la structure des projets Go est l’imbrication excessive. Go favorise les hiérarchies peu profondes — généralement une ou deux niveaux de profondeur. Une imbrication profonde augmente la charge cognitive et rend les imports fastidieux.

Quelles sont les erreurs les plus courantes ?

Imbrication excessive des répertoires : Évitez les structures comme internal/services/user/handlers/http/v1/. Cela crée une complexité de navigation inutile. Utilisez plutôt internal/user/handler.go.

Noms de packages génériques : Des noms comme utils, helpers, common ou base sont des odeurs de code. Ils ne transmettent pas de fonctionnalité spécifique et deviennent souvent des dépotoirs pour du code non lié. Utilisez des noms descriptifs : validator, auth, storage, cache.

Dépendances circulaires : Lorsque le package A importe le package B, et que B importe A, vous avez une dépendance circulaire — une erreur de compilation en Go. Cela signale généralement une mauvaise séparation des préoccupations. Introduisez des interfaces ou extrayez des types partagés dans un package séparé.

Mélange des préoccupations : Gardez les gestionnaires HTTP concentrés sur les préoccupations HTTP, les repositaires de base de données sur l’accès aux données et la logique métier dans les packages de services. Placer les règles métier dans les gestionnaires rend les tests difficiles et couple votre logique de domaine à HTTP.

Conception orientée domaine et architecture hexagonale

Pour les applications plus importantes, en particulier les microservices, la Conception Orientée Domaine (DDD) avec l’Architecture Hexagonale fournit une séparation claire des préoccupations.

Comment structurer un microservice suivant la conception orientée domaine ?

L’Architecture Hexagonale organise le code en couches concentriques avec des dépendances qui coulent vers l’intérieur :

internal/
├── domain/
│   └── user/
│       ├── entity.go        # Modèles de domaine et objets valeur
│       ├── repository.go    # Interface du repositaire (port)
│       └── service.go       # Services de domaine
├── application/
│   └── user/
│       ├── create_user.go   # Cas d'utilisation : créer un utilisateur
│       ├── get_user.go      # Cas d'utilisation : récupérer un utilisateur
│       └── service.go       # Orchestration du service d'application
├── adapter/
│   ├── http/
│   │   └── user_handler.go  # Adaptateur HTTP (points de terminaison REST)
│   ├── postgres/
│   │   └── user_repo.go     # Adaptateur de base de données (implémente le port du repositaire)
│   └── redis/
│       └── cache.go         # Adaptateur de cache
└── api/
    └── http/
        └── router.go        # Configuration des routes et middleware

Couche Domaine (domain/) : Logique métier centrale, entités, objets valeur et interfaces de service de domaine. Cette couche n’a aucune dépendance aux systèmes externes — pas d’importations HTTP, pas de base de données. Elle définit les interfaces de repositaire (ports) que les adaptateurs implémentent.

Couche Application (application/) : Cas d’utilisation qui orchestrent les objets de domaine. Chaque cas d’utilisation (par exemple, “créer un utilisateur”, “traiter un paiement”) est un fichier ou un package séparé. Cette couche coordonne les objets de domaine mais ne contient aucune règle métier elle-même.

Couche Adaptateur (adapter/) : Implémente les interfaces définies par les couches internes. Les gestionnaires HTTP convertissent les requêtes en objets de domaine, les repositaires de base de données implémentent la persistance, les files d’attente de messages gèrent la communication asynchrone. Cette couche contient tout le code spécifique aux frameworks et à l’infrastructure.

Couche API (api/) : Routes, middleware, DTO (objets de transfert de données), versionnement API et spécifications OpenAPI.

Cette structure assure que votre logique métier centrale reste testable et indépendante des frameworks, des bases de données ou des services externes. Vous pouvez remplacer PostgreSQL par MongoDB, ou REST par gRPC, sans toucher au code du domaine. Si vous adoptez CQRS dans cette mise en page, Implémentation de CQRS en Go montre comment la couche application/ se mappe naturellement à des packages de gestionnaires de commandes et de requêtes séparés, gardant le côté commande strict et le côté requête orienté DTO.

Modèles pratiques pour différents types de projets

Petit outil CLI

Pour les applications en ligne de commande, vous voudrez une structure qui prend en charge plusieurs commandes et sous-commandes. Envisagez d’utiliser des frameworks comme Cobra pour la structure des commandes et Viper pour la gestion de la configuration. Notre guide sur la construction d’applications CLI en Go avec Cobra & Viper couvre ce modèle en détail.

mytool/
├── main.go
├── command/
│   ├── root.go
│   └── version.go
├── go.mod
└── README.md

Service REST API

Lors de la construction d’APIs REST en Go, cette structure sépare clairement les préoccupations : les gestionnaires gèrent les préoccupations HTTP, les services contiennent la logique métier et les repositaires gèrent l’accès aux données. Pour un guide complet couvrant les approches de la bibliothèque standard, les frameworks, l’authentification, les modèles de test et les meilleures pratiques prêtes pour la production, consultez notre guide complet pour construire des APIs REST en Go. Pour la journalisation JSON structurée, la corrélation des requêtes et des traces, et des formes de journaux qui prennent en charge l’alerte, consultez Journalisation structurée en Go avec slog pour l’observabilité et l’alerte.

myapi/
├── cmd/
│   └── api/
│       └── main.go
├── internal/
│   ├── config/
│   ├── middleware/
│   ├── user/
│   │   ├── handler.go
│   │   ├── service.go
│   │   └── repository.go
│   └── product/
│       ├── handler.go
│       ├── service.go
│       └── repository.go
├── pkg/
│   └── httputil/
├── go.mod
└── README.md

Monorepo avec plusieurs services

myproject/
├── cmd/
│   ├── api/
│   ├── worker/
│   └── scheduler/
├── internal/
│   ├── shared/        # Packages internes partagés
│   ├── api/          # Code spécifique à l'API
│   ├── worker/       # Code spécifique au worker
│   └── scheduler/    # Code spécifique au planificateur
├── pkg/              # Bibliothèques partagées
├── go.work           # Fichier d'espace de travail Go
└── README.md

Tests et documentation

Placez les fichiers de test à côté du code qu’ils testent en utilisant le suffixe _test.go :

internal/
└── user/
    ├── service.go
    ├── service_test.go
    ├── repository.go
    └── repository_test.go

Cette convention garde les tests proches de l’implémentation, les rendant faciles à trouver et à maintenir. Pour les tests d’intégration qui touchent plusieurs packages, créez un répertoire test/ séparé à la racine du projet. Pour des conseils complets sur l’écriture de tests unitaires efficaces, y compris les tests pilotés par table, les mocks, l’analyse de couverture et les meilleures pratiques, consultez notre guide sur la structure et les meilleures pratiques des tests unitaires Go.

La documentation appartient à :

  • README.md : Aperçu du projet, instructions de configuration, utilisation de base
  • docs/ : Documentation détaillée, décisions architecturales, références API
  • api/ : Spécifications OpenAPI/Swagger, définitions protobuf

Pour les APIs REST, la génération et la fourniture de la documentation OpenAPI avec Swagger sont essentielles pour la découvrabilité de l’API et l’expérience développeur. Notre guide sur l’ajout de Swagger à votre API Go couvre l’intégration avec les frameworks populaires et les meilleures pratiques.

Gestion des dépendances avec Go Modules

Chaque projet Go devrait utiliser Go Modules pour la gestion des dépendances. Pour une référence complète sur les commandes Go et la gestion des modules, consultez notre Cheat Sheet Go. Initialisez avec :

go mod init github.com/yourusername/myproject

Cela crée go.mod (dépendances et versions) et go.sum (sommes de contrôle pour la vérification). Gardez ces fichiers sous contrôle de version pour des builds reproductibles.

Mettez à jour les dépendances régulièrement :

go get -u ./...          # Mettre à jour toutes les dépendances
go mod tidy              # Supprimer les dépendances inutilisées
go mod verify            # Vérifier les sommes de contrôle

Points clés à retenir

  1. Commencez simple, évoluez naturellement : Ne sur-ingéniez pas votre structure initiale. Ajoutez des répertoires et des packages lorsque la complexité l’exige.

  2. Privilégiez les hiérarchies plates : Limitez l’imbrication à un ou deux niveaux. La structure de packages plate de Go améliore la lisibilité.

  3. Utilisez des noms de packages descriptifs : Évitez les noms génériques comme utils. Nommez les packages d’après ce qu’ils font : auth, storage, validator.

  4. Séparez clairement les préoccupations : Gardez les gestionnaires concentrés sur HTTP, les repositaires sur l’accès aux données et la logique métier dans les packages de services.

  5. Exploitez internal/ pour la confidentialité : Utilisez-le pour le code qui ne devrait pas être importé externes. La plupart du code d’application appartient ici.

  6. Appliquez les modèles d’architecture lorsque nécessaire : Pour les systèmes complexes, l’Architecture Hexagonale et le DDD fournissent des limites claires et la testabilité.

  7. Laissez Go vous guider : Suivez les idiomes Go plutôt que d’importer des modèles d’autres langages. Go a sa propre philosophie sur la simplicité et l’organisation.

Liens utiles

Autres Articles Connexes

S'abonner

Recevez de nouveaux articles sur les systèmes, l'infrastructure et l'ingénierie IA.