Задаётесь вопросом, стоит ли использовать GraphQL в своём проекте? Ваши разработчики спорят, выдвигая аргументы типа «GraphQL — это будущее» и «REST проще»? Мы с моей командой обсуждали эту тему бесконечно. В статье я приведу краткие выводы.

Предисловие: GraphQL в моде, вы найдёте множество статей, насколько он потрясающий, однако спустя три года его использования я немного огорчён и разочарован этой технологией, поэтому не воспринимайте мои слова, как истину в последней инстанции.

1. Зачем начинать с ним работать?


При оценке новой технологии я первым делом задаю такой вопрос: «Зачем её нужно использовать?»

В случае GraphQL для ответа на этот вопрос лучше всего вернуться к исходной проблеме, с которой столкнулся Facebook*. Пост от сентября 2015 года прекрасно описывает вопрос.

«В 2012 году мы начали проект по перестройке нативных мобильных приложений Facebook*. В то время наши приложения для iOS и Android были тонкими обёртками поверх визуализации нашего мобильного веб-сайта. Хотя это приблизило нас к платоновскому идеалу мобильного приложения „напиши один раз, используй везде“, на практике это заставляло приложения mobile-webview работать на пределе своих возможностей. В процессе усложнения мобильных приложений Facebook* они начинали страдать от плохой производительности и часто вылетали. Когда мы перешли к нативно реализованным моделям и представлениям, то обнаружили, что нам впервые нужна API-версия данных News Feed, которая до этого момента передавалась только как HTML.

Мы рассмотрели варианты передачи данных News Feed на наши мобильные приложения, в том числе ресурсы сервера RESTful и таблицы FQL (API Facebook* в стиле SQL). Нас напрягали различия между данными, которые нужно было использовать в приложениях, и запросами к серверу, которых они требовали. Мы не рассуждали о данных с точки зрения URL ресурсов, вторичных ключей или join-таблиц; мы думали о них с точки зрения графа объектов».

Facebook* столкнулся со специфической проблемой и создал собственное решение: GraphQL. Для представления данных в виде графа компания спроектировала язык иерархических запросов. Иными словами, GraphQL естественным образом следует взаимосвязям между объектами. Теперь можно получать вложенные объекты и возвращать их все за один сетевой вызов.

Однако компания создала протокол, а не базу данных. У Facebook* уже было хранилище. Каждое поле GraphQL на сервере опиралось на произвольную функцию, изолировавшую бизнес-логику от хранилища.

Наконец, для пользователей со всего мира с не всегда дешёвыми тарифными планами мобильного Интернета протокол GraphQL был оптимизирован, позволяя передавать только то, что необходимо пользователям.

Очень легко понять, как GraphQL решает проблемы Facebook*. Остаётся вопрос: «Решает ли он вашу?»

2. Преимущества GraphQL


Несмотря на то, что он решает очень нишевую проблему, GraphQL убедил большую часть сообщества разработчиков использовать его благодаря своим преимуществам:

  • Один запрос, много ресурсов: по сравнению с REST, при котором необходимо выполнять множественные сетевые запросы к каждой конечной точке, с помощью GraphQL можно запрашивать все ресурсы одним вызовом.
  • Получение точных данных: GraphQL минимизирует объём передаваемых по проводам данных, селективно подбирая их на основании потребностей клиентского приложения. Таким образом, мобильный клиент с маленьким экраном может получать меньше информации.
  • Сильная типизация: каждый запрос, ввод и объекты ответа имеют тип. В веб-браузерах отсутствие типов в JavaScript стало слабостью, которую пытаются компенсировать различные инструменты (Dart компании Google, TypeScript компании Microsoft). GraphQL позволяет обмениваться типами между бэкендом и фронтендом.
  • Более качественный инструментарий и удобство для разработчиков: интроспективному серверу можно отправлять запросы о поддерживаемых им типах, что позволяет применять API explorer, автодополнение и предупреждения редактора. Больше не нужно полагаться на бэкенд-разработчиков в документировании их API. Достаточно просто исследовать конечные точки и получить нужные данные.
  • Независимость от версий: вид возвращаемых данных определяется исключительно запросом клиента, поэтому серверы становятся проще. При добавлении в продукт новых фич на стороне сервера можно добавить новые поля, не влияя на существующие клиенты.

Я полностью понимаю привлекательность этих особенностей и с большим энтузиазмом воспринял саму технологию.

3. Медовый месяц


Наша команда мобильной разработки активно продвигала в компании GraphQL. Нашей команде десктопного фронтенда тоже нравилась идея типов. У нас уже был REST API, однако мы внедрили эту технологию в 2019 году. Команда потратила время на создание новых конечных точек для GraphQL. Мы выбрали библиотеку Apollo, предоставляющую клиенты на React.js, Kotlin и Swift.

Первую конечную точку настроить было очень легко. Сервер Apollo хорошо сработался с express.js в бэкенде, а две конечные точки API Rest и GraphQL могут сосуществовать в одном приложении.

Благодаря принципу «один запрос, много ресурсов» код фронтенда стал намного проще с GraphQL. Представьте ситуацию, когда пользователь хочет получить подробности о конкретном исполнителе, например, (имя, id, композиции и так далее). При традиционном интуитивно понятном паттерне REST это бы потребовало множество перекрёстных запросов между двумя конечными точками /artists и /tracks, которые фронтенд должен был бы потом объединить. Однако благодаря GraphQL мы можем определить все необходимые данные в запросе, как показано ниже:

artists(id: "1") {
  id
  name
  avatarUrl
  tracks(limit: 2) {
    name
    urlSlug
  }
}

Так как с первыми конечными точками всё прошло хорошо, мы начали добавлять новые, и в 2020 году их было уже 50.

4. Соблазн микрооптимизаций


Спустя два года я осознал, что некоторые из преимуществ GraphQL не добавляют никакой ценности нашему проекту.

4.1. Один запрос, много ресурсов


Как говорилось выше, основное преимущество такого паттерна в упрощении клиентского кода. Однако некоторые разработчики хотели использовать его для оптимизации сетевых вызовов и для ускорения запуска приложения.

Не думаю, что такая оптимизация допустима:

  • По большей части это проблема для мобильных приложений. Если вы работаете с десктопным приложением или с API «машина-машина», это не приносит никакой дополнительной ценности с точки зрения производительности.
  • Вы не делаете код быстрее; вы просто переносите сложность на сторону бэкенда, имеющую бОльшую вычислительную мощь. Однако метрики нашего проекта показывали, что REST API оставался более быстрым, чем GraphQL.

4.2. Получение точных данных


GraphQL нацелен на минимизацию количества передаваемых по проводам данных, селективно подбирая данные в зависимости от потребностей клиентского приложения. Мобильный клиент с маленьким экраном может получать меньше информации, чем большой экран веб-приложения. Поэтому вместо конечной точки, возвращающей фиксированные структуры данных, сервер GraphQL раскрывал только одну конечную точку и в точности отвечал на запрошенные клиентом данные.

Не думаю, что этот аргумент важен.

  • Не у всех есть мобильное и десктопное приложения. Это преимущество может быть даже неприменимо к вашему проекту. И я проигнорирую аргумент «А как насчёт умных часов?»
  • Теоретически, это интересно, но на практике десктопные и мобильные приложения схожи, и это не экономит особо много данных. Давайте рассмотрим два примера: Spotify и Amazon.


