Architettura di dati e sistemi, dalle fondamenta Lezione 2 / 80

Requisiti funzionali e non funzionali

La trappola di concentrarsi sulle funzionalità. Le qualità che guidano l'architettura: latency, throughput, availability, durability, consistency, sicurezza, evolvibilità.

Nella lezione precedente siamo arrivati a una definizione di lavoro: l’architettura è l’insieme delle decisioni che sono costose da cambiare in seguito. Ora dobbiamo fare la domanda ovvia che ne consegue. Cosa guida quelle decisioni? Qual è l’input che usi davvero per capire quale database, quale topologia di deployment, quale stile di comunicazione è giusto per il tuo sistema?

La risposta sbagliata, e quella che la maggior parte dei team raggiunge nella prima settimana di un progetto, è “la lista delle funzionalità”. I product owner consegnano agli ingegneri una lista di cose che il sistema deve fare. Registrarsi. Fare login. Sfogliare un catalogo. Effettuare un ordine. Inviare un’email di conferma. Il team legge la lista, abbozza un diagramma veloce, sceglie uno stack e inizia a costruire. Sei mesi dopo scoprono di aver fatto la scelta sbagliata su tre delle decisioni che valgono per anni, e ora ne stanno pagando le conseguenze.

Il motivo per cui questo succede è che le liste di funzionalità, da sole, non contengono abbastanza informazioni per progettare un sistema. Due sistemi con liste di funzionalità identiche possono richiedere architetture radicalmente diverse. Quello che li separa è la seconda classe di requisiti, quella che viene ignorata o sottospecificata, ed è il soggetto di questa lezione.

Requisiti funzionali: cosa fa il sistema

I requisiti funzionali descrivono il comportamento del sistema. Rispondono alla domanda “cosa fa?”.

Un piccolo sito di e-commerce potrebbe avere requisiti funzionali come:

  • Gli utenti possono creare un account con email e password.
  • Gli utenti possono sfogliare un catalogo di prodotti.
  • Gli utenti possono aggiungere prodotti a un carrello e fare checkout.
  • Gli utenti possono pagare con carta di credito o addebito diretto SEPA.
  • Il sistema invia un’email di conferma ordine dopo un checkout riuscito.
  • Gli admin possono aggiungere e modificare prodotti tramite un pannello interno.
  • Il sistema supporta il calcolo dell’IVA per gli stati membri UE.

Questi sono i bullet che finiscono sulla roadmap del product owner e nelle user story. Sono essenziali, sono il modo in cui il team e il business si mettono d’accordo su cosa si sta costruendo, e non sono sufficienti per progettare il sistema. Ognuno di quei bullet potrebbe essere implementato come un singolo script Python che gira sul portatile di qualcuno, oppure come un sistema distribuito globalmente che gestisce un milione di transazioni al minuto. I bullet non ti dicono quale dei due.

C’è un esercizio famoso che gli intervistatori adorano: “progetta Twitter”. Se leggi il brief alla lettera, i requisiti funzionali sono più o meno:

  • Gli utenti possono postare un messaggio breve.
  • Gli utenti possono seguire altri utenti.
  • Gli utenti possono vedere un feed di messaggi delle persone che seguono.

Quei quattro bullet (anzi, tre). È l’intera lista delle funzionalità. Potresti implementarlo in un pomeriggio con un database Postgres, una app Flask e una query SELECT ... ORDER BY created_at DESC LIMIT 50. Fatto.

Ovviamente non è questo che rende il problema di design interessante. La versione interessante di “progetta Twitter” emerge solo quando l’intervistatore aggiunge la seconda categoria di requisiti: 500 milioni di utenti, 100 milioni di utenti attivi giornalieri, picco di carico di mezzo milione di tweet al minuto, letture di feed medie di due miliardi al giorno, latency p99 sotto i 200 millisecondi, target di availability del 99,9%, residenza dei dati per ragioni regolamentari in tre giurisdizioni. Ora il design è interessante. Ora la scelta del database conta, la strategia di caching conta, il deployment geografico conta, e la domanda “il feed va calcolato in lettura o pre-calcolato in scrittura?” diventa il dibattito architetturale centrale.

