MidТеория4 min

REST vs GraphQL vs gRPC vs WebSocket vs SSE

Пять главных API-протоколов: сильные стороны, codec, streaming, browser support, когда какой выбрать

Что это

Пять протоколов, между которыми сегодня выбирают для API.

  • REST -- HTTP-методы на ресурсах. JSON. Проще всего.
  • GraphQL -- один endpoint, клиент описывает нужные поля, сервер возвращает ровно их.
  • gRPC -- бинарный (protobuf) + HTTP/2; типизированные контракты, streaming.
  • WebSocket -- постоянное bidirectional соединение поверх HTTP upgrade.
  • SSE (Server-Sent Events) -- однонаправленный поток от сервера клиенту по обычному HTTP.

Каждый оптимизирован под разный access pattern. "REST vs gRPC" -- часто false dichotomy; в реальном проекте используются несколько.

Когда REST лучше

  • Публичный HTTP API. Всё знает REST, все инструменты (curl, Postman, CDN) работают из коробки.
  • CRUD-ресурсы. GET /users/:id, POST /users, DELETE /users/:id -- естественно.
  • Cache на уровне HTTP. GET с ETag / Cache-Control кешируется CDN без кастомного кода.
  • Команда разная. REST знает каждый junior-разработчик.
  • Нет жёстких требований на payload size / latency.

Минусы: over-fetching (возвращается больше, чем нужно), under-fetching (нужно несколько запросов), нет стандарта схемы (OpenAPI помогает, но он описание, не код).

Когда GraphQL лучше

  • Клиент хочет гибко выбирать поля. Мобильный клиент -- мало полей и данных; admin-панель -- много.
  • Один запрос вместо N. Пользователь + его заказы + платежи -- один GraphQL-запрос вместо 3 REST.
  • Быстрая эволюция UI. Добавить новое поле в ответ -- без новой версии API.
  • Схема -- единственный источник правды. Генерация типов для TS / Flow / Swift.

Минусы:

  • Сложнее кеширование (persisted queries помогают).
  • N+1 без DataLoader.
  • Overhead на GraphQL-движок.
  • Сложнее rate limit и авторизация per-field.
  • Деплой схемы -- важный процесс (breaking changes).

Когда gRPC лучше

  • Сервис-to-сервис (internal). Меньший payload (protobuf ~30% от JSON), HTTP/2 multiplexing, типы из коробки.
  • Streaming. Server streaming, client streaming, bidirectional streaming.
  • Полиглот-стек. .proto -> клиенты на Go/Java/Python/TS/...
  • Строгие контракты. Добавить поле -- unchanged. Удалить -- требует деприкации.

Минусы:

  • В браузере нужен gRPC-Web и grpc-gateway/Envoy.
  • Бинарный payload нельзя посмотреть в Chrome DevTools без расширений.
  • Тяжелее учиться, чем REST.

Когда WebSocket лучше

  • Bidirectional низколатентный канал: чат, game state, collaboration (Figma, Google Docs), trading.
  • Push с клиентских действий. Клиент шлёт команды, сервер стримит обновления.
  • Long-lived сессия с много маленьких сообщений.

Минусы:

  • Состояние на сервере (см. Stateful vs Stateless).
  • Нет HTTP-кеша, нет retries, нет стандартного auth потока (JWT в первое сообщение или при handshake).
  • Firewall/proxy иногда ломают WS.
  • Балансировка сложнее (sticky или sharded).

Когда SSE лучше

  • Один-направленный server push по HTTP. Обновления статуса, фид новостей, логи в браузер.
  • Проще WebSocket: просто EventSource в JS, GET-endpoint на сервере.
  • Авто-переподключение встроено.
  • Работает через HTTP-прокси.
  • Не нужен bidi.

Минусы:

  • Только server → client (для обратного направления нужен отдельный REST).
  • Один EventSource = одно TCP-соединение; в HTTP/1.1 лимит 6 соединений на домен (в HTTP/2 не проблема).
  • Нет бинарного протокола.

Сравнительная таблица

