Skip to content
07Étude de casSW · 07 sur 10

Une marketplace musicale async-first bâtie en deux niveaux pour un opérateur qui possède le catalogue.

Une startup pilotée par son fondateur avait besoin d'une plateforme où les artistes vendent morceaux, albums et lots de pochettes directement aux auditeurs. Le vrai problème était la surface de commerce : panier et paiement asynchrones, et un niveau d'API prêt pour le mobile bâti avant même que le client mobile existe.

ClientConfidentiel
Année2022
Durée1 yrs
StackPHP · Symfony · Postgres · Redis · InfluxDB
Hero image for ugc-music-platformFig 01 · Hero

Une marketplace, pas un service de streaming. La différence compte sur le plan architectural.

L'opérateur ne construisait pas un service de streaming où les artistes téléversent pour la visibilité et où le revenu arrive plus tard sous forme de part de publicité. Les artistes possèdent leur contenu. Les auditeurs le paient. La plateforme est une marketplace : fiches produits, panier, paiement, historique de commandes, rapports de ventes. Le modèle économique vit dans ce flux.

Cette distinction remodèle l'architecture. Le problème le plus dur d'un service de streaming, c'est le débit d'ingestion et la latence de lecture. Le problème le plus dur d'une marketplace, c'est la surface de commerce : opérations de panier concurrentes, cohérence des commandes, un paiement qui ne perd pas un achat si un worker d'arrière-plan est lent. Ratez ça et vous perdez de vraies transactions d'artistes déjà sceptiques face à une nouvelle plateforme.

L'opérateur avait aussi un plan pour un client mobile — iOS et Android, construit plus tard — et voulait que le niveau d'API soit prêt pour lui. Pas rajouté après coup. Prêt dès le départ, avec des endpoints pilotés par DTO versionnés et une séparation Admin/Mobile claire. L'application mobile n'a pas été livrée dans la fenêtre 2022. Le niveau d'API, si.

Le brief : une surface de commerce complète pour un catalogue détenu par les artistes, un cœur asynchrone qui ne bloque pas le paiement sur du travail d'arrière-plan, et un niveau d'API qu'il ne faudra pas repenser à l'arrivée du client mobile.

Deux niveaux, un bus de messages et trois magasins aux rôles distincts.

Le système s'est scindé en deux applications Symfony dès le premier jour. Le monorepo — l'admin et l'UI web — gérait la gestion des profils d'artistes, les fiches produits, les téléversements de pochettes et le back-office de l'opérateur. Le niveau d'API a été construit séparément, comme un service REST à DTO versionnés avec les espaces de noms de routes /Mobile/v1/ et /Admin/v1/, 96 endpoints mobile et 130 endpoints admin, documentés et prêts pour le client mobile dès son arrivée.

Les flux de panier et de commande passent par Symfony Messenger, pas par des écritures synchrones. AddToCartController dispatche un message ; le handler traite la mutation du panier de façon asynchrone. Les enregistrements de commande — Order et ProductOrder, avec quantité et coût total suivis par ligne — suivent le même motif. Le paiement ne bloque ni sur les écritures de télémétrie, ni sur le traitement des pochettes, ni sur quoi que ce soit d'autre tournant sur le bus. La base de données opérationnelle reste rapide parce que l'éventail des écritures est maîtrisé.

Trois magasins, chacun avec une seule responsabilité. PostgreSQL est l'état canonique : 25 entités, plus de 40 index sur les tables chaudes — utilisateurs, produits, commandes, articles de panier, genres, tags, relations artiste-vers-produit. Redis fait tourner à la fois le broker de file Messenger et la couche de cache pour les lectures chaudes. InfluxDB détient la télémétrie des actions utilisateur. Les événements de comportement à haute fréquence ne touchent jamais la base de données opérationnelle.

Les profils d'artistes et les descriptions de produits — titres d'albums, noms de morceaux, étiquettes de genre, actualités — sont traduisibles au niveau du schéma via KnpLabs DoctrineBehaviors. Ajouter une locale est une migration de données, pas un changement de code. L'UI web faisait tourner Plyr pour la lecture audio et vidéo ; CropperJS gérait le recadrage et le téléversement des pochettes en ligne.

