Aller au contenu

PD-171 — Retour d'Expérience

EPIC de référence : PD-186 — BACKEND CORE


1. Résumé exécutif

Objectif : Implémenter un mécanisme de synchronisation multi-device avec détection et résolution de conflits (C1/C2/C3/C4, règles R1-R4), garantissant idempotence, historisation immuable et traçabilité complète.

Résultat : L'implémentation a nécessité deux itérations. La première version a échoué à l'audit d'acceptabilité avec deux écarts bloquants (E-01 et E-02) liés à la distinction C1/C2. Après correction, l'implémentation est conforme à la spécification.

Verdict : ✅ ACCEPTÉ après correction — 253 tests passants, conformité retrouvée.


2. Points fluides

  • Architecture modulaire : La structure src/sync/ avec séparation services/entities/dto/guards a permis une implémentation claire et maintenable.

  • Entities et DTOs : Le mapping spec → entités TypeORM s'est fait sans friction (SyncObject, SyncVersion, SyncRequestLog).

  • Enums exhaustifs : Les constantes ConflictType, ResolutionRule, VersionStatus ont fourni un vocabulaire commun clair.

  • Idempotence (§5.9) : L'implémentation via IdempotencyService et table sync_request_logs a fonctionné du premier coup.

  • Historisation (§5.8) : L'archivage append-only des versions dans sync_versions via HistoryService a été implémenté sans difficulté.

  • Export audit (§5.10) : Le format canonique JSON via AuditExportUtil a respecté les contraintes normatives.

  • Guards et Interceptors : DeviceAuthGuard, SchemaVersionGuard, SyncAuditInterceptor ont apporté une séparation des préoccupations efficace.

  • Couverture de tests : 253 tests couvrant les scénarios nominaux, conflits, et edge cases.


3. Points difficiles

3.1 Distinction C1 vs C2 non triviale

Contexte : La spécification définit deux types de conflits pour les requêtes avec base_version < canonical_version : - C1 (Concurrent) : Requêtes dans la même fenêtre d'évaluation - C2 (Stale) : Requêtes hors fenêtre

Difficulté : L'implémentation initiale ne distinguait pas ces deux cas. La condition base_version < canonical_version déclenchait systématiquement C2.

Impact : Écart E-01 bloquant — rejet de requêtes légitimes.

3.2 Fenêtre d'évaluation concurrente en mode requête unitaire

Contexte : Le plan prévoyait un batch temporel regroupant les requêtes sur 75ms avant traitement.

Difficulté : En pratique, chaque requête HTTP est traitée individuellement ([batchRequest]). Le batch est toujours de taille 1, rendant la détection C1 impossible par la méthode initialement prévue.

Impact : Écart E-02 bloquant — C1/LWW jamais appliqué.

3.3 Dépendance à l'état de l'historique pour C1 rétrospectif

Contexte : La correction E-02 a introduit une détection C1 "rétrospective" basée sur recentVersion — la dernière version archivée dans la fenêtre.

Difficulté : Cela crée une dépendance temporelle sur l'historique qui n'était pas explicitement prévue dans le plan initial.


4. Hypothèses révélées tardivement

Hypothèse découverte Impact
Les requêtes arrivent individuellement (pas de batch HTTP natif) La stratégie "batch temporel" du plan ne s'applique pas directement
La fenêtre d'évaluation doit être implémentée rétrospectvement Nécessité de consulter l'historique récent pour C1
base_version == canonical_version - 1 est un cas frontière Peut être C1 OU C2 selon la présence d'une version récente
L'ordre chronologique des commits impacte la classification Une requête B arrivant après commit A mais avec même base_version est C1, pas C2

5. Invariants complexes

5.1 Distinction C1/C2 (§5.4.1)

Énoncé : Deux requêtes avec la même base_version reçues dans la fenêtre d'évaluation sont C1 ; hors fenêtre, c'est C2.

Complexité : Quand une requête A est traitée et incrémente la version, une requête B arrivant juste après a base_version < canonical_version. Pourtant, si B est dans la fenêtre de A, c'est C1 (pas C2).

Implémentation : - C2 si base_version < canonical_version - 1 (plus d'une version de retard) - Si base_version == canonical_version - 1 : consulter recentVersion dans la fenêtre - Si recentVersion.baseVersion == request.baseVersion → C1 - Sinon → C2

5.2 Immuabilité stricte des tables (§5.8.2, §5.9.3)

Énoncé : sync_versions et sync_request_logs sont append-only.

Complexité : Nécessite triggers PostgreSQL + REVOKE des privilèges UPDATE/DELETE. Non vérifiable au niveau applicatif seul.


6. Dette technique

Élément Description Justification
Pas de vrai batch temporel Les requêtes sont traitées une par une, le C1 est détecté rétrospectivement Simplifie l'implémentation, comportement correct via recentVersion
Fenêtre de 75ms codée en dur SYNC_CONCURRENT_WINDOW_MS = 75 est une constante Acceptable, configurable via constantes, dans la limite des 1000ms spec
Triggers PostgreSQL non inclus L'immuabilité §5.8.2/§5.9.3 n'est pas garantie au niveau DB dans l'implémentation actuelle Dépend du déploiement des triggers mentionnés dans le plan
Tests d'intégration DB limités Les tests utilisent des mocks, pas de vrai PostgreSQL Acceptable pour les tests unitaires, tests e2e à prévoir

7. Risques résiduels

Risque Probabilité Impact Mitigation suggérée
Fenêtre 75ms insuffisante en haute latence Faible Moyen (C1 classé C2) Rendre configurable, monitoring
Race condition sur recentVersion Faible Faible Lock pessimiste déjà en place
Triggers d'immuabilité non déployés Moyen Élevé (violation §5.8.2) Vérifier déploiement en production
Croissance volumétrique sync_versions Moyen Moyen (perf) Archivage froid prévu mais non implémenté

8. Améliorations processus

8.1 Pour la spécification

  • Clarifier la frontière C1/C2 : La spec gagnerait à expliciter que base_version == canonical_version - 1 peut être C1 ou C2 selon le contexte temporel.

  • Exemple numérique : Ajouter un exemple concret de timeline C1 vs C2 dans la spec elle-même.

8.2 Pour le plan d'implémentation

  • Valider l'hypothèse de batch : Le plan supposait un batch de requêtes ; cette hypothèse aurait dû être validée avant implémentation.

  • Prototyper les cas frontières : Les scénarios C1 rétrospectif auraient bénéficié d'un spike technique.

8.3 Pour l'audit d'acceptabilité

  • Tests de scénarios temporels : Inclure des tests simulant la séquence exacte A→commit→B pour valider C1 rétrospectif.

9. Enseignements clés

  1. La distinction C1/C2 est critique : Ce n'est pas un détail d'implémentation mais un invariant fonctionnel. La première version a échoué sur ce point précis.

  2. Le batch temporel est une abstraction logique : En HTTP synchrone, chaque requête arrive individuellement. La "fenêtre d'évaluation" doit être implémentée rétrospectivement via l'historique.

  3. base_version == canonical_version - 1 est le cas frontière : C'est LE cas où la distinction C1/C2 se joue. Il mérite un traitement explicite et des tests dédiés.

  4. Les tests nommés explicitement aident l'audit : Les tests "E-01 FIX" et "E-02 FIX" ont permis de tracer directement la correction des écarts.

  5. L'immuabilité DB nécessite des triggers : L'application ne peut pas garantir seule l'append-only ; le déploiement des triggers PostgreSQL est un prérequis de conformité.


Document généré le 2025-12-30 Version : 1.0