Десктопное и мобильное приложения (Spotify)



Десктопное и мобильное приложения (Amazon)

На экране альбома десктопной версии Spotify есть только три дополнительных поля (количество прослушиваний, длительность трека и длительность альбома, то есть на каждый JSON экономится примерно 30 байтов). На десктопном экране Amazon есть только два дополнительных поля данных. Наше приложение Spendesk имеет одинаковый размер и никак не выигрывает от этой возможности оптимизации размера полезной нагрузки.

Если вы действительно хотите оптимизировать время загрузки, то лучше загружать на мобильные изображения более низкого качества, но, как мы увидим дальше, GraphQL не очень хорошо работает с документами.

С другой стороны, GraphQL даёт клиентам возможность исполнять запросы так, чтобы получить точно то, что им нужно. Также это значит, что пользователи могут запрашивать столько полей и в таком количестве ресурсов, как им нужно. Потенциально запрос может получить в ответе тысячи атрибутов, сильно нагрузив сервер.

Микрооптимизации обычно становятся ошибкой. Не следует принимать архитектурные решения, чтобы сэкономить миллисекунды или пару байтов.

5. Просто неудобные особенности



5.1. Головоломка со строгой типизацией


Как и WSDL годами ранее, GraphQL определяет все типы, команды и запросы API в файле graphql.schema. Я огромный фанат типизации. Однако я обнаружил, что типизация при помощи GraphQL может быть запутанной.

Во-первых, здесь много дублирования. GraphQL определяет тип в схеме, однако нам нужно переопределять типы для нашего бэкенда (TypeScript с node.js) и для мобильных приложений (на Swift и Kotlin).

Для устранения этой проблемы возникли два решения:

а. Типизация сначала в коде


Первое решение (как в nexus, typegraphql) позволяет определять типы на TypeScript, а затем на их основании генерировать схему GraphQL.


Наша команда пробовала nexus в 2020 году и спустя месяц отказалась от него. Код был запутанным и плохо сочетался с zod.js, который мы также использовали для типизации. Он не поддерживал фичи наподобие образования федераций, а значения null работали некорректно. Отладка сгенерированной схемы GraphQL была непростым процессом. Это оказалось ужасным опытом, и я не рекомендую его никому.

б. Типизация сначала в схеме


Другое решение противоположно первому; оно используется в Apollo (JS), в Ariadne (Python) и в CodeGen. Сначала мы создаём схему, а затем скрипт генерирует типы из файла schema.graphql.


Сейчас мы используем Codegen, который автоматически генерирует TypeScript. Этот опыт очевидно лучше, но он всё равно неидеален. Нам нужен способ передавать схему между фронтендом и бэкендом. Фронтенд должен пересобираться при каждом изменении схемы, в противном случае он не получит последних изменений. Для получения последних обновлений мы подтягиваем схему при помощи интроспекции.

Всё равно возникают проблемы с не допускающими значений null типами, которые TypeScript не распознаёт как опциональные, с enum в GraphQL, которое не существует в TypeScript, и с ресолверами, возвращающими свёрнутые типы.

Enum в GraphQL преобразовали в два типа TypeScript, которые выглядят так:

enum FourEyesProcedureStatus {
  VALIDATION_PENDING
  CANCELLED
  VALIDATED
  REJECTED

export type FourEyesProcedureStatus =
  | 'CANCELLED'
  | 'REJECTED'
  | 'VALIDATED'
  | 'VALIDATION_PENDING';

export type FourEyesStatusEnum =
  | 'CANCELLED'
  | 'REJECTED'
  | 'VALIDATED'
  | 'VALIDATION_PENDING';

Сгенированные для ресолверов типы являются свёрнутыми. Проблема возникает для вложенных объектов, когда ресолвер не возвращает полный объект. Вместо этого он возвращает частичный объект и позволяет другому resolver заполнять остальные поля.

Языки типизации не поддерживают этот паттерн в готовом виде. В TypeScript Codegen добавляет в код any и создаёт очень сложные типы.

blockAccount: async (_, args, context): Promise<any> => {}

export type BlockAccountResultResolvers<
  ContextType = any,
  ParentType extends ResolversParentTypes['BlockAccountResult'] = ResolversParentTypes['BlockAccountResult'],
> = ResolversObject<{
  account?: Resolver<ResolversTypes['AccountDetails'], ParentType, ContextType>;
  __isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
}>;

Для нашего REST API мы просто определяем внутренние типы и внешние DTO. Это всё равно добавляет дублирование, но остаётся гораздо проще, чем GraphQL.

5.2. Более качественный инструментарий, за исключением Google Chrome


Я со скепсисом относился к инструментарию, поскольку когда мы начинали использовать GraphQL, продукты наподобие insomnia или Postman не поддерживали GraphQL. Сейчас ситуация лучше, они все поддерживают его по умолчанию.

Однако меня кое-что по-прежнему напрягает. Отладка сложнее. Просто взгляните на эти два веб-сайта, в одном повсюду применяется GraphQL, а другой имеет конечные точки REST. В инспекторе Chrome сложно найти то, что ты ищешь, потому что все конечные точки выглядят одинаково. В REST можно узнать, какие данные ты получаешь, просто посмотрев на URL.



На верхнем сайте используется только GraphQL, и невозможно понять, какой запрос получает пользователь

5.3. Нет поддержки кодов ошибок


REST позволяет использовать коды ошибок HTTP наподобие «404 not found», «400 bad request» и так далее, но у GraphQL этого нет. GraphQL вынуждает возвращать код 200 с сообщением об ошибке в полезной нагрузке ответа. Чтобы понять, в какой конечной точке произошёл сбой, нужно проверить каждую полезную нагрузку.

Кроме того, некоторые объекты могут быть пустыми, потому что их нельзя найти или потому что возникла ошибка. И понять разницу бывает сложно.

5.4. Мониторинг


Эта проблема связана с предыдущей: выполнять мониторинг ошибок HTTP очень просто по сравнению с GraphQL, потому что все они имеют собственный код ошибки. А исправить ситуацию с GraphQL нелегко. Apollo работает над этим, и я уверен, что скоро разработчики найдут решение.

6. Большая наглая ложь


Предыдущие проблемы — это просто неудобства. Однако спустя три года работы с GraphQL я обнаружил и более серьёзные недостатки.

6.1. Независимость от версий


У всего есть своя цена. При модифицировании GraphQL API вы можете сделать некоторые поля устаревшими, однако вынуждены поддерживать обратную совместимость. Они всё равно должны присутствовать для старых клиентов, которые их используют. Не нужно поддерживать версии GraphQL, однако расплачиваться за это приходится поддержкой каждого поля.

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

6.2. Пагинация


На страницах GraphQL Best practices можно прочесть следующее:

В спецификации GraphQL намеренно ничего не говорится о нескольких важных вопросах, связанных с API, например, о работе с сетью, авторизацией и пагинацией.

Как удобно. В целом, как выяснилось, пагинация в GraphQL очень мучительна.

6.3. Кэширование


Смысл кэширования заключается в том, чтобы получать ответ сервера быстрее благодаря сохранению результатов предыдущих вычислений. Для REST уникальные идентификаторы ресурсов, доступ к которым пытаются получить пользователи, — URL. Поэтому можно выполнять кэширование на уровне ресурсов. Кэширование встроено в спецификацию HTTP. Кроме того, браузер и мобильное устройство тоже могут использовать этот идентификатор URL и кэшировать ресурсы локально (как они делают это с изображениями и CSS).

В GraphQL это становится сложным, потому что каждый запрос может отличаться, несмотря на то, что он работает с той же сущностью. Для него требуется кэширование на уровне полей, которое непросто организовать с GraphQL, поскольку он использует единую конечную точку. Для помощи в подобных сценариях разработаны библиотеки наподобие Prisma и Dataloader, но они всё равно далеки от возможностей REST.

6.4. Проблемы N+1


Эта проблема возникает, когда ваши данные не имеют форму графа. Представим, что вы хотите получить автора и все его книги. Хранилища наподобие SQL хранят книги и авторов в разных таблицах.

query {
  authors {
    name
    books {
      title
    }
  }
}

resolver будет выглядеть так:

resolvers = {
  Query: {
    authors: async () => {
      return ORM.getAllAuthors()
    }
  }
  Author: {
    books:  async (authorObj, args) => {
      return ORM.getBooksBy(authorObj.id)
    }
  },
}

Так как ресолвер книг вызывается для каждого id, запрос пример следующий вид:

SELECT * FROM authors; 

SELECT * FROM books WHERE author_id == 1;
SELECT * FROM books WHERE author_id == 2; 
SELECT * FROM books WHERE author_id == 3; 

Нам придётся выполнять вызов к базе данных для каждого автора, потеряв возможность использовать фичи SQL наподобие SELECT * FROM books WHERE author_id in (1,2,3), и делая N+1 запросов к базе данных вместо двух. Одно из решений заключается в использовании dataloader, однако оно вынуждает нас добавлять в код ещё один слой сложности, что усложняет отладку проблем производительности.

6.5. Типы медиа


У нас есть API для загрузки на сервер документов и их отображения. GraphQL не поддерживает загрузку документов на сервер, использующей по умолчанию multipart-form-data. Разработчики Apollo работали над решением file-uploads, но оно сложно в настройке. Кроме того, GraphQL не поддерживает при получении документа заголовок типов медиа, который позволяет браузеру отображать файл правильно.

6.6. Безопасность


При работе с GraphQL можно запросить ровно то, что вам нужно, но вы должны иметь в виду, что это приводит к сложным последствиям для безопасности. Если злоумышленник попытается отправить затратный запрос с вложениями, чтобы перегрузить сервер, то вы будете уязвимы к DDoS-атакам.

Также он сможет получить доступ к полям, не предназначенным для открытого доступа. При использовании REST можно управлять разрешениями на уровне URL. Для GraphQL это должен быть уровень полей.

user {
  username <-- могут видеть все
  email <-- приватное
  post {
    title <-- некоторые посты приватны
  }
}

7. Кошмар разрастающейся схемы


С ростом графа данных вы можете разделить данные на несколько сервисов. Stitching и Federation позволяют разделить схему GraphQL на несколько более мелких. Однако есть важная разница.

7.1. Federation


Federation предполагает, что схема компании будет распределённой ответственностью. Вот как это выглядит:


При использовании Federation (на основе Apollo) каждый сервис отвечает за поддержку своей части схемы GraphQL. Сервер Federation подтягивает каждую схему и объединяет их в одну.

Federation помогает организациям с несколькими командами GraphQL обеспечить федеративное разделение глобальной схемы GraphQL.

Federation хорошо работает, если вы остаётесь в границах GraphQL. Всё типизировано, поэтому вы легко можете выявлять ошибки. Можно определять key для объединения объектов среди нескольких схем. Но по умолчанию она не поддерживает горячую перезагрузку. Это значит, что придётся или при каждом изменении подграфа перезагружать шлюз, или использовать проприетарное решение Apollo Managed Federation.

7.2 Stitching


Stitching предполагает, что схема компании будет централизованной ответственностью.


Stitching (на основе mesh, hasura или stepzen) помогает объединять схемы между несколькими источниками данных, поддерживаемых центральной командой API данных. Мы определяем одну основную схему GraphQL в шлюзе и используем ресолвер для получения данных автоматически от каждого сервиса, предоставляющего данные (gRPC, REST, SQL).

Stitching гибче, чем Federation, потому что не вынуждает подсервисы использовать GraphQL. Можно потребить любой источник данных. Однако я обнаружил, что Stitching обычно направляет тебя по опасной дорожке, которая может включать в себя следующее:

  • Прямое раскрытие внутренней схемы базы данных через API немного опасно, так как создаёт тесную связь с хранилищем. ORM наподобие Prisma или Graphile имеют нативную интеграцию с GraphQL, ещё больше развивая эту идею.
  • Другие источники данных, в отличие от GraphQL, не типизированы. Так что нужно учитывать дополнительную сложность, когда некоторые объекты имеют одинаковое имя или когда вам нужно объединить данные по заданному ключу.
  • Можно смешивать stitching и federation, делая систему ещё более странной.

Откровенно говоря, оба решения довольно сложны. Выбор здесь не только технический; он связан со структурой вашей организации. Если команды не зависят друг от друга и вы не хотите принудительно использовать GraphQL везде, то лучше выбрать stitching.

8. Экосистема


По моему мнению, экосистема ещё недостаточно зрелая, и это последняя проблема.

GraphQL был создан как внутренний продукт Facebook* в 2012 году и выложен в опенсорс в 2015 году. В 2019 году Facebook* создал GraphQL Foundation — нейтральную некоммерческую организацию, поддерживающую спецификации и базовую реализацию для Node.js (graphql.js).

С тех пор появилось много новых действующих лиц и экосистема стала более сложной. На май 2021 года существовало четыре других реализации для одного только Node.js (Apollo, Express, yoga, Helix), шесть на Go и четыре на Python.

Facebook* до сих пор активно развивает инструменты наподобие relay, однако не является основной стороной, принимающей решения. Теперь в экосистеме есть два главных игрока:

