L’architettura server/client è la scelta giusta per il tuo progetto? Oggi esploriamo la struttura e i componenti di questo design per aiutarti a scoprirlo, e avviarti al tuo viaggio di sviluppo di server.


L’architettura server/client è uno dei pilastri dello sviluppo web, ed è anche molto usata per software locale (p. es., molti sistemi di database).

Questo design è la scelta giusta per il tuo progetto? Il nostro post ti aiuterà a scoprirlo, e ti offrirà una guida alle scelte da fare per creare un applicativo server di base.

Nei prossimi post esploreremo anche un programma di questo tipo sul campo, per capire come costruirlo in pratica.

Architettura client/server

L’idea fondamentale di un sistema client/server è dividere il compito di comunicare con l’utente dal compito di eseguire la business logic.

Questi compiti sono in genere assegnati a sezioni separate dello stesso programma: un’architettura client/server fa un passo oltre, assegnando questi due compiti a programmi separati.

Il primo di questi è chiamato server (o backend): esso esegue la business logic del programma e gestisce risorse accessibili dall’utente (per esempio, database). Questo programma non comunica direttamente con l’utente, ma espone una application program interface (API), un insieme di “indirizzi” a cui altri programmi possono spedire richieste in formato HTTP per spedire e ricevere dati.

Il server interagisce con un client (o frontend), che comunica con l’utente e spedisce le sue richieste al server tramite l’API, mostrando all’utente i dati ricevuti in risposta.

In un progetto che segue l'architettura server/client, il backend/server (che fornisce il servizio) è separato dal frontend (dove il servizio è "consumato" dall'utente), e l'API media fra i due.

L’Architettura Server/Client è la scelta giusta per il tuo progetto?

Il miglior modo per rispondere è considerare i suoi punti di forza e decidere l’importanza che questi hanno per il tuo prodotto.

Uno dei primi aspetti da considerare è che un singolo server (collegato a risorse centralizzate) può gestire le richieste di molti client. Al contrario, un’applicazione monolitica richiede di distribuire ad ogni utente sia la business logic e le risorse, sia l’interfaccia utente.

Questo è ovviamente fondamentale nello sviluppo web: per esempio, un sito web può avere solo un (o al massimo alcune copie del) database di credenziali, e non può (e non deve!) distribuirlo a tutti gli utenti. Lo stesso principio si applica anche a software in locale: un singolo server di database può offrire funzionalità di storage di dati ad ogni programma sul tuo computer, senza dover essere reinstallato per ogni client.

Un altro grande vantaggio è l’incapsulamento: visto che l’API media tutte le richieste del client, non è necessario comprendere i dettagli del funzionamento dei componenti del server per scrivere un client (in pratica, basta comprendere le specifiche dell’API).

Questo permette un enorme potenziale di estensione per questo tipo di software: client e interfacce al server possono essere aggiunte liberamente senza dover ricompilare o integrare codice proveniente dal server. In effetti, gli sviluppatori del server possono non essere a conoscenza di quale client accederà al server, decidere di rimpiazzarlo, o usarne più di uno simultaneamente.

L’architettura server/client permette anche di usare strumenti mirati: dato che server e client sono essenzialmente indipendenti, e comunicano solo tramite richieste HTTP, è molto facile costruirli con strumenti, framework e linguaggi differenti.

Questo permette di scegliere gli strumenti ottimali per ciascuno di questi due compiti, senza doversi preoccupare della loro interoperabilità. Per esempio, molte interfacce web sono scritte in linguaggi (p. es., Javascript) molto diversi da quelli usati per i server a cui si connettono (p. es., Python, C, C++, o Rust, a seconda di preferenze e necessità di performance).

Se questi vantaggi sono importanti per il tuo progetto, un’architettura server/client probabilmente è più adatta di un programma monolitico. Questo è in genere il caso nello sviluppo web, mentre per applicazioni in locale dipenderà dalle circostanze.

Progettazione e Componenti di Applicazioni Server

Se pensi che un design server/client sia la scelta giusta per il tuo progetto, dovrai costruire entrambe le componenti: questa guida si concentra perlopiù sulla parte di server, pur offrendo alcuni suggerimenti per costruire client semplici ma efficaci.

Per iniziare, dovrai capire gli elementi e i componenti di cui il tuo server avrà bisogno, e scegliere gli strumenti giusti per implementarli a seconda dei tuoi requisiti. Le operazioni di base che la maggior parte dei server dovranno svolgere saranno:

  • Storage di dati (in genere in un database),
  • Recuperare i dati in base a dei parametri di ricerca, e
  • Comunicare con altri programmi per passare i dati ottenuti,

il che porta a quattro scelte da fare:

  1. Quale linguaggio usare per il progetto (a seconda dei requisiti di performance e scalabilità);
  2. Quale sistema di database usare per conservare e fare ricerche sui dati;
  3. Se interagire con il database nativamente o via ORM (vedi sotto);
  4. Quale framework usare per costruire l’API.

Il resto di questa guida esplorerà possibili opzioni per questi aspetti del design. In genere non esistono soluzioni migliori a priori, e le scelte giuste dipenderanno da cosa hai in mente di costruire.

Linguaggio

In prima approssimazione, codice scritto in linguaggi interpretati (p. es., Python, Javascript) in genere è più lento di codice equivalente scritto in linguaggi compilati (p. es., C, C++, Rust). Di conseguenza,

  • I linguaggi compilati in genere sono preferiti dove la performance è fondamentale (p. es., dove è necessario gestire data set molto grandi, o un gran numero di richieste).
  • I linguaggi interpretati sono una buona scelta altrimenti, dato che in genere permettono un ciclo di sviluppo più rapido.

Va detto che in generale considerazioni di performance fatte a priori possono essere ingannevoli, perché la maggior parte del codice di un server può essere scritta in un linguaggio diverso da quello scelto dallo sviluppatore. In pratica, la maggior parte dei software che svolgono il ruolo di server usano librerie ottimizzate per svolgere operazioni comuni, ottenendo maggiore semplicità, sicurezza, e performance.

Sistema di Database

La prima cosa da decidere per scegliere un sistema di database è se debba essere relazionale o non-relazionale.

I database relazionali organizzano i dati in tabelle, le cui righe sono associate agli oggetti messi in storage, e le cui colonne sono associate ai campi (ovvero alle proprietà) che li descrivono.

Per esempio, per classificare spese domestiche, si può specificare una data, un tipo (una classificazione di base, come “vitto” o “extra”), un importo e una descrizione. Una tabella di esempio in questo caso avrebbe la forma

id |    data    | tipo  | importo | descrizione 
---+------------+-------+---------+-------------
 1 | 2023-11-11 | vitto |  -10.00 | hamburger
 2 | 2023-11-12 | extra |   -5.00 | drinks out

(avere un ID identificativo non è necessario, ma è comodo per molte operazioni del database). Per costruire questo tipo di tabelle è necessario conoscere il modello dei dati, ovvero i campi degli oggetti da mettere in storage.

Al contrario, i database non-relazionali non fanno assunzioni sul modello dei dati, e possono mettere in storage dati con modelli differenti. In pratica, questo è possibile mettendo in storage ogni oggetto sotto forma di documento (un formato comune per tutti gli oggetti, in genere JSON):

{
  "id": 1,
  "data": 2023-11-11,
  "tipo": "vitto",
  "importo": -10.00,
  "descrizione": "hamburger"
}

Documenti con chiavi JSON differenti possono essere messi in storage nello stesso database non-relazionale.

In generale,

  • I database relazionali in genere offrono performance migliore se il modello dei dati è noto e fissato.
  • I database non-relazionali sono una scelta migliore se il modello dei dati è sconosciuto o variabile, e sono in genere ottimizzati per eseguire ricerche sul contenuto dei campi sotto forma di testo.

Una volta scelto il tipo di database giusto per il tuo progetto, dovrai scegliere il servizio di database che più si confà alle tue esigenze. Nel caso relazionale ce ne sono di due tipi:

  • File-based (p. es.., SQLite), che conservano i dati in un file; il programma di database è lanciato on-demand per aprire il database e accedere ai dati in esso contenuti.
  • Service-based (p. es., MySQL or PostgreSQL), in cui il programma di database è a sua volta un server, che gira permanentemente sul sistema. Altri processi possono comunicare con il database via richieste HTTP, per richiedere e ricevere dati.

In generale, i sistemi file-based sono più semplici da gestire e più portabili, mentre quelli server-based offrono maggiore performance e versatilità.

ORM

L’Object-relational mapping (ORM) è una tecnica che permette di usare idiomi della programmazione object-oriented per manipolare dati provenienti da un database. L’approccio alternativo all’ORM è l’uso di idiomi relazionali, passando istruzioni in linguaggio SQL o simili (sanitizzate per evitare problemi di sicurezza) direttamente al motore del database.

L’ORM offre diversi vantaggi, grazie alla sua integrazione con il linguaggio. Per esempio,

  • Si può sfruttare il modello dei dati per definire simultaneamente classi (per il programma) e tabelle (per il database), godendo di una integrazione più profonda fra le due.
  • I framework ORM gestiscono nativamente problemi di sicurezza come le SQL injections (a meno che comandi SQL grezzi non vengano passati esplicitamente, nel qual caso sarà lo sviluppatore a doversene occupare).

D’altro canto, anche le interfacce a basso livello hanno alcuni vantaggi:

  • Possono essere più espressive in alcuni casi (e in effetti la maggior parte dei framework ORM permettono accesso a basso livello per eseguire operazioni specifiche).
  • L’SQL è (in gran parte) una lingua franca per tutti i sistemi di database, mentre al contrario diversi framework ORM possono avere set di istruzioni abbastanza diversi.
  • Usare l’ORM potrebbe causare performance sub-ottimali in alcuni casi in cui l’SQL generato dall’ORM non è su misura per il problema.

Framework per API

Una API è essenzialmente una lista di funzioni che ricevono parametri e restituiscono dati in un formato compatibile con il protocollo HTTP. Queste funzioni vengono in genere costruite tramite framework dedicati: la disponibilità, il numero e la maturità di queste librerie dipendono dal linguaggio che si è scelto per il progetto.

In questa guida ci concentreremo sul Python, una scelta popolare per lo sviluppo di applicazioni server, che offre potenti framework quali django, flask, e FastAPI.

  • Django è una soluzione molto matura e estremamente completa, che offre una vasta gamma di strumenti per gestire le funzioni di un server (p. es., gestione di database nativa).
  • FastAPI è più performante, più leggero, e dedicato esplicitamente alla scrittura di API, mentre altre librerie specializzate dovrebbero essere usate per gestire gli altri aspetti del server.
  • Flask è un framework versatile, intermedio fra django and FastAPI per quanto riguarda la specializzazione.

Pronti a partire

Ed è tutto: queste sono le scelte da fare per una semplice applicazione server. Nei prossimi post, esamineremo il design di un programma del genere, componente per componente, ed esploreremo la sua implementazione.


Autore: Adriano Angelone

Dopo aver ottenuto la Laurea Magistrale in Fisica all’Università di Pisa nel 2013, ha ricevuto il Dottorato in Fisica all’Università di Strasburgo nel 2017. Ha lavorato come ricercatore post-dottorale all’Università di Strasburgo, alla SISSA (Trieste) e all’Università Sorbona (Parigi), prima di entrare in eXact-lab come Sviluppatore di Software Scientifico nel 2023.

In eXact-lab, lavora all’ottimizzazione di codici computazionali e allo sviluppo di software di gestione dati e data engineering.