🔧 Dev

OffreThe Augmented Engineering Programdès 2 500 € / mois

Tech Lead & équipe · ou MVP livré en 3 mois + recrutement

Découvrir

Comment bien architecturer son projet Golang en hexagonal ?

Jean-Sébastien Techer23/06/2025
#Golang#Architecture#Hexagonal#Clean Code#Backend

Comment bien architecturer son projet Golang en hexagonal ?

Quand on démarre un projet en Go, il est tentant de tout mettre dans un seul main.go ou de multiplier les packages sans réelle cohérence.
Le problème ? On finit vite avec une application rigide, difficile à tester et à faire évoluer.

C’est là qu’entre en jeu l’architecture hexagonale (aussi appelée ports & adapters).
Elle permet de séparer le cœur métier des interfaces techniques (API, DB, CLI…), et d’obtenir un code clair, testable et maintenable.


🧩 Les 3 couches principales

  1. Domain (Core)

    • Contient la logique métier pure.
    • Aucun import vers des frameworks ou librairies externes.
    • Exemples : entités, règles métier, interfaces (ports).
  2. Application (Use Cases)

    • Orchestration des cas d’usage.
    • Exemples : CreateUser, ProcessOrder.
  3. Infrastructure (Adapters)

    • Implémentations concrètes : HTTP, DB, cache, queues…
    • Exemples : PostgresUserRepository, GinHTTPHandler.

📂 Exemple de structure projet

/project
├── cmd/
│   └── app/          # point d’entrée main.go
├── internal/
│   ├── domain/       # entités + ports
│   │   └── user.go
│   ├── app/          # use cases
│   │   └── user_service.go
│   └── infra/        # implémentations techniques
│       ├── http/     # handlers REST
│       └── db/       # repository SQL
└── pkg/              # libs partagées

👨‍💻 Exemple de code

1. Domain — entité et port

// internal/domain/user.go
package domain

type User struct {
    ID    string
    Name  string
    Email string
}

// Port : contrat pour persistance
type UserRepository interface {
    Save(user *User) error
    FindByEmail(email string) (*User, error)
}

2. Application — use case

// internal/app/user_service.go
package app

import "project/internal/domain"

type UserService struct {
    repo domain.UserRepository
}

func NewUserService(r domain.UserRepository) *UserService {
    return &UserService{repo: r}
}

func (s *UserService) RegisterUser(name, email string) (*domain.User, error) {
    user := &domain.User{
        ID:    "uuid-generated", // simplifié
        Name:  name,
        Email: email,
    }
    if err := s.repo.Save(user); err != nil {
        return nil, err
    }
    return user, nil
}

3. Infrastructure — adapter DB (ex. in-memory)

// internal/infra/db/user_repo.go
package db

import "project/internal/domain"

type InMemoryUserRepo struct {
    users map[string]*domain.User
}

func NewInMemoryUserRepo() *InMemoryUserRepo {
    return &InMemoryUserRepo{users: make(map[string]*domain.User)}
}

func (r *InMemoryUserRepo) Save(user *domain.User) error {
    r.users[user.Email] = user
    return nil
}

func (r *InMemoryUserRepo) FindByEmail(email string) (*domain.User, error) {
    if u, ok := r.users[email]; ok {
        return u, nil
    }
    return nil, nil
}

4. Infrastructure — adapter HTTP (ex. avec Gin)

// internal/infra/http/user_handler.go
package http

import (
    "net/http"
    "project/internal/app"
    "github.com/gin-gonic/gin"
)

type UserHandler struct {
    service *app.UserService
}

func NewUserHandler(r *gin.Engine, s *app.UserService) {
    handler := &UserHandler{service: s}
    r.POST("/users", handler.Register)
}

func (h *UserHandler) Register(c *gin.Context) {
    var req struct {
        Name  string `json:"name"`
        Email string `json:"email"`
    }
    if err := c.ShouldBindJSON(&req); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }
    user, err := h.service.RegisterUser(req.Name, req.Email)
    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
        return
    }
    c.JSON(http.StatusOK, user)
}

5. Entrypoint — main

// cmd/app/main.go
package main

import (
    "project/internal/app"
    "project/internal/infra/db"
    "project/internal/infra/http"

    "github.com/gin-gonic/gin"
)

func main() {
    repo := db.NewInMemoryUserRepo()
    service := app.NewUserService(repo)

    r := gin.Default()
    http.NewUserHandler(r, service)

    r.Run(":8080")
}

✅ Avantages de cette approche

Testabilité : je peux tester UserService avec un repo in-memory, sans DB réelle.

Évolutivité : remplacer InMemoryUserRepo par PostgresUserRepo ne touche pas la logique métier.

Clarté : chaque couche a une responsabilité unique.

🚀 Conclusion

L’architecture hexagonale en Go n’est pas un dogme, mais un cadre pratique pour séparer votre métier de la technique. Avec cette approche, vous obtenez un projet plus clair, maintenable et prêt à évoluer.

🤝 Besoin d’aide ?

J’accompagne les équipes dans la mise en place d’architectures robustes en Go. Si vous voulez poser des bases solides ou refactorer un projet existant, je peux vous guider.

👉 Contactez-moi et gagnons ensemble du temps sur vos futurs développements Golang.

Respect de votre vie privée

Nous utilisons des cookies pour améliorer votre expérience, analyser le trafic et personnaliser le contenu. Vous pouvez choisir quels cookies accepter.