Характеристика REST GraphQL gRPC WebSocket SSE
Транспорт HTTP 1/2 HTTP 1/2 HTTP/2 TCP over HTTP upgrade HTTP 1/2
Формат JSON JSON Protobuf (бинарный) Любой (обычно JSON) text/event-stream
Схема OpenAPI (опционально) SDL (обязательно) .proto (обязательно) Нет стандарта Нет
Streaming Нет (chunked transfer -- хак) Subscriptions (over WS) Да (4 модели) Да (bidi) Да (server → client)
Browser native Да Да Нет (нужен gRPC-Web) Да Да (EventSource)
HTTP-кеш Да Сложно Нет Нет Ограниченно
Payload size Средний (JSON) Средний Маленький (protobuf) Маленький (custom) Средний (text)
Latency Средняя Средняя Низкая (HTTP/2 mux) Очень низкая Низкая
Типизация Слабая (runtime) Строгая (schema) Строгая (codegen) Своя Нет
Кривая обучения Низкая Средняя Средняя-высокая Средняя Низкая
Лучшее применение CRUD, публичный API BFF, мобильные, сложные UI Внутренние сервисы Realtime bidi Server push один в один

Гибриды в реальном проекте

Типичный крупный стек:

Mobile / Web
      │
      ▼
  GraphQL / REST gateway (BFF)
      │
      ▼
  gRPC между сервисами (orders, users, payments)
      │
      ▼
  Postgres / Kafka / Redis

WebSocket -- отдельный сервис для чата.
SSE        -- обновления уведомлений в веб.

GraphQL на периферии (клиент-facing), gRPC внутри (service-to-service), WS/SSE -- для стриминга.

Матрица выбора

Сценарий Рекомендация
Публичный API для внешних разработчиков REST (OpenAPI)
Мобильный клиент, экономит payload GraphQL или gRPC (если Android/iOS SDK)
Микросервисы внутри, полиглот gRPC
Одностраничник с гибкими формами GraphQL
Realtime чат, games WebSocket
Dashboard с live-обновлением SSE
Сервис с jerky UI (лайки, счётчики) SSE или WS (вклад зависит от количества)
IoT телеметрия MQTT / gRPC streaming
Internal CRUD admin tool REST

Код: один и тот же запрос пятью способами

Задача: получить пользователя по id.

<?php

declare(strict_types=1);

final class UsersController
{
    public function __construct(private readonly UserService $users) {}

    public function show(string $id): Response
    {
        $user = $this->users->find($id) ?? throw new NotFoundHttpException();

        return new JsonResponse([
            'id' => $user->id,
            'email' => $user->email,
            'name' => $user->name,
        ], 200, ['Cache-Control' => 'private, max-age=30']);
    }
}
// GET /users/42  → {"id":"42","email":"a@b","name":"..."}
## Комбинирование стоит ожидать

Редко проект обходится одним протоколом:

  • REST + WS: админка + live-дашборд.
  • GraphQL + gRPC: BFF на GraphQL, backend-to-backend на gRPC.
  • REST + SSE: получить список задач (REST), подписаться на статус (SSE).
  • gRPC + gRPC-streaming: обычные вызовы + телеметрия.

Антипаттерны

Антипаттерн Почему плохо
GraphQL где достаточно REST Overengineering, N+1 без DataLoader
gRPC для публичного API Браузеры без хака не умеют
WebSocket ради "модно" Усложняет scale, auth, observability
REST + polling вместо SSE Бешеная нагрузка при частом опросе
Версия через URL только, без deprecation API превращается в зоопарк

Выводы

  • REST -- дефолт для публичных CRUD API, максимум инструментов и совместимости.
  • GraphQL -- BFF для клиентов с гибкими потребностями (мобильные, сложные UI).
  • gRPC -- внутренние сервисы, полиглот-стек, streaming, низкий overhead.
  • WebSocket -- реалтайм bidirectional (чат, игры, collab).
  • SSE -- дешёвый server push, когда не нужен обратный канал.
  • В реальности используются вместе: REST/GraphQL снаружи, gRPC внутри, WS/SSE для realtime.
  • Выбор -- по access pattern, не по моде.