F · 01Panier et paiement via Messenger
Les mutations de panier et le traitement des commandes passent par Symfony Messenger, pas par des écritures synchrones. Le paiement accuse réception immédiatement ; le bus gère l'éventail des écritures.
F · 02Architecture à deux niveaux
Monorepo (admin et UI web) et un service d'API à DTO versionnés séparé avec les espaces de noms /Mobile/v1/ et /Admin/v1/ — 96 endpoints mobile et 130 endpoints admin — bâti avant que le client mobile n'existe.
F · 03Séparation en trois magasins
PostgreSQL pour l'état canonique (25 entités, plus de 40 index), Redis pour le broker de file et le cache, InfluxDB pour la télémétrie des actions utilisateur. Chaque magasin a un seul rôle.
F · 04Contenu traduisible au niveau du schéma
KnpLabs DoctrineBehaviors sur les descriptions de produits, les profils d'artistes, les étiquettes de genre et les actualités. Ajouter une locale est une migration, pas un changement de code.
F · 05Pipeline de télémétrie InfluxDB
Les événements d'action utilisateur transitent par des handlers Messenger vers InfluxDB. Le pipeline est architecturé pour la télémétrie complète de lecture, de navigation et de saut sans toucher la base de données opérationnelle.
F · 06Traitement asynchrone des commandes
Les entités Order et ProductOrder suivent les achats avec quantité et coût par ligne. Les mutations de commande passent par le bus de messages pour que les opérations de commerce n'entrent pas en concurrence entre elles.

Le panier passe par le bus. L'API existe avant l'application qui l'appellera.

La décision d'acheminer les mutations de panier via Symfony Messenger plutôt que par des écritures synchrones ne tenait pas à l'échelle. Elle tenait à la correction. Les opérations de panier dans une marketplace impliquent des contrôles d'inventaire, une validation de l'état du produit et la gestion de sessions concurrentes. Les mettre sur le bus de messages découple l'accusé de réception côté utilisateur de l'éventail des écritures. L'auditeur obtient une réponse ; la plateforme rattrape son retard. La base de données opérationnelle n'entre jamais en concurrence avec la surface de commerce.

Le même principe se prolonge dans la lecture. PlayFileHandler et PlayedFileService découplent les événements de lecture de la base de données opérationnelle. La télémétrie des actions utilisateur écrit dans InfluxDB via le bus de messages — 21 handlers au total sur le niveau d'API. Le pipeline InfluxDB est architecturé pour la télémétrie complète de lecture, de navigation et de saut ; câbler ces événements supplémentaires est l'étape suivante que l'opérateur peut franchir sans rien ré-architecturer.

Trois magasins, c'est la réponse quand chaque magasin gagne sa place en faisant le travail pour lequel il est le meilleur — et rien d'autre.

Le niveau d'API a été volontairement surdimensionné pour 2022. /Mobile/v1/ existait avec 96 endpoints avant qu'une seule ligne de code de l'app mobile ne soit écrite. Le client mobile n'a pas été livré dans la fenêtre 2022, mais l'opérateur a hérité d'un service d'API déjà versionné, documenté et séparé de la surface d'administration. Le coût de le faire plus tard — avec un produit en production, de vraies données utilisateur et un schéma ayant dérivé — n'est pas comparable au coût de le faire une seule fois au départ.

Huit mois. Trois dépôts. Une surface de commerce que l'opérateur peut étendre.

Huit mois de développement actif. 494 commits sur trois dépôts — le monorepo, le niveau d'API et un site vitrine minimal HTML/Gulp. Le système a été livré avec une surface de commerce artiste-vers-auditeur complète : fiches produits, panier, paiement, historique de commandes et rapports de ventes. L'architecture asynchrone — bus Messenger, broker Redis, pipeline de télémétrie InfluxDB — était en production dès la première version publique.

Le client mobile a été la seule surface planifiée qui n'a pas été livrée dans la fenêtre 2022. Le niveau d'API bâti pour l'accueillir est intact. L'espace de noms /Mobile/v1/, les contrats DTO, la gestion de l'authentification — tout cela attend le client, et non l'inverse.

L'architecture dont a hérité l'opérateur a trois frontières claires : Postgres pour l'état, Redis pour la file et le cache, InfluxDB pour les séries temporelles. Chacune peut tomber en panne, être mise à niveau ou montée en charge indépendamment. L'ingénieur suivant n'a pas besoin de six heures d'orientation. Le problème intéressant — la surface de commerce asynchrone — est résolu. Tout ce qui vient après, c'est du travail produit.

Citation / 04
Nous avons construit pour le client mobile avant qu'il n'existe. Quand il sortira, la plateforme ne s'en apercevra pas.
FondateurMarketplace musicale
Outcome
Active development
8 months
Commits across 3 repos
494
Symfony Messenger handlers
21
Entities in canonical schema
25
NEXTÉtude de cas 08SW · 08 sur 10
Fintech2022 — 2026

Calendrier de résultats et exposition gamma SPX, fusionnés en un seul tableau de bord.