Protocol Buffers (Protobuf) è un formato binario di serializzazione per dati strutturati, sviluppato da Google per lo scambio efficiente di informazioni tra servizi. Il formato richiede una definizione preliminare dello schema in file .proto, dai quali viene generato il codice per vari linguaggi. Secondo la documentazione ufficiale di Google, Protobuf offre dimensioni dei messaggi da 3 a 10 volte inferiori rispetto a JSON. Protobuf è utilizzato in gRPC, Google Maps e migliaia di servizi interni.
Punti chiave
Protocol Buffers è un meccanismo di serializzazione di dati strutturati sviluppato da Google, simile a JSON e XML, ma con una differenza fondamentale: i dati vengono codificati in formato binario. Ciò significa che un messaggio Protobuf non può essere letto a occhio nudo, ma occupa molto meno spazio e viene elaborato più velocemente delle controparti testuali.
Protobuf è stato creato internamente da Google per risolvere problemi di prestazioni nello scambio di dati tra servizi. Nel 2008, la tecnologia è diventata un progetto open source con supporto per molti linguaggi: C++, Java, Python, Go, JavaScript, Kotlin, Swift, Dart e altri. La versione proto3, rilasciata nel 2016, ha semplificato la sintassi e aggiunto il supporto per più linguaggi.
La differenza principale tra Protobuf e JSON è la necessità di definire uno schema prima di iniziare lo scambio di dati. Il file dello schema (.proto) descrive la struttura del messaggio: campi, tipi e numeri univoci dei campi. Da questo schema, il compilatore protoc genera classi nel linguaggio di destinazione per la serializzazione e deserializzazione.
L’architettura di Protobuf include tre componenti chiave: il linguaggio di definizione dello schema (.proto), il compilatore protoc e le librerie runtime per linguaggi specifici. Lo sviluppatore descrive la struttura dei dati in un file .proto, esegue la compilazione e riceve classi pronte per lavorare con questi dati.
Ogni campo in un messaggio Protobuf ha un numero univoco (field number) — non è un numero sequenziale, ma un tag utilizzato nel formato binario per identificare il campo. I numeri da 1 a 15 sono codificati in un byte, da 16 a 2047 in due byte. Pertanto, i campi importanti con alta frequenza di utilizzo dovrebbero essere numerati da 1 a 15 per ottimizzare le dimensioni.
Protobuf supporta un’ampia gamma di tipi: scalari (int32, int64, float, double, bool, string, bytes), enumerazioni (enum), compositi (message) e tipi speciali (oneof, map). Ogni tipo ha una rappresentazione binaria specifica ottimizzata per il caso d’uso corrispondente.
| Tipo .proto | Tipo C++ | Java/Kotlin | Descrizione |
|---|---|---|---|
| double | double | double | Numero a virgola mobile a 64 bit |
| float | float | float | Numero a virgola mobile a 32 bit |
| int32 | int32 | int | 32 bit, codifica a lunghezza variabile |
| int64 | int64 | long | 64 bit, codifica a lunghezza variabile |
| string | string | String | Stringa UTF-8 |
| bytes | string | ByteString | Byte arbitrari |
| bool | bool | boolean | true / false |
Protobuf offre tre vantaggi principali rispetto ai formati testuali: dimensione del messaggio, velocità di serializzazione e tipizzazione forte. Nei sistemi ad alto carico e nelle applicazioni mobili con traffico limitato, questi vantaggi diventano critici.
La rappresentazione binaria di Protobuf utilizza la codifica a lunghezza variabile (Varint) per i numeri: i numeri piccoli occupano 1 byte, quelli grandi fino a 10 byte. Ciò consente di codificare in modo efficiente identificatori, flag e contatori che occuperebbero decine di byte come stringhe di testo in JSON o XML. Ad esempio, il numero 150 in Protobuf occupa 2 byte, in JSON — 3 byte (come testo “150”), in XML — 3 byte + tag.
Un vantaggio aggiuntivo è la retrocompatibilità. L’aggiunta di un nuovo campo allo schema non rompe i vecchi client: ignorano semplicemente i campi sconosciuti. L’eliminazione di un campo richiede solo la riserva del suo numero per evitare collisioni future.
Lo schema .proto descrive la struttura dei dati in un linguaggio speciale. Il file inizia con la specifica della sintassi (proto3), del pacchetto e delle dipendenze di importazione. Ogni messaggio è definito con la parola chiave message con campi, dove ogni campo ha un tipo, un nome e un numero univoco.
Buone pratiche di schema: numeri di campo da 1 a 15 per i campi usati frequentemente, nomi significativi, raggruppamento dei campi correlati in un message separato, uso di oneof per i campi che possono essere solo una di diverse opzioni.
syntax = "proto3";
package mobileapp;
message User {
string user_id = 1;
string name = 2;
string email = 3;
int32 age = 4;
UserRole role = 5;
repeated string tags = 6;
map<string, string> metadata = 7;
}
enum UserRole {
USER_ROLE_UNSPECIFIED = 0;
USER_ROLE_USER = 1;
USER_ROLE_ADMIN = 2;
USER_ROLE_MODERATOR = 3;
}
I messaggi possono essere annidati: un campo profile di tipo Profile conterrà i dati utente al suo interno. Il supporto all’annidamento consente di descrivere strutture gerarchiche complesse senza duplicare le definizioni.
syntax = "proto3";
package mobileapp;
message Order {
string order_id = 1;
repeated OrderItem items = 2;
double total_price = 3;
PaymentInfo payment = 4;
}
message OrderItem {
string product_id = 1;
string title = 2;
int32 quantity = 3;
double price = 4;
}
message PaymentInfo {
string method = 1;
string transaction_id = 2;
double amount = 3;
}
Il compilatore protoc converte i file .proto in codice sorgente nel linguaggio di destinazione. Per Kotlin/Java si usa il parametro --java_out, per Swift --swift_out (tramite il plugin Apple Swift Protobuf), per Dart --dart_out. Le classi generate contengono metodi builder per costruire messaggi e metodi di serializzazione/deserializzazione.
Su Android, per lavorare con Protobuf si usa il plugin com.google.protobuf versione 0.9+ in Gradle. Dopo aver aggiunto il plugin e specificato i file .proto, la build genera automaticamente classi Kotlin pronte per l’uso nell’applicazione.
import com.google.protobuf.kotlin.toByteString
import com.example.mobileapp.UserOuterClass.User
fun createUser(): User {
return User.newBuilder()
.setUserId("usr_001")
.setName("IT Sectr")
.setEmail("team@itsectr.com")
.setAge(5)
.setRole(UserOuterClass.UserRole.USER_ROLE_ADMIN)
.addTags("mobile")
.addTags("backend")
.build()
}
fun serializeAndDeserialize(user: User): User {
// Serializzazione in formato binario
val bytes = user.toByteArray()
// Deserializzazione dal formato binario
return User.parseFrom(bytes)
}
Protobuf è particolarmente efficace nell’architettura a microservizi e nelle applicazioni mobili. Consideriamo uno scenario tipico: un’applicazione mobile riceve un elenco di prodotti dal server tramite gRPC. Il messaggio Protobuf contiene le informazioni sul prodotto, tra cui identificatore, nome, prezzo e categoria. Il formato binario riduce le dimensioni della risposta da 5 a 8 volte rispetto a JSON.
Esempio di un servizio gRPC con Protobuf lato server. Il servizio dichiara il metodo RPC GetProducts, che accetta una richiesta con parametri di paginazione e restituisce un elenco di prodotti. L’implementazione in Kotlin utilizza classi generate per lavorare con richieste e risposte.
import com.example.mobileapp.ProductServiceGrpcKt
import com.example.mobileapp.ProductOuterClass.Product
import com.example.mobileapp.ProductOuterClass.GetProductsRequest
import com.example.mobileapp.ProductOuterClass.GetProductsResponse
class ProductService : ProductServiceGrpcKt.ProductServiceCoroutineImplBase() {
override suspend fun getProducts(
request: GetProductsRequest
): GetProductsResponse {
val products = fetchProductsFromDb(
page = request.page,
limit = request.limit
)
return GetProductsResponse.newBuilder()
.addAllProducts(products)
.setTotalCount(products.size)
.build()
}
}
La configurazione di Protobuf in un progetto Android viene eseguita tramite il plugin Gradle com.google.protobuf. Il plugin esegue automaticamente il compilatore protoc durante la build e genera classi Kotlin dai file .proto. Per funzionare, è necessario aggiungere il plugin al build.gradle a livello di progetto e applicarlo nel modulo dell’applicazione.
Dopo aver configurato Gradle, i file .proto vengono inseriti nella directory src/main/proto. Il compilatore protoc li elabora durante ogni build, generando classi Kotlin che possono essere utilizzate nel codice dell’applicazione. È importante specificare correttamente le versioni di protobuf e protoc per evitare conflitti di dipendenza con altre librerie del progetto.
Per i progetti iOS, Protobuf viene collegato tramite CocoaPods o Swift Package Manager. Il plugin Swift Protobuf genera automaticamente strutture Swift conformi al protocollo Codable. Nei progetti Dart per Flutter, viene utilizzato il pacchetto protobuf e la compilazione viene eseguita tramite dart run protoc_plugin.
Domande frequenti
Protobuf è un formato binario con schema obbligatorio, da 3 a 10 volte più compatto di JSON. JSON è testuale, leggibile dall’uomo e non richiede schema. Protobuf serializza e deserializza più velocemente ma richiede la compilazione di file .proto. JSON è più facile da debuggare e non richiede configurazione preliminare.
Scarica protoc dalla release GitHub di protobuf per la tua piattaforma. Per macOS, installa tramite brew install protobuf. Per Windows, scarica l’archivio zip e aggiungi protoc.exe al PATH. Per Android/Kotlin, usa il plugin Gradle com.google.protobuf, che esegue automaticamente la compilazione durante la build.
gRPC è un framework RPC ad alte prestazioni di Google che utilizza Protobuf come linguaggio di definizione dell’interfaccia (IDL) e formato di serializzazione. gRPC definisce servizi e metodi RPC in file .proto, genera codice client e server e supporta lo streaming e i protocolli binari.
Supportati ufficialmente da Google: C++, Java, Kotlin, Python, Go, Ruby, C#, PHP, JavaScript, Objective-C, Swift e Dart. La comunità ha sviluppato il supporto per Rust, TypeScript, Scala, Lua e altri linguaggi. Per lo sviluppo mobile, sono disponibili Kotlin/Java (Android) e Swift/Objective-C (iOS).
Protobuf supporta la retrocompatibilità attraverso queste regole: non modificare i numeri dei campi, non eliminare i campi (usa reserved), aggiungi nuovi campi con nuovi numeri. I vecchi client ignorano i campi sconosciuti, i nuovi client ottengono valori predefiniti per i vecchi campi mancanti.
Riepilogo
Svilupperemo un'applicazione mobile chiavi in mano
IT Sectr crea applicazioni iOS e Android per startup e aziende dal 2017. Ti consulteremo e ti proporremo la soluzione migliore.
Leggi anche