📖 Documentation : https://edwinalkins.github.io/fastapi-hexagonal/ — le
cours complet, la section infrastructure et la carte du dépôt, publiés depuis
docs/.
Dépôt full-stack conçu pour servir de référence : une application réelle (gestionnaire de tâches) illustrant une architecture hexagonale (ports & adapters), du DDD tactique, des pratiques de Software Craftsmanship, et un déploiement local (Docker Compose) puis Kubernetes (Kind).
Le métier est volontairement simple. L’intérêt n’est pas le produit, mais comment le code, les tests, les contrats d’architecture et l’infra sont organisés pour rester reproductibles sur un autre projet.
Le cœur métier ne dépend d’aucun détail technique (BDD, framework web, JSON). Les détails se branchent sur le cœur, jamais l’inverse.
| Sujet | Où le voir |
|---|---|
| Règle de dépendance (hexagonale) | backend/src/task_manager/ + cours |
| DDD tactique (VO, entités, agrégats, 1→n) | contextes task / user + cours |
| Où placer une règle (le chapitre central) | cours |
| Use cases, ports, DTO neutres | application/ + cours |
| Adaptateurs (ORM, HTTP, worker, CLI) | infrastructure/, presentation/ |
| Craftsmanship (lint, types, imports, tests, hooks) | backend/Makefile, pyproject.toml, .pre-commit-config.yaml |
| Stack locale reproductible | docker-compose.yml |
| Déploiement K8s (Kind, CNPG, opérateurs, HPA, NetworkPolicy) | k8s/ |
Le cours progressif (15 chapitres) vit dans
backend/docs/cours/. C’est le fil à suivre
si l’objectif est de reproduire l’exercice ailleurs.
┌─────────────┐ HTTP ┌──────────────┐ ports ┌─────────────┐
│ Frontend │ ───────────► │ API FastAPI │ ─────────────► │ Use cases │
│ Preact SPA │ /api/v1 │ Gunicorn │ │ + domaine │
└─────────────┘ └──────┬───────┘ └──────┬──────┘
│ │
┌──────────────┼──────────────┐ │
▼ ▼ ▼ ▼
PgBouncer Valkey RabbitMQ Worker
│ (cache) (événements) (e-mails)
▼
PostgreSQL
Deux bounded contexts : user et task, liés par une relation 1→n
(Task référence User par identité, pas par objet). Le partage d’une tâche
publie un événement RabbitMQ ; un worker envoie l’e-mail (Mailpit en local).
.
├── backend/ # API, worker, CLI — architecture hexagonale
│ ├── src/task_manager/
│ │ ├── domain/ # cœur pur (entités, VO, ports, exceptions)
│ │ ├── application/ # use cases + ports transverses (cache, smtp, bus)
│ │ ├── infrastructure/ # adaptateurs driven (SQLAlchemy, Valkey, RabbitMQ…)
│ │ ├── presentation/ # adaptateurs driving (API, worker, CLI)
│ │ └── main.py
│ ├── tests/ # unitaires (purs) + intégration (testcontainers)
│ ├── alembic/ # migrations (schéma versionné)
│ ├── docs/cours/ # cours d’architecture
│ └── README.md # prise en main backend
├── frontend/ # SPA Preact (Vite, TanStack Query, Tailwind)
├── plugins/ # Agent Skill installable (Claude Code / Codex / Cursor)
│ └── fastapi-hexagonal-ddd/
├── docker-compose.yml # stack locale complète
├── .env.template # secrets / ports (copier en .env)
└── k8s/ # Kind + CloudNativePG + Gateway API + durcissement
presentation → infrastructure → application → domain
(HTTP, worker, CLI) (use cases) (sans framework)
Chaque couche est tranchée par contexte (domain/task/, domain/user/, …).
La règle de dépendance n’est pas un vœu pieux : import-linter la vérifie
sur le graphe d’imports réel (make imports depuis backend/).
Détail des conventions : backend/README.md et
backend/CLAUDE.md.
| Couche | Choix |
|---|---|
| API | FastAPI, Gunicorn + Uvicorn workers, Pydantic v2 |
| Langage | Python 3.14, uv |
| Persistance | SQLAlchemy 2 async, asyncpg, PostgreSQL 17, PgBouncer (mode transaction) |
| Schéma | Alembic (migrate avant api / worker) |
| Cache | Valkey (protocole Redis) |
| Messagerie | RabbitMQ + worker dédié (même image que l’API) |
| Mail (dev) | Mailpit |
| Observabilité | Prometheus (/metrics) |
| Front | Preact (preact/compat), TypeScript, Vite 8, TanStack Query, Tailwind 4 |
| Qualité | ruff, mypy strict, import-linter, pytest, testcontainers, pre-commit |
Prérequis : Docker (Compose).
cp .env.template .env # adapter les secrets
docker compose up --build| Service | URL |
|---|---|
| Application (front) | http://localhost:5173 |
| API + OpenAPI | http://localhost:8000/docs |
| Health / ready | http://localhost:8000/health · /ready |
| Mailpit | http://localhost:8025 |
| RabbitMQ management | http://localhost:15672 |
| RedisInsight | http://localhost:5540 |
| Prometheus | http://localhost:9090 |
Les migrations Alembic tournent dans le service one-shot migrate, en
connexion directe Postgres (pas via PgBouncer : Alembic a besoin d’une session
stable — son verrou consultatif est de session, et certaines opérations comme
CREATE INDEX CONCURRENTLY ne peuvent pas vivre dans une transaction). L’API et
le worker passent ensuite par le pooler.
docker compose up -d postgres pgbouncer valkey rabbitmq mailpit
cp .env.template .env
cd backend
uv sync
uv run alembic upgrade head
make run # → http://localhost:8000/docs
make worker # consumer RabbitMQ (autre terminal)L’API doit déjà tourner (docker compose up -d api ou make run).
cd frontend
npm install
npm run dev # → http://localhost:5173 (proxy Vite → localhost:8000)L’architecture se défend dans la CI locale, pas seulement dans les docs.
Depuis backend/ :
| Commande | Rôle |
|---|---|
make lint |
ruff + import-linter + mypy strict |
make test |
unitaires + intégration |
make test-unit |
domaine / use cases, sans I/O |
make test-integration |
API de bout en bout (Docker / testcontainers) |
make check |
lint + tests (équivalent CI locale) |
make precommit-install |
hooks git (ruff, mypy, contrats d’imports) |
Principes visibles dans le code :
- Domaine pur : pas de FastAPI / SQLAlchemy / Pydantic dans
domain/. - Un use case = une action ; DTO dataclasses, pas de schémas HTTP.
- Identités UUIDv7 générées par le domaine.
- Agrégats liés par ID ; les contrôles cross-agrégat vivent dans l’application.
- Transaction = requête HTTP (les repositories ne commitent pas).
- Exceptions métier → codes HTTP via des bases sémantiques (
NotFound→ 404, etc.). - Composition root : le câblage ports → adaptateurs est isolé dans
presentation/api/dependencies/.
Recette pour démarrer un autre projet : chapitre 13 (y compris quand ne pas utiliser cette architecture).
Une tâche appartient toujours à un utilisateur. Création et liste : routes
imbriquées sous le propriétaire. Mutations d’une tâche : /api/v1/tasks/{id}.
| Méthode | Route | Description |
|---|---|---|
| POST / GET / PATCH | /api/v1/users … |
Utilisateurs (GET by id caché) |
| POST / GET | /api/v1/users/{id}/tasks |
Créer / lister les tâches d’un user |
| GET / PATCH / DELETE | /api/v1/tasks/{id} |
Lire, renommer, supprimer |
| POST | /api/v1/tasks/{id}/start · /complete · /share |
Cycle de vie + partage |
Exemple :
USER=$(curl -s -X POST localhost:8000/api/v1/users \
-H 'content-type: application/json' \
-d '{"name": "Ada Lovelace", "email": "ada@example.com"}' | jq -r .id)
curl -X POST localhost:8000/api/v1/users/$USER/tasks \
-H 'content-type: application/json' \
-d '{"title": "Rédiger le rapport"}'Liste complète : backend/README.md.
Deux niveaux, même règles applicatives (Alembic, PgBouncer en transaction,
prepared_statement_cache_size=0, worker = même image que l’API).
docker-compose.yml : postgres, pgbouncer, valkey, rabbitmq, mailpit,
prometheus, migrate, api, worker, frontend.
Variables : .env.template (préfixe APP_ côté application).
Manifestes dans k8s/ : cluster Kind, Gateway API (Envoy Gateway), CloudNativePG
(Postgres + Pooler), opérateur RabbitMQ, Job de migration, HPA, PDB,
NetworkPolicies, SecurityContext.
./k8s/deploy.shApplication : http://localhost. Consoles : mailpit.localhost,
rabbitmq.localhost, redisinsight.localhost, prometheus.localhost.
Guide pas à pas, durcissement et backups : k8s/README.md.
Correspondance compose → K8s :
section infrastructure.
Ce n’est pas un runbook de production cloud (pas de TLS managé, pas de secret store distant). C’est une cible pédagogique proche d’une prod sérieuse : opérateurs, pooling, migrations one-shot, isolation réseau, autoscaling.
Le savoir de ce dépôt est distribué comme Agent Skill — utilisable par Claude Code, OpenAI Codex et Cursor sur n'importe quel projet, pas seulement celui-ci. Il transmet la méthode (règle de dépendance, placement des règles, frontières d'agrégats, ports, query services, transactions) plutôt que le code.
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-hexagonalCursor
/add-plugin https://github.com/EdwinAlkins/fastapi-hexagonal
Puis installer fastapi-hexagonal-ddd depuis Customize.
Détails et contenu : plugins/fastapi-hexagonal-ddd/.
- Ce README (carte du dépôt).
- Cours — chapitres 00 → 14.
- Code du contexte
user(tranche verticale minimale) puistask(1→n + worker). backend/README.mdpour ajouter un use case.k8s/README.mdpour le déploiement.plugins/fastapi-hexagonal-ddd/pour réutiliser la méthode ailleurs.
Articles / notes annexes (historique du POC) : dossier resources/.
| Objectif | Prérequis |
|---|---|
| Jouer avec l’app | Docker |
| Développer le backend | Python 3.14, uv, Docker (tests d’intégration) |
| Développer le front | Node / npm, API déjà up |
| Déployer sur Kind | docker, kind, kubectl ; ports 80/443 libres |
Ce dépôt est sous licence GNU AGPL v3 (copyright William Nauroy, 2026).
Le code peut être étudié, modifié et redistribué, y compris commercialement, mais toute version modifiée — distribuée ou exposée à des utilisateurs via un réseau (SaaS, API) — doit être publiée sous la même licence, avec son code source rendu accessible à ces utilisateurs.
Ce n’est pas un produit à déployer tel quel : secrets, durcissement et observabilité restent à adapter avant un usage réel.