Stessa lista di funzionalità. Architettura completamente diversa. Quello che è cambiato sono i requisiti non funzionali.

Requisiti non funzionali: quanto bene

I requisiti non funzionali descrivono le qualità del sistema. Rispondono alla domanda “quanto bene fa quello che fa, e in quali condizioni?”.

Questa è la categoria di requisiti che guida l’architettura. Indovinali e le scelte architetturali derivano per lo più da soli. Sbagliali, o peggio ignorali, e costruirai il sistema giusto per l’universo sbagliato.

Ci sono grossomodo sette qualità non funzionali che ricorrono quando si dà forma a un sistema. Diversi autori le organizzano in modo leggermente diverso e i confini sono un po’ sfumati, ma questo è il set di lavoro.

Latency

La latency è quanto tempo impiega una singola richiesta per completarsi, misurato dal punto di vista dell’utente. Si cita quasi sempre come percentile, non come media, perché le medie nascondono la coda.

Un tipico target di latency suona così: “p50 sotto 80 ms, p95 sotto 200 ms, p99 sotto 500 ms” per un endpoint API. Significa che metà di tutte le richieste finisce sotto gli 80 ms, il 95% sotto i 200 ms, il 99% sotto i 500 ms. L’1% oltre quella soglia è la coda, e la coda è dove vivono le brutte esperienze utente. La media (il “p50” è la mediana, ma per distribuzioni ben educate la media è simile) è il numero che fa bella figura nelle slide. Il p99 è il numero che senti davvero.

I target di latency guidano: dove metti i tuoi server (più vicini agli utenti significa minore latency), se fai caching aggressivo, se pre-calcoli i risultati, se usi comunicazione sincrona o asincrona, e se ti è permesso fare una chiamata al database lungo l’hot path.

Throughput

Il throughput è quante richieste, eventi o unità di lavoro il sistema gestisce per unità di tempo. Spesso citato come richieste al secondo (RPS) per le API, transazioni al secondo (TPS) per i database, messaggi al secondo per le code, o eventi al secondo per i sistemi di streaming.

Un target tipico: “5.000 richieste al secondo sostenute, 20.000 al secondo di picco, con picco fino a 30 minuti durante una flash sale”. La parte interessante è il rapporto tra sostenuto e picco, e la durata del picco. Un sistema che gestisce 5.000 RPS allegramente ma fonde a 8.000 RPS va bene se il tuo traffico è piatto, ed è un disastro se hai degli spike.

I target di throughput guidano: come scali (verticalmente o orizzontalmente), se ti serve un load balancer, come partizioni i dati, come dimensioni i tuoi pool di worker, e se ti serve assorbire i burst con una coda.

Availability

L’availability è la percentuale di tempo in cui il sistema è up e serve richieste con successo. Si cita in nove.

  • 99% di availability è “due nove”: 3,65 giorni di downtime all’anno. Accettabile per uno strumento interno.
  • 99,9% è “tre nove”: 8,76 ore all’anno. Tipico SaaS.
  • 99,95% è “tre nove e mezzo”: 4,38 ore all’anno. SLO comune per B2B a pagamento.
  • 99,99% è “quattro nove”: 52,6 minuti all’anno. Aggressivo. Non puoi più fare deploy con leggerezza.
  • 99,999% è “cinque nove”: 5,26 minuti all’anno. Telco. Ti serve ridondanza vera a ogni livello.

La cosa che frega la gente è che ogni nove aggiuntivo costa grossomodo un ordine di grandezza in più di sforzo ingegneristico. Passare dal 99% al 99,9% è soprattutto una questione di disciplina: non spedire bug in produzione, avere una storia di rollback, monitorare quello che hai. Passare dal 99,9% al 99,99% è una questione di ridondanza: più availability zone, failover automatico, niente single point of failure. Passare dal 99,99% al 99,999% è una questione che riguarda tutto: multi-region, change management formale, chaos engineering, pager che squillano una domenica mattina.

