Progetto personale di lunga durata: un bot Discord da una parte, un motore statistiche su League of Legends dall’altra.
PRIMO COMMIT
3 febbraio 2022
EVOLUZIONE
Bot Discord → API LoL
DATI
MongoDB + Redis
STACK
Java, Spring, JDA
Origine
Prima le persone nel server. Poi il motore dei dati.
Il primo commit di Beebot è del 3 febbraio 2022. Il punto di partenza è un bot Discord in Java: comandi per il server, riproduzione audio e, poco dopo, caricamento di suoni personalizzati. La storia non comincia con una piattaforma di statistiche già progettata: le funzioni crescono insieme agli usi del bot.
League of Legends diventa progressivamente un dominio a sé. All’inizio ci sono comandi per evocatori, campioni e rune. Nel 2024 arriva il tracking degli LP; quando il tracker comincia a conservare molto più del risultato in classifica, cambia anche nome: da LPTracker a MatchTracker.
Il passaggio importante è dalla consultazione di dati esterni alla costruzione di uno storico proprio. Partite persistite, statistiche derivate e build diventano riutilizzabili da più interfacce. Oggi i comandi Discord e le API HTTP condividono i servizi di dominio; Iodid usa queste API per presentare i dati nel browser.
Come funziona
Dietro una richiesta di profilo.
Gli scenari illustrano il flusso logico tra cache, persistenza e job Riot. I tempi sono dimostrativi: la risposta HTTP distingue dati disponibili, parziali e ancora in preparazione.
beebot / api trace
API
Redis
MongoDB
Riot
GET/api/lol/EUW1/profile/{puuid}
REDISGET beebot:lol:ls:EUROPE:EUW1:{puuid}:summonerHIT · 2 ms
200SummonerView6 ms
API
Redis
MongoDB
Riot
GET/api/lol/EUW1/profile/{puuid}
REDISGET beebot:lol:ls:EUROPE:EUW1:{puuid}:summonerMISS · 1 ms
MONGOsummoner.findOne({ puuid })HIT · 9 ms
REDISSET beebot:lol:ls:EUROPE:EUW1:{puuid}:summoner1 ms
200SummonerView15 ms
API
Redis
MongoDB
Riot
GET/api/lol/EUW1/profile/{puuid}
REDISGET beebot:lol:ls:EUROPE:EUW1:{puuid}:summonerMISS · 1 ms
RIOTGET /riot/account/v1/accounts/by-puuid/{puuid}200 · 141 ms
RIOTGET /lol/summoner/v4/summoners/by-puuid/{puuid}200 · 152 ms
MONGOsummoner.insertOne(profile)7 ms
REDISSET beebot:lol:ls:EUROPE:EUW1:{puuid}:summoner1 ms
200SummonerViewRETRY · 6 ms
API
Redis
MongoDB
Riot
GET/api/lol/EUW1/profile-by-name/Player/EUW
REDISRiot ID lookup · Player#EUW · EUW1MISS · 1 ms
MONGOsaved summoner lookup by Riot IDMISS · 8 ms
RIOTGET /riot/account/v1/accounts/by-riot-id/Player/EUW404 · 138 ms
404not_found149 ms
Esempio con tempi indicativi. Le chiamate a Riot passano sempre dalla coda.
Framework
Un’app Java, quattro strati.
App avvia insieme API Spring, scheduler e bot JDA. I comandi Discord e i controller HTTP non contengono logica di dominio: passano entrambi dagli stessi servizi, che delegano il lavoro lento a code e scheduler.
INGRESSO
JDA per i comandi Discord, Spring su Tomcat embedded per le API.
SERVIZI
Profili, partite, campioni e classifiche, condivisi da bot e API.
JOB
Code e scheduler per chiamate Riot, tracker e calcoli in differita.
DATI
MongoDB canonico, Redis derivato, MariaDB per guild e impostazioni.
Logica
Dall’identità del giocatore alla risposta HTTP.
01
Un nome per cercare, un PUUID per identificare
La ricerca accetta un Riot ID o un prefisso e, facoltativamente, uno shard. Il risultato conserva regione e identità del giocatore. Dopo la risoluzione, il PUUID è la chiave stabile per profilo e partite: il nome visibile può cambiare senza diventare una nuova identità.
02
Servire ciò che esiste, preparare ciò che manca
I servizi leggono cache Redis e dati persistiti in MongoDB. Se manca ancora l’identità o la base del profilo, l’API restituisce 202. Quando la base c’è ma alcune componenti mancano, può restituire 200 con una vista parziale e metadata.refresh=true. Il client non deve trasformare “in preparazione” in statistiche uguali a zero.
03
Una richiesta non deve duplicare un lavoro già avviato
Il registro dei job deduplica il lavoro attivo per tipo, rotta logica e chiave. RiotScheduler separa le chiamate per shard; ComputeScheduler gestisce i calcoli e SyncScheduler coordina le sincronizzazioni. Un lavoro richiesto con priorità maggiore può promuovere quello già in coda senza avviarne una copia.
04
Lo storico delle partite ha un contratto separato
La lista delle partite legge i match già persistiti in MongoDB. Filtri, paginazione e faccette non richiedono di attendere Riot durante la GET. Il dettaglio di una partita aggiunge gli eventi della timeline; la lista mantiene partecipanti e ban, ma evita quel carico aggiuntivo.
API ed esempi
API reali, esempi leggibili.
Gli URL usano localhost e identità dimostrative. I frammenti descrivono il contratto del codice: non sono misurazioni o risposte catturate in produzione.
Esempio 01 / bash
Cercare senza confondere nome e regione
Il carattere # fa parte del valore della query e va codificato. Omettendo shard la ricerca è globale; passando EUW1 viene limitata a quella regione. La risposta è una lista, anche quando non ci sono risultati.
Il nome e il tag sono segmenti distinti del percorso. La risposta disponibile è un SummonerView, senza un wrapper data. Un 202 indica che la risoluzione o il caricamento della base sono ancora in corso; un 200 può già contenere componenti parziali.
GET /api/lol/EUW1/profile-by-name/Player/EUW?queue=RANKED_SOLO_5X5&season=2026
200 OK → profilo disponibile, eventualmente parziale
202 Accepted → base del profilo ancora in preparazione
404 Not Found → identità o profilo non trovato
Esempio 03 / bash
Leggere una pagina di partite
Sostituisci example-puuid con il PUUID ottenuto dal profilo. season=previous seleziona la stagione precedente configurata; limit e offset controllano la pagina. Il filtro lane riguarda il giocatore selezionato, non un partecipante qualsiasi della partita.
Quattro anni di evoluzioni, non una riscrittura magica.
Le date indicano modifiche documentate in Git. Una modifica o un merge non dimostrano, da soli, la data di un rilascio in produzione.
Il primo bot Java
Il repository parte con l’app Java e le prime funzioni Discord e audio. A marzo compaiono upload e riproduzione di suoni personalizzati: una delle funzioni che resteranno nel progetto.
Dai comandi testuali agli slash command
Comincia l’introduzione degli slash command, seguita dalla conversione delle famiglie di comandi. Nel frattempo crescono gestione server, permessi e configurazioni per guild.
League of Legends diventa una funzione riconoscibile
Il comando champion e i successivi miglioramenti a rune, profili e autocomplete strutturano l’integrazione LoL. Il bot non è più soltanto un insieme di comandi audio.
La prima soundboard
La cronologia documenta una prima release della soundboard e il lavoro su suoni pubblici e privati. La parte community continua a evolvere accanto alle funzioni di gioco.
Il tracker degli LP
Arrivano tracking degli LP, comando summoner track e grafico dedicato. Non basta più mostrare uno stato corrente: occorre conservare come quel valore cambia nel tempo.
Da LPTracker a MatchTracker
Il tracker cambia nome perché raccoglie ormai dati delle partite, non soltanto LP. Seguono ban, oggetti e lavoro di analisi: lo storico diventa la base delle statistiche.
Statistiche dai dati del progetto
Il comando champion comincia a usare dati raccolti dal sistema. Il significato architetturale è importante: raccogliere, conservare e analizzare sostituisce la sola lettura di una fonte esterna.
Un’interfaccia HTTP per il dominio LoL
Un ramo dedicato introduce il lavoro su Spring per i dati di League of Legends. Nei mesi successivi i servizi e i contratti diventano un confine riutilizzabile anche fuori da Discord.
Redis entra nel flusso
La cache Redis viene introdotta e poi estesa a profili, partite, build e dati statici. Le chiavi e le scadenze diventano responsabilità esplicite del sistema.
La migrazione LoL verso MongoDB
Il merge lol-api-mongo riunisce modelli, persistenza, tracker e documentazione della migrazione. Il dominio LoL passa dai vecchi percorsi SQL a MongoDB; MariaDB mantiene altre responsabilità del bot.
Il lavoro in background diventa osservabile
Il refactor dei job riorganizza dispatcher, priorità e stato operativo. Il sistema attuale distingue il registro logico dei lavori dalle code e dai worker che li eseguono.
Una cache esterna per i payload Riot
La cronologia documenta il passaggio a EmptyCacheProvider per R4J e a Redis per le risposte Riot. Cache applicativa, dati canonici e invalidazione hanno confini più espliciti.
Separare senza fingere che sia già tutto distribuito
L’estrazione dell’artefatto S4B e l’alpha di lol-service esplorano confini indipendenti. Sono tappe della separazione, non una prova che ogni parte del prodotto sia già un servizio autonomo in produzione.
Import dello storico e promozione dei job
Un nuovo endpoint per lo storico si accompagna al lavoro su MatchService e sulle priorità. La deduplicazione deve restare utile anche quando una richiesta interattiva arriva dopo un import in background.
Stato attuale
Il prodotto attuale è una piattaforma condivisa.
Il punto d’arrivo non è soltanto “un bot con più comandi”. È un nucleo Java che raccoglie e conserva partite, espone contratti HTTP e riusa gli stessi servizi nei comandi Discord. La separazione tra persistenza, cache, calcolo e presentazione permette a Iodid di esistere senza replicare il backend.
Le dimensioni del database e la latenza dipendono da dati e ambiente: questa ricostruzione non attribuisce nuovi numeri di traffico o prestazioni al progetto. Le risposte parziali, il rate limit e il costo di rigenerazione degli aggregati restano vincoli reali del sistema.
Scelte progettuali
Le scelte che reggono il sistema.
Mongo è la fonte, Redis si ricostruisce
Partite, profili e aggregati vivono in MongoDB. Redis conserva cache ricostruibili: componenti del profilo, payload Riot, cataloghi statici e proiezioni.
CostoL’invalidazione non è automatica: va gestita a ogni scrittura.
Le API non aspettano Riot
Un profilo assente risponde 202 e accoda i job. I dati parziali arrivano con 200 e un segnale di refresh.
CostoIl client deve gestire gli stati PENDING, PARTIAL e READY.
Priorità e deduplicazione prima delle copie
Le richieste Riot passano da code per shard. Il registro dei job riusa il lavoro attivo e può promuoverne la priorità quando arriva una richiesta interattiva.
CostoLifecycle, priorità e completamento dei figli devono restare coerenti.