Livrable 27
Architecture technique
flowchart TB
subgraph clients [Une application web]
publicUI[Client public SSR + îlots]
proUI[Espace pro]
cityUI[City OS / Admin]
end
subgraph app [Modular monolith]
identity[Identity et RBAC]
city[City DNA]
places[Places]
events[Events]
offers[Offers]
live[Live / Freshness / Trust]
search[Search]
reco[Reco et planner]
geo[GeoStore]
content[CMS]
analytics[Analytics]
notify[Notifications]
billing[Billing - inactif]
ads[Ads - inactif]
end
db[(PostgreSQL)]
jobs[Job runner]
obj[Object storage UE]
maps[MapsProvider]
route[RoutingProvider]
llm[AIProvider optionnel]
pay[PaymentProvider]
publicUI --> app
proUI --> app
cityUI --> app
app --> db
app --> jobs
jobs --> db
app --> obj
geo --> maps
reco --> route
reco --> llm
billing --> payADR-001 — Monolithe modulaire
- Choix : un process déployable, modules par dossier, interfaces internes (fonctions), pas de réseau entre domaines.
- Alternatives : microservices ; monorepo multi-apps dès le jour 1.
- Compromis : montée en charge et déploiement couplés. Au volume d'un pilote ville, c'est le bon problème à ne pas avoir.
- Risques : « big ball of mud ». Parade : frontières de dossiers, pas d'import UI → tables d'un autre domaine sans passer par son service.
- Multi-ville : le module City est appelé par les autres ; personne ne fork.
ADR-002 — PostgreSQL, PostGIS si présent
- Choix : Postgres comme source de vérité. PostGIS quand
postgis_version()répond. Sinon lat/lng + GeoJSON. - Alternatives : Mongo géo ; service geo externe pour chaque point (coût, lock-in, vie privée).
- Compromis : deux chemins à tester.
- Pourquoi : relationnel + temps + ville. La géo est une colonne, pas le produit.
ADR-003 — TanStack Start ici, pas un second frontend Next.js
- Choix : livrer sur TanStack Start (React 19, routes fichier, SSR, Vite, déploiement Vercel, version de framework déjà au-dessus du correctif de sécurité connu de la plateforme).
- Alternative : Next.js comme le prompt l'écrit — pertinent pour un dépôt greenfield hors de ce runtime, pas pour deux stacks en parallèle.
- Compromis : pas d'ISR Next ; SEO via SSR + cache HTTP. Pas d'App Router.
- Risque : un lecteur du prompt croit que Next est en place. Ce document est la rectification.
ADR-004 — Carte sans fournisseur fantôme
Voir document 17. Renderer MapLibre prévu. Tuiles seulement avec droit. Sinon schéma.
ADR-005 — Jobs
Cible : table jobs + worker, ou Redis queue si Redis existe.
MVP / serverless :
- expiration calculée à la lecture ;
- un déclencheur périodique si la plateforme d'hébergement en offre un (cron), sinon bouton admin « recalculer les statuts » + lecture correcte.
- Pas de Kafka. Pas de Kubernetes.
Tâches prévues : expiration, sitemap, agrégats analytics, imports, e-mails.
ADR-006 — Temps réel transport
SSE seulement si le runtime tient une connexion (dev server oui, fonctions courtes souvent non). Produit : refetch / poll sur la vue Now, libellé « actualisé à … ». WebSocket non retenu (pas de besoin bidirectionnel).
ADR-007 — Fournisseurs
Interfaces : MapsProvider, RoutingProvider, GeocodingProvider, PaymentProvider, EmailProvider, AIProvider, ObjectStorage. Implémentations nulles explicites (Unavailable) qui dégradent. Aucune implémentation « fake success ».
ADR-008 — Observabilité
Fiche : docs/adr/008-observability.md. Les trois axes de confiance, numérotés 008 dans un ancien README, sont ADR-009 (docs/adr/009-trust-axes.md) pour ne plus partager cet identifiant.
Logs structurés JSON (sans PII). Erreurs avec identifiant de corrélation. Métriques : taux d'erreur, latence des handlers Now et Search, échecs de jobs. Pas de SLO chiffré inventé. Avant prod : définir disponibilité et latence avec l'hébergeur réel. Alerting : à brancher, pas simulé.
Tracing sur : publish, claim, now, search. Plus tard.
Domaines et dépendances autorisées
identity ← tous les writes. city ← places, events, search, reco. live ← places, events, offers. search et reco lisent, n'écrivent pas les fiches. billing et ads ne sont pas importés par le ranker organique.
Config
Variables validées au boot : DATABASE_URL optionnelle ici (repli embarqué uniquement dans cet environnement de preview — en prod réelle, une base managée est requise, pas un fichier local). Secrets absents ⇒ le provider correspondant est Unavailable, le boot ne plante pas pour une clé carte ou IA. Le boot échoue s'il manque un secret déclaré obligatoire (ex. secret de session quand l'auth est allumée).
i18n et temps
FR. Chaînes externalisées. Tri et FTS français. Affichage en timezone ville. Stockage UTC. Événements cross-midnight : ends_at > starts_at toujours (le lendemain est un timestamp, pas une heure 25).
Honnêteté d'implémentation future
Tant que le code n'existe pas : rien n'est IMPLEMENTED. Quand il existera sans usage : IMPLEMENTED — NOT YET VALIDATED.