I target di availability guidano: ridondanza (quante copie di ogni servizio), topologia di deployment (single AZ, multi-AZ, multi-region), come gestisci i deploy (blue/green, canary, rolling), e se puoi proprio buttare giù il sistema per manutenzione.

Durability

La durability è la probabilità che i dati, una volta committati, siano ancora lì quando li chiedi. Si cita in nove, come l’availability, ma i numeri sono più grandi.

S3, l’esempio canonico, è documentato come “11 nove di durability”. Cioè 99,999999999%. L’interpretazione è che, se memorizzi 10.000.000 di oggetti, ti aspetteresti di perderne uno ogni 10.000 anni in media. Questo si ottiene memorizzando ogni oggetto in più strutture fisicamente separate e verificando l’integrità in continuazione.

Un database relazionale senza backup ha una durability grossomodo pari a quella del disco su cui vive, ovvero “buona finché il disco non muore, poi catastrofica”. È per questo che esistono backup, replica e replica cross-region.

I target di durability guidano: strategia di backup, topologia di replica, ogni quanto fai snapshot, dove memorizzi gli snapshot, e se fai point-in-time recovery.

Consistency

La consistency è la garanzia su cosa vedono i lettori dopo che uno scrittore ha aggiornato i dati. C’è un intero zoo di modelli di consistency (passeremo gran parte dei moduli 3 e 8 su questo); per ora la distinzione principale è tra strong consistency e eventual consistency.

Strong consistency: dopo che una scrittura si completa, ogni lettura successiva vede il nuovo valore. Senza eccezioni. È quello che ti dà un singolo database Postgres, e quello che la maggior parte degli sviluppatori ingenuamente assume di avere ovunque.

Eventual consistency: dopo che una scrittura si completa, le letture potrebbero vedere il vecchio valore per un po’ di tempo, ma alla fine vedranno tutte il nuovo valore. È quello che ottieni dalla maggior parte degli store distribuiti chiave-valore, dalla replica cross-region, e da qualunque cosa coinvolga il caching.

Il teorema CAP (lo copriremo nella lezione 27) dice che in un sistema distribuito, quando la rete si partiziona, devi scegliere tra consistency e availability. Non puoi avere entrambe. La maggior parte dei sistemi in produzione sceglie l’availability e convive con l’eventual consistency, ma la scelta deve essere consapevole, perché le conseguenze si propagano in tutta l’esperienza utente.

I target di consistency guidano: la scelta del database, se puoi usare una cache, come gestisci le letture-dopo-scritture, e come i flussi rivolti all’utente recuperano quando i dati non si sono ancora propagati.

Sicurezza

La sicurezza, in senso ampio, riguarda chi può fare cosa, come si stabiliscono e si verificano le identità, come i dati sono protetti a riposo e in transito, e come il sistema resiste agli abusi. È un requisito non funzionale nel senso che non cambia cosa il sistema fa per gli utenti legittimi: cambia i vincoli su come lo fa.

I requisiti di sicurezza coprono tipicamente: autenticazione (chi sei), autorizzazione (cosa puoi fare), encryption in transit (TLS ovunque), encryption at rest (cifratura di disco e database), gestione dei secret (dove vivono le API key), audit logging (chi ha fatto cosa quando), compliance (GDPR, HIPAA, PCI-DSS, SOC 2 a seconda del tuo settore), e threat modelling (quali attacchi dobbiamo temere).

La sicurezza guida: la scelta del provider di auth, la topologia di rete (internet pubblico contro VPC privato), se usi un servizio gestito o ti fai il tuo, dove vivono fisicamente i dati (data residency), e l’intero processo di deployment.

Evolvibilità e manutenibilità

