Skip to content

Architecture Technique

IlanAMG edited this page Oct 14, 2026 · 12 revisions

Architecture technique

Statut : 🟡 Brouillon · Màj : 2026-10-14 · Tickets : #125 #135 #162 #397

Choix et justifications

Domaine Choix Pourquoi
Mobile Expo (SDK courant), expo-router, TypeScript React Native avec build cloud (EAS), mises à jour OTA, routage par fichiers
Carte Mapbox (@rnmapbox/maps) Style personnalisé conforme à la maquette, marqueurs riches, geocoding, directions ; coût nul aux volumes du MVP
UI @gorhom/bottom-sheet, react-native-reanimated, composants maison Bottom sheet fluide sans dépendance à une lib UI lourde
Style NativeWind, thème généré depuis tokens.json ; StyleSheet occasionnellement, pour les cas que NativeWind couvre mal (styles animés, couches Mapbox, valeurs calculées à l'exécution) Décision d'Ilan du 2026-09-24 (D-024) : standard mobile de l'agence, connu de l'équipe et rapide à écrire ; le preset Tailwind généré pourra servir aussi à l'admin web. Une seule source de valeurs, aucune valeur de style en dur. Écarté : StyleSheet partout avec un thème recopié à la main, qui avait divergé de tokens.json. Essai de compatibilité concluant (résultat sur #125) : NativeWind 4.2.7, dernière version stable, et Tailwind CSS 3.4.19, avec Expo SDK 57 et React Native 0.86 ; rendu et rechargement à chaud vérifiés en build de développement, sur simulateur iOS 26.4 et émulateur Android API 34. Écartée : la v5 de NativeWind, qui n'existait qu'en RC. Livré par la PR #132, fusionnée le 2026-09-25 ; précisions dans la section Mobile
Données client TanStack Query, Zustand pour l'état local, MMKV pour la persistance Cache, retry, invalidation ; état de filtres et brouillons hors ligne
API Node 22, Fastify 5, TypeScript, Zod Validation par schéma, performances, typage partagé ; Express reste possible mais n'apporte rien de plus ici
Base PostgreSQL 16 + PostGIS sur Cloud SQL, Drizzle ORM Requêtes géographiques et filtres combinés natifs, relations, migrations versionnées ; Firestore écarté (une seule clause array-contains-any par requête, géo par geohash, pas de jointures)
Auth Firebase Authentication Apple, Google, e-mail gérés ; jeton vérifié par l'API
Push Firebase Cloud Messaging via Expo Notifications
Stockage média Google Cloud Storage, URL signées, redimensionnement à l'upload
Hébergement API Cloud Run (europe-west9, Paris) Sans état, scale à zéro puis horizontal, VPC vers Cloud SQL
Tâches planifiées Cloud Scheduler → endpoints internes protégés Recalcul nocturne, rappels de parcours, statuts stale
Traduction Modèle de langage via Vertex AI (projet GCP, région UE) Traduction des parcours et des avis à la publication (route_translations) ; modèle choisi sur un test de 3 parcours (Gemini ou Claude selon disponibilité en UE) ; même contrat et même facture que GCP (décision d'Ilan du 2026-09-28)
E-mails Brevo (UE) E-mails transactionnels en français et en anglais : lien de connexion envoyé par l'API (lien Firebase généré par l'Admin SDK, modèle Widoo), confirmation de changement d'adresse, réponses du support ; offre gratuite au lancement (décision d'Ilan du 2026-09-28). Lien de connexion par Brevo à partir de la v0.4 ; d'ici là, l'e-mail standard de Firebase, pour les tests de l'équipe (décision d'Ilan du 2026-10-14, D-090)
Site web Next.js (apps/web du monorepo) sur Cloud Run (europe-west9), détail dans Site-Web Landing et pages de lien /r/:id (E-21) : rendu serveur pour l'aperçu des liens (Open Graph), React et types partagés avec l'API ; liens universels et App Links sur le même domaine ; mesure des clics vers les stores sans cookie, donc sans bannière de consentement (décision d'Ilan du 2026-09-28)
Météo Open-Meteo Gratuit, sans clé, cache 30 min
Abonnements RevenueCat (react-native-purchases) Achats intégrés Apple et Google, reçus, renouvellements, restauration, webhooks vers l'API ; gratuit sous 2 500 $ de revenus mensuels
Analytics PostHog (cloud UE) Événements produit, funnels, feature flags
Crash Sentry Mobile et API
Admin web Next.js (App Router), Tailwind, shadcn/ui thémé depuis tokens.json, même monorepo, même API, rendu des données côté client derrière connexion ; Cloud Run (choix d'Ilan du 2026-10-09, D-085, comme le site) Modération et gestion, voir Admin-Web
Monorepo pnpm workspaces + Turborepo Types et schémas partagés
CI GitHub Actions Lint, types, tests, build ; EAS pour les binaires

Le vrai enjeu de scalabilité n'est pas la charge du MVP mais le modèle de données le jour où il y a vingt villes et des créateurs ; Postgres tient ce rôle sans réécriture.

Monorepo

widoo-app/
  apps/
    mobile/          Expo, expo-router
    api/             Fastify, Drizzle, orchestration
    admin/           Next.js (App Router), Tailwind, shadcn/ui
  packages/
    shared/          schémas Zod, types, énumérations, libellés FR, constantes
    orchestration/   moteur de cohérence, pur, testé sans BDD
    api-client/      client typé généré depuis les schémas Zod, utilisé par mobile et admin
  infra/             Terraform ou scripts gcloud, Dockerfile API
  docs/              plans, procédures
  .github/           workflows, template de PR, CODEOWNERS
  CLAUDE.md          règles de travail des agents
  SECURITY.md        signalement des vulnérabilités

Langue : code, identifiants, commits et noms de fichiers en anglais ; wiki, issues, commentaires de PR et libellés utilisateur en français.

Environnements

Env API BDD Mobile Usage
local pnpm dev Postgres+PostGIS en Docker Expo Go ou dev build développement
preview Cloud Run par PR (optionnel v1) ou staging Cloud SQL staging, données fictives build EAS preview (canal staging) recette
production Cloud Run widoo-api Cloud SQL prod, sauvegardes quotidiennes, PITR stores, canal production

Une fusion sur main n'autorise pas un déploiement en production. L'API staging se déploie automatiquement depuis main ; la production se déploie par une action manuelle d'Ilan ou de Paul, après un staging vert. Un rollback applicatif ne restaure pas une donnée supprimée.

Secrets dans Secret Manager, injectés par Cloud Run ; jamais dans Git ni dans l'app (seules les clés publiques Mapbox et Firebase sont embarquées, restreintes par bundle id).

PostHog et Sentry par environnement

Règle (D-059, #162) :

Env PostHog Sentry
local (Metro, build development) aucune clé : rien n'est envoyé ; journal console seulement (EXPO_PUBLIC_ANALYTICS_DEBUG=1) aucun DSN par défaut ; pour tester un rapport, DSN du projet existant avec l'environnement development
preview (staging) projet Widoo — staging projets widoo-mobile et widoo-api, environnement preview (mobile) et staging (API)
production projet Widoo — production mêmes projets, environnement production
  • Le développement local n'envoie aucun événement produit. Une clé PostHog dans apps/mobile/.env.local n'est pas prévue ; et comme ce fichier est relu à l'exécution même quand la variable est vidée dans le shell, l'app ignore la clé en développement : seul un build preview ou production crée le client PostHog. Pour vérifier un événement, le journal local suffit (nom, propriétés, consentement) ; l'arrivée réelle dans PostHog se vérifie sur un build preview, dans le projet staging.
  • Staging et production sont deux projets PostHog distincts, dans la même organisation PostHog Cloud UE, chacun avec les réglages obligatoires de Securite-et-RGPD (« Discard client IP data », pas de PostHog AI). Les tableaux de Analytics se construisent sur le projet production ; le projet staging sert à la recette et peut être vidé sans perte.
  • Sentry garde un projet par surface (widoo-mobile, widoo-api) et sépare les environnements par son champ environment, toujours renseigné (EXPO_PUBLIC_APP_ENV posé par eas.json pour le mobile, SENTRY_ENVIRONMENT pour l'API). Les alertes et le suivi des régressions filtrent sur production.
  • Les clés vivent dans l'environnement EAS du profil (preview, production) pour le mobile et dans Secret Manager pour l'API ; jamais dans un fichier versionné, ni dans l'environnement EAS development. La clé PostHog et le DSN Sentry sont publics par nature (écriture seule) : le risque n'est pas la fuite, c'est le mélange des données.

Pourquoi : les hypothèses du MVP se mesurent sur les funnels et les personnes de PostHog, qui n'ont pas de filtre d'environnement fiable : un seul événement de développement ou de recette fausse un funnel, crée une personne et touche les feature flags, et ne se retire qu'à la main. Sentry, lui, traite l'environnement comme une dimension de premier rang (issues, alertes, releases) : un projet par environnement doublerait les vérifications de D-019 sans rien séparer de plus.

Écarté :

  • un projet PostHog de développement partagé : les sessions de chacun s'y mêlent sans rien valider que le journal local ne montre déjà, et il faudrait tenir ses réglages RGPD pour un projet qui ne sert à aucune mesure ;
  • la clé de staging en local : c'est le cas relevé dans #162, qui mêle les sessions de développement à la recette et, par erreur de copie, à la production ;
  • un seul projet PostHog pour staging et production, filtré par une propriété d'environnement : chaque tableau et chaque cohorte devrait porter le filtre, et un oubli fausse la mesure ;
  • un projet Sentry par environnement : voir la raison ci-dessus.

API

  • REST JSON, versionnée par préfixe /v1. Voir API.
  • Validation Zod à l'entrée et à la sortie (fastify-type-provider-zod), OpenAPI généré.
  • Auth : en-tête Authorization: Bearer <firebase id token>, vérifié par le SDK Admin, utilisateur chargé ou créé à la volée. Endpoints publics sans jeton.
  • Erreurs : format unique { code, message, details } avec codes stables.
  • Limitation de débit par IP et par utilisateur (@fastify/rate-limit), plus stricte sur la création de lieux et les signaux.
  • Journalisation structurée (pino), sans donnée personnelle ; identifiant de requête propagé.

Mobile

  • Structure app/(tabs)/index|routes|profile, modales app/route/[id], app/plan/[id], app/go/[id], app/create/*, app/auth.
  • Hors ligne : consultation du cache TanStack Query et brouillons persistés ; création complète requiert le réseau (géocodage, calcul).
  • Performance : première carte affichée sous 3 s en 4G, liste virtualisée, images via expo-image avec tailles servies adaptées.
  • Deep links : widoo://route/<id> et https://widoo.app/r/<id|share_token> (App Links et Universal Links).
  • i18n : i18next, français et anglais livrés (D-062), clés en anglais ; langue du téléphone par défaut, choix appliqué sans redémarrer ; users.locale pour les textes envoyés par le serveur.
  • Style (D-024) : NativeWind, livré par #125 (PR #132, fusionnée le 2026-09-25).
    • Une seule source de valeurs : le paquet packages/tokens garde une copie de tokens.json, tenue identique à celle du wiki et vérifiée en CI, et en génère le preset Tailwind et le module typé @widoo/tokens, qui porte les valeurs et les correspondances (familles, catégories de lieux, icônes de filtre, pastilles, points d'ambiance, mouvement). Le preset remplace les valeurs par défaut de Tailwind, et un lint bloquant refuse toute valeur de style en dur.
    • Le preset de NativeWind garde ses plugins, qui traduisent les classes en styles React Native, mais son thème est écarté : seuls les tokens restent.
    • Ombre : shadow-tabbar est posée par le boxShadow natif, avec la valeur du token, et non par une classe : NativeWind ferait d'une classe d'ombre une elevation Android, différente des maquettes.
    • Mouvement : les animations passent par Reanimated et les constantes générées de mappings.motion, jamais par les classes transition-* ou animate-* : une telle classe, activée après le premier rendu, fait remonter le composant par NativeWind.
    • Un Pressable ne prend pas de style en fonction (({ pressed }) => …) : NativeWind ne l'applique plus. L'état d'appui passe par les variantes active: ou par un état React.
    • Avec pnpm, react-native-css-interop est une dépendance directe de l'app : NativeWind en injecte les imports dans chaque fichier.
    • Interligne sur Android : le composant Text garde l'interligne de la police plutôt que celui des tokens, qu'il applique sur iOS, parce qu'Android 14 rogne les jambages à 150 % de texte. La règle reste à valider dans Direction-Artistique par #135.
    • StyleSheet : à la livraison, un seul cas, le voile animé du bouton (Reanimated), avec la couleur du thème généré.
    • Des composants de base (texte, icône, bouton, chip, pastille, point d'ambiance, pastille d'étape, card) portent les règles d'accessibilité de Direction-Artistique ; les écrans les assemblent.
  • Accessibilité : labels sur tous les contrôles, contraste AA, tailles dynamiques.

Qualité

  • TypeScript strict partout, ESLint, Prettier.
  • Tests : Vitest (orchestration, API avec base de test), React Native Testing Library pour les composants critiques (filtres, éditeur), Maestro pour deux parcours de bout en bout (découverte → fiche ; création → soumission).
  • CI bloquante sur chaque PR : lint, types, tests, build API ; build EAS preview sur main.
  • Taille des changements, découpage et fusion des PR : règles de travail dans CLAUDE.md du dépôt.

Observabilité

Sentry (mobile et API), logs Cloud Run, alertes sur taux d'erreur 5xx et latence p95 de la recherche (> 800 ms). Tableau PostHog pour les KPI de Analytics.

Coûts prévisibles (MVP, ordre de grandeur mensuel)

Cloud SQL petite instance ~30 à 60 €, Cloud Run ~0 à 20 €, Mapbox et Firebase dans les paliers gratuits, PostHog et Sentry gratuits aux volumes initiaux, EAS selon formule. À réévaluer à la validation du MVP.

Clone this wiki locally