Modèles architecturaux en Go : comment ne pas se noyer dans les goroutines

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

Introduction : Pourquoi l'architecture n'est pas un luxe, mais une nécessité

\n\n

Lorsque vous commencez tout juste à coder en Go, tout semble simple : les goroutines volent, les canaux bourdonnent, et le compilateur ravit par sa rapidité. Mais un mois ou deux passent, et votre pet-project se transforme en monstre avec un package main de 3000 lignes et des prières « pourvu que ça ne plante pas ». Ça vous dit quelque chose ?

\n\n

Go offre des outils puissants, mais n'impose pas de cadres rigides. C'est à la fois un avantage et un inconvénient. Sans une architecture claire, vous vous perdrez rapidement dans vos propres goroutines. C'est là que les modèles architecturaux en Go viennent à la rescousse — des solutions éprouvées pour les problèmes de conception typiques.

\n\n

Dans cet article, nous allons analyser quatre modèles clés qui simplifieront vraiment la vie : Repository, Service Layer, Worker Pool et Pipeline. Vous verrez des exemples de code, comprendrez quand appliquer chaque modèle, et pourrez les intégrer immédiatement dans vos projets. C'est parti !

\n\n

1. Repository Pattern : séparer la logique métier de la base de données

\n\n

Le premier modèle que tout développeur Go devrait maîtriser est le Repository. Son essence est simple : créer une couche intermédiaire entre la logique métier et le stockage de données (BDD, fichiers, API externes).

\n\n

Pourquoi est-ce nécessaire ?

\n
    \n
  • Testabilité : on peut facilement remplacer la vraie BDD par un mock.
  • \n
  • Flexibilité : migration de PostgreSQL vers MongoDB ? On change uniquement le repository.
  • \n
  • Propreté du code : le handler ne sait pas d'où viennent les données — c'est la tâche du repository.
  • \n
\n\n

Exemple d'implémentation

\n

Supposons que nous ayons un modèle utilisateur :

\n\n
type User struct {\n    ID    int64\n    Name  string\n    Email string\n}
\n\n

Nous définissons l'interface du repository :

\n\n
type UserRepository interface {\n    GetByID(ctx context.Context, id int64) (*User, error)\n    Create(ctx context.Context, user *User) error\n    Update(ctx context.Context, user *User) error\n    Delete(ctx context.Context, id int64) error\n}
\n\n

Et son implémentation pour PostgreSQL :

\n\n
type PostgresUserRepo struct {\n    db *sql.DB\n}\n\nfunc (r *PostgresUserRepo) GetByID(ctx context.Context, id int64) (*User, error) {\n    user := &User{}\n    err := r.db.QueryRowContext(ctx, "SELECT id, name, email FROM users WHERE id = $1", id).\n        Scan(&user.ID, &user.Name, &user.Email)\n    if err != nil {\n        return nil, fmt.Errorf("get user by id: %w", err)\n    }\n    return user, nil\n}
\n\n

Désormais, votre handler ou service travaille uniquement avec l'interface UserRepository, et non avec une BDD spécifique. C'est le modèle architectural Repository en Go en action.

\n\n

2. Service Layer : la logique métier sous contrôle

\n\n

Si le Repository s'occupe des données, le Service Layer s'occupe des règles métier. Ce modèle regroupe des opérations disparates en actions cohérentes.

\n\n

Le problème sans Service Layer

\n

Je vois souvent du code où le handler fait tout : valider la requête, vérifier les droits, écrire dans la BDD et journaliser. Cela viole le principe de responsabilité unique (SRP).

\n\n

Comment y remédier ?

\n

Introduisons une couche de service :

\n\n
type UserService interface {\n    Register(ctx context.Context, name, email string) (*User, error)\n    GetProfile(ctx context.Context, userID int64) (*User, error)\n}\n\ntype userService struct {\n    repo UserRepository\n    mail MailSender\n}\n\nfunc (s *userService) Register(ctx context.Context, name, email string) (*User, error) {\n    // 1. Validation\n    if name == "" || email == "" {\n        return nil, ErrInvalidInput\n    }\n\n    // 2. Logique métier : vérifier si l'email n'est pas déjà pris\n    existing, _ := s.repo.GetByEmail(ctx, email)\n    if existing != nil {\n        return nil, ErrEmailAlreadyExists\n    }\n\n    // 3. Création de l'utilisateur\n    user := &User{Name: name, Email: e                

Blogs

Book Recommendations