L’evolvibilità è quanto è facile cambiare il sistema nel tempo. La manutenibilità è la cugina operativa: quanto è facile tenerlo in piedi. Queste due a volte vengono separate, a volte messe insieme, e coprono le preoccupazioni di lunga coda che non si presentano il primo giorno ma dominano dagli anni due al dieci.

Un sistema con buona evolvibilità ha confini chiari tra i moduli, basso coupling, test automatizzati al livello giusto, documentazione decente, e la capacità di assorbire nuovi requisiti senza riscritture importanti. Un sistema con buona manutenibilità ha buona observability, deploy prevedibili, log utili, e una superficie abbastanza piccola da rendere produttivo un nuovo ingegnere in due settimane invece che in due mesi.

Evolvibilità e manutenibilità sono facili da deprioritizzare perché non compaiono nella checklist di lancio. Sono anche la differenza tra un sistema che dopo cinque anni continua ad aiutare il business, e uno che viene riscritto perché nessuno riesce a toccarlo senza rompere qualcosa.

I trade-off sono obbligatori, non opzionali

La trappola che frega gli architetti inesperti è trattare i requisiti non funzionali come una wishlist in cui ogni qualità è impostata su “alta”, e poi dichiarare vittoria. I sistemi reali coinvolgono trade-off. Stringere una qualità quasi sempre allenta un’altra.

Una breve lista dei trade-off che non puoi evitare:

  • Più availability costa di più. Il deploy multi-AZ costa grossomodo 2x rispetto al single-AZ. Il multi-region costa grossomodo 3-4x. Il risparmio dato dall’essere down meno spesso deve giustificare la spesa.
  • Più availability spesso costa consistency. Se vuoi continuare a servire traffico durante una partizione di rete, devi accettare che alcune letture saranno stale. CAP, di nuovo.
  • Meno latency spesso costa throughput. Un sistema tarato per la latency per richiesta più bassa possibile fa meno lavoro in parallelo e si ferma a un throughput aggregato più basso. Un sistema tarato per il massimo throughput tipicamente batchizza e accoda, il che alza la latency per richiesta.
  • Una consistency più forte costa latency. Replicare una scrittura sincronicamente su tre macchine significa che l’utente aspetta tutte e tre. Replicare in modo asincrono dà scritture più veloci e garanzie più deboli.
  • Più sicurezza costa velocità di sviluppo. Ogni rotazione di secret, ogni policy IAM, ogni regola di WAF è attrito. Vale la pena, ma è reale.
  • Una migliore evolvibilità costa fatica all’inizio. Disegnare confini di modulo puliti il primo giorno è più lento che spedire semplicemente un singolo file. Il payoff arriva nell’anno due.

Non scappi da questi trade-off essendo furbo. Ci navighi scegliendo cosa ottimizzare in questo sistema, dato questo business, in questa fase di crescita. L’architettura è la forma di quelle scelte.

Estrarre gli NFR dai product owner

La maggior parte delle spec di prodotto che riceverai avrà i requisiti funzionali in primo piano e i requisiti non funzionali completamente assenti. È normale e non è cattiveria: i product owner pensano per funzionalità, perché è quello per cui il loro ruolo li premia. Il tuo lavoro, prima di disegnare un singolo box, è estrarre i requisiti non funzionali che non sono nel documento.

