Go et l'architecture Hexagonale : écrire du code qui n'a pas peur d'être modifié

Formateur Python en ligne pour débutants

Apprenez Python facilement sans surcharge théorique. Résolvez des tâches pratiques avec vérification automatique et écrivez du code directement dans le navigateur.

Start Course

Découvrez : votre code isolé du monde extérieur

\n\n

Avez-vous déjà réécrit une logique métier simplement parce que vous avez changé de base de données, passant de PostgreSQL à MongoDB ? Ou vous êtes-vous réveillé en sueur froide à l'idée de devoir ajouter un nouveau point de terminaison HTTP, alors que toute l'application n'est qu'un gigantesque main.go de 3000 lignes ?

\n\n

Si oui — bienvenue au club. Si non — soit vous codez en Go depuis peu, soit vous utilisez déjà l'architecture Hexagonale (également appelée Ports & Adaptateurs).

\n\n

Dans cet article, nous allons voir comment construire une application en Go qui n'a pas peur du changement. Ni le changement de base de données, ni le passage de REST à gRPC, ni un nouveau framework ne vous obligeront à réécrire le cœur du système. Que du code. Que du Go. Sans blabla.

\n\n

Qu'est-ce que l'architecture Hexagonale en termes simples

\n\n

Imaginez que votre application est un vaisseau spatial. Au centre se trouve la cabine de pilotage (la logique métier). Autour, des ports d'amarrage. Via ces ports, vous connectez différents modules : les moteurs (base de données), les capteurs (requêtes HTTP), la communication avec la Terre (files de messages).

\n\n

Si vous décidez de changer les moteurs — vous déconnectez simplement les anciens et en connectez de nouveaux. La cabine de pilotage ne remarquera même pas la substitution. C'est cela, l'architecture Hexagonale.

\n\n

Concepts de base :

\n
    \n
  • Port — une interface qui définit comment le monde extérieur peut interagir avec le cœur. Par exemple, UserRepository avec les méthodes Save et FindByID.
  • \n
  • Adaptateur — une implémentation concrète du port. Un port peut avoir plusieurs adaptateurs : PostgreSQLAdapter, InMemoryAdapter, MockAdapter pour les tests.
  • \n
  • Cœur (Core) — la logique métier qui ne connaît rien du monde extérieur. Elle fonctionne uniquement via les ports.
  • \n
\n\n

Cette approche offre trois super-pouvoirs : la testabilité (du cœur sans base de données), la flexibilité (remplacement de n'importe quel composant) et des limites de responsabilité claires.

\n\n

Pourquoi Go est idéal pour l'architecture Hexagonale

\n\n

Go n'impose pas la POO, mais offre des interfaces — la seule chose nécessaire pour Ports & Adaptateurs. Les interfaces en Go sont implicites : une structure satisfait automatiquement une interface si elle implémente ses méthodes. Cela signifie que vous pouvez remplacer un adaptateur sans changer une seule ligne dans le cœur.

\n\n

De plus, Go se compile en binaire, ce qui rend l'architecture particulièrement utile pour les microservices. Et les tests intégrés ainsi que les benchmarks permettent de vérifier immédiatement l'isolation des composants.

\n\n

Structure du projet : comment organiser les dossiers

\n\n

Il n'existe pas de standard unique, mais voici un schéma éprouvé utilisé dans les projets de production :

\n\n
myapp/\n├── cmd/\n│   └── app/\n│       └── main.go          # Point d'entrée, assemblage des dépendances\n├── internal/\n│   ├── core/\n│   │   ├── domain/          # Entités, value objects\n│   │   ├── ports/           # Interfaces (ports)\n│   │   └── services/        # Logique métier (cas d'utilisation)\n│   └── adapters/\n│       ├── api/             # Adaptateurs HTTP/gRPC\n│       ├── db/              # Implémentations des repositories\n│       └── mq/              # Files de messages\n└── go.mod\n
\n\n

Important : le dossier internal interdit les importations depuis l'extérieur — idéal pour protéger le cœur. Les adaptateurs sont séparés et peuvent être facilement remplacés.

\n\n

Écrire le code : des interfaces à l'implémentation

\n\n

Créons un service simple pour gérer les utilisateurs. Commençons par le cœur.

\n\n

Étape 1. Domaine et ports

\n\n
// internal/core/domain/user.go\npackage domain\n\nimport "time"\n\ntype User struct {\n    ID        string\n    Email     string\n    Name      string\n    CreatedAt time.Time\n}\n\n// internal/core/ports/repositories.go\npackage ports\n\nimport "myapp/internal/core/domain"\n\ntype UserRepository interface {\n    Save(user domain.User) error\n    FindByID(id string) (domain.User, error)\n    FindByEmail(email string) (domain.User, error)\                

Blogs

Book Recommendations