  • The Guild — группа опенсорс-разработчиков, работающая над yoda server, mesh или codegen.
  • Apollo — частная компания, выкладывающая в опенсорс свои серверы и клиенты (Kotlin, iOS и React), однако продающая обучение и SAAS-функции наподобие apollo studio и managed federation.
  • Более мелкие игроки наподобие hasura.io, stepzen, тоже предлагающие SaaS-решения для GraphQL.

Из-за множества предложений в экосистеме сложно ориентироваться. Кроме того, я с трудом находил чёткие руководства. Некоторые действующие лица не достигли согласия относительно будущего GraphQL. Одна из интересных тем для разногласий — выбор между Stitching и Federation.

С другой стороны, React, ещё один опенсорсный проект, полностью поддерживается Facebook*. Это упрощает получение чётких руководств. Когда Facebook* решил мигрировать с компонентов классов на хуки, он сделал чёткое заявление об этом. Я предпочитаю экосистему, стремящуюся к общему решению.

Заключение


REST стал новым SOAP; теперь GraphQL стал новым REST. История повторяется. Сложно сказать, станет ли GraphQL просто новым популярным трендом, который постепенно забудется, или он действительно поменяет правила игры. Одно известно точно: он по-прежнему на ранних этапах развития и пока не убедил в своей надёжности нашу команду.

Наши команды мобильной разработки и фронтенда любят GraphQL. Потрясающий инструментарий, возможность с лёгкостью исследовать API и строгая типизация (особенно с Kotlin и Swift) упрощают жизнь разработчиков. Это логично, если вспомнить исходную проблему Facebook*. GraphQL разрабатывался для решения проблемы с мобильной платформой. Однако эти преимущества могут быть и не значимыми для вашего проекта.

Большинство проблем всплывает, когда ты начинаешь разговаривать с бэкенд-разработчиками. Отсутствие надлежащей пагинации, кэширования и типов MIME — серьёзные проблемы. Управление типами GraphQL и нативными типами вашего проекта становится сложной задачей, а ресолвер тяжело поддерживать. С ростом проекта для исправления больших схем нужно вкладываться в очень дорогостоящие инструменты наподобие Stitching или Federation. Наконец, я считаю, что экосистема ещё недостаточно созрела, а затраты на поддержку этого решения слишком высоки по сравнению с REST API.

Чтобы принять обоснованное решение, необходимо понять потребности вашего бизнеса, особенно формат данных, их местоположение, владельцев, и разобраться, кто должен иметь к ним доступ. Кроме того, это зависит от организационных ограничений, например, от количества разработчиков, уровня их опыта работы с GraphQL и организации команд. Как гласит закон Конвея, «организации проектируют системы, которые копируют структуру коммуникаций в этой организации».

* Meta Platforms, а также принадлежащие ей Facebook и Instagram: признана экстремистской организацией, её деятельность в России запрещена; запрещены в России

Комментарии (74)


  1. gmtd
    21.04.2023 08:55
    -4

    JSON-RPC

    Остальное от лукавого


    1. AndreySu
      21.04.2023 08:55
      -7

      SOAP (WSDL, XML) ...
      Остальное от лукавого


      1. nin-jin
        21.04.2023 08:55
        -2

        Самый гуманный всё же HARP.


    1. bugy
      21.04.2023 08:55
      +7

      Graphql и Json-RPC это немного разные вещи. Graphql про снижение зависимости разработчиков клиента от разработчиков сервера (graphql - query language). Json-RPC про оптимизацию коммуникации (remote procedure call protocol).

      Для разных клиентов (например разные экраны в приложении) одни и те же данные нужны в разном виде. И если с обычными полями всё просто (можно сразу все отсылать), то со связанными сущностями сложнее, т.к. нельзя их загружать все, на всякий случай.

      Поэтому либо бекенд на каждый чих клиента должен делать новый api (например, api: getOrderWithProducts, getOrderWithCustomers, getOrderWithProductsAndCustomers). Либо бекенд делает только "одноуровневый" API, но тогда клиент должен это всё склеивать у себя. И если у вас несколько разных клиентов (например, другие сервисы, интеграция с другой стороной и frontend), то значит и склейка будет реализована несколько раз.

      У нас frontend очень радуется graphql, потому что это сильно упрощает им работу. Бекенд не рад, т.к. это много работы по созданию резолверов и не самая тривиальная реализация, для изначальной схемы. А то, что бекендеров не дёргают на каждый чих, они и не замечают.


      1. gmtd
        21.04.2023 08:55
        +4

        Я могу из своего опыта сказать (соответственно, моя предметная область - малые и средние сайты c2b, скажем так. Несколько десятков эндпойнтов/методов на backend API)

        80% запросов на бэк - кастомные. Нужно писать контроллеры со своей логикой.

        20% - из разряда CRUD хоть с сущностями хоть с нет (которые типа ложатся на REST или GraphQL - ваш getOrderWithProductsAndCustomers ).
        Если нет вложенных сущностей - все ок. Если есть- написать свой универсальный функционал добавления одного вложенного уровня в зависимости от параметра запроса - несложно. Свой маленький удобный QL

        80% запросов - кастомные. Иначе если логику переносить на фронт (бэк - типа просто доступ к БД), то будет огромный оверхед по запросам

        GraphQL - это невероятное усложнение коммуникации фронта с бэком, подходит наверно для каких-то очень редких случаев public API

        REST - это зоопарк без удобных стандартов и даже особо бест практик.

        JSON-RPC дает строгость, стандарты и порядок, который ты сам и задаешь. Всё гениальное - просто

        Вот моя статья о нем - https://habr.com/ru/articles/709362/


        1. bugy
          21.04.2023 08:55
          -1

          Честно говоря не знаю, почему это невероятное усложнение коммуникаций. Graphql клиент не очень сложный, оверхед помимо реста, это только указать список необходимых полей.

          Да, он даёт нехилый оверхед на бекенде, если речь идёт о нескольких десятках несвязанных запросов (типа один для заказов, другой для заказчиков, третий для продуктов).

          Однако, если одни и те же данные нужны в десятке разных срезов, то оверхед на бойлерплейт и поддержку гораздо выше.

          Не уверен насчёт вашей предметной области, но из того, что встречал на практике, кастомные ендпоинты нужны только для модификации данных (ака мутации в графкл), но эти эндпоинты могут возвращать одинаковые данные. По крайней мере из одной центральной модели. А не так, что у нас есть UseCase1OrderDto, UseCase2OrderDto, итд.

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


      1. noodles
        21.04.2023 08:55
        +1

        Либо бекенд делает только "одноуровневый" API, но тогда клиент должен это всё склеивать у себя. И если у вас несколько разных клиентов (например, другие сервисы, интеграция с другой стороной и frontend), то значит и склейка будет реализована несколько раз.

        В чём проблема склейки несколько раз? Каждый клиент - это отдельная банановая республика, у каждого может быть своя разная стратегия склейки. Это его обязанность, сам пусть и решает как склеивать. Подумаешь, пару раз повторится, но на третий раз - появится исключение какое-то (ещё и в рантайме на том же клиенте, например).


  1. oisee
    21.04.2023 08:55
    +1

    А OData пробовали/рассмативали?

    ресурсы - конечные точки,
    навигация от одних ресурсов к другим через связи, возможность запросить или запостить вложенные объекты.

    Интроспекция и самоописание.

