Protocol Buffers (Protobuf) е бинарен формат за сериализация на структурирани данни, разработен от Google за ефективен обмен на информация между услуги. Форматът изисква предварително дефиниране на схема в .proto файлове, от които се генерира код за различни езици. Според официалната документация на Google, Protobuf осигурява размер на съобщенията 3-10 пъти по-малък от JSON. Protobuf се използва в gRPC, Google Maps и хиляди вътрешни услуги.
Основни моменти
Protocol Buffers е механизъм за сериализация на структурирани данни, разработен от Google, подобен на JSON и XML, но с фундаментална разлика: данните са кодирани в бинарен формат. Това означава, че съобщението на Protobuf не може да бъде прочетено с невъоръжено око, но заема значително по-малко място и се обработва по-бързо от текстовите аналози.
Protobuf е създаден вътрешно в Google за решаване на проблеми с производителността при обмен на данни между услуги. През 2008 г. технологията става проект с отворен код, подкрепящ много езици: C++, Java, Python, Go, JavaScript, Kotlin, Swift, Dart и други. Версията proto3, публикувана през 2016 г., опрости синтаксиса и добави подкрепа за повече езици.
Ключовата разлика между Protobuf и JSON е необходимостта от дефиниране на схема преди започване на обмена на данни. Файлът на схемата (.proto) описва структурата на съобщението: полета, типове и уникални номера на полета. От тази схема компилаторът protoc генерира класове на целевия език за сериализация и десериализация.
Архитектурата на Protobuf включва три ключови компонента: език за дефиниране на схеми (.proto), компилатор protoc и runtime библиотеки за конкретни езици. Разработчикът описва структурата на данни в .proto файл, пуска компилацията и получава готови класове за работа с тези данни.
Всяко поле в съобщение на Protobuf има уникален номер (field number) — това не е пореден номер, а етикет, използван в бинарния формат за идентификация на полето. Номерата от 1 до 15 се кодират с един байт, от 16 до 2047 — с два байта. Ето защо важните полета с висока честота на използване трябва да се номерират от 1 до 15 за оптимизиране на размера.
Protobuf подкрепя широк спектър от типове: скаларни (int32, int64, float, double, bool, string, bytes), енумерации (enum), съставни (message) и специални (oneof, map). Всеки тип има определено бинарно представяне, оптимизирано за съответния сценарий на използване.
| .proto тип | C++ тип | Java/Kotlin | Описание |
|---|---|---|---|
| double | double | double | 64-битово число с плаваща запетая |
| float | float | float | 32-битово число с плаваща запетая |
| int32 | int32 | int | 32-битово, variable-length encoding |
| int64 | int64 | long | 64-битово, variable-length encoding |
| string | string | String | UTF-8 низ |
| bytes | string | ByteString | Произволни байтове |
| bool | bool | boolean | true / false |
Protobuf предоставя три ключови предимства пред текстовите формати: размер на съобщението, скорост на сериализация и строго типизиране. В натоварени системи и мобилни приложения с ограничен трафик, тези предимства стават критични.
Бинарното представяне на Protobuf използва variable-length encoding (Varint) за числа: малките числа заемат 1 байт, големите до 10 байта. Това позволява ефективно кодиране на идентификатори, флагове и броячи, които в JSON или XML биха заели десетки байта като текстови низове. Например числото 150 в Protobuf заема 2 байта, в JSON 3 байта (като текст „150“), в XML 3 байта + етикети.
Допълнително предимство е обратната съвместимост. Добавянето на ново поле към схемата не разбива стари клиенти: те просто игнорират непознатите полета. Премахването на поле изисква само резервиране на номера му за предотвратяване на колизии в бъдеще.
.proto схемата описва структурата на данни на специален език. Файлът започва с посочване на синтаксис (proto3), пакет и импортиране на зависимости. Всяко съобщение се дефинира с ключовата дума message с полета, където всяко поле има тип, име и уникален номер.
Правила за добра схема: номера на полета от 1 до 15 за често използвани полета, смислови имена, групиране на свързани полета в отделни message, използване на oneof за полета, които могат да бъдат само една от няколко варианти.
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;
}
Съобщенията могат да бъдат вложени: полето profile от тип Profile ще съдържа потребителски данни. Подкрепата за вложеност позволява описване на сложни йерархични структури без дублиране на дефиниции.
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;
}
Компилаторът protoc преобразува .proto файлове в изходен код на целевия език. За Kotlin/Java се използва параметърът --java_out, за Swift --swift_out (чрез Apple Swift Protobuf приставка), за Dart --dart_out. Генерираните класове съдържат builder методи за конструиране на съобщения и методи за сериализация/десериализация.
В Android за работа с Protobuf се използва приставката com.google.protobuf версия 0.9+ в Gradle. След добавяне на приставката и посочване на .proto файловете, компилацията автоматично генерира Kotlin класове, готови за употреба в приложението.
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 {
// Сериализация в бинарен формат
val bytes = user.toByteArray()
// Десериализация от бинарен формат
return User.parseFrom(bytes)
}
Protobuf е особено ефективен в микроуслужбата архитектура и мобилните приложения. Нека разгледаме типичен сценарий: мобилно приложение получава списък продукти от сървъра чрез gRPC. Съобщението на Protobuf съдържа информация за продукта, включително идентификатор, име, цена и категория. Бинарният формат намалява размера на отговора 5-8 пъти в сравнение с JSON.
Пример за gRPC услуга с Protobuf на сървърната страна. Услугата декларира RPC метода GetProducts, който приема заявка с параметри за пагинация и връща списък от продукти. Имплементацията на Kotlin използва генерираните класове за работа със заявки и отговори.
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()
}
}
Настройката на Protobuf в Android проект се осъществява чрез Gradle приставката com.google.protobuf. Приставката автоматично стартира компилатора protoc при компилация и генерира Kotlin класове от .proto файлове. За работа е необходимо да добавите приставката в build.gradle на ниво проект и да я приложите в модула на приложението.
След конфигуриране на Gradle, .proto файловете се поставят в директорията src/main/proto. Компилаторът protoc ги обработва при всяка компилация, генерирайки Kotlin класове, които могат да се използват в кода на приложението. Важно е да посочите правилните версии на protobuf и protoc, за да се избегнат конфликти на зависимостите с други библиотеки на проекта.
За iOS проекти Protobuf се свързва чрез CocoaPods или Swift Package Manager. Приставката Swift Protobuf автоматично генерира Swift структури, съответстващи на протокол Codable. В Dart проекти за Flutter се използва пакетът protobuf, а компилацията се извършва чрез dart run protoc_plugin.
Често задавани въпроси
Protobuf е бинарен формат със задължителна схема, 3-10 пъти по-компактен от JSON. JSON е текстов, четим за човека, не изисква схема. Protobuf се сериализира и десериализира по-бързо, но изисква компилация на .proto файлове. JSON е по-лесна за дебъгване и не изисква предварителна конфигурация.
Изтеглете protoc от GitHub изданието на protobuf за вашата платформа. За macOS инсталирайте чрез brew install protobuf. За Windows изтеглете zip архив и добавете protoc.exe в PATH. За Android/Kotlin използвайте Gradle приставката com.google.protobuf, която автоматично стартира компилацията при изграждане.
gRPC е високопроизводителна RPC рамка от Google, която използва Protobuf като интерфейсен език (IDL) и формат за сериализация. gRPC дефинира услуги и RPC методи в .proto файлове, генерира клиентски и сървърен код, подкрепя стрийнг и бинарни протоколи.
Google официално подкрепя: C++, Java, Kotlin, Python, Go, Ruby, C#, PHP, JavaScript, Objective-C, Swift и Dart. Общността разработи подкрепа за Rust, TypeScript, Scala, Lua и други езици. За мобилно разработване са налични Kotlin/Java (Android) и Swift/Objective-C (iOS).
Protobuf подкрепя обратна съвместимост чрез правила: не променяйте номерата на полета, не изтривайте полета (използвайте reserved), добавяйте нови полета с нови номера. Старите клиенти игнорират непознатите полета, новите клиенти получават стандартни стойности за липсващи стари полета.
Заключение
Ще разработим мобилно приложение под ключ
IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.
Прочетете също