Что такое gRPC-Gateway? Расскажите о его назначении, плюсах и минусах.

Ответ

gRPC-Gateway — это плагин для protoc (компилятора Protocol Buffers), который читает описание gRPC-сервиса и генерирует обратный прокси-сервер. Этот прокси-сервер преобразует RESTful JSON API запросы в gRPC вызовы.

Назначение: Основная цель — предоставить единый API как для современных клиентов, использующих gRPC, так и для legacy-систем или браузеров, которые могут работать только с REST/JSON.

Как это работает: Вы добавляете специальные аннотации google.api.http в ваш .proto файл, описывая, как HTTP-запросы должны мапиться на gRPC-методы.

// service.proto
import "google/api/annotations.proto";

service YourService {
  rpc GetEntity(GetEntityRequest) returns (Entity) {
    option (google.api.http) = {
      get: "/v1/entities/{id}"
    };
  }
}

После генерации кода вы получаете HTTP-обработчик, который можно запустить на отдельном порту. Он будет принимать HTTP-запросы (например, GET /v1/entities/123), преобразовывать их в gRPC-сообщения и вызывать ваш основной gRPC-сервис.

Плюсы:

  • Единый источник правды: Описание API (и для gRPC, и для REST) находится в одном .proto файле.
  • Автогенерация: Генерирует не только прокси, но и OpenAPI (Swagger) спецификацию для вашего REST API, что упрощает документирование.
  • Экономия времени: Не нужно вручную писать отдельный REST-слой и логику преобразования данных.
  • Постепенная миграция: Позволяет существующим REST-клиентам работать с новой системой на gRPC.

Минусы:

  • Дополнительный слой: Прокси — это еще один сетевой хоп и дополнительная операция (un)marshalling JSON ↔ Protobuf, что добавляет задержку и потребляет ресурсы.
  • Ограниченная гибкость: Сложно реализовать кастомную логику (например, сложную аутентификацию, middleware) на уровне самого гейтвея. Обычно это выносится на уровень выше (например, в API Gateway).
  • Усложнение сборки: В CI/CD пайплайн добавляется еще один шаг кодогенерации.

Ответ 18+ 🔞

Давай я тебе на пальцах, без этой охуевшей бюрократии, объясню, что за зверь такой gRPC-Gateway.

Представь себе, блядь: у тебя есть крутой, современный бар (это твой gRPC-сервер). Там всё быстро, по протоколу, коктейли мешают в шейкерах из протобуфера. Но с улицы заходят старые алкаши (это твои легаси-клиенты или фронтенд), которые мычат только "Дай двести грамм и колы!" — то есть, говно-JSON по старому доброму HTTP.

Так вот, gRPC-Gateway — это такой швейцар-переводчик на входе в этот бар. Он стоит у двери, слушает этих алкашей, кивает: "Ага, понял, 'двести колы'", — заходит внутрь, и на чистом gRPC-протоколе заказывает у бармена VodkaColaRequest { volume_ml: 200 }. Бармен делает, отдаёт, а швейцар переводит обратно в понятный алкашам формат: { "drink": "vodka-cola", "amount": 200 }.

Как это технически происходит, ёпта? Ты в описание своего сервиса (.proto файл) добавляешь специальные пометки, типа инструкции для этого швейцара.

rpc GetUser(GetUserRequest) returns (User) {
  // Вот эта строчка — и есть инструкция для швейцара.
  option (google.api.http) = {
    get: "/v1/users/{user_id}" // Если алкаш пришёл по такому адресу...
  };
}

Потом ты запускаешь кодогенератор, и он тебе из этих пометок создаёт целый HTTP-сервер. Этот сервер — и есть наш швейцар. Ты его запускаешь на каком-нибудь порту (например, 8080), а свой настоящий gRPC-бар — на другом (например, 9000). Швейцар принимает HTTP-запросы, превращает их в gRPC-вызовы к твоему основному серверу, результат обратно в JSON — и вуаля, алкаш доволен.

Плюсы, блядь, очевидны:

  • Один чертёж на всех. Ты описал АПИ один раз в .proto — и у тебя автоматом есть и gRPC, и REST. Не нужно отдельно, блядь, роуты на Go писать и JSON в структуры парсить. Красота!
  • Документация сама пишется. Из этих же пометок можно сгенерить OpenAPI (Swagger) спецификацию. Фронтендеры тебе спасибо не скажут, но хотя бы не будут каждый день писать "а как этот метод работает?".
  • Можно мигрировать потихоньку. Старые клиенты продолжают тупить по REST, а новые, умные, уже могут напрямую в gRPC-бар ходить. Все счастливы.

Но и минусы, сука, есть, куда без них:

  • Лишняя сущность. Это же ещё один сервис, блядь! Ещё один хоп в сети, ещё одна операция сериализации/десериализации (JSON <> Protobuf). Задержка чуть подрастёт, CPU кушать будет.
  • Гибкость — пизда. Хочешь сделать какую-то хитрожопую проверку прав доступа прямо на входе? Или кастомный формат ошибок для REST? С этим могут быть танцы с бубном, потому что швейцар сгенерированный, тупой как пробка. Всю сложную логику всё равно придётся пихать либо в основной gRPC-сервис, либо ставить ещё одного швейцара перед этим швейцаром (называется API Gateway, но это уже другая история).
  • Сборку усложняет. Раньше у тебя в пайплайне был один шаг: protoc --go_out=.. А теперь их два: protoc --go_out=. и protoc --grpc-gateway_out=.. Мелочь, а бесит, когда что-то ломается.

Короче, инструмент охуенный, чтобы быстро дать REST-морду твоему gRPC-сервису. Но это именно прокси, а не серебряная пуля. Если у тебя сложное АПИ с кучей кастомной логики на входе — готовься либо дорабатывать сгенерированный код, либо ставить что-то посерьёзнее.