Come è fatta BusZilla, sotto il cofano.
Niente scatola nera. Qui raccontiamo come sono messi insieme i pezzi: le app native, il backend, il database, il real-time e le integrazioni esterne. La stessa trasparenza che mettiamo nei dati la mettiamo anche nel codice.
Visione d'insieme
BusZilla è organizzata a strati: in alto i client che usano le persone, al centro il backend che applica le regole e parla con i dati, in basso i servizi esterni da cui peschiamo orari e meteo. Ogni strato conosce solo quello immediatamente sotto.
Client
API gateway — Azure
Backend .NET 8 — superficie
Backend .NET 8 — dominio
Dati
Integrazioni esterne
I client
Tre superfici diverse, tre toni diversi, ma gli stessi dati dietro.
-
App iOS — Swift 5.9 e SwiftUI, target iOS 16+,
architettura MVVM con l'Observation framework. Networking
URLSessionasync/await, mappe MapKit, persistenza locale SwiftData e token nel Keychain. La mascotte è animata conlottie-ios. -
App Android — Kotlin e Jetpack Compose, min SDK 26,
MVVM con
ViewModel+StateFlow, speculare a iOS. Networking Ktor, mappe MapLibre, persistenza Room +EncryptedSharedPreferences, mascotte conlottie-android. - Portale web — ASP.NET Core MVC con Razor Views per le pagine pubbliche (questa che stai leggendo), isole interattive leggere e mappe MapLibre GL JS. Stesso backend delle app.
Il backend: una soluzione, sei progetti
Il cuore è una singola soluzione .NET 8 LTS (BusZilla.sln),
divisa per responsabilità così che il dominio resti indipendente
dall'infrastruttura:
| Progetto | Responsabilità |
|---|---|
BusZilla.Api | Web API per le app mobile, autenticazione JWT con refresh token. |
BusZilla.Web | Portale MVC: vista pubblica + dashboard B2B, cookie auth. |
BusZilla.Core | Entità di dominio, interfacce, regole di business. Zero dipendenze esterne. |
BusZilla.Infrastructure | MongoDB, Redis, provider meteo, parser GTFS dietro interfacce. |
BusZilla.Hubs | Hub SignalR per notifiche live cross-studenti e dashboard. |
BusZilla.Jobs | Job ricorrenti Hangfire (import, meteo, predizioni, push). |
Regole di igiene del codice: nullable reference types
attivi, niente dynamic, niente eccezioni per il controllo di
flusso. Validazione con FluentValidation, mapping DTO con Mapster,
logging strutturato.
Dati e persistenza
Il database principale è MongoDB 7+. Le fermate sono
Point GeoJSON con indice 2dsphere: è ciò che
permette le query "nelle vicinanze" e la validazione geofance di ogni
check-in ($geoWithin + $centerSphere). Le
collezioni principali:
stops— fermate con posizione geospaziale.lines— linee con sequenza fermate e orari teorici.checkins— eventi immutabili, arricchiti con lo snapshot meteo, TTL 13 mesi.stop_line_stats— statistiche pre-aggregate per query veloci, con ilbuszilla_mood.predictions— previsioni con TTL 36 ore.weather_snapshots— meteo per cluster geografico, TTL 7 giorni.users,schools,transit_agencies,alerts,achievements.
Redis fa da cache, da contatore per il rate limiting anti-abuso e da backplane per SignalR quando il backend gira su più istanze.
Real-time
Gli aggiornamenti live — "3 persone hanno appena visto BusZilla in ritardo sulla tua linea", o i numeri che si muovono nella dashboard B2B — passano da SignalR su WebSocket. I client mobile usano il client SignalR ufficiale per Swift e Kotlin; il portale usa il client JS. Con più istanze del backend, Redis tiene sincronizzati i messaggi tra tutte.
Job ricorrenti
Il lavoro che non deve far aspettare l'utente gira in background con Hangfire:
| Job | Cosa fa | Cadenza |
|---|---|---|
| Import GTFS | Scarica e importa gli orari teorici ufficiali. | periodico |
| WeatherFetchJob | Aggiorna il meteo per cluster geografici (~5 km) da Open-Meteo. | ogni 30 min |
| TrafficFetchJob | Campiona il flusso di traffico TomTom per cluster, con budget Redis. | periodico |
| Aggregazioni | Ricalcola stop_line_stats e il mood della mascotte. | ogni 10 min |
| Predizioni notturne | Ricostruisce le previsioni del giorno dopo per ogni bucket. | 02:00 UTC |
| Push serali | Invia le notifiche predittive della sera. | serale |
Integrazioni esterne
-
Meteo — Open-Meteo come provider primario (free,
qualità ECMWF/ICON), OpenWeatherMap come fallback. Tutto dietro
l'astrazione
IWeatherProvider: cambiare provider non tocca la logica di business. -
Traffico — TomTom Traffic Flow come
provider primario, HERE come fallback, dietro l'astrazione
ITrafficProvider. IlTrafficFetchJobcampiona il flusso sui segmenti stradali per cluster geografico, con un budget di chiamate gestito da Redis per restare nel free tier. Il rapporto velocità/flusso libero diventa una feature del modello predittivo, salvata nello snapshot per spiegabilità. - GTFS — orari statici dai portali ministeriali e regionali (parser CSV), e GTFS-Realtime dove disponibile.
-
Push — APNs per iOS, FCM per Android, dietro
l'astrazione
INotificationService.
Il motore di calcolo
Storia dei check-in, meteo e traffico sono solo gli ingredienti. A trasformarli in qualcosa di utile — il mood della mascotte e le previsioni del giorno dopo — è il motore di calcolo proprietario di BusZilla, il pezzo di intelligenza che ci distingue.
- Algoritmi proprietari di scoring e predizione. Il motore combina segnali eterogenei (puntualità storica per bucket, feature meteo, congestione TomTom, reputazione di chi segnala) con pesi e correttori sviluppati e tarati internamente. È qui che vive il nostro know-how.
-
Calcolo del mood.
StatsService.ComputeMood()assegna a ogni tratta uno dei sei stati della mascotte in base a soglie di puntualità e segnalazioni attive. - Confidenza e spiegabilità. Ogni previsione porta con sé un punteggio di confidenza e la basis dei dati che l'hanno generata: gli algoritmi sono proprietari, ma il risultato resta verificabile.
I principi e le soglie di base li raccontiamo apertamente nella metodologia: la trasparenza sui criteri convive con la proprietà industriale sull'implementazione che li orchestra.
Il percorso di un check-in
Tutto si tiene insieme nel momento in cui uno studente preme "check-in". Ecco il viaggio end-to-end:
Tap sull'app
Il client legge la posizione GPS una volta sola e invia fermata, linea, stato e coordinate all'API via HTTPS.
Validazione server
Il backend verifica il geofence con MongoDB, applica il rate-limit Redis, poi scarta le coordinate.
Arricchimento e salvataggio
Il check-in viene arricchito con gli snapshot di meteo e traffico
e salvato come evento immutabile in checkins.
Aggregazione
Il job ricalcola stop_line_stats e il mood della
mascotte per quel bucket.
Notifica live
Se rilevante, SignalR avvisa in tempo reale gli altri studenti della stessa linea.
Previsione di domani
Di notte il dato entra nel motore di calcolo proprietario, che con meteo e traffico genera le previsioni del giorno dopo.
Privacy e anti-abuso, by design
L'architettura non è neutra: alcune scelte esistono apposta per proteggere chi usa l'app.
- Nessun tracciamento continuo: il GPS si legge solo al check-in, in foreground.
- Posizioni usa-e-getta: scartate dopo la validazione, mai salvate nel database.
- ID pseudonimi: nessuna identità reale legata ai check-in.
- Non fidarsi mai del client: geofence e rate-limit sono sempre server-side.
Infrastruttura e hosting su Azure
BusZilla gira su Microsoft Azure. Il backend è impacchettato in container Docker (Dockerfile multi-stage), quindi resta portabile — ma il target di produzione è il cloud Azure.
-
Azure App Service — ospita
BusZilla.Api(Web API mobile) eBusZilla.Web(portale + dashboard B2B). L'app è "reverse-proxy aware": rispetta gli header inoltrati dal load balancer Azure. - Azure API Management (APIM) — fa da API gateway davanti alla Web API mobile e alle API pubbliche B2B. Centralizza routing, throttling e rate limiting, gestione e validazione delle API key B2B, versionamento e observability, così il backend resta concentrato sulla logica di dominio.
- MongoDB Atlas — il database gestito, con IP access list sugli IP in uscita dell'App Service. In alternativa, MongoDB self-hosted.
- Azure Cache for Redis — cache, rate limit e backplane SignalR.
- Azure Key Vault — i segreti di produzione (chiavi JWT, chiave TomTom, credenziali APNs/FCM) non sono mai nel repo.
- Application Insights — logging strutturato, metriche e tracing distribuito.
Qualità e contratti
- Test — xUnit + Moq per il backend, XCTest per iOS, JUnit + MockK per Android.
- API contract-first — schemi OpenAPI generati da .NET e usati come riferimento per i client.
- CI/CD — GitHub Actions: pipeline Linux per il backend (build, test, deploy su Azure App Service), macOS per iOS, Linux per Android.
- Container — Dockerfile multi-stage, docker-compose locale con api + mongo + redis.
Stack moderno, confini netti tra gli strati, e la privacy scritta nell'architettura prima ancora che nelle informative. BusZilla è trasparente dentro come fuori.