Vai al contenuto
Architettura tecnica

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.

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 URLSession async/await, mappe MapKit, persistenza locale SwiftData e token nel Keychain. La mascotte è animata con lottie-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 con lottie-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:

ProgettoResponsabilità
BusZilla.ApiWeb API per le app mobile, autenticazione JWT con refresh token.
BusZilla.WebPortale MVC: vista pubblica + dashboard B2B, cookie auth.
BusZilla.CoreEntità di dominio, interfacce, regole di business. Zero dipendenze esterne.
BusZilla.InfrastructureMongoDB, Redis, provider meteo, parser GTFS dietro interfacce.
BusZilla.HubsHub SignalR per notifiche live cross-studenti e dashboard.
BusZilla.JobsJob 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 il buszilla_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:

JobCosa faCadenza
Import GTFSScarica e importa gli orari teorici ufficiali.periodico
WeatherFetchJobAggiorna il meteo per cluster geografici (~5 km) da Open-Meteo.ogni 30 min
TrafficFetchJobCampiona il flusso di traffico TomTom per cluster, con budget Redis.periodico
AggregazioniRicalcola stop_line_stats e il mood della mascotte.ogni 10 min
Predizioni notturneRicostruisce le previsioni del giorno dopo per ogni bucket.02:00 UTC
Push seraliInvia 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.
  • TrafficoTomTom Traffic Flow come provider primario, HERE come fallback, dietro l'astrazione ITrafficProvider. Il TrafficFetchJob campiona 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:

1

Tap sull'app

Il client legge la posizione GPS una volta sola e invia fermata, linea, stato e coordinate all'API via HTTPS.

2

Validazione server

Il backend verifica il geofence con MongoDB, applica il rate-limit Redis, poi scarta le coordinate.

3

Arricchimento e salvataggio

Il check-in viene arricchito con gli snapshot di meteo e traffico e salvato come evento immutabile in checkins.

4

Aggregazione

Il job ricalcola stop_line_stats e il mood della mascotte per quel bucket.

5

Notifica live

Se rilevante, SignalR avvisa in tempo reale gli altri studenti della stessa linea.

6

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) e BusZilla.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.

Come calcoliamo la puntualità → Leggi la privacy policy