Protocol Buffers (Protobuf) — to binarny format serializacji danych strukturalnych, opracowany przez Google do efektywnej wymiany informacji między serwisami. Format wymaga wcześniejszego zdefiniowania schematu w plikach .proto, z których generowany jest kod dla różnych języków. Według oficjalnej dokumentacji Google, Protobuf zapewnia rozmiar wiadomości 3-10 razy mniejszy niż JSON. Protobuf jest używany w gRPC, Google Maps i tysiącach wewnętrznych serwisów.
Najważniejsze
Protocol Buffers — to opracowany przez Google mechanizm serializacji danych strukturalnych, podobny do JSON i XML, ale z zasadniczą różnicą: dane są kodowane w formacie binarnym. Oznacza to, że wiadomość Protobuf nie może być odczytana gołym okiem, ale zajmuje znacznie mniej miejsca i jest przetwarzana szybciej niż tekstowe odpowiedniki.
Protobuf został stworzony wewnątrz Google do rozwiązywania problemów wydajnościowych przy wymianie danych między serwisami. W 2008 roku technologia stała się otwartym projektem z obsługą wielu języków: C++, Java, Python, Go, JavaScript, Kotlin, Swift, Dart i innych. Wersja proto3, wydana w 2016 roku, uprościła składnię i dodała obsługę większej liczby języków.
Kluczowa różnica między Protobuf a JSON — konieczność zdefiniowania schematu przed rozpoczęciem wymiany danych. Plik schematu (.proto) opisuje strukturę wiadomości: pola, typy i unikalne numery pól. Z tego schematu kompilator protoc generuje klasy w języku docelowym do serializacji i deserializacji.
Architektura Protobuf obejmuje trzy kluczowe komponenty: język definicji schematu (.proto), kompilator protoc i biblioteki runtime dla konkretnych języków. Deweloper opisuje strukturę danych w pliku .proto, uruchamia kompilację i otrzymuje gotowe klasy do pracy z tymi danymi.
Każde pole w wiadomości Protobuf ma unikalny numer (field number) — nie jest to numer porządkowy, ale tag używany w formacie binarnym do identyfikacji pola. Numery od 1 do 15 kodowane są jednym bajtem, od 16 do 2047 — dwoma. Dlatego ważne pola o wysokiej częstotliwości używania warto numerować od 1 do 15 w celu optymalizacji rozmiaru.
Protobuf obsługuje szeroki zakres typów: skalarne (int32, int64, float, double, bool, string, bytes), wyliczenia (enum), złożone (message) i specjalne (oneof, map). Każdy typ ma określoną binarną reprezentację, zoptymalizowaną pod odpowiedni scenariusz użycia.
| Typ .proto | Typ C++ | Java/Kotlin | Opis |
|---|---|---|---|
| double | double | double | Liczba zmiennoprzecinkowa 64-bit |
| float | float | float | Liczba zmiennoprzecinkowa 32-bit |
| int32 | int32 | int | 32-bit, variable-length encoding |
| int64 | int64 | long | 64-bit, variable-length encoding |
| string | string | String | Ciąg UTF-8 |
| bytes | string | ByteString | Dowolne bajty |
| bool | bool | boolean | true / false |
Protobuf daje trzy kluczowe zalety w porównaniu z formatami tekstowymi: rozmiar wiadomości, szybkość serializacji i ścisłe typowanie. W systemach o wysokim obciążeniu i aplikacjach mobilnych z ograniczonym ruchem sieciowym zalety te stają się krytyczne.
Binarna reprezentacja Protobuf używa variable-length encoding (Varint) dla liczb: małe liczby zajmują 1 bajt, duże — do 10 bajtów. Pozwala to efektywnie kodować identyfikatory, flagi i liczniki, które w JSON lub XML zajęłyby dziesiątki bajtów jako ciągi tekstowe. Na przykład liczba 150 w Protobuf zajmuje 2 bajty, w JSON — 3 bajty (jako tekst „150”), w XML — 3 bajty + znaczniki.
Dodatkową zaletą jest wsteczna kompatybilność. Dodanie nowego pola do schematu nie psuje starych klientów: po prostu ignorują one nieznane pola. Usunięcie pola wymaga jedynie zarezerwowania jego numeru, aby uniknąć kolizji w przyszłości.
Schemat .proto opisuje strukturę danych w specjalnym języku. Plik zaczyna się od określenia składni (proto3), pakietu i importu zależności. Każda wiadomość jest definiowana za pomocą słowa kluczowego message z polami, gdzie każde pole ma typ, nazwę i unikalny numer.
Zasady dobrego schematu: numery pól od 1 do 15 dla często używanych pól, sensowne nazwy, grupowanie powiązanych pól w osobne message, używanie oneof dla pól, które mogą być tylko jednym z kilku wariantów.
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;
}
Wiadomości mogą być zagnieżdżone: pole profile typu Profile będzie zawierać w sobie dane użytkownika. Obsługa zagnieżdżania pozwala opisywać złożone hierarchiczne struktury bez powielania definicji.
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;
}
Kompilator protoc przekształca pliki .proto w kod źródłowy w docelowym języku. Dla Kotlin/Java używany jest parametr --java_out, dla Swift --swift_out (poprzez wtyczkę Apple Swift Protobuf), dla Dart --dart_out. Wygenerowane klasy zawierają metody builder do konstruowania wiadomości oraz metody serializacji/deserializacji.
W Androidzie do pracy z Protobuf używana jest wtyczka com.google.protobuf w wersji 0.9+ w Gradle. Po dodaniu wtyczki i wskazaniu plików .proto, kompilacja automatycznie generuje klasy Kotlin gotowe do użycia w aplikacji.
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 {
// Serializacja do formatu binarnego
val bytes = user.toByteArray()
// Deserializacja z formatu binarnego
return User.parseFrom(bytes)
}
Protobuf jest szczególnie efektywny w architekturze mikroserwisowej i aplikacjach mobilnych. Rozważmy typowy scenariusz: aplikacja mobilna pobiera listę produktów z serwera przez gRPC. Wiadomość Protobuf zawiera informacje o produkcie, w tym identyfikator, nazwę, cenę i kategorię. Format binarny zmniejsza rozmiar odpowiedzi 5-8 razy w porównaniu z JSON.
Przykład serwisu gRPC z Protobuf po stronie serwerowej. Serwis deklaruje metodę RPC GetProducts, która przyjmuje zapytanie z parametrami paginacji i zwraca listę produktów. Implementacja w Kotlinie używa wygenerowanych klas do pracy z zapytaniami i odpowiedziami.
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()
}
}
Konfiguracja Protobuf w projekcie Android odbywa się poprzez wtyczkę Gradle com.google.protobuf. Wtyczka automatycznie uruchamia kompilator protoc podczas kompilacji i generuje klasy Kotlin z plików .proto. Do pracy wymagane jest dodanie wtyczki w build.gradle poziomu projektu i zastosowanie jej w module aplikacji.
Po skonfigurowaniu Gradle, pliki .proto umieszczane są w katalogu src/main/proto. Kompilator protoc przetwarza je przy każdej kompilacji, generując klasy Kotlin, które można użyć w kodzie aplikacji. Ważne jest prawidłowe określenie wersji protobuf i protoc, aby uniknąć konfliktów zależności z innymi bibliotekami projektu.
W przypadku projektów iOS Protobuf jest podłączany przez CocoaPods lub Swift Package Manager. Wtyczka Swift Protobuf automatycznie generuje struktury Swift zgodne z protokołem Codable. W projektach Dart dla Flutter używany jest pakiet protobuf, a kompilacja odbywa się przez dart run protoc_plugin.
Często zadawane pytania
Protobuf — binarny format z obowiązkowym schematem, 3-10 razy bardziej kompaktowy niż JSON. JSON — tekstowy, czytelny dla człowieka, nie wymaga schematu. Protobuf szybciej się serializuje i deserializuje, ale wymaga kompilacji plików .proto. JSON jest łatwiejszy w debugowaniu i nie wymaga wstępnej konfiguracji.
Pobierz protoc z wydania GitHub protobuf dla swojej platformy. Dla macOS zainstaluj przez brew install protobuf. Dla Windows pobierz archiwum zip i dodaj protoc.exe do PATH. Dla Android/Kotlin użyj wtyczki Gradle com.google.protobuf, która automatycznie uruchamia kompilację podczas budowania.
gRPC — to wysokowydajny framework RPC od Google, używający Protobuf jako języka interfejsu (IDL) i formatu serializacji. gRPC definiuje serwisy i metody RPC w plikach .proto, generuje kod kliencki i serwerowy, obsługuje strumieniowanie i protokoły binarne.
Google oficjalnie obsługuje: C++, Java, Kotlin, Python, Go, Ruby, C#, PHP, JavaScript, Objective-C, Swift i Dart. Społeczność opracowała wsparcie dla Rust, TypeScript, Scala, Lua i innych języków. Do programowania mobilnego dostępne są Kotlin/Java (Android) i Swift/Objective-C (iOS).
Protobuf obsługuje wsteczną kompatybilność poprzez zasady: nie zmieniaj numerów pól, nie usuwaj pól (użyj reserved), dodawaj nowe pola z nowymi numerami. Starzy klienci ignorują nieznane pola, nowi klienci otrzymują wartości domyślne dla brakujących starych pól.
Podsumowanie
Opracujemy aplikację mobilną pod klucz
IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.
Przeczytaj również