Vai al contenuto

(04) Sistemi / Caso di studio

yourFinance

I saldi li garantisce il database.

yourFinance è un'app di finanza personale multi-utente: un'API in Rust, PostgreSQL e un solo client per iOS, Android, web e desktop. Il codice è privato; qui racconto com'è costruita e perché.

Inizio
Luglio 2025
Versione
1.2.0, ottobre 2026
Team
Due sviluppatori
Codice
Privato

Rust · Axum · SQLx · PostgreSQL 18 · Expo · React Native · Tauri · Docker

Il problema

I dati di finanza personale servono solo se ci si può fidare dei saldi. L'app è usata da più persone su più dispositivi, e un errore in un client o in un handler non deve poter lasciare un conto con la cifra sbagliata.

Deve anche gestire più valute, importare tutto lo storico di un'altra app (Money Manager) e mostrare statistiche che non sono mai vecchie.

Vincoli

  • L'API non scrive mai un saldo: ogni variazione è un movimento, anche la rettifica.
  • Ogni riga personale appartiene a un utente, e PostgreSQL ripete la regola con la row-level security su ogni connessione del pool.
  • Un solo codice per iOS, Android e web; l'app desktop è l'export web dentro una shell Tauri.
  • Una modifica allo schema o all'API deve rompere la build, non l'app in esecuzione: l'SQL è verificato in compilazione e i tipi del client sono generati dal documento OpenAPI.

Architettura

Tre livelli: il client presenta soltanto, l'API autentica, valida e aggrega, il database è la fonte di verità. L'API è un workspace Cargo di crate a strati: ognuno dipende solo da quelli sotto, e la CI verifica che il dominio resti senza I/O.

  1. yourf-api

    HTTP: rotte sottili, autenticazione, mappatura degli errori, OpenAPI.

    yourf-worker

    Job in background: notifiche push, riepilogo degli avvisi, movimenti ricorrenti, riconciliazione dei saldi.

  2. yourf-runtime

    Ciò che i due binari condividono: configurazione, telemetria, ciclo di vita.

  3. yourf-db

    Repository SQLx, ambito della row-level security, scritture in una sola transazione.

    yourf-import

    Lettori dei file di altre app verso un formato neutro, senza database.

  4. yourf-domain

    Modelli, contratti e regole di business. Nessun I/O.

Due binari, una sola immagine: API e worker si rilasciano e scalano separatamente.

Una richiesta, dall'inizio alla fine

La creazione di un movimento:

  1. 01Client

    Il form valida la bozza e la invia con il token.

  2. 02API

    Autentica il token, carica l'utente, valida il corpo e inserisce il movimento con le sue ripartizioni per conto e categoria in una sola transazione.

  3. 03Database

    I trigger differiti controllano che le ripartizioni tornino e che conti, categorie e tag siano dell'utente, poi aggiornano i saldi. Una violazione annulla tutto e diventa un 400 con un codice di regola stabile.

  4. 04Client

    Se va a buon fine, elenchi, totali e dashboard si ricaricano.

Scelte e compromessi

  1. 01

    L'integrità sta nel database

    Saldi, proprietà dei dati e regole sulle categorie sono garantiti dai trigger. Significa scrivere logica in SQL invece che in Rust; in cambio nessun client e nessun handler può corrompere i dati.

  2. 02

    Saldi verificabili, non una partita doppia completa

    Ogni effetto su un saldo è una registrazione con segno, con un'unica definizione usata sia dal trigger che scrive i saldi sia dal job che li riconcilia. Una partita doppia completa varrebbe la riscrittura solo se una funzione avesse bisogno di uno stato patrimoniale.

  3. 03

    Un trasferimento è un movimento con due importi

    Quanto è uscito dal conto di origine e quanto è arrivato a destinazione. La differenza copre sia un cambio di valuta sia una commissione, che quindi non compare come spesa nelle statistiche.

  4. 04

    Nessuna coda di job

    Il worker prende le righe in scadenza con FOR UPDATE SKIP LOCKED e la coda delle push è svegliata da LISTEN/NOTIFY. Ogni passata è idempotente: eseguirla due volte, o su più repliche, dà lo stesso risultato.

  5. 05

    Statistiche calcolate a ogni richiesta

    Dashboard e statistiche aggregano i movimenti in SQL ogni volta: nessun aggregato precalcolato e nessuna cache, quindi le cifre non sono mai vecchie. Il prezzo è che le query devono essere veloci da sole.

Misure

Il profiling delle query analitiche ne ha mostrate due che crescevano con tutto lo storico invece che con il periodo scelto. Con il piano generico di un prepared statement, il planner sceglieva un nested loop che rieseguiva un join per ogni movimento. Gli indici non c'entravano: la correzione è stata riscrivere la vista in modo che ogni ramo parta dal movimento.

Profilo demo, un anno di dati, piani generici, misurato con EXPLAIN ANALYZE con il ruolo dell'applicazione.
QueryPrimaDopo
Flussi giornalieri (andamento delle statistiche)daily_flows64.5 ms1.9 ms
Spese essenziali (riepilogo della dashboard)essential_expenses48.0 ms1.5 ms

L'app

Dashboard di yourFinance su desktop: patrimonio netto, FIRE number, tasso di risparmio, fondo di emergenza e il grafico del patrimonio netto su dodici mesi.
Dashboard: sei metriche, patrimonio netto su dodici mesi, asset allocation. Dati demo.
Schermata Statistiche: entrate, uscite, netto e tasso di risparmio degli ultimi dodici mesi, con un grafico a barre mensile.
Statistiche: entrate e uscite per mese, con filtri e ripartizione per categoria, conto o tag. Dati demo.
Elenco dei movimenti su telefono: totali di entrate e uscite, poi una scheda per movimento.
Lo stesso codice su telefono: l'elenco dei movimenti. Dati demo.

Cosa viene dopo

  • Una scadenza sui conti, con un avviso prima che arrivi.
  • L'ammortamento: un costo ripartito su più mesi nelle statistiche, mentre il saldo resta di cassa.
  • Un assistente che risponde a domande su statistiche e conti con strumenti di sola lettura: il modello sceglie lo strumento, ogni numero lo calcola il backend.

Codice e demo

Il repository è privato. Su richiesta mostro il codice e l'app in funzione.

ScrivimiTorna a Sistemi