gRPC (Google Remote Procedure Calls):
система удалённого вызова процедур (RPC) с открытым исходным кодом, первоначально разработанная в Google в 2015 году. В качестве транспорта используется HTTP/2, в качестве языка описания интерфейса — Protocol Buffers. gRPC предоставляет такие функции как аутентификация, двунаправленная потоковая передача и управление потоком, блокирующие или неблокирующие привязки, а также отмена и тайм-ауты. Генерирует кроссплатформенные привязки клиента и сервера для многих языков. Чаще всего используется для подключения служб в микросервисном стиле архитектуры и подключения мобильных устройств и браузерных клиентов к серверным службам.
Protocol Buffers:
протокол сериализации (передачи) структурированных данных, предложенный Google как эффективная бинарная альтернатива текстовому формату XML. Разработчики сообщают, что Protocol Buffers проще, компактнее и быстрее, чем XML, поскольку осуществляется передача бинарных данных, оптимизированных под минимальный размер сообщения.
Почему gRPC лучше REST для внутренних сервисов:
HTTP/2 бинарный формат (protobuf) → меньше трафика и CPU.
Мультиплексирование в одном TCP-соединении → меньше соединений, ниже латентность.
Односторонние, серверные и двусторонние стримы без long-polling.
Сгенерированные типобезопасные клиенты на 15+ языках.
Контракт-first: изменения схемы ловятся на этапе компиляции.
Встроенные deadline, retry, load-balancing, TLS.
Наблюдаемость: метаданные (headers/trailers) подходят для трассировок и метрик.
Простой перенос наружу: можно положить grpc-gateway перед сервисом и получить REST/JSON без переписывания кода.
Для примера сделаем grpc-server и grpc-client
Структура проекта
simple-grpc │ settings.gradle.kts ├─grpc-server │ ├─build.gradle.kts │ ├─src/main/proto/hello.proto │ └─src/main/kotlin/com/example/grpc/Server.kt └─grpc-client ├─build.gradle.kts ├─src/main/proto/hello.proto └─src/main/kotlin/com/example/grpc/Client.kt
В файле *.proto записано, какие сообщения и методы понимает сервис.
Примерно как договор между клиентом и сервером:
service:
перечисляет доступные операции (RPC-методы).
message:
описывает поля запросов и ответов (тип, имя, порядковый номер).
Версия файла лежит в VCS; обе стороны генерируют код из этой одной схемы и получают строго типобезопасные классы.
Зачем нужен:
Единый источник правды – ни клиент, ни сервер не могут «рассинхронизироваться» по формату.
Автогенерация – писать надо только логику, а не сериализацию/десериализацию.
Проверка на этапе сборки – изменил поле в контракте компилятор сразу покажет, где в коде нужно поправить.
Мультиязычность – из одного и того же proto можно в пару команд получить клиент на Kotlin, Go, Python и т.д.
*.proto описывает API однажды; всё остальное генерируется.
После генерации появятся GreeterGrpcKt.GreeterCoroutineImplBase (для сервера) и GreeterCoroutineStub (для клиента).
src/main/proto/hello.proto (копируем в оба модуля)
syntax = "proto3"; package greet; service Greeter { rpc SayHello (HelloRequest) returns (HelloReply); } message HelloRequest { string name = 1; } message HelloReply { string message = 1; }
grpc-server/build.gradle.kts
plugins { kotlin("jvm") version "2.2.0" id("application") id("com.google.protobuf") version "0.9.4" } repositories { mavenCentral() } dependencies { implementation("io.grpc:grpc-kotlin-stub:1.4.3") implementation("io.grpc:grpc-protobuf:1.73.0") implementation("io.grpc:grpc-stub:1.73.0") implementation("com.google.protobuf:protobuf-kotlin:4.31.1") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.21") runtimeOnly("io.grpc:grpc-netty-shaded:1.73.0") } application { mainClass.set("com.example.grpc.ServerKt") } protobuf { protoc { artifact = "com.google.protobuf:protoc:4.31.1" } plugins { id("grpc") { artifact = "io.grpc:protoc-gen-grpc-java:1.73.0" } id("grpckt") { artifact = "io.grpc:protoc-gen-grpc-kotlin:1.4.3:jdk8@jar" } } generateProtoTasks.all().forEach { t -> t.plugins { id("grpc"); id("grpckt") } } }
grpc-client/build.gradle.kts (отличается только mainClass)
plugins { kotlin("jvm") version "2.2.0" id("application") id("com.google.protobuf") version "0.9.4" } repositories { mavenCentral() } dependencies { implementation("io.grpc:grpc-kotlin-stub:1.4.3") implementation("io.grpc:grpc-protobuf:1.73.0") implementation("io.grpc:grpc-stub:1.73.0") implementation("com.google.protobuf:protobuf-kotlin:4.31.1") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.21") runtimeOnly("io.grpc:grpc-netty-shaded:1.73.0") } application { mainClass.set("com.example.grpc.ClientKt") } protobuf { protoc { artifact = "com.google.protobuf:protoc:4.31.1" } plugins { id("grpc") { artifact = "io.grpc:protoc-gen-grpc-java:1.73.0" } id("grpckt") { artifact = "io.grpc:protoc-gen-grpc-kotlin:1.4.3:jdk8@jar" } } generateProtoTasks.all().forEach { t -> t.plugins { id("grpc"); id("grpckt") } } }
Server.kt
package com.example.grpc import greet.GreeterGrpcKt import greet.HelloReply import greet.HelloRequest import io.grpc.ServerBuilder class GreeterService : GreeterGrpcKt.GreeterCoroutineImplBase() { override suspend fun sayHello(request: HelloRequest): HelloReply = HelloReply.newBuilder() .setMessage("Hello, ${request.name}") .build() } fun main() { val server = ServerBuilder .forPort(9090) .addService(GreeterService()) .build() .start() println("gRPC server on 9090") Runtime.getRuntime().addShutdownHook(Thread { server.shutdown() }) server.awaitTermination() }
Client.kt
package com.example.grpc import greet.GreeterGrpcKt import greet.HelloRequest import io.grpc.ManagedChannelBuilder import kotlinx.coroutines.runBlocking fun main() = runBlocking { val channel = ManagedChannelBuilder .forAddress("localhost", 9090) .usePlaintext() .build() val stub = GreeterGrpcKt.GreeterCoroutineStub(channel) val reply = stub.sayHello( HelloRequest.newBuilder().setName("Roman").build() ) println(reply.message) channel.shutdownNow() }
После запуска клиент выведет Hello, Roman
, подтверждая работу gRPC-вызова.
Примеры сгенерированных классов:
HelloRequestKt.kt, HelloReplyKt.kt:
Kotlin-версии сообщений; у каждого есть Builder, методы parseFrom, toByteArray.
GreeterGrpcKt.kt содержит два класса:
GreeterCoroutineImplBase:
базовый abstract-класс, от которого наследуется сервер; нужно реализовать sayHello.
GreeterCoroutineStub:
типобезопасный клиент; метод sayHello — обычный suspend.
GreeterGrpc.java (не показан):
то же самое, но без корутин; остаётся, если нужен Java-код.
HelloRequestKt.kt (protobuf-kotlin)
// AUTO-GENERATED BY PROTOC, DO NOT EDIT package greet import com.google.protobuf.kotlin.* @OptIn(ProtobufSyntaxSupport::class) class HelloRequest private constructor( _name: String = "" ) : com.google.protobuf.GeneratedMessageLite< HelloRequest, HelloRequest.Builder>(DEFAULT_INSTANCE) { var name: String = _name private set // builder pattern class Builder : com.google.protobuf.GeneratedMessageLite.Builder< HelloRequest, Builder>(DEFAULT_INSTANCE) { fun setName(value: String): Builder = apply { instance.name = value } fun build(): HelloRequest = instance } companion object { private val DEFAULT_INSTANCE = HelloRequest() fun newBuilder(): Builder = Builder() @JvmStatic fun parseFrom(bytes: ByteArray) = DEFAULT_INSTANCE.parseFrom(bytes) } }
HelloReplyKt.kt
package greet import com.google.protobuf.kotlin.* class HelloReply private constructor( _message: String = "" ) : com.google.protobuf.GeneratedMessageLite< HelloReply, HelloReply.Builder>(DEFAULT_INSTANCE) { var message: String = _message private set class Builder : com.google.protobuf.GeneratedMessageLite.Builder< HelloReply, Builder>(DEFAULT_INSTANCE) { fun setMessage(v: String): Builder = apply { instance.message = v } fun build(): HelloReply = instance } companion object { private val DEFAULT_INSTANCE = HelloReply() fun newBuilder(): Builder = Builder() } }
GreeterGrpcKt.kt (gRPC-Kotlin plugin)
// AUTO-GENERATED BY PROTOC, DO NOT EDIT package greet import io.grpc.kotlin.* import kotlinx.coroutines.flow.Flow object GreeterGrpcKt { const val SERVICE_NAME: String = "greet.Greeter" abstract class GreeterCoroutineImplBase( coroutineContext: kotlin.coroutines.CoroutineContext = kotlinx.coroutines.Dispatchers.Default ) : AbstractCoroutineServerImpl(coroutineContext) { open suspend fun sayHello(request: HelloRequest): HelloReply = throw io.grpc.Status.UNIMPLEMENTED.asRuntimeException() final override fun bindService() = service { unaryRpc( method = METHOD_SAY_HELLO, implementation = ::sayHello ) } } class GreeterCoroutineStub private constructor( channel: io.grpc.Channel, callOptions: io.grpc.CallOptions ) : AbstractCoroutineStub<GreeterCoroutineStub>(channel, callOptions) { suspend fun sayHello(request: HelloRequest): HelloReply = unaryRpc( channel, METHOD_SAY_HELLO, request, callOptions, HelloReply.getDefaultInstance() ) override fun build(channel: io.grpc.Channel, callOptions: io.grpc.CallOptions) = GreeterCoroutineStub(channel, callOptions) } private val METHOD_SAY_HELLO = io.grpc.MethodDescriptor.newBuilder<HelloRequest, HelloReply>() .setType(io.grpc.MethodDescriptor.MethodType.UNARY) .setFullMethodName(io.grpc.MethodDescriptor.generateFullMethodName( SERVICE_NAME, "SayHello")) .setRequestMarshaller(protoLiteRequestMarshaller(HelloRequest.getDefaultInstance())) .setResponseMarshaller(protoLiteResponseMarshaller(HelloReply.getDefaultInstance())) .build() }