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. Текущий tRPC API не генерирует OpenAPI. Самостоятельный HTTP-контракт и его проверка разобраны в лаборатории 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 и пагинация дополняют контракт.