    Поддержка действий над объектами (ресурсами) и пагинации =)


  1. yar3333
    21.04.2023 08:55
    +6

    Всегда считал, что использование GraphQL для проектов масштаба меньше Facebook будет оверкилом. По факту, нарушается KISS-принцип. Поэтому даже пробовать GraphQL не стал на практике, всегда обходился простым REST.


    1. ggo
      21.04.2023 08:55
      +2

      Верно. GraphQL проявляет себя на достаточно больших проектах. На которых несколько каналов, условно веб, андроид, ios и один backend. И активная поставка изменений в ui на все каналы.


      1. ggo
        21.04.2023 08:55

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

        да, все эти удобства не достаются бесплатно. расплачиваться приходится всем, что упомянуто в статье в качестве недостатков: проблема N+1, сложностью отладки при отсутствии детального логирования и трейсинга, вопросы по безопасности


  1. pda0
    21.04.2023 08:55
    +5

    GraphQL вынуждает возвращать код 200 с сообщением об ошибке в полезной нагрузке ответа. Чтобы понять, в какой конечной точке произошёл сбой, нужно проверить каждую полезную нагрузку.

    Это нормально и правильно. Разделяет канал и данные. В результате GraphQL не обязан работать исключительно поверх http.


  1. VirtualProgramer
    21.04.2023 08:55
    +2

    мы думали о них с точки зрения графа объектов

    а чем это лучше обычного подхода?


    1. nin-jin
      21.04.2023 08:55

      Это и есть обычный подход у тех, кто закончил школу.


  1. vpedak
    21.04.2023 08:55
    +2

    Автор сам себе гемора насоздавал и жалуется... Никто не мешает не делать такую большую схему, сделай ограничение на каком то уровне и все, не давай ходить дальше и не будет проблем.

    Пагинация - вообще без проблем, откуда там проблемы?

    Запросы видишь ли в браузере одинаковые... Дык сделай разные. Можно тупо послать /graphql/nameOfMyRequest и сервер отлично это отработает, а nameOfMyRequest игнорирует, хотя в браузере все будет видно.

    И т.д. В общем, надо уметь пользоваться инструментом. Мы используем 3 года на небольшом проекте и только рады...


    1. nin-jin
      21.04.2023 08:55
      +1

      А как вы боретесь с дублированием связанных данных из-за денормализации? У нас денормализация давала раздутие выдачи на несколько порядков.


      1. vpedak
        21.04.2023 08:55
        +1

        А какая проблема с раздутием выдачи? Если выдача несколько сот килобайт то современные браузеры это скушают и нет проблем, если у вас дерево выдачи очень большое то в этом и фишка... Вы сами решаете внутренний тип будете выдавать полностью или выдадите его как другой тип (обрезанный) и не дадите ходить дальше. Надо просто понять, что нет одного правильного пути. Для каждого проекта свое... Мы например выдаем 2 уровня максимум и на втором уровне не выдаем уже сущность вложенную, а выдаем ее ID. Хочешь ее получить, делай следующий запрос...

        В этом и смысл что никто не обязывает вас выдавать все дерево объектов на 5 уровней например. Вы сами определяете как вы считаете правильным. Обычно 2-3 уровня хватает (я бы даже про третий уровень подумал уже). Это в любом случае уже лучше чем прсто rest но при этом и решает все проблемы что он тут описал


        1. nin-jin
          21.04.2023 08:55
          +1

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

          А вам, кажется, GQL вообще не нужен, если полтора уровня хватает для всего.


          1. Ndochp
            21.04.2023 08:55

            Кажется не должно раздуваться, если у нас корректно определены запрашиваемые поля.
            Да, "контрагент"/"договоры" дадут на третий уровень у "договора" массив "стороны" типа "контрагент" у которого снова "договоры"(четвертый уровень) и потом снова "Стороны" (пятый)
            Но обычно вы не будете разворачивать второй уровень до контрагентов, а если и будете, то их снова до договоров точно не станете.


            1. nin-jin
              21.04.2023 08:55
              +1

              Конкретно у нас была проблема с обычным иерархическим меню, которое, разумеется, должно было загружаться целиком, а не на 2 уровня.


              1. Ndochp
                21.04.2023 08:55

                Но если в меню нет циклических ссылок "наверх", то вам вернется только то, что запрошено, сверху вниз. То есть или никаких мегабайт не будет, или они все равно должны быть, так как их и запросили.
                Откуда в меню может лезть денормализация? (ну и за меню, показываемое пользователю как один объект с больше чем 3 уровнями уже кажется надо лишать премии, а если объект не один, то можно и подгружать по мере углубления)
                Я сейчас открыл в браузере меню "другие закладки", добрался до 5 уровня вложенности. Это страшно даже на 4к в 100%. На телефоне это вообще не возможно.


                1. nin-jin
                  21.04.2023 08:55

                  Меню не дерево, а DAG. При денормализации в дерево происходит комбинаторный взрыв.


                  1. vpedak
                    21.04.2023 08:55

                    а кто вам мешал выдавать ссылки на ноды вложенные, а не снова ноды?


                    1. nin-jin
                      21.04.2023 08:55

                      Необходимость работы с данными всего меню, а не только корневыми пунктами. Собственно данных там как правило было в районе килобайта. У некоторых пользователей доходило до нескольких десятков кб. Но из-за адского дублирования при денормализации, объём раздумался так, что IE падал в попытке распарсить огромный JSON.


                      1. ionicman
                        21.04.2023 08:55

                        Если я верно понял, то в чем проблема работать с данными всего меню, если все ноды в корне по id, а все ссылки на ноды везде - это id этих нод? Да, это не совсем по GQL, но сделать вполне можно.


                      1. JArik
                        21.04.2023 08:55

                        а что нельзя ввести спец. обьекты в выдаче graphql которые бы указывали что они рефы. Клиент бы сам ресолвил такое, причем оффлайн и очень тривиально.

                        {id: 1, name: test, items: [{ref_id: 1}, ... ]}


                      1. nin-jin
                        21.04.2023 08:55

                        Вы в GraphQL ответе куда положите данные связанных сущностей, которые заменили на ref_id?


          1. vpedak
            21.04.2023 08:55
            +2

            Ну конечно, куда нам до вас... Не надо передергивать, никто не писал что хватает. Мы сами решаем на каком уровне не выдавать дальше (если надо дальше то уже надо будет посылать новый запрос). Это зависит от ситуации, но главное, что это возможно и мы сами это контролируем. Ножом можно и хлеб резать и вены... Надо самому решать как использовать инструмент.


            1. nin-jin
              21.04.2023 08:55
              -2

              если надо дальше то уже надо будет посылать новый запрос

              Привет, N+1.

              Не надо ... сами ... надо... мы сами ... Надо самому ...

              Фейсбук на ровном месте создал проблему, которую теперь вам надо регулярно решать самим.


      1. bugy
        21.04.2023 08:55

        Можете пжл пояснить на примере? В т.ч. "сильно связный граф"

        Я не знаю как получить пару десятков мегабайт на фронте на паре уровней вложенности. И более того, не знаю как должен выглядеть UI для отображения всех этих данных. Разумеется если мы не рисуем таблицу на десятки тысяч строк и 100 столбцов.


        1. nin-jin
          21.04.2023 08:55
          +1

          1. Ndochp
            21.04.2023 08:55

            Юзеры и лейблы записались для каждой ишью полным текстом возможно по нескольку раз? Так мелочь это дало.
            Место там боди сожрало, которое собственно основная полезная нагрузка и не дублируется.


            1. nin-jin
              21.04.2023 08:55

              Не "возможно" и не "по нескольку". Боди можете вырезать, оно там вообще не нужно.


              1. Ndochp
                21.04.2023 08:55

                "возможно" в том плане, что теоретически 140 (вроде примерно столько) ишью могли создать 140 пользователей, разных. Ну и с метками аналогично. Выкинуть боди — размер упал в 10 раз, сколько процентов дают выкинутые неуникальные юзеры и метки я искать не буду, башка уже не варит.


                С произвольным DAG конечно хуже, но если меню — произвольный сильносвязный DAG, то проблемы с меню не только в размере ответа GRAPHQL


                1. nin-jin
                  21.04.2023 08:55
                  +1

                  Ваша попытка подобрать условия, когда проблема себя не проявляет, не избавляет от самой проблемы. Если вы будете проектировать систему, думая лишь об удобных кейсах, то вам будет очень больно прилетать с прода. Смотрите не на сам палец, а туда, куда он указывает.


                  1. Ndochp
                    21.04.2023 08:55
                    -1

                    Ну так проблема дедубликации решается известно как в общем виде, а значит она не прооблема. Я приводил схему из 1С, вы привели схему из GraphQL.
                    Единственно, я не знаю если в с клиента запрашивать 2 раза одну и ту же таблицу с разным количеством полей, то это считается одним типом или двумя. Возможно это потребует небольших дополнительных плясок, или можно решать "в лоб" через 2 типа и дублирование (уже гораздо меньшее)
                    И это не "денормализовывать-нормализовывать" в терминах РСУБД, это скорее безсхемная объектная форма записи, вариации на тему NoSql, а то и HEAP. но так как задача не в хранении, а в передаче ответа с бека на клиент, то никаких сложностей в кодировании, кэшировании и прочих ускорениях поиска это не создает. (до тех пор, пока вы не поймаете таки цикл, но кажется и это можно успешно обойти)


              1. bugy
                21.04.2023 08:55

                Не совсем понятно, при чём тут graphql тогда, если данные так же раздуваются через REST или через большинство других протоколов.

                И, как вам справедливо отметили, в представленном примере порядок увеличен за счёт body, а не дупликации.

                Более того, в отличии от стандартных подходов, graphql вам позволяет снизить ущерб от дупликации, тем что вы гораздо меньше полей будете доставать

                В реальном UI для списка из issues, у вас несколько полей всего.

                Но в целом проблема дубликации существует, тут вы правы. Но то что у вас это всё раздувалось до десятков мегабайт мне кажется очень странным.


                1. nin-jin
                  21.04.2023 08:55
                  +1

                  данные так же раздуваются через REST

                  Не раздуются, если не делать (бессмысленную) денормализацию.

                  graphql вам позволяет снизить ущерб от дупликации, тем что вы гораздо меньше полей будете доставать

                  Это есть в большинстве обобщённых REST протоколов, начиная с древней OData, заканчивая новомодным HARP.

                  то что у вас это всё раздувалось до десятков мегабайт мне кажется очень странным

                  Вот так всегда и бывает. Ты думаешь "да такого не моет быть", а потом прилетают багрепорты от пользователей, которые слишком любят твой сервис, и используют его на полную катушку, а потом у них бац и у них приложение вообще не открывается из-за кривого протокола.


      1. Ndochp
        21.04.2023 08:55

        А кстати типовых средств не предлагается для нормализации?
        Ну типа в в ответе давать ответ на вопрос, но сложные поля возвращать как ссылку (ИД объекта, ИД состава полей) + таблица соответствия (ИД объекта, ИД состава полей) — представление объекта. И пусть уже на стороне клиента собирают рекурсивный денормализованный ужас.


        1. nin-jin
          21.04.2023 08:55

          1. Ndochp
            21.04.2023 08:55

            Ну да, не совсем то, что я имел в виду, но почти.
            Не совсем понятно как работать дедубликатору со схемой


            events(fromDate: "2018-02-14", toDate: "2018-02-14") {
              id
              date
              time
              actor{
                id
                name
                movie{
                  id
                  name
                  __typename
                }
                __typename
              }
              movie {
                id
                name
                synopsis
                __typename
              }
              __typename
            }

            То есть когда в первом встреченном муви не будет синопсиса. Или это будут разные типы?


            Я то свою схему вспомнил по мотивам файлов обмена в 1С — там тоже развесистая структура переплетенных справочников


          1. Mingun
            21.04.2023 08:55
            +1

            Какая-то странная схема дедупликации там предлагается. Зачем хранить первый элемент полностью, а последующие ссылкой? Чтобы обработчик усложнить втыканием двух веток кода «если встретился полный набор данных, работает с ним, в противном случае ищем, где он встречался ранее»? Почему бы не всегда хранить только ссылки, а сами данные в отдельной, индексируемой по ключу, карте? Обработка данных сразу станет единообразной: достали ключ, сходили в карту.


            1. nin-jin
              21.04.2023 08:55

              Поздравляю, вы изобрели нормализованную выдачу. Но это не про GQL.


              1. Mingun
                21.04.2023 08:55

                Тут как бы вся ветка о том, как убрать дублирование в GraphQL, зачем вы её тогда начали, если «это не про GQL»? Ну и комментировал я решение по ссылке для дедупликации, где «почти» это и предлагают, только в какой-то извращённой манере.


                1. nin-jin
                  21.04.2023 08:55

                  Ну а я комментировал прекрасный тезис "надо уметь пользоваться инструментом. Мы используем 3 года на небольшом проекте и только рады" и уточнил, как правильно пользоваться этим инструментом, чтобы не приходилось нормализованную выдачу СУБД денормализовывать в GQL резолверах, чтобы потом нормализовывать для передачи по сети, чтобы потом опять денормализовыват для GQL клиента, чтобы потом опять нормализовывать, чтобы разные части UI не противоречили друг другу.


  1. cstrike
    21.04.2023 08:55

    5.2. Более качественный инструментарий, за исключением Google Chrome

    Просто добавляйте ?operation=xxx к url. Например /graphql?operation=getUser

    5.4. Мониторинг

    application/graphql-response+json в помощь. https://github.com/graphql/graphql-over-http/blob/main/spec/GraphQLOverHTTP.md#applicationgraphql-responsejson


  1. hyragano
    21.04.2023 08:55

    Видимо оригинальный автор 3 года как-то странно работал с GraphQL, проблем есть, но они решаются достаточно не сложно.

    Про N+1 уже в 20-ом году были пути решения, а точнее тот же appollo gateway умеет в него, hasura (для прототипирования), тоже не болеет этим. Есть несколько другая проблема, которую решить сложнее (выборка из базы именно тех, полей, которые запросили), но и ее можно решить.

    Про коды ответов - вообще не понял, 401 - ок тут нужно отдавать и да, это вполне себе возможно, 404 - отдавать в текущих реалиях уже не комильфо, 400 - зачастую связано с косяками передачи данных для запроса, но тут строгая типизация помогает.

    Инструментарий и консоль разработчика - еще в 2019-ом был плагин для chrome, где видны все запросы.

    Кастомные типы - в json та же трабла, только в gql хотя бы можно подчеркнуть на уровне схемы, что тут нужно что-то делать с этим типом, в json вообще может прийти что угодно.

    Решение проблем деприкейтед полей через версионирование апи - можно, но спорно, 2 апи поддерживать сложнее, которые живут в конечном итоге годами и нужно вносить правки уже в 2-х источниках.

    Злая проблема gql, на мой взгляд - отсутствие int64, только int32.... конечно можно заменить на float или кастомный тип, но иногда просто больно без него.

    Пагинация наоборот в gql проще и лучше организовать в тех кейсах, когда есть вложенная структура с несколькими списками, а клиенту вот нужна пагинация (да еще и с фильтрацией) на каждой из этих коллекций (а кому-то и не нужна).

    Конечно gql вообще не серебряная пуля и не лишен недостатков, но это как и любой другой инструмент - решает одни проблемы, добавляет другие.

    Реально брать не стоит для небольших приложений с одним клиентом (веб) да монолитом, но точно стоит брать, когда система это несколько команд разработки, MSA, да еще и клиентов несколько видов (2 МП, веб, десктоп, да еще и сторонние подключаются).


    1. gmtd
      21.04.2023 08:55
      +1

      Вот эта аргументация, что если несколько типов клиентов, то gql лучше rest или json-rpc - она откуда берется?

      В чем именно лучше?


      1. Ndochp
        21.04.2023 08:55
        -2

        Лучше реста, так как можно за один запрос получить что нужно и не терять на сетевом взаимодействии, лучше RPC тем, что специфика клиента лежит в клиенте, а не на бэке.


        1. gmtd
          21.04.2023 08:55

          специфика клиента лежит в клиенте, а не на бэке

          Это о чем?
          Пример


          1. Ndochp
            21.04.2023 08:55
            +1

            Ну вот нам на десктопе (в браузере) нужен по результатам запроса один набор полей, а на андроид другой.
            В случае РПЦ мы делаем 2 процедуры (специфика клиента пролезла в бэк) или одну, которая возвращает все. А клиенты потом половину выкидывают. (зря гоняем трафик и бэк)
            В случае GQL мы делаем с разных клиентов разные запросы, бэк единообразно выбирает только то, что запросили и все довольны. Специфичные для клиентов вещи лежать в кодовой базе клиентов, лишние данные по сети не ходят.


            1. gmtd
              21.04.2023 08:55

              То есть вы считаете, что указать в селекте / отфильтровать на бэке нужные клиенту поля это бОльший оверхед, чем весь тот код, который gql привносит на бэк своим существованием?


              1. Ndochp
                21.04.2023 08:55

                Ну смотрите, если будете делать разные конкретные процедуры для разных клиентов — будет протечка абстракций, она имхо хуже, чем работающий черный ящик gql. А если будете использовать одну процедуру, с указанием ей что там и как фильтровать/возвращать, то в итоге изобретете несовместимый gql


              1. hyragano
                21.04.2023 08:55

                Вопрос ведь в другом, оверхед есть, вопрос тех проблем, что решает gql. Любая технология решает одни проблемы добавляя другие.


      1. hyragano
        21.04.2023 08:55

        Лучше брать то, что подходит.

        Если мучаемся от просьб добавлять, изменять атрибутный состав, то с рестом точно жить несколько не удобно. Тут даже не стоит вопрос об экономии сетевого трафика, тут вопрос про удобство предоставления апи и да, клиенты, кто не любит gql, могут смотреть на него как на простые рест запросы (чем оно и является).

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

        В небольших проектах и небольших командах принесет скорее боль из-за сложности входа и решения проблем. В. Больших (особенно с развесистыми струткрами) скорее даст гибкость и независимость команд разработки (кстати всякие там api first ложиься достаточно не плохо в gql). Опять же большой и нагруженный проект, ну тут что, много денег, много команд, хоть рест, хоть json-rpc (я думал он rip), хоть grpc.


        1. hyragano
          21.04.2023 08:55

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


        1. gmtd
          21.04.2023 08:55
          +2

          Пока что единственный прозвучавший обоснованный аргумент в пользу gql - он удобен на больших проектах

          Чем? Тем, что там очень много вызовов, и фронтэндеры могут сразу реализовывать новые вызовы на затрудняя бэкэндеров, архитекторов и менеджеров, что на больших проектах скорей всего довольно заморочистая история.

          Другими словами, gql отвязывает фронт от бэка на уровне разработки.

          Но ведь это означает, что фронт напрямую работает с БД. Бэк - просто прослойка к базе, без бизнес логики. Транскриптор GQL в SQL.

          Это правильно? Вопрос сложный. Но пока что всё, что я вижу - люди хотят работать с БД "напрямую". Как я написал выше, на моих проектах таких вызовов 20%-30%. И они прекрасно реализуются кастомно-шаблонно без подключения фейсбуковских сложных конструкций.


          1. hyragano
            21.04.2023 08:55

            Ну на самом деле мы этим и занимаемся на бэке, просто затягивая технологии для повышения своего JSI (шутка, с долей правды).

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

            1. Обязательность полей: застолбив контракт как not null, то вы гарантировано получите из бэка консистентные данные, да, есть конечно в open spec required - но это больше соглашение, чем обязанность, в gql бэк сам обделается и ответит ошибкой.

            2. Строгая типизация: тут немного с другой стороны попробую показать. Вот у нас есть условно банковские счета и метод, который их возвращает. Как нам в rest (при использовании json) подчекрнуть тип? Я пока знаю только 2 способа, но оба упираются в поле. Давайте по-простому сделаем?

              {

              "type": "DEBI",

              "title": "any",

              "number": "1111",

              "balance": 1000.00

              }

              {

              "type": "CREDIT",

              "title": "any",

              "number": "1111",

              "balance": 1000.00,

              "availableCredit": 10000.00

              }

              Тут у нас только соглашение, что у json в котором поле type имеет значение CREDIT должен быть availableCredit, но не у json с type DEBIT.

              Как выглядит в gql?

              interface Account {

              title: String!

              number: Int!

              balance: Float!

              }

              type DebitAccount implements Account {

              title: String!

              number: Int!

              balance: Float!

              }

              type CreditAccount implements Account {

              title: String!

              number: Int!

              balance: Float!

              availableCredit: Float!

              }

              Может быть неочевидным, но второй вариант и может быть расширен еще другими типами и будет нести гарантии в контракте, что удобно и клиентам и бэку.

            3. Ошибки и graceful degradation: допустим бэк написал BFF на ресте и джоинит сам dto от других сервисов (конечно это такой себе вариант и лучше бы тогда CQRS использовать, НО реальность такова, что это достаточно дешевый и быстрый подход), как можно сообщить клиенту, что какую-то часть данных он не получит? Можно допустим кинуть ошибку и отдать, то что есть, можно ничего не отдавать, можно не отдавать данные, которые достать не получилось. В общем, доводить клиентов до истерии.

              Как решено в gql? те данные, при выборке которых произошел фейл - отдается ошибка, оставшиеся приходят клиенту, т.е. клиент осведомлен о том, что чего-то не хватает и принимает решение, как с этим работать, т.е. может отобразить ту часть данных, которую можно -> graceful degradation возможен без приседаний (пользуются ли этим? не всегда, клиенты привыкли получать 4хх или 5хх)


          1. hyragano
            21.04.2023 08:55

            1. Фильтрация и пагинация в долбанутых кейсах: допустим у нас есть замечательная структура:

            {
              "profile": {
                "accounts": [{
                  "number": "123",
                  "title": "hi"
                  }],
                "transactionsHistory": [{
                  "description": "open that account",
                  "date": ""
                }]
              }
            }

            Ну не подумали, что не желательно так делать. Какой будет выход, когда нужно будет в accounts и transactionsHistory пагинацию или фильтрацию? Причем не общую, а именно на каждую отдельно? конечно разделим методы, переведем новые клиенты на новые ручки, НО что предлагает gql?

            До:

            Query {

            getProfile: Profile!

            }

            type Profile {

            accounts: [Account!]!

            recommendations: [Recommendation!]!

            }

            После:

            type Profile {

            accounts(page: Int = 0, size: Int = 10): [Account!]!

            recommendations(page: Int = 0, size: Int = 10): [Recommendation!]!

            }

            Это backward compatible.


            1. nin-jin
              21.04.2023 08:55
              +1

              Это backward compatible.

              Нет, клиент ожидает полный список, а вы ему молча обрезали всё после 10 элемента - это сломает его логику. Давайте я вам лучше покажу, как это выглядит на HARP.

              До:

              profile(accounts(title);recomendations(description;date))

              После:

              profile(accounts(title;_num=0@10=);recomendations(description;date;_num=0@10=))

              Да, схема даже не поменялась. Кто хочет - грузит всё. Кто не хочет всё - фильтрует.


              1. hyragano
                21.04.2023 08:55

                С харп не знаком, возможно кто-то его использует и применимость очень интересная, но на рынке найти готовых спецов будет сложно, с gql не очень, но он хоть маркетингово раскачан и на том им спасибо.

                По примеру: если изначально не заложена пагинация и фильирация, то дальнейшее добавление не будет ломать старых клиентов? Не будет имплицитно резать историю?


                1. nin-jin
                  21.04.2023 08:55

                  Ох уж этот Marketing Driven Development..

                  Не будет, конечно, ибо на уровне языка запросов она есть всегда и везде, но если не реализована, то просто не фильтрует.


                  1. hyragano
                    21.04.2023 08:55

                    В примере с gql я указал дефолт, как вариант, там будет null и не будет филтрации, если оставить без дефолтов, конечно логику бэку нужно будет прописать, но это отдельная история.


        1. nin-jin
          21.04.2023 08:55
          -1

          кто не любит gql, могут смотреть на него как на простые рест запросы (чем оно и является)

          Вообще-то GQL - это RPC. От того, что вы завернули все GQL вызовы в один HTTP метод, он не становится REST.


          1. hyragano
            21.04.2023 08:55

            Не становится, но кто не хочет gql - можно как рест


    1. nin-jin
      21.04.2023 08:55
      +1

      Кастомные типы - в json та же трабла, только в gql хотя бы можно подчеркнуть на уровне схемы, что тут нужно что-то делать с этим типом, в json вообще может прийти что угодно.

      Странное противопоставление, учитывая, что GQL возвращает как раз JSON. Ну и, если вы не в курсе: https://json-schema.org/


      1. hyragano
        21.04.2023 08:55

        Вкурсе, что json, только это типизируется по схеме. Суть, что схема жесткая, в json при ресте - нет


        1. nin-jin
          21.04.2023 08:55
          +1

          Жёсткость схемы зависит исключительно от реализации бэкенда, в обоих случаях.


          1. hyragano
            21.04.2023 08:55

            В одном моменте это соглашения, в другом контракт, если бэк нарушит контракт, то сам же и пофейлиться


  1. Nnnnoooo
    21.04.2023 08:55
    +2

    Самая большая проблема любой абстракции по типу GraphQL что на вообще не учитывает особенности хранения данных, т.е. не учитывает проблемы и узкие места хранилища. И это исходит из одной из первоначальных задач технологии — универсальность. Отсюда и проблема N+1 и все остальное.
    Как результат часто в итоге все забывают что данные должны храниться в какой-то конкретной БД у которой есть свои узкие места, которые GQL в принципе не может учитывать, ибо не знает о ничего (а если учитывать что большинство фронтэндеров к сожалению очень далеки от бекенда и особенностей хранения данных, то вообще становится очень грустно). В результате появляются костыли-комбайны-оптимизаторы которые пытаются решить проблемы на уровне абстракций значительно выше, чем они должны решаться.


    1. hyragano
      21.04.2023 08:55

      По-факту любая абстрация (и база данных не исключение) - ставит трейд-офф перед выбором ее применения и тут либо соглашаемся, либо отказываемся. Оптимизировать gql бывает достаточно проблемно, но это цена, которую можно заплатить, ну и оптимизировать можно


      1. Nnnnoooo
        21.04.2023 08:55
        +1

        В том то и дело, что часто оптимизацию невозможно сделать без переписывания бека / без довольно сильного изменения схемы данных. Самая большая боль в таких абстракциях и в таком подходе (дадим прямой доступ к базе фронту), что к огромному сожалению бОльшая часть фронтендеров очень далека от понимания как работает БД — для большинства это магия, которая обязана отдать данные которые они запросили (таких непонимающих много и среди бекендеров, но если быть честным, то процент значительно меньше).
        Т.е. с одной стороны все узкие места конкретного хранилища обычно вылазят таблицах от несколько лямов записей, а с другой GQL из-за дополнительной абстракции имеет смысл только на очень больших проектах (это не мои слова, это многие здесь в топике писали не раз). Но только вот проблема больших проектов — это большой объем данных и значительно бОльший шанс, что фронтенд приложит прод каким нибудь N+1 запросом.


        И это не камень в сторону GQL, а просто проблема любой подобной абстракции поверх конкретного хранилища (аналогично когда юзают какой-либо навороченный ORM для сложных выборок, а не для записи и выборок по id т.д.)


        1. bugy
          21.04.2023 08:55
          -1

          В целом согласен с вашей точной зрения, что ещё один уровень абстракции снижает производительность.

          Но не согласен с логикой про БД и N+1 (и очень большие проекты).

          Графкл это абстракция над сервисами (а не над БД). Соответственно все оптимизации с БД ложатся на плечи соответствующих сервисов. Задача графкл это их склеить.

          Если сервисы предоставляют убогий и не оптимальный АПИ, то это проблема не графкл, вам ни одна технология не решит эту проблему.

          По факту, кто-то всегда должен склеивать данные для UI:

          • Либо это делает бэк в кастомных запросах и отдает толстые ДТО (увеличение времени разработки + дорогой maintenance)

          • Либо это делает фронт, дёргая десяток ендпоинтов (много работы на фронте + никаких возможностей для оптимизации)

          • Либо это делает дополнительная прослойка, например графкл (сложность поддержки этой прослойки + нет возможностей для оптимизации, только страховка от больших запросов)

            Каждый выбирает сам. По мне, если мы говорим о команде из нескольких человек и долгоживущем продукте, первый и последний пункты несостоятельны. Инвестиция в графкл + оптимизацию АПИ сервисов на большом отрезке себя окупает. Потому что доменная модель меняется гораздо реже, чем UI.

            Если вы разрабатываете в одиночку проект за полгода и забываете о нём, то разумеется такая инвестиция ничего не даёт.


          1. Nnnnoooo
            21.04.2023 08:55
            +1

            Графкл это абстракция над сервисами (а не над БД). Соответственно все оптимизации с БД ложатся на плечи соответствующих сервисов. Задача графкл это их склеить.

            Это в идеальном мире. А по факту куда ни глянь GQL — это транспортная абстракция к БД для фронта, когда никто вообще не думает о том как это все выполняется на беке. Тут же все очень логично если есть возможность сделать просто (и не важно как это будет тупить на беке прода), то именно так просто и будут делать. Ну действительно много ли вы знаете из чистых фронтендеров (без бекенд опыта), кто знает особенности разных БД?


            Чтобы не показалось что я только о GQL говорю, то точно так же тормознуто можно сделать и рестом (привет тупая пагинация на обычных БД), но GQL имеет значительно больше способов выстрелить себе в ногу (следствие универсальности и полного абстрагирования от способа хранения конкретных данных)