Découvrez : votre code isolé du monde extérieur
\n\nAvez-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 ?
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\nDans 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\nQu'est-ce que l'architecture Hexagonale en termes simples
\n\nImaginez 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\nSi 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\nConcepts de base :
\n- \n
- Port — une interface qui définit comment le monde extérieur peut interagir avec le cœur. Par exemple,
UserRepositoryavec les méthodesSaveetFindByID. \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
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\nPourquoi Go est idéal pour l'architecture Hexagonale
\n\nGo 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\nDe 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\nStructure du projet : comment organiser les dossiers
\n\nIl n'existe pas de standard unique, mais voici un schéma éprouvé utilisé dans les projets de production :
\n\nmyapp/\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\nImportant : 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.
Écrire le code : des interfaces à l'implémentation
\n\nCré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)\