Ecco una lista di lavoro di domande da fare, ordinate per quanto spesso rivelano qualcosa che cambia l’architettura.

  1. Quanti utenti ci aspettiamo al lancio? A sei mesi? A due anni? Ancora i target di throughput e informa la strategia di scaling.
  2. Qual è il picco di carico rispetto alla media? Flash sale, orari di ufficio, fusi orari, spike virali. Un rapporto picco/media di 10x è normale; un rapporto di 100x significa che devi progettare per il picco.
  3. Qual è il budget di latency per l’endpoint più usato? “Sembra istantaneo” di solito significa sotto i 100 ms. “Sembra reattivo” di solito significa sotto i 300 ms. “Sembra lento” è qualunque cosa oltre il secondo.
  4. Cosa succede se il sistema è down per 5 minuti? Per un’ora? Per un giorno? È la domanda che calibra l’availability. Se la risposta a “down per un’ora” è “vabbè, è uno strumento interno”, non ti servono quattro nove. Se la risposta è “perdiamo 2 milioni di euro e ci telefona il regolatore”, invece sì.
  5. Cosa succede se perdiamo dati? Calibra la durability. Alcuni dati sono critici (transazioni finanziarie, foto caricate dagli utenti). Altri dati sono rigenerabili (risultati di ricerca in cache, aggregati calcolati).
  6. Ci sono requisiti hard di latency o di location dai regolatori? GDPR, data residency, la legge cinese sulla cybersecurity, ecc. Sono spesso decisioni che valgono per anni e da cui non puoi tornare indietro.
  7. Qual è il bad actor realistico nel caso peggiore? Un adolescente annoiato, un gruppo criminale sofisticato, uno stato-nazione, un dipendente interno. La risposta cambia in modo significativo la postura di sicurezza.
  8. Quanto spesso ci aspettiamo di cambiare il sistema? Ogni settimana? Ogni trimestre? Una volta in cinque anni? Calibra l’investimento in evolvibilità.

Se fai queste domande e la risposta del product owner è “non lo so, cosa raccomandi tu?”, in realtà va bene. Significa che hai la possibilità di proporre dei target, farli mettere per iscritto, e farvi riferimento quando in seguito qualcuno si lamenterà che il sistema è “troppo costoso” o “troppo lento”. Gli NFR sono un contratto con il resto del business, tanto quanto sono un input ingegneristico.

flowchart TD
    A[New feature request] --> B{What's the load?}
    B -->|low, less than 100 RPS| C[Single instance is fine]
    B -->|medium, 100 to 5000 RPS| D{What's the latency budget?}
    B -->|high, 5000 RPS plus| E[Horizontal scaling, caching, sharding]
    D -->|under 100 ms| F[Cache aggressively, colocate data]
    D -->|under 1 second| G[Standard web stack]
    D -->|background, async OK| H[Queue and worker pool]
    C --> I{What's the availability target?}
    F --> I
    G --> I
    H --> I
    E --> I
    I -->|99 percent| J[Single AZ deployment]
    I -->|99.9 percent| K[Multi-AZ with health checks]
    I -->|99.99 percent plus| L[Multi-region, formal SLOs]
    J --> M[Architectural choices follow]
    K --> M
    L --> M

Il diagramma è deliberatamente semplicistico. La versione reale ha più rami e più interazioni tra le qualità, ma la forma del ragionamento è giusta: cammini dai requisiti verso le implicazioni architetturali, e lo fai esplicitamente, su carta, con il product owner nella stanza.

Cosa dovresti portarti via da questa lezione

I requisiti funzionali sono necessari ma non sufficienti. I requisiti non funzionali sono quelli che guidano la forma del sistema. Sono grossomodo sette: latency, throughput, availability, durability, consistency, sicurezza, evolvibilità. Ognuno fa trade-off con almeno uno degli altri. Il tuo lavoro, prima di progettare qualunque cosa, è inchiodare target numerici realistici per quelli che contano, facendo le domande giuste alle persone che hanno l’outcome di business in carico.

Nella prossima lezione passeremo da “cosa chiedere” a “come disegnare”, e guarderemo il modello C4: una convenzione di diagrammazione a quattro livelli di zoom che rende “abbozziamo il sistema” davvero produttivo.

Riferimenti

  • Bass, Clements, Kazman. Software Architecture in Practice, 4a edizione (2021), i capitoli da 4 a 13 coprono in profondità gli attributi di qualità.
  • Beyer et al. Site Reliability Engineering (2016), capitolo 4 sui Service Level Objectives.
  • Werner Vogels, “Eventually Consistent” (CACM 2009), l’articolo fondamentale sui trade-off di consistency.
  • ISO/IEC 25010 quality model (2011), il vocabolario standardizzato per gli attributi di qualità del software.
Cerca