Учебник веб-разработки
Разделы учебника
На этой странице

VII. HTTP и API

REST, GraphQL, tRPC и gRPC: какие контракты мы сравниваем

Оглавление · HTTP · API Atmanki

Задача: выбрать форму взаимодействия по потребителям, границам и стоимости сопровождения. Нужны типы, схемы и различие запроса и результата. Эти названия не обозначают четыре взаимозаменяемых формата JSON.

Одна задача, разные границы

Учебная задача — получить заголовок статьи по slug. В Atmanki она реализована через tRPC и CMS repository; остальные варианты ниже — проектируемые контракты. Ни GraphQL-сервер, ни gRPC-сервис в проект не добавляются.

ПодходОсновная единицаГде описан контракт
HTTP API в ресурсном стилеРесурс и представлениеHTTP-операции и описание данных, например OpenAPI
GraphQLОперация над типизированной схемойСхема и выбранные клиентом поля
tRPCВызов процедурыTypeScript router, входные runtime-схемы
gRPCМетод сервиса и сообщенияОпределения сервиса, обычно Protobuf

Во всех вариантах остаются ошибки, полномочия, лимиты, задержки, повторы и совместимость. Выбор инструмента не решает эти задачи автоматически.

REST и практический HTTP API

REST — архитектурный стиль: разделение клиента и сервера, stateless-запросы, кешируемость, слои и единообразный интерфейс, включая гипермедиа. Stateless не запрещает БД: запрос должен содержать необходимый контекст взаимодействия. Один endpoint с JSON ещё не демонстрирует все ограничения REST. Определение Fielding.

В учебном ресурсном API можно описать GET /api/articles/{slug}, ответы 200/404/503, разрешённые фильтры и пагинацию списка. У сортировки нужен устойчивый порядок с разрешением равных значений; offset и cursor имеют разные свойства при появлении новых записей. Не переносите параметры прямо в SQL.

OpenAPI описывает пути, операции, параметры, схемы, ответы и требования доступа. Документ может служить основой документации и генерации клиента, но сам не исполняет валидацию или проверку полномочий. Спецификация OpenAPI 3.1.1. В этом этапе OpenAPI-документ не создаётся; полноценная лаборатория остаётся в программе.

GraphQL: клиент выбирает поля

Иллюстративная схема и операция:

type Article {
  slug: String!
  title: String!
}
type Query {
  article(slug: String!): Article
}
query ArticleTitle($slug: String!) {
  article(slug: $slug) {
    title
  }
}

Resolvers реализуют получение полей. Query предназначена для чтения, mutation — для изменений; корневые поля mutation исполняются последовательно. Subscription задаёт поток результатов по событиям; конкретный транспорт требуется отдельно. Допустимы частичные данные вместе с errors, а non-null влияет на распространение ошибки к родителям. HTTP-статус и ошибки выполнения не следует смешивать. Спецификация GraphQL.

Исходник схемы
flowchart LR
  Operation["Операция и переменные"] --> Validate["Проверка по схеме"]
  Validate --> Resolve["Resolvers выбранных полей"]
  Resolve --> Sources["Источники данных"]
  Sources --> Response["Data и ошибки выполнения"]

Выбор полей не равен произвольному запросу к БД. Для учебной статьи сервер по-прежнему определяет, какие записи публичны. Вложенные связи могут давать N+1 чтений, а глубокий запрос — большую стоимость. Измеряйте работу источников, задавайте пределы и проверяйте доступ; одна валидная схема этого не обеспечивает.

tRPC: общий TypeScript-контракт

В router news.bySlug имеет input slugSchema и query с результатом repository. Клиент получает тип AppRouter, сервер проверяет вход Zod. TypeScript-тип исчезает при исполнении; runtime-схема остаётся. tRPC validators.

Исходник схемы
flowchart LR
  Types["AppRouter: тип при компиляции"] -.-> Client["Типизированный клиент"]
  Client --> Adapter["HTTP adapter"]
  Adapter --> Input["Zod: вход"]
  Input --> Procedure["Процедура"]
  Caller["Серверный caller"] --> Input
  Procedure --> Repository["CMS repository"]

Серверный caller вызывает процедуру без сетевого запроса к своему серверу; HTTP-клиент использует adapter и httpBatchLink. Не копируйте wire-формат по догадке: реальные команды приведены в главе API. Текущие процедуры не задают отдельный output validator: тип результата следует из реализации, а данные CMS проверяет слой repository/адаптеров.

tRPC удобен при совместном развитии TypeScript-клиента и сервера. Независимому потребителю на другом языке нужен явно доступный контракт взаимодействия; одного import type AppRouter ему недостаточно. Батчинг не делает несколько процедур одной транзакцией. Пустой context не становится моделью пользователей.

gRPC и Protobuf — разные роли

gRPC описывает вызовы сервиса: unary, клиентский, серверный и двунаправленный streaming, metadata, статусы, deadline и отмену. Deadline ограничивает ожидание; отмена не доказывает откат эффекта. Основные понятия gRPC.

Иллюстративный proto3-контракт, не сервис Atmanki:

syntax = "proto3";
package lesson;

message ArticleRequest {
  string slug = 1;
}
message ArticleReply {
  string slug = 1;
  string title = 2;
  optional string summary = 3;
}
service Articles {
  rpc BySlug(ArticleRequest) returns (ArticleReply);
}

Protobuf задаёт сообщения и бинарное кодирование, используется и без gRPC. Номера полей являются частью wire-контракта: удалённые номера и имена следует резервировать, а не отдавать новым значениям. optional позволяет отличать отсутствие скалярного поля от явно заданного значения по умолчанию. Новый код и старые сообщения должны сохранять бизнес-смысл, а не только читаться. Руководство proto3.

Браузерный gRPC-Web — отдельный клиентский путь, обычно с прокси; возможности streaming зависят от выбранной реализации и режима. Его нельзя приравнивать к любому нативному gRPC-клиенту. Учебник gRPC-Web, ограничения streaming реализации.

Выбор и эволюция

Для Atmanki уже есть tRPC; менять стек ради таблицы не требуется. Для публичного многоязычного API важны доступность спецификации и независимое обновление клиентов; для внутренних потоков — ограничения транспорта и инфраструктуры. GraphQL полезно оценивать по потребности в разных выборках, вместе со стоимостью resolvers. Это критерии обсуждения, а не рейтинг скорости протоколов.

Добавление поля может быть совместимым для одного клиента и ломать другой, который отвергает неизвестные поля. В Zod проверьте политику strip/passthrough/strict, в GraphQL — nullable/non-null и операции старого клиента, в Protobuf — присутствие, номера и смысл. Сначала определите допустимые сочетания версий.

Практика

Для каждой из четырёх моделей опишите чтение публичной статьи, отсутствие slug, отказ CMS и запрещённое чтение черновика. Отдельно укажите, где проверяется вход, доступ, результат и совместимость. Сравните потребителя на TypeScript и на другом языке.

В коде Atmanki найдите input, тип результата, context, caller и HTTP adapter. Подтвердите, что страницы читают repository напрямую, а клиентский helper подготовлен, но не используется для загрузки списка. GraphQL- и Protobuf-блоки здесь иллюстративны; лаборатория четырёх API запускает HTTP/gRPC и проверяет GraphQL/tRPC внутри процесса на одном поведении. OpenAPI и пагинация дополняют контракт.