Разработка gRPC API для мобильного приложения
gRPC — это не «быстрый REST». Это принципиально другой подход: бинарный протокол (Protocol Buffers), строго типизированный контракт через .proto файлы, встроенная streaming, кодогенерация клиентов для любой платформы. Для мобильных приложений gRPC добавляет весомые плюсы (компактный payload, типобезопасность, bidirectional streaming) и специфические сложности (HTTP/2 ограничения, отладка бинарных данных).
Когда gRPC на мобильном оправдан
gRPC выигрывает у REST/JSON в сценариях:
- Высокочастотные запросы с предсказуемой схемой (трекинг, телеметрия, real-time данные)
- Streaming: одно долгое соединение вместо polling
- Микросервисная архитектура, где mobile gateway уже на gRPC
JSON-over-REST проще в отладке, больше библиотек, нет зависимости от HTTP/2. Для стандартного CRUD-приложения gRPC избыточен.
Определение API через .proto
Контракт — единственный источник правды. Изменение .proto файла приводит к перегенерации клиентов на всех платформах:
syntax = "proto3";
package mobile.api.v1;
service ProductService {
rpc GetProduct (GetProductRequest) returns (Product);
rpc ListProducts (ListProductsRequest) returns (stream Product);
rpc WatchInventory (WatchInventoryRequest) returns (stream InventoryUpdate);
}
message Product {
string id = 1;
string name = 2;
int64 price_cents = 3;
repeated string image_urls = 4;
}
message GetProductRequest {
string id = 1;
}
stream Product в ListProducts — server-side streaming: сервер отправляет продукты по одному по мере готовности, не ждёт все. WatchInventory — server-side stream для real-time обновлений.
Android: gRPC-Java / gRPC-Kotlin
// build.gradle
implementation 'io.grpc:grpc-android:1.x.x'
implementation 'io.grpc:grpc-okhttp:1.x.x' // транспорт через OkHttp (Android)
OkHttp-транспорт предпочтительнее Netty на Android — Netty увеличивает APK на ~3 МБ и медленнее инициализируется.
val channel = ManagedChannelBuilder
.forAddress("api.example.com", 443)
.useTransportSecurity() // TLS обязательно в production
.intercept(AuthInterceptor(tokenProvider))
.build()
val stub = ProductServiceGrpcKt.ProductServiceCoroutineStub(channel)
// Unary вызов
val product = stub.getProduct(getProductRequest { id = productId })
// Server streaming
stub.listProducts(listProductsRequest { categoryId = "electronics" })
.collect { product ->
// каждый product приходит по мере отправки сервером
}
AuthInterceptor реализует ClientInterceptor — добавляет Authorization метаданные к каждому вызову:
class AuthInterceptor(private val tokenProvider: TokenProvider) : ClientInterceptor {
override fun <ReqT, RespT> interceptCall(
method: MethodDescriptor<ReqT, RespT>,
callOptions: CallOptions,
next: Channel
): ClientCall<ReqT, RespT> {
return object : ForwardingClientCall.SimpleForwardingClientCall<ReqT, RespT>(
next.newCall(method, callOptions)
) {
override fun start(responseListener: Listener<RespT>, headers: Metadata) {
headers.put(
Metadata.Key.of("Authorization", Metadata.ASCII_STRING_MARSHALLER),
"Bearer ${tokenProvider.getToken()}"
)
super.start(responseListener, headers)
}
}
}
}
iOS: gRPC-Swift
Apple поддерживает gRPC через grpc-swift пакет:
// Package.swift
.package(url: "https://github.com/grpc/grpc-swift.git", from: "1.0.0")
// Создание канала
let group = PlatformSupport.makeEventLoopGroup(loopCount: 1)
let channel = try GRPCChannelPool.with(
target: .host("api.example.com", port: 443),
transportSecurity: .tls(.makeClientDefault(compatibleWith: group)),
eventLoopGroup: group
)
let client = ProductService_ProductServiceNIOClient(channel: channel)
// Unary вызов
let request = ProductService_GetProductRequest.with { $0.id = productId }
let response = try await client.getProduct(request).response.get()
Server streaming через AsyncStream в Swift Concurrency:
let call = client.listProducts(listRequest)
for try await product in call.responses {
// обрабатываем каждый продукт
}
HTTP/2 и мобильные проблемы
gRPC требует HTTP/2. Это приносит мультиплексирование (несколько запросов на одном TCP соединении), но создаёт проблемы:
Intermediary proxies. Многие корпоративные прокси и некоторые мобильные операторы не поддерживают HTTP/2 полностью или terminateHTTP/2. Решение — gRPC-Web (HTTP/1.1 транспорт через специальный прокси Envoy), но теряем streaming возможности.
Connection keepalive. gRPC-Android поддерживает keepalive pings для поддержания соединения через NAT:
ManagedChannelBuilder.forAddress(host, port)
.keepAliveTime(30, TimeUnit.SECONDS)
.keepAliveTimeout(10, TimeUnit.SECONDS)
.keepAliveWithoutCalls(false)
Deadline. Каждый gRPC вызов должен иметь deadline — иначе зависший запрос висит вечно:
stub.withDeadlineAfter(10, TimeUnit.SECONDS).getProduct(request)
Отладка
JSON читается в Charles/Proxyman, Protocol Buffers — нет. Инструменты: grpcurl (command-line), grpc-ui (web UI), Wireshark с gRPC dissector. В dev-сборке логируем через LoggingClientInterceptor из grpc-java.
Protobuf эволюция схемы
Обратная совместимость — ключевое преимущество Protobuf. Правила:
- Новые поля добавляем с новым номером, никогда не переиспользуем удалённые номера
-
requiredне используем (Protobuf 3 убрал его не случайно) - Для переименования: добавляем новое поле, помечаем старое
reserved
Нарушение этих правил — binary incompatibility: старые версии приложения будут падать при получении новой схемы.
Что входит в работу
Проектируем .proto схему, настраиваем кодогенерацию в CI (protoc плагины), реализуем gRPC клиент на Android/iOS с TLS, auth interceptor, deadline и retry policy, настраиваем отладочную инфраструктуру.
Срок: 2–4 недели с учётом серверной части и тестирования на разных сетевых условиях.







