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,VersionStatusont fourni un vocabulaire commun clair. -
Idempotence (§5.9) : L'implémentation via
IdempotencyServiceet tablesync_request_logsa fonctionné du premier coup. -
Historisation (§5.8) : L'archivage append-only des versions dans
sync_versionsviaHistoryServicea été implémenté sans difficulté. -
Export audit (§5.10) : Le format canonique JSON via
AuditExportUtila respecté les contraintes normatives. -
Guards et Interceptors :
DeviceAuthGuard,SchemaVersionGuard,SyncAuditInterceptoront 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 - 1peut ê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¶
-
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.
-
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.
-
base_version == canonical_version - 1est 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. -
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.
-
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