Pour un groupe de presse comme Le Monde, qui gère des millions de visiteurs chaque mois sur leurs différent titres Le Monde, Le Monde Diplomatique, Courrier International, Le Nouvel Obs, et Telerama, le tunnel d'abonnement et l'espace abonné sont des briques applicatives stratégiques.
Cependant il existe une fracture architecturale critique entre l'application, "le Tunnel" destinée aux utilisateurs (qui gère les inscriptions, le tableau de bord et les actions des utilisateurs) et le système du prestataire qui gère les abonnements, le paiement, la distribution des journaux papier, etc... appelé "Mage" (géré par Majorel).
En tant que développeur chez Le Monde, j'ai été confronté à des challenges étonnamment complexes, notamment lors du développement d'une fonctionnalité : la suspension d'abonnement (ou "mise en pause").
Cette fonctionnalité, bien que simple en apparence pour l'utilisateur (un formulaire permettant de choisir les dates de suspension), a révélé une rupture majeure entre notre tunnel et le système de gestion du partenaire. Sa mise en œuvre a nécessité des ajustements techniques profonds, notamment en manipulant les dates et en introduisant une logique de checksum pour garantir l'intégrité des données.
La Division Architecturale : Tunnel vs. Mage
Fait étonnant : Majorel éteint physiquement les serveurs de nuit ; ils sont complètement mis hors ligne après 18 heures. Donc toutes les requètes qui concernent les abonnements et les utilisateurs sont d'abord stockées dans la base de données du Tunnel, pour être ensuite dépilées par petit paquets au cours de la journée par l'intermédiaire d'un service qu'on appelle la Synchro.
La stack du Tunnel chargée de la gestion des utilisateurs et du flux d'abonnement côté Le Monde est basée sur PHP7, Phalcon, PostgreSQL, Docker, RabbitMQ et Google Cloud Platform ; de l'autre côté Mage met à disposition un endpoint API pour maintenir la synchronisation des données utilisateur entre les deux systèmes.
Le composant SubscriptionSynchronizer
garantie la cohérence des données à travers un mécanisme robuste (Extract, Transform, Load). Cette synchronisation est essentielle car diverses actions des utilisateurs, telles que la modification d'adresses ou d'abonnements, déclenchent des mises à jour entre les systèmes.
Car c'est l'API du prestataire qui fait office de source faisant autorité pour les droits d'abonnement et les services qui y sont liés.
Suspension d'abonnement : Le défi Asynchrone
Vous l'aurez compris, l'un des principaux problèmes autour de la gestion des abonnements est son caractère asynchrone : une demande de suspension initiée par l'abonné dans le Tunnel est enregistrée en base lors de la validation du formulaire, puis est envoyée à l'API de Mage au cours du prochain cycle de synchronisation.
Malheureusement, l'API de Mage répond sytematiquement avec un code 202
, et ce même si une demande échoue et "tombe dans Taboo" (l'outil dans lequel Mage fait remonter les erreurs pour être traitées par le service client du Monde).
Aussi, la nature du traitement de l'API externe de Mage introduit un retard critique, qui affecte l'expérience utilisateur dans le cas où la demande de suspension échoue :
- Comme la validité de la demande n'est pas confirmeé en temps réel il n'est pas informé de l'échec de sa demande.
- Le bandeau de confirmation ("Votre abonnement va être mis en pause...") s'affiche tout de même dans son tableau de bord.
- L'abonnement est toujours prélevé et l'abonné conserve ses services premium.
Tricher sur la Date : L'étrange Décalage d'une Journée
Les cas dans lesquels la demande de suspension peut être refusée chez Mage sont les suivants :
- Les demandes de suspension qui commence le jour même sont toujours refusées par Mage et tombent dans Taboo.
- Une demande pour le lendemain, si elle est soumise après 18 heures, va également échouer et tomber en erreur.
Aussi Mage ignore systématiquement le premier jour de suspension dans l'enregistrement des suspensions initiées par l'abonné, par rapport aux autres types de suspensions (comme celles dues à un impayé). Par exemple, une demande de suspension d'un mois commençant le 1er juillet et se terminant le 1er août ne donnerait lieu qu'à 30 jours de suspension effective.
Pour compenser ce décalage et présenter au client des dates reflétant la période réelle de suspension, la solution technique a été de retirer un jour à la date de début avant de l'envoyer à Mage.
Cependant, cette manipulation crée un problème d'affichage pour l'utilisateur. Pour que le bandeau de suspension dans le tableau de bord ou l'email de confirmation affiche la date correcte de fin de suspension, il a été nécessaire de modifier la méthode getSuspensionEndDate
du modèle Subscription
pour lui appliquer un modificateur facultatif +1 day
.
De plus, les règles d'éligibilité ont dû être ajustées : normalement l'accès à la suspension doit être bloqué si l'abonnement a été souscrit il y a moins de 8 jours, donc la première date possible pour la suspension a été décalée de J+2 à J+3. Par exemple un abonné qui souhaite faire une demande de suspension un lundi 1 septembre, ne peut suspendre son abonnement qu'à partir du jeudi 4.
La Pipeline de Somme de Contrôle : Créer un ID Unique
L'un des obstacles techniques les plus importants dans la synchronisation des données de suspension était l'absence d'un identifiant unique fiable provenant du système externe : l'API de Mage ne fournit pas d'ID pour les suspensions.
En l'absence clé primaire l'application ne pouvait pas comparer de manière fiable les enregistrements de suspension locaux avec les données fournies par l'API de Mage lors du processus de synchronisation. La solution technique a consisté à créer un mécanisme centré sur la génération de sommes de contrôle, géré par le SuspensionSynchronizer
.
Le SuspensionSynchronizer
est chargé de gérer la transformation des périodes de suspension renvoyées par l'API Mage (via getCustomerSubscriptionsRights
), en veillant à ce que chaque période soit transformée en un objet et que sa somme de contrôle unique soit générée avant l'enregistrement des données localement.
Extraction : L'objectif principal du processus de synchronisation est dans un premier temps de télécharger l'historique validé des suspensions depuis l'API de Mage. Lors de la synchronisation des droits d'abonnement (sur
/customers/:customerId/subscriptionsRights
), leSuspensionExtractor
récupère les périodes de suspension.Transformation : À cette étape, pour chaque élément de l'historique, l'entité
Suspension
génére un SHA1 à partir de ses caractéristiques immuables : l'ID d'abonnement, la date de début de la suspension, la date de fin de la suspension et le code de motif.Identifiant local unique : cette somme de contrôle calculée est ensuite stockée dans une nouvelle colonne indexée dans la table locale
users.suspension
. Cette clé synthétique sert d'identifiant unique fiable nécessaire pour déterminer si un enregistrement existe ou a été modifié.Gestion des orphelins : Puis le processus de synchronisation identifie et supprime les enregistrements de suspension locaux "orphelins", c'est-à-dire ceux qui ne disposent pas d'une somme de contrôle valide ou qui n'existent plus chez Mage.
Ceci est crucial pour permettre à l'abonné de refaire une demande si la première est tombée en erreur. Car si l'enregistrement local reste en place, l'utilisateur ne peut pas soumettre une nouvelle demande et le tableau de bord va afficher une bannière d'information incorrecte.
En résumé pour garantir une expérience utilisateur cohérente et pour pallier les contraintes de l'API Mage, la fonctionnalité de suspension a nécessité la mise en œuvre de règles d'éligibilité, et d'une logique de synchronisation complexes afin de maintenir l'intégrité des données et permettre d'afficher des dates correctes dans le tableau de bord de l'abonné et dans e-mails transactionnels.
Top comments (2)
La complexité des projets Grand Compte est assez rarement documentée dans le détail.
J'aime bien le fait que l'expérience utilisateur soit prise en compte côté back qui encaisse le niveau de complexité au lieu de faire des compromis qui pourraient engendrer des frictions.
La documentation "fournisseur" évoquait tous ces cas ou c'est au moment du dév que le problème est apparu ?
La documentation a été bricolée au fil du temps par les équipes de dev précédentes, elle était assez rudimentaire :
La plupart du temps quand il s'agissait de créer de nouvelles features il fallait avancer à l'aveugle.
Parfois on pouvait ouvrir un ticket chez Mage pour obtenir une évol de l'API...
Le Monde avait ce contrat historique avec ce prestataire dans la mesure où ce sont eux qui s'occupe de la logistique de la distribution du quotidien.
Le Monde avait demandé une documentation de l'API, ce que le presta avait proposé comme un service payant.
Devant le fait accompli, la direction avait donc refusé de payer un supplément pour ce qu'on pouvait légitimement supposer étant inclu de base dans le service.
C'est cette situation qui à motivé la décision de migrer l'essentiel de la gestion des abonnement numériques en interne dans le tunnel.