FastAPI Hexagonal
Une référence exécutable, pas un schéma. Un dépôt à lire et à copier pour faire du FastAPI hexagonal comme en vrai, avec les filets de sécurité et le déploiement qui vont avec — pas un CRUD déguisé en slides. Et la même méthode s'installe dans ton assistant de code, Claude Code, Codex ou Cursor, pour l'appliquer sur tes propres projets.
Pourquoi ce dépôt existe#
Dans une application mal structurée, le code métier finit par se dissoudre dans les détails techniques. On ne s'en aperçoit pas en écrivant : on s'en aperçoit en essayant de tester une règle sans lever une base, en changeant d'outil, ou en cherchant six mois plus tard où vit une décision.
Le symptôme n'est pas esthétique. C'est qu'une règle comme « une tâche terminée ne peut pas être terminée deux fois » n'a pas de domicile : elle est réécrite dans un router, oubliée dans un worker, contournée par un script d'import. Ce dépôt donne une adresse à chaque type de décision, et vérifie mécaniquement qu'elles y restent.
La règle est vérifiée, pas affichée#
« Le domaine ne dépend d'aucun framework » est une phrase que tout le monde écrit dans son README. Une règle d'architecture qui n'est pas vérifiée automatiquement se dégrade en quelques mois — alors ici, elle casse le build.
On ajoute un import interdit dans une entité du domaine :
from __future__ import annotations
from datetime import UTC, datetime
+ from sqlalchemy import ColumnLe build casse, avec make imports et les hooks installés :
$ make imports
Analyzed 130 files, 437 dependencies.
Architecture hexagonale (dépendances vers l'intérieur) KEPT
Domaine sans framework BROKEN
…
task_manager.domain is not allowed to import sqlalchemy:
- task_manager.domain.task.entities -> sqlalchemy (l.10)import-linter suit le
graphe d'imports réel, y compris transitif — là où un grep ne
verrait rien. Deux contrats sont déclarés dans pyproject.toml :
l'empilement des couches, et l'interdiction faite au domaine d'atteindre
fastapi, sqlalchemy, pydantic ou
starlette. Tout le reste de ce dépôt — ports, mappers, DTO,
composition root — n'est que la machinerie qui rend cette règle tenable.
La forme que ça donne#
import. À l'exécution, l'application
appelle la base ; à la compilation, c'est la base qui dépend d'une interface
définie plus au centre.Ce qu'on y trouve#
- Une règle, vérifiée
Deux contrats import-linter sur le graphe d'imports réel, dans make lint et les hooks pre-commit. Pas un schéma dans un README.
- Un domaine sans framework
Value objects auto-validés, invariants dans les entités, identités UUIDv7 générées par le domaine. Zéro I/O.
- Trois adaptateurs entrants
API HTTP, CLI et worker RabbitMQ appellent les mêmes use cases. C'est la preuve opérationnelle de l'isolation.
- Erreurs métier ≠ pannes
Une hiérarchie sémantique mappée en 404/409/422, une autre pour les dépendances indisponibles en 503.
- Trois niveaux de tests
Domaine pur, use cases sur ports doublés, intégration sur un PostgreSQL et un Valkey jetables. Pas de SQLite de complaisance.
- Un Agent Skill installable
La même méthode, utilisable par Claude Code, Codex et Cursor sur tes propres projets. Le principe transmis, pas le code à recopier.
- De Compose à Kind
CloudNativePG, opérateur RabbitMQ, NetworkPolicy testées, HPA, securityContext durci. La même rigueur jusqu'à l'infra.
Jusqu'au cluster#
La plupart des dépôts d'architecture s'arrêtent au docker compose up.
Celui-ci va jusqu'à un cluster Kubernetes, parce que c'est là que les décisions
prises dans le code se paient — ou se vérifient.
Le pooler fonctionne en mode transaction : la connexion PostgreSQL peut changer entre deux transactions. Les usages liés à une session demandent donc une attention particulière. Les réglages SQLAlchemy et le Job Alembic en connexion directe sont détaillés dans le chapitre sur les données. Ces contraintes existent déjà avec Compose ; Kubernetes y ajoute le placement et le basculement des instances.
| Brique | Ce qui tourne | Pourquoi c'est là |
|---|---|---|
| Cluster | Kind, 1 control-plane + 3 workers | Reproduire l'anti-affinité et la perte d'un nœud sur un portable |
| PostgreSQL | CloudNativePG, 3 instances + pooler ×2 en mode transaction | Bascule primaire gérée par l'opérateur, pas par un script |
| Broker | RabbitMQ, 3 nœuds via l'opérateur officiel | Le worker d'e-mails consomme une vraie file, avec DLQ |
| Montée en charge | HPA api 2→6, frontend 2→4 · PDB minAvailable: 1 | Limiter les évictions applicatives simultanées |
| Durcissement | runAsNonRoot, readOnlyRootFilesystem, capabilities drop ALL, seccomp | Sur les pods qu'on maîtrise ; les images tierces restent en défaut, et c'est écrit |
| Migrations | Job Alembic contre PostgreSQL en direct, avant l'API | Connexion directe pour maîtriser la session |
Le tout se monte par un script (./k8s/deploy.sh) et se démonte par
un autre. La section infrastructure
détaille chaque brique : les données, les workloads, la résilience, le réseau et
l'exploitation.
Ce que ce n'est pas#
| Ce que tu cherches | Ici | Où regarder sinon |
|---|---|---|
| Comprendre comment on isole un domaine, avec la preuve | Oui | — |
| Un exemple complet de use case à la NetworkPolicy | Oui | — |
| Un support de cours ou un kata d'équipe | Oui | — |
| Une méthode que ton assistant de code applique avec toi | Oui | L'Agent Skill, dans Claude Code, Codex et Cursor — il transmet les décisions, il ne génère pas l'appli à ta place |
| Un starter « clone and ship to prod » | Non | full-stack-fastapi-template |
| Un framework, une bibliothèque, un paquet PyPI | Non | Le code se lit et se copie ; seul l'Agent Skill s'installe |
| La bonne architecture, valable partout | Non | Quand ne pas l'utiliser |
| Un domaine métier riche à étudier | Non | Le métier est un prétexte assumé — c'est discuté ici |
Le métier — un gestionnaire de tâches — est volontairement simple. Il n'est pas le produit : le comment est le produit.
Cette liste de « non » est le point important. Un dépôt qui prétend tout couvrir n'apprend rien à personne, et le chapitre sur les cas où cette architecture est du sur-dimensionnement est écrit avant tous les autres.
Le cours#
Quinze chapitres progressifs, chacun ancré dans le code de ce dépôt. Il n'enseigne pas une arborescence de dossiers : il enseigne deux questions — à qui appartient cette décision ? et qu'est-ce qui doit être cohérent dans la même transaction ?
- 00Le problèmePourquoi cette architecture existe, avant/après
- 01La règle de dépendanceLa seule règle qui compte, et comment la vérifier
- 02Où vit une règle ?L'arbre de décision central du cours
- 03Le domaineDDD stratégique et tactique, value objects, entités, invariants
- 04Les agrégatsFrontière de cohérence, racine, comment la choisir
- 05Relations entre agrégatsRéférence par identité, cardinalités, N+1
- 06Application & portsUse cases, DTO, où placer un port
- 07Les adaptateursORM, mappers, HTTP, CLI, worker
- 08Transactions & erreursFrontière transactionnelle, erreurs métier vs techniques, DI
- 09Lire sans passer par le domaineQuery services, N+1, CQRS léger
- 10Écrire en masseReconstitution, bulk writers, idempotence,
UnitOfWork - 11Cache & événementsCache-aside, événements de domaine vs d'intégration
- 12Organisation & testsTranches verticales, les trois niveaux de tests
- 13Démarrer un projetLa recette, la checklist, et quand ne pas l'appliquer
- 14AnnexesPrincipe vs notre choix, glossaire, corrigés
En complément, un carnet de terrain sur le DDD stratégique : la procédure d'atelier qu'un dépôt de code ne peut pas enseigner, et un diagnostic honnête des frontières de ce projet.
Le cours, utilisable par un agent#
La méthode de ce dépôt est aussi distribuée comme Agent Skill, installable dans Claude Code, OpenAI Codex et Cursor, et utilisable sur n'importe quel projet — pas seulement celui-ci. Il ne dit pas « recopie ce Task Manager » : il aide un agent à décider où placer une règle, où tracer une frontière d'agrégat, quel port créer, quand court-circuiter le domaine en lecture — et, tout aussi important, quand cette architecture est surdimensionnée.
# Claude Code
/plugin marketplace add EdwinAlkins/fastapi-hexagonal
/plugin install fastapi-hexagonal-ddd@fastapi-hexagonal# OpenAI Codex
codex plugin marketplace add EdwinAlkins/fastapi-hexagonal
codex plugin add fastapi-hexagonal-ddd@fastapi-hexagonal# Cursor
/add-plugin https://github.com/EdwinAlkins/fastapi-hexagonalLe SKILL.md reste court : dix fiches de references/
— architecture, DDD stratégique, domaine, application, persistance,
transactions et erreurs, lectures et écritures, événements, tests, structure de
projet — ne sont chargées que lorsque la tâche l'exige. Le périmètre est volontairement
restreint à l'architecture applicative : ni Kubernetes, ni observabilité, ni
frontend, ni CI. Un skill qui essaie de tout faire se déclenche mal.
Le skill est écrit en anglais — le déclenchement est plus fiable côté Claude, Codex et Cursor — alors que le cours reste en français. Il distingue systématiquement le principe, transférable, du choix de ce dépôt (UUIDv7, RabbitMQ, Valkey, PostgreSQL, import-linter, testcontainers) : ces derniers sont présentés comme des options défendables, jamais comme des lois. Toute correction de fond se fait d'abord dans le cours, puis se répercute dans le skill.
Contenu détaillé et développement :
plugins/fastapi-hexagonal-ddd/.
Démarrage#
Python 3.14, uv et un démon Docker. La base et le cache tournent en conteneurs ; l'API tourne en local.
git clone https://github.com/EdwinAlkins/fastapi-hexagonal.git
cd fastapi-hexagonal
cp .env.template .env
docker compose up -d postgres pgbouncer valkey rabbitmq mailpit
docker compose run --rm migrate
cd backend
uv sync
make run # → http://localhost:8000/docsPour lancer la stack entière (API, worker, frontend, RabbitMQ, Mailpit,
Prometheus) : docker compose up --build depuis la racine.
Et pour vérifier que tout tient — lint, contrats d'architecture, typage strict et les trois niveaux de tests :
# Depuis backend/
make checkLicence AGPL-3.0. Tout à lire, et une méthode à emporter.