FastAPI Hexagonal Code source

13. Démarrer un nouveau projet

Les concepts transformés en recette — et le chapitre qui t'autorise à ne pas les appliquer.

TL;DR — Pars du cœur vers l'extérieur. Vérifie la checklist. Et surtout : sache reconnaître les projets où rien de tout ça n'est justifié.

Ce chapitre est volontairement agnostique : les principes valent en Python, Java, C#, TypeScript, Go.

La recette, couche par couche#

Commencer par le cœur est contre-intuitif — on veut souvent partir de la base ou de l'API. C'est justement ce qui garde le domaine pur : tout ce que tu écris avant d'avoir une base de données est, par construction, indépendant d'elle.

Étape 0 — Le langage avant le code#

Avant tout fichier, écris la liste des noms du métier et leur définition : qu'est-ce qu'une « tâche » ? Qu'est-ce que « terminer » veut dire exactement ? Qui a le droit de le faire ? Si tu ne sais pas répondre, aucune architecture ne te sauvera.

C'est aussi ici que tu cherches les bounded contexts — des frontières à l'intérieur desquelles un modèle et son langage restent cohérents (ch. 03). L'indice le plus visible est le vocabulaire : un « utilisateur » au sens authentification (identifiants, sessions, rôles) n'est pas le même concept qu'un « utilisateur » au sens métier (propriétaire de tâches), même s'ils partagent un identifiant. Un mot qui recouvre deux concepts est un signal fort — mais c'est la cohérence du modèle qui tranche, pas le lexique seul.

Étape 1 — Le domaine (aucune dépendance technique)#

  1. Identifie les value objects : concepts définis par leur valeur, avec leurs règles de validité (Email, Money, TaskTitle). Auto-validés à la construction.
  2. Identifie les entités et agrégats : les concepts avec une identité, et surtout les frontières de cohérence transactionnelle (ch. 04). Exprime les invariants en méthodes (order.confirm(), pas order.status = ...).
  3. Écris les exceptions métier, hiérarchisées par sémantique (NotFound, Conflict, Validation).
  4. Déclare les ports de persistance dont le modèle a besoin.

✅ Vérifie : le domaine s'importe-t-il sans aucun framework ? Ses tests unitaires tournent-ils en millisecondes ? Écris-les maintenant, pas plus tard.

Étape 2 — L'application (dépend du domaine seul)#

  1. Un use case par action (CreateOrder, CancelOrder). Il reçoit des ports, orchestre, ne porte pas les invariants.
  2. Des DTO neutres en entrée (commandes) et en sortie.
  3. Les ports techniques dont les cas d'usage ont besoin (cache, notification, publication) — dans application/, pas dans le domaine (ch. 06).

Étape 3 — Les adaptateurs#

  1. Persistance : modèles ORM (distincts des entités), mappers, et l'implémentation concrète des ports. Les repositories ne committent pas et reçoivent leur session.
  2. Entrée : schémas de transport, routers/commandes/consumers qui traduisent, et rien d'autre.

Étape 4 — Le câblage transverse#

  1. La frontière transactionnelle, fournie par chaque adaptateur entrant.
  2. La traduction erreurs métier / erreurs techniques → protocole.
  3. La composition root qui relie ports et adaptateurs.
  4. Les tests d'intégration de bout en bout, sur les moteurs réels.

Étape 5 — Les garde-fous, tout de suite#

  1. Le contrôle automatique de la règle de dépendance (import-linter ou équivalent), en CI dès le premier jour. Ajouté après six mois, il ne passe jamais du premier coup.

Squelette de démarrage#

mkdir -p src/monprojet/domain/{shared,mon_contexte}
mkdir -p src/monprojet/application/{shared,mon_contexte}
mkdir -p src/monprojet/infrastructure/persistence/mon_contexte
mkdir -p src/monprojet/presentation/api/v1/{routers,schemas}
mkdir -p tests/{unit/domain,unit/application,integration}

Dans ce dépôt, le contexte user est la référence complète : domaine, application, persistance, DI, schémas, routers, tests aux trois niveaux. Pour un use case seul, RenameUser est l'exemple minimal (mutation + invalidation de cache).

Checklist de revue#

Avant de dire « c'est propre » :

Les pièges les plus fréquents#

Quand NE PAS utiliser cette architecture#

Elle a un coût réel — plus de fichiers, des mappers, de l'indirection — qui se rentabilise quand le métier est riche et la durée de vie longue. Les signaux qu'elle est surdimensionnée :

Dans ces cas : FastAPI + Pydantic + un ORM à plat suffisent, et sont plus lisibles. Savoir ne pas appliquer un pattern fait partie de sa maîtrise.

Et l'entre-deux ?#

C'est le cas le plus fréquent, et il n'oblige pas à choisir en bloc. Ces éléments se prennent séparément, par ordre de rapport bénéfice/coût :

  1. Des value objects auto-validés — quasiment gratuits, bénéfice immédiat.
  2. Des règles dans les entités plutôt que dans les routers — gratuit aussi.
  3. Des use cases appelables hors HTTP — le jour où un CLI ou un cron arrive.
  4. Des ports et des mappers — le plus coûteux ; à prendre quand la substituabilité ou la testabilité sans I/O devient un vrai besoin.

Beaucoup de projets gagnent énormément aux niveaux 1 et 2, et n'ont jamais besoin du 4.

Pour aller plus loin#

Le mot de la fin#

Ne récite pas les patterns. Ce cours tient en deux questions à te poser en permanence :

Cette décision, à qui appartient-elle ?
Qu'est-ce qui doit être cohérent au sein d'une même transaction ?

Ports, mappers, DTO, composition root ne sont que la machinerie qui rend les réponses exécutables. Si un choix protège l'isolation du cœur métier tout en gardant le code lisible, il est probablement bon — même s'il ne suit aucun pattern nommé. Et s'il suit tous les patterns nommés sans que personne ne sache dire quel problème il résout, il est probablement mauvais.

À toi de jouer#

  1. Un service qui reçoit des webhooks Stripe, les valide et les range en base. Combien de niveaux de la liste « entre-deux » lui appliquerais-tu ?
  2. Pour un e-commerce, propose trois bounded contexts et cite un mot qui n'a pas le même sens dans deux d'entre eux.
  3. Reprends la checklist et applique-la à un projet que tu connais. Quel point échoue en premier, et quel serait le correctif le moins coûteux ?

→ Corrigés