gRPC + Protocol Buffers
Цель урока
Сделать полноценный CRUD для задач с правильными HTTP-статусами (201/204/404), обработкой ошибок, и подготовить Task к подключению JPA.
Теория · для собеса
1 Что такое gRPC ▸
gRPC — фреймворк для межсервисного общения, созданный Google.
- Protocol Buffers (protobuf) — бинарный формат сериализации (в 3-10x компактнее JSON)
- HTTP/2 — multiplexed, bidirectional streaming
- Строгая типизация через
.protoконтракт
2 Когда gRPC, а когда REST ▸
| | REST + JSON | gRPC + protobuf |
|---|---|---|
| Когда | Публичный API, фронтенд | Микросервисы, mobile backend |
| Размер | Большой (JSON) | Маленький (binary) |
| Скорость | Средняя | Высокая |
| Типизация | Опционально (OpenAPI) | Обязательно (.proto) |
| Читаемость | Да (curl, Postman) | Нет (нужен grpcurl) |
| Streaming | Только WebSocket / SSE | Нативно (4 типа) |
3 4 типа gRPC вызовов ▸
- Unary — клиент отправляет 1 запрос, получает 1 ответ (как REST)
- Server streaming — клиент отправляет 1, получает stream
- Client streaming — клиент отправляет stream, получает 1
- Bidirectional streaming — оба стримят
---
Практика: улучшаем CRUD
-
1Зависимости
plugins { id("com.google.protobuf") version "0.9.4" } dependencies { implementation("io.grpc:grpc-netty-shaded:1.66.0") implementation("io.grpc:grpc-protobuf:1.66.0") implementation("io.grpc:grpc-stub:1.66.0") compileOnly("org.apache.tomcat:annotations-api:6.0.53") // для @Generated } -
2
src/main/proto/task.protosyntax = "proto3"; option java_package = "com.taskflow.grpc"; option java_multiple_files = true; service TaskService { rpc GetTask (TaskRequest) returns (TaskResponse); rpc ListTasks (ListTasksRequest) returns (ListTasksResponse); rpc CreateTask (CreateTaskRequest) returns (TaskResponse); rpc StreamTasks (StreamTasksRequest) returns (stream TaskResponse); // server streaming } message TaskRequest { int64 id = 1; } message ListTasksRequest { int32 page = 1; int32 size = 2; } message ListTasksResponse { repeated TaskResponse tasks = 1; } message CreateTaskRequest { string title = 1; string description = 2; bool done = 3; } message StreamTasksRequest { string owner_username = 1; } message TaskResponse { int64 id = 1; string title = 2; string description = 3; bool done = 4; string owner_username = 5; int64 created_at_ms = 6; // epoch millis } -
3Gradle protobuf
protobuf { protoc { artifact = "com.google.protobuf:protoc:3.25.5" } plugins { id("grpc") { artifact = "io.grpc:protoc-gen-grpc-java:1.66.0" } } generateProtoTasks { all().forEach { task -> task.plugins { create("grpc") { /* options */ } } } } } sourceSets { main { java { srcDirs("build/generated/source/proto/main/grpc") srcDirs("build/generated/source/proto/main/java") } } }После
./gradlew buildсгенерируются:TaskServiceGrpc.java— базовый класс сервераTaskRequest.java,TaskResponse.java— POJO
-
4
grpc/TaskServiceImpl.java(сервер)package com.taskflow.grpc; import com.taskflow.model.Task; import com.taskflow.repository.TaskRepository; import io.grpc.stub.StreamObserver; import net.devh.boot.grpc.server.service.GrpcService; import org.springframework.beans.factory.annotation.Autowired; import java.util.List; @GrpcService public class TaskServiceImpl extends TaskServiceGrpc.TaskServiceImplBase { @Autowired private TaskRepository taskRepository; @Override public void getTask(TaskRequest request, StreamObserverresponseObserver) { Task task = taskRepository.findById(request.getId()) .orElseThrow(() -> new RuntimeException("Not found")); TaskResponse response = TaskResponse.newBuilder() .setId(task.getId()) .setTitle(task.getTitle()) .setDescription(task.getDescription() != null ? task.getDescription() : "") .setDone(task.isDone()) .setOwnerUsername(task.getOwner().getUsername()) .setCreatedAtMs(task.getCreatedAt().toEpochMilli()) .build(); responseObserver.onNext(response); responseObserver.onCompleted(); } @Override public void listTasks(ListTasksRequest request, StreamObserver responseObserver) { List tasks = taskRepository.findAll(); ListTasksResponse.Builder builder = ListTasksResponse.newBuilder(); for (Task t : tasks) { builder.addTasks(toProto(t)); } responseObserver.onNext(builder.build()); responseObserver.onCompleted(); } @Override public void streamTasks(StreamTasksRequest request, StreamObserver responseObserver) { // Server streaming List tasks = taskRepository.findByOwnerUsername(request.getOwnerUsername()); for (Task t : tasks) { responseObserver.onNext(toProto(t)); try { Thread.sleep(100); } catch (InterruptedException e) {} } responseObserver.onCompleted(); } private TaskResponse toProto(Task t) { return TaskResponse.newBuilder() .setId(t.getId()) .setTitle(t.getTitle()) .setDescription(t.getDescription() != null ? t.getDescription() : "") .setDone(t.isDone()) .setOwnerUsername(t.getOwner().getUsername()) .setCreatedAtMs(t.getCreatedAt().toEpochMilli()) .build(); } } -
5Клиент
@Component public class TaskGrpcClient { private final TaskServiceGrpc.TaskServiceBlockingStub blockingStub; public TaskGrpcClient(@Value("${grpc.client.host:localhost}") String host, @Value("${grpc.client.port:9090}") int port) { ManagedChannel channel = ManagedChannelBuilder.forAddress(host, port) .usePlaintext() .build(); this.blockingStub = TaskServiceGrpc.newBlockingStub(channel); } public TaskResponse getTask(Long id) { return blockingStub.getTask(TaskRequest.newBuilder().setId(id).build()); } } -
6application.yml
grpc: server: port: 9090 -
7Тест через grpcurl
# Установка brew install grpcurl # Unary call $ grpcurl -plaintext -d '{"id":1}' localhost:9090 taskflow.TaskService/GetTask # Server streaming $ grpcurl -plaintext -d '{"owner_username":"alice"}' localhost:9090 taskflow.TaskService/StreamTasks---
Зачем это на собесе
После урока ты должен уметь ответить на:
.proto — единый для всех языков (Java, Go, Python, Node)..proto → сервер на Java, клиент на Go.5 вопросов на углубление
Раскрой вопрос и нажми «🤔 Хочу разобрать подробнее» — он попадёт в страницу ответов.
Разбор внутри: protobuf schema, codegen и REST-документация.
Разбор внутри: компилятор proto и генерация Java/gRPC кода.
Разбор внутри: multiplexing, header compression и streaming.
Разбор внутри: grpc-web, Envoy proxy и ограничения браузера.
Разбор внутри: grpcurl, interceptors и reflection.
Готов идти дальше?
Выбери вопросы, которые тебе интересны, и изучи их. Потом — к следующему уроку.
🚀 Перейти к Уроку 29Сначала пройди все секции и выбери хотя бы 1 вопрос
⬅️ Назад к Уроку 27