Introduction : Pourquoi l'architecture n'est pas un luxe, mais une nécessité
\n\nLorsque 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 ?
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\nDans 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\n1. Repository Pattern : séparer la logique métier de la base de données
\n\nLe 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\nPourquoi 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
Exemple d'implémentation
\nSupposons que nous ayons un modèle utilisateur :
\n\ntype User struct {\n ID int64\n Name string\n Email string\n}\n\nNous définissons l'interface du repository :
\n\ntype 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\nEt son implémentation pour PostgreSQL :
\n\ntype 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\nDé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.
2. Service Layer : la logique métier sous contrôle
\n\nSi 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\nLe problème sans Service Layer
\nJe 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\nComment y remédier ?
\nIntroduisons une couche de service :
\n\ntype 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