Origini ed Evoluzione
Protocol Buffers (protobuf) sono nati dalle esigenze interne di sviluppo di Google all'inizio degli anni 2000. Di fronte alla sfida di serializzare dati strutturati in modo efficiente tra diversi servizi e linguaggi, gli ingegneri di Google hanno sviluppato questo meccanismo indipendente dal linguaggio e dalla piattaforma. Rilasciato come open-source nel 2008, protobuf è diventato una pietra miliare per lo scambio efficiente di dati nei sistemi distribuiti.
Cosa sono i Protocol Buffers?
I Protocol Buffers sono un metodo per serializzare dati strutturati da utilizzare nei protocolli di comunicazione e nell'archiviazione dei dati. A differenza di formati basati su testo come JSON o XML, protobuf utilizza un formato binario, il che si traduce in dimensioni dei dati più ridotte e una più rapida analisi. Alla base di protobuf ci sono i file di definizione dello schema (.proto) che specificano la struttura dei dati:
syntax = "proto3";
message Person {
string name = 1;
int32 age = 2;
repeated string hobbies = 3;
}
Questo approccio basato sullo schema offre diversi vantaggi:
Sicurezza dei tipi: Lo schema garantisce la consistenza dei dati tra produttori e consumatori, prevenendo errori a runtime.
Generazione di codice: Creazione automatica di tipi e modelli in più linguaggi al momento della compilazione (protobuf supporta praticamente tutti i linguaggi).
Supporto al versioning: Meccanismi integrati per evolvere le strutture dei dati.
Quando si lavora con protobuf, il flusso di lavoro è il seguente: definire i file .proto → generare il codice → utilizzare i serializer e deserializer generati in modo sicuro per i tipi nel proprio linguaggio.
Nota che quando si definiscono i messaggi, è necessario specificare un numero per ogni proprietà. Questo rappresenta l'identificatore effettivo nella codifica binaria. Ci sono diversi aspetti da tenere a mente, consultabili nella guida ufficiale.
Casi d'uso
Protobuf eccelle in scenari che richiedono:
Trasferimenti di dati ad alte prestazioni
Validazione rigorosa dello schema
Compatibilità tra linguaggi
Dimensioni ottimizzate per l'archiviazione dei dati
Google ha anche utilizzato Protocol Buffers come principale formato di trasferimento dati per il proprio framework RPC. gRPC è un framework RPC ad alte prestazioni, utilizzato principalmente per la comunicazione server-to-server (ma può essere impiegato anche in altre applicazioni, inclusi i browser), garantendo:
Contratti di servizio fortemente tipizzati
Comunicazione binaria efficiente
Generazione automatica di codice client/server
Un aspetto interessante di gRPC è la sua stretta integrazione con i file .proto. È possibile definire servizi, richieste e risposte direttamente nei contratti, e il compilatore protobuf genererà client e server fortemente tipizzati per qualsiasi linguaggio.
syntax = "proto3";
service TodoService {
rpc CreateTodo (CreateTodoRequest) returns (TodoResponse);
rpc GetTodo (GetTodoRequest) returns (TodoResponse);
rpc ListTodos (Empty) returns (TodoListResponse);
rpc DeleteTodo (DeleteTodoRequest) returns (Empty);
}
message Todo {
string id = 1;
string title = 2;
string description = 3;
bool completed = 4;
}
message CreateTodoRequest {
string title = 1;
string description = 2;
}
message GetTodoRequest {
string id = 1;
}
message DeleteTodoRequest {
string id = 1;
}
message TodoResponse {
Todo todo = 1;
}
message TodoListResponse {
repeated Todo todos = 1;
}
message Empty {}
Questo esempio mostra una classica applicazione CRUD definita interamente in un singolo file .proto. Questo genererà funzioni e metodi utilizzabili in qualsiasi linguaggio supportato, per implementare client e server direttamente in fase di compilazione.
Il nostro caso d'uso: Comunicazione WebSocket ad alta frequenza
In Quinck, abbiamo sfruttato protobuf per un caso d'uso unico: abilitare una comunicazione WebSocket binaria ad alta frequenza e sicura dal punto di vista dei tipi tra browser (TypeScript) e server (Go). Poiché scambiavamo dati a una frequenza piuttosto elevata (circa 100 messaggi al secondo), volevamo evitare il sovraccarico dovuto alle dimensioni e alle prestazioni di analisi di JSON.
L'implementazione è molto semplice: basta utilizzare un server e un client WebSocket standard, configurarli entrambi per la comunicazione binaria e generare in fase di build i tipi corretti per ciascuno di essi a partire dal singolo file .proto.
Inoltre, avevamo bisogno di scalare orizzontalmente i WebSocket. Per farlo, abbiamo deciso di utilizzare il sistema di pub/sub di Redis per instradare i messaggi tra le istanze. Dato che Redis supporta i payload binari, può ovviamente trasferire i nostri messaggi protobuf codificati in modo sicuro!
Vantaggi principali che abbiamo ottenuto:
Riduzione della larghezza di banda: Il formato binario ha minimizzato il trasferimento dei dati (di molto, circa il 70% delle dimensioni originali dei messaggi).
Sicurezza dei tipi: Il controllo dei tipi end-to-end ha evitato errori a runtime.
Prestazioni: La rapida serializzazione/deserializzazione ha migliorato le capacità in tempo reale e le prestazioni del frontend.
Sfide e considerazioni
Sebbene Protocol Buffers offrisse vantaggi significativi in diversi casi d'uso, abbiamo incontrato alcune difficoltà. Il formato binario di protobuf rendeva più difficile il debug manuale e l'ispezione dei dati rispetto all'uso di JSON. Inoltre, per il nostro caso d'uso, abbiamo dovuto integrare strumenti e dipendenze aggiuntivi (il compilatore protoc e i plugin) per generare modelli e deserializer per TypeScript nel frontend e Go nel backend, a differenza di JSON, che è standard in entrambi i linguaggi.
Conclusioni
Protocol Buffers e gRPC si sono dimostrati strumenti inestimabili nel nostro stack di sviluppo. Sebbene JSON rimanga eccellente per molti casi d'uso, l'efficienza e la sicurezza dei tipi di protobuf lo rendono la scelta migliore per applicazioni sicure, ad alta frequenza e critiche per le prestazioni. La nostra implementazione di successo nella comunicazione browser-server dimostra la sua versatilità oltre i tradizionali servizi backend.