Cinq vues Mermaid dans docs/architecture (vue d'ensemble, infra, backend, frontend, donnees), plus leur index, les conventions de statut et la regle de maintenance en PR. Reprend les jalons J1-J4, disparus de dev lors de la reecriture du README (2670483) et restes seulement sur main : plus rien sur la branche de travail ne disait ce que le projet doit prouver. Fige les decisions du module Terraform k3s, qui ne vivaient jusqu'ici que dans des commentaires de code et des description de variables : version epinglee obligatoire, Traefik desactive, kubeconfig en 600/root, state local. Corrige trois affirmations devenues fausses : le frontend classe "a initialiser" alors que le squelette existe depuis49f4697, le port 4200 dit attendu par docker-compose.yml qui n'a aucun service frontend, et l'arborescence core/ prescrite par TESTING.md sans exister.
6.4 KiB
Backend
API FastAPI, Python 3.14, SQLAlchemy asynchrone sur asyncpg. Source dans apps/backend.
Couches
La doctrine est posée dans apps/backend/README.md et
TESTING.md : endpoints appelle services, qui appelle
repositories, qui seuls touchent les models. Le sens de dépendance ne s'inverse jamais.
Dans les faits, trois de ces couches sont des dossiers vides.
flowchart TB
ep["endpoints<br/>2 routes"]
sc["schemas<br/>2 modèles Pydantic"]
sv["services<br/>vide"]
rp["repositories<br/>vide"]
md["models<br/>vide"]
db[("PostgreSQL")]
ep --> sc
ep -.-> sv
sv -.-> rp
rp -.-> md
ep -->|"SQL brut, état actuel"| db
rp -.-> db
Le trait plein de endpoints vers la base n'est pas une erreur de dessin : /health/ready
exécute aujourd'hui son SELECT directement, sans repository. C'est acceptable pour une sonde
d'infrastructure, qui vérifie la base elle-même et non une donnée métier. Ce raccourci ne doit
pas servir de modèle au premier endpoint métier.
app/models/__init__.py ne contient qu'un avertissement, qui mérite d'être connu avant la
première migration : tout modèle absent de ce module reste invisible d'un
alembic revision --autogenerate, qui produirait alors un drop de sa table.
Démarrage
Point d'entrée : une factory, uvicorn app.main:create_app --factory. Aucune configuration
n'est lue à l'import du module, ce qui rend l'application testable et les migrations
indépendantes de l'environnement d'exécution.
sequenceDiagram
participant U as uvicorn --factory
participant F as create_app
participant S as get_settings
participant A as FastAPI
U->>F: create_app()
F->>S: Settings depuis .env et variables APP_*
S-->>F: resolved
F->>F: configure_logging(resolved)
F->>A: FastAPI, docs fermés si prod
F->>A: CORSMiddleware, seulement si allowed_origins
F->>A: Instrumentator, expose /metrics
F->>A: include_router, préfixe /api/v1
A-->>U: application
Le lifespan n'ouvre aucune connexion. Au démarrage il journalise le nom, la version et
l'environnement ; à l'arrêt il libère l'engine. L'engine lui-même est construit paresseusement au
premier appel de get_engine(), mis en cache par lru_cache. Conséquence directe : une API qui
démarre ne prouve rien sur la base, la première connexion réelle a lieu au premier
GET /api/v1/health/ready. C'est ce qui rend cette sonde indispensable.
Configuration
Settings est un BaseSettings Pydantic, lu depuis .env avec le préfixe APP_.
| Variable | Défaut | Rôle |
|---|---|---|
APP_SECRET_KEY |
aucun | Secret applicatif, SecretStr |
DATABASE_URL |
aucun | Chaîne de connexion, postgresql+asyncpg://... |
APP_ENV |
local |
local, dev, staging ou prod |
APP_DEBUG |
false |
Active aussi l'écho SQL de l'engine |
APP_LOG_LEVEL |
INFO |
|
APP_CORS_ORIGINS |
"" |
Liste séparée par des virgules. Vide, aucun middleware CORS n'est posé |
APP_API_PREFIX |
/api/v1 |
|
APP_DATABASE_POOL_SIZE |
5 |
|
APP_DATABASE_MAX_OVERFLOW |
10 |
Deux pièges :
DATABASE_URLne prend pas le préfixeAPP_. C'est le seul réglage dans ce cas, parvalidation_alias, pour rester compatible avec la convention d'Alembic et des hébergeurs.APP_SECRET_KEYetDATABASE_URLn'ont pas de valeur par défaut. L'application refuse de démarrer si l'un manque. C'est délibéré : mieux vaut un échec au démarrage qu'un service qui tourne avec un secret de démonstration.
Deux fichiers d'environnement, deux usages : .env à la racine alimente docker-compose.yml,
apps/backend/.env alimente l'API lancée sur le poste.
Routes exposées
| Méthode | Chemin | Dans l'OpenAPI | Rôle |
|---|---|---|---|
| GET | /api/v1/health/live |
oui | Le processus répond. Ne touche pas la base |
| GET | /api/v1/health/ready |
oui | La base répond et l'extension TimescaleDB est chargée |
| GET | /metrics |
non | Format Prometheus, exposé par l'instrumentator |
| GET | /docs, /redoc, /openapi.json |
non | Désactivés quand APP_ENV=prod |
Aucune route métier n'existe à ce jour.
/health/ready
Cette sonde porte une garde décrite dans l'ADR 0001 : un bootstrap de base sauté ne se voit pas au démarrage de l'API, elle le rend visible.
sequenceDiagram
participant C as Client
participant R as readiness
participant E as get_engine
participant D as PostgreSQL
C->>R: GET /api/v1/health/ready
R->>E: session, engine créé au premier appel
R->>D: SELECT extversion FROM pg_extension WHERE extname = 'timescaledb'
alt base injoignable
D--xR: SQLAlchemyError ou OSError
R-->>C: 503 Base de donnees injoignable
else extension absente
D-->>R: NULL
R-->>C: 503 Extension TimescaleDB absente
else
D-->>R: version de l'extension
R-->>C: 200 status ready
end
Sécurité
Voir la vue consolidée dans 00-vue-ensemble.md. Côté backend :
- Aucune authentification, aucune autorisation. Les deux routes sont publiques. Le premier endpoint métier imposera de trancher ce point.
- Le CORS n'autorise que les origines listées, et n'existe pas si la liste est vide.
/docs,/redocet/openapi.jsondisparaissent en production.- Le conteneur tourne en utilisateur non-root, avec un
HEALTHCHECKsur/api/v1/health/live. - Ni limitation de débit, ni journalisation des accès, ni en-têtes de sécurité.
Observabilité
- Journalisation par
dictConfig: format console en développement, JSON dèsAPP_ENV=prod.sqlalchemy.engineest forcé àWARNINGpour ne pas noyer les journaux. /metricsau format Prometheus. Aucun collecteur ne le lit :monitoring/est vide.
Tests
Conventions, gabarits et arborescence : apps/backend/TESTING.md.
Deux points structurants y sont fixés : les doubles passent par app.dependency_overrides et
jamais par unittest.mock, et les tests qui touchent la vraie base portent le marqueur
integration, exclu par défaut.
Questions ouvertes
- Authentification et autorisation : quel mécanisme, quelle granularité.
- Pagination et fenêtrage des lectures de séries temporelles, qui conditionnent la forme des endpoints métier.
- Politique de versionnement de l'API au-delà du préfixe
/api/v1.