Un marketplace, non un servizio di streaming. La differenza conta a livello architetturale.
L'operatore non stava costruendo un servizio di streaming in cui gli artisti caricano per visibilità e i ricavi arrivano dopo come quota pubblicitaria. Gli artisti possiedono i loro contenuti. Gli ascoltatori li pagano. La piattaforma è un marketplace: listini di prodotti, carrello, checkout, cronologia degli ordini, report di vendita. Il modello di business vive in quel flusso.
Quella distinzione rimodella l'architettura. Il problema più difficile di un servizio di streaming è il throughput di ingest e la latenza di riproduzione. Il problema più difficile di un marketplace è la superficie di commercio: operazioni di carrello concorrenti, coerenza degli ordini, un checkout che non perde un acquisto se un worker in background è lento. Sbaglia questi e perdi transazioni reali da artisti che già sono scettici verso una nuova piattaforma.
L'operatore aveva anche in programma un client mobile — iOS e Android, da costruire in seguito — e voleva che il livello API fosse pronto per esso. Non adattato a posteriori. Pronto fin dall'inizio, con endpoint versionati basati su DTO e una chiara separazione Admin/Mobile. L'app mobile non è stata pubblicata nella finestra del 2022. Il livello API sì.
Il brief: una superficie di commercio completa per un catalogo di proprietà degli artisti, un nucleo asincrono che non blocca il checkout sul lavoro in background e un livello API che non avrà bisogno di essere ripensato quando arriverà il client mobile.
Due livelli, un bus di messaggi e tre store con compiti distinti.
Il sistema si è suddiviso in due applicazioni Symfony fin dal primo giorno. Il monorepo — l'amministrazione e la UI web — gestiva la gestione dei profili degli artisti, i listini di prodotti, gli upload di artwork e il back-office dell'operatore. Il livello API è stato costruito separatamente, come servizio REST versionato a DTO con namespace di route /Mobile/v1/ e /Admin/v1/, 96 endpoint mobile e 130 endpoint admin, documentato e pronto per il client mobile in qualunque momento fosse arrivato.
I flussi di carrello e ordine passano per Symfony Messenger, non per scritture sincrone. AddToCartController dispaccia un messaggio; l'handler elabora la mutazione del carrello in modo asincrono. I record degli ordini — Order e ProductOrder, con quantità e costo totale tracciati per riga — seguono lo stesso schema. Il checkout non si blocca su scritture di telemetria, elaborazione di artwork o qualsiasi altra cosa giri sul bus. Il database operativo resta veloce perché il fan-out delle scritture è gestito.
Tre store, ciascuno con una singola responsabilità. PostgreSQL è lo stato canonico: 25 entità, oltre 40 indici sulle tabelle calde — utenti, prodotti, ordini, articoli del carrello, generi, tag, relazioni artista-prodotto. Redis fa girare sia il broker della coda di Messenger sia il livello di cache per le letture calde. InfluxDB conserva la telemetria delle azioni degli utenti. Gli eventi comportamentali ad alta frequenza non toccano mai il database operativo.
I profili degli artisti e le descrizioni dei prodotti — titoli degli album, nomi delle tracce, etichette di genere, notizie — sono traducibili a livello di schema tramite KnpLabs DoctrineBehaviors. Aggiungere una lingua è una migrazione di dati, non una modifica al codice. La UI web faceva girare Plyr per la riproduzione di audio e video; CropperJS gestiva il ritaglio e l'upload dell'artwork in linea.
- F · 01Carrello e checkout tramite Messenger
- Le mutazioni del carrello e l'elaborazione degli ordini passano per Symfony Messenger, non per scritture sincrone. Il checkout dà riscontro immediato; il bus gestisce il fan-out delle scritture.
- F · 02Architettura a due livelli
- Monorepo (amministrazione e UI web) e un servizio API separato versionato a DTO con namespace /Mobile/v1/ e /Admin/v1/ — 96 endpoint mobile e 130 endpoint admin — costruito prima che esistesse il client mobile.
- F · 03Separazione in tre store
- PostgreSQL per lo stato canonico (25 entità, oltre 40 indici), Redis per broker della coda e cache, InfluxDB per la telemetria delle azioni degli utenti. Ogni store ha un solo compito.
- F · 04Contenuti traducibili a livello di schema
- KnpLabs DoctrineBehaviors su descrizioni dei prodotti, profili degli artisti, etichette di genere e notizie. Aggiungere una lingua è una migrazione, non una modifica al codice.
- F · 05Pipeline di telemetria InfluxDB
- Gli eventi delle azioni degli utenti passano attraverso gli handler di Messenger verso InfluxDB. La pipeline è architettata per la telemetria completa di riproduzione, scrubbing e skip senza toccare il database operativo.
- F · 06Elaborazione asincrona degli ordini
- Le entità Order e ProductOrder tracciano gli acquisti con quantità e costo per riga. Le mutazioni degli ordini passano per il bus di messaggi così le operazioni di commercio non si contendono a vicenda.
Il carrello passa per il bus. L'API esiste prima dell'app che la chiamerà.
La decisione di instradare le mutazioni del carrello attraverso Symfony Messenger invece che con scritture sincrone non riguardava la scala. Riguardava la correttezza. Le operazioni di carrello in un marketplace comportano controlli di inventario, validazione dello stato del prodotto e gestione di sessioni concorrenti. Metterle sul bus di messaggi disaccoppia il riscontro rivolto all'utente dal fan-out delle scritture. L'ascoltatore ottiene una risposta; la piattaforma si mette in pari. Il database operativo non corre mai contro la superficie di commercio.
Lo stesso principio si estende alla riproduzione. PlayFileHandler e PlayedFileService disaccoppiano gli eventi di riproduzione dal database operativo. La telemetria delle azioni degli utenti scrive su InfluxDB tramite il bus di messaggi — 21 handler in totale nel livello API. La pipeline InfluxDB è architettata per la telemetria completa di riproduzione, scrubbing e skip; collegare quegli eventi aggiuntivi è il passo successivo che l'operatore può compiere senza ri-architettare nulla.
Tre store è la risposta quando ciascuno store si guadagna il proprio posto facendo il lavoro in cui è migliore — e nient'altro.
Il livello API è stato deliberatamente sovradimensionato per il 2022. /Mobile/v1/ esisteva con 96 endpoint prima che fosse scritta una sola riga di codice dell'app mobile. Il client mobile non è stato pubblicato nella finestra del 2022, ma l'operatore ha ereditato un servizio API già versionato, documentato e separato dalla superficie di amministrazione. Il costo di farlo dopo — con un prodotto live, dati reali degli utenti e uno schema che si è disallineato — non è paragonabile al costo di farlo una volta sola all'inizio.
Otto mesi. Tre repository. Una superficie di commercio che l'operatore può estendere.
Otto mesi di sviluppo attivo. 494 commit su tre repository — il monorepo, il livello API e un sito di atterraggio minimale in HTML/Gulp. Il sistema è stato pubblicato con una superficie di commercio completa da artista ad ascoltatore: listini di prodotti, carrello, checkout, cronologia degli ordini e report di vendita. L'architettura asincrona — bus Messenger, broker Redis, pipeline di telemetria InfluxDB — era in produzione fin dalla prima release pubblica.
Il client mobile è stata l'unica superficie pianificata a non essere pubblicata nella finestra del 2022. Il livello API costruito per riceverlo è intatto. Il namespace /Mobile/v1/, i contratti DTO, la gestione dell'autenticazione — tutto attende il client, anziché il contrario.
L'architettura che l'operatore ha ereditato ha tre confini chiari: Postgres per lo stato, Redis per coda e cache, InfluxDB per le serie temporali. Ciascuno può guastarsi, essere aggiornato o scalato in modo indipendente. Il prossimo ingegnere non ha bisogno di un orientamento di sei ore. Il problema interessante — la superficie di commercio asincrona — è risolto. Tutto ciò che viene dopo è lavoro di prodotto.
Abbiamo costruito per il client mobile prima che esistesse. Quando verrà pubblicato, la piattaforma non se ne accorgerà.
