Как стать автором
Обновить

Комментарии 47

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


Своего рода proxy для api которое может запрашивать и изменять данные в куче разрозненых сервисов.


По сути wsdl нашего времени, но в виде proxy на node.js а не в виде большой ESB торчащей наружу.

скорее как предоставление унифицированной точки доступа с единым форматом

Вот только это так просто не работает. Пока у вас нет грамотного dsl, описывающего всё то, что можно извлечь через запросы — весь graphQL даёт не больше, чем любой нормальный транспортный формат данных (JSON). А когда у вас есть грамотный dsl, то вы его и на REST спокойно реализуете с тем же успехом.

Своего рода proxy для api которое может запрашивать и изменять данные в куче разрозненых сервисов.

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

ЗЫ: Я вообще имел дело с внедрением graphql несколько лет назад, и охотно соглашусь с заглавным тезисами статьи — «делать меньше запросов» было очень весомым плюсом graphql, это давало реальный прирост производительности в деле вытягивания данных на фронт и вообще выглядело хорошо. В остальном — большая часть того, что есть в graphql — не появляется там сама по себе только потому, что вы возьмете graphql.

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

Чем плох этот репозиторий?

С жесткой привязкой к постгресу? Зато на святом хаскеле, да…
Уважаемые читатели! Если вы пользуетесь технологией GraphQL — просим рассказать нам о том, что вам больше всего в ней не нравится

Конкретно GraphQL:


  • Нельзя просто вернуть все поля типа в запросе, нужно их перечислить (привет гора копипасты и регрессии из-за забытых полей при обновлении типа)
  • Нет адекватного наследования
  • Идеологически не совместим с REST

Плюс у разных имплементаций (например AppSync от AWS) хватает своих проблем сверх этого

Согласен с первыми двумя пунктами, но чем плоха несовместимость с REST? Зачем она?

В идеальном мире она не нужна, но в реальном мире это означает, что если вы делаете рефакторинг существующего REST приложения и хотите импользовать GraphQL, то вам по сути нужно выкинуть все что есть и начать с нуля

НЛО прилетело и опубликовало эту надпись здесь
Это в корне противоречит идее graphql, когда сам клиент говорит серверу что ему сейчас конкретно нужно

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


Чтобы избавиться от копипасты существуют Fragments

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


Ах да, я вспомнил еще один недостаток: input type'ы. Не знаю как у других, но у меня в проекте они применяются при создани объектов и представляют собой точную копию самого типа в 99% случаев. И вот тут бы как раз и пригодилось наследование


Не совсем понял о чем вы

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

НЛО прилетело и опубликовало эту надпись здесь
В каком смысле больше, чем нужно?

Скорее всего подразумевалось что больше чем нужно для отображения ui. То есть плохой пользователь может вручную собрать н-этажный запрос и отправить на сервер
Чтобы решить эту проблему можно пытаться анализировать вложенность, сложность, ставить таймауты на время запроса но есть куда более простое решение — на этапе сборки фронта вытащить все graphql-запросы и заменить их айдишниками в клиентском коде и загрузить эти запросы на сервер. И тогда клиент просто не сможет построить н-этажный запрос потому что сервер будет принимать только те запросы которые были вытащены из клиентского кода при сборке фронта.

все graphql-запросы и заменить их айдишниками в клиентском коде и загрузить эти запросы на сервер

Как такому запросу передать параметры?

Вместе с айдишником запроса. Например если где-то в компоненте нужно загрузить какие-то данные


const projectWithTasks = await gql`{
  project(id: ${router.params.id}){
    id,
    name,
    tasks(archived: ${router.params.archived}){
      id,
      name,
      completed
    }
  }
}`

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


const projectWithTasks = await gql("id3433523", router.params.id, router.params.archived)

ну а сервер получив айдишник вытащит нужный запрос и подставит в него переданные аргументы

некий плагин

Это мы сейчас фантазируем или это есть в спецификации GQL? Так-то плагин такой можно хоть для SQL запросов сделать. А можно пойти дальше и прямо фигачить JS-код получения данных, который так же выносится на сервер, оставляя на клиенте лишь id ручки.


Впрочем, дебажить такое приложение, где все запросы имеют вид GET /3433523/473842/true — то ещё удовольствие.

Это мы сейчас фантазируем или это есть в спецификации GQL?

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


А можно пойти дальше и прямо фигачить JS-код получения данных, который так же выносится на сервер, оставляя на клиенте лишь id ручки.

Вооот, вы все правильно поняли — как только мы имеем возможность написать код на клиенте который будет загружен на бэк то рано или поздно появится мысль — а зачем нам этот ограниченный формат graphql который годится всего лишь для вытаскивания вложенных сущностей для которого все равно нужно будет писать большую часть логики в резолверах (проверку прав, фильтрацию и т.д) если можно сразу написать код любой логики обработки запроса используя удобный орм и этот код на этапе сборки заменится айдишником и будет загружен на сервер. В итоге что graphql что rest становятся лишней абстракцией (или технической деталью не говоря уже про транспорты в виде http с его статус-кодами и кучей способов пробросить данные (url, headers, body) или вебсокетов) на пути к высокоуровневой коммуникации клиента с сервером


Впрочем, дебажить такое приложение, где все запросы имеют вид GET /3433523/473842/true — то ещё удовольствие.

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

Н-этажный запрос составит нерадивый фронтендер, а злоумышленних им просто воспользуется.


Успех протокола HTTP во многом обязан удобству его дебага. А успех REST во многом обязан его независимости от языков и фреймворков.

И graphql превращается… в удалённый вызов процедур. Почему бы тогда сразу какой-нибудь json-rpc не заюзать, зачем эти велосипеды?

в концепции graphQL есть концепция — сложность запроса. На каждое поле ставится «магический» коэффициент, который говорит, что для данного поля требуется «5 очков». Когда будет запрошено 10 таких полей сложность запроса будет уже 50 (5 * 10). В настройках приложения мы ставим, что наш лимит — 100 очков на запрос, в связи с чем клиент физически не сможет отправить слишком тяжелый запрос ( массива объектов, вес каждого — 50 очков т.к. в нем 10 полей по 5 очков)

Список друзей всех комментаторов данной статьи — это сколько очков?

На каждое поле объекта по умолчанию (в зависимости от реализации сервера) ставится 1 очко. Можно указать любое абстрактное значение на каждое поле. Затем их сумма будет умножена на кол-во объектов. На сервере ты ставишь абстрактный лимит(любое число, какое захотел) и сравнивается. Сколько будет а данном случае? Не знаю, N комментаторов * на кол-во друзей каждого. Как посчитать стоимость объекта — ты уже знаешь

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

Ну почему? Предположим, у комментатора 2 поля — это имя(1 очко) и друзья(2 очка). У друга тоже 2 поля — имя(1 очко) и имя компании (3 очка). Эти характеристики просто настройка приложения (гипер параметры). Я запросил у сервера, что я хочу получить список первых 10 комментаторов и первых каждого 5 друзей. В итоге на моменте парсинга запроса я делаю: считаю сколько стоит один комментатор и потом умножаю на его кол-во.


1 коментатор: (1(имя ком)+(2(друзья)+1(имя др)+3(имя компании))5(лимит кол-ва)) = 31 очко.
2) мне нужно первые 10 комментаторов: 31
10 = 310 очков.


Все, это считается автоматически и позволяет избежать супер тяжёлых запросов. В данном примере я максимум получу 50 сущностей. Тут даже нет дополнительных выделений памяти, простая математика 2-3го класса на целых числах — проц даже не заметит

Мне не нужны 10 комментариев и 5 друзей, мне нужны все.

Тогда будет ддос сервера. Почему на каждом первом сайте мы не видим сразу весь контент, а только первые 10? Все всегда пагинируется (кроме комментариев на Хабре) с помощью перехода на новые страницы или автоскролл.

Когда я сказал 10 комментаторов — я имел ввиду лимит 10 и страница 1, лимит 10 страница 2, лимит…

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

Лимит влияет на «сложность» в запросе, если это реализовано(в моей библиотеке на это можно савиь разные правила). Если не стоит сложность на пагинации, то, по крайней мере, за каждый вложенный объект клиент будет «платить» и в итоге не сможет сделать супер сложный, тяжелый запрос т.к. не хватит лимита сложности (ты сам его указываешь, 1, 10, 100000...)

P.S. просчет сложности запроса не ест ресурсов т.к. это делается во время парсинга запроса и до его исполнения (распарсить строку запроса сильно сложнее, чем 2+2*2).

Про паджинацию предлагаю почитать тут: https://habr.com/ru/post/413133/


Ну и я бы посмотрел, как вы будете паджинировать дерево, которое выдаёт GQL.

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

GraphQL типы описываются в GraphQL схеме и никакого отношения к языкам программирования не имеют


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

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


Схема чаще всего генерируется на языках программирования (для js, например, это делается с помощью graphql-js)

Не для всех кейсов подходят схемо-генераторы. Например не думаю, что у вас получится подружить graphql-js и AWS AppSync. Когда приходится писать схему руками — замечаешь указанные мной проблемы.

Одна из самых больших проблем GraphQL — что он изначально не рассчитан на то, чтобы на сервере работать единым запросом. Это удобно, когда нужно собрать данные из разных источников, но когда нужно получить данные из реляционной БД — начинаются костыли. Есть отдельные библиотеки для того, чтобы собирать SQL-запросы с JOIN на сервере, но они работают через боль. Плюс отсутствие версионирования API из коробки и многие другие ограничения.

Самое смешное, что есть отличный стандарт oData, в котором это реализуется очень удобно.

А представьте какая боль с NoSQL, когда у вас даже джоинов нет

Пихать всюду NoSQL когда нужно SQL в принципе боль.

НЛО прилетело и опубликовало эту надпись здесь
Да, NoSQL не отменяет джойнов, в конце концов он же «Not only» и позволяет получить данные одним запросом. Вопрос в том, насколько это позволяют сделать реализации GraphQL. Тот же сервер Apollo по умолчанию делает отдельные запросы к провайдеру данных для каждого случая.
НЛО прилетело и опубликовало эту надпись здесь
Если писать полностью свою реализацию, то всё просто. Но тогда не получится нормально съэкономить. А вот с готовыми решениями — проблема в том, что они по умолчанию каждую сущность дёргают отдельно.

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

А про графовую структуру на JOIN-ах:

Во-первых, вполне можно. Мы ещё в 2006-ом делали графовую БД на постгресе с использованием рекурсивных запросов.

Во-вторых, GraphQL, не взирая на своё название, всё-таки далеко не только про графы.
Но только для постгреса и с ограничениями. Да, костылей много, но смысл от этого не меняется — в graphql изначально не продумали эффективность запросов и теперь эти дыры приходится затыкать.
НЛО прилетело и опубликовало эту надпись здесь
В 2005-ом мы её делали такую штуку на MySQL, а в 2006-ом переписали на постгрес, потому что MySQL на какой-то глубине запросов переставал оптимизировать джойны и начинал тупо их перемножать, что на миллионах записей не выдерживали никакие таймауты.
Возможно, EdgeDB поможет решить проблему.
Проблема не в БД — так-то многие базы совмещают разные типы данных, тот же постгрес отлично работает и с графами, и с json и с парами ключ-значение. Проблема именно в прослойке, в данном случае GraphQL
Поищите, например, по словам «Rack/Rails over HTTP/2». Будет интересно.… В результате переход на концепцию потоков HTTP/2 — это не так уж и просто. Особенно — в случае с некоторыми фреймворками.

А теперь замените слово HTTP/2 на GraphQL
я уверен в том, что многие уже исследуют такие GraphQL-директивы, как defer, stream и live.

Зря уверены: в Apollo поддержку defer впилили и анонсировали, а потом выпилили. Про остальные и говорить не приходится. А Relay… ну если кто прорвется через него, то вроде бы там тоже «вот-вот будет, но пока не»: https://github.com/facebook/relay/issues/2194#issuecomment-526901224

Филя слишком утомился, считая байтики, и за деревьями не увидел леса. Такие технологии как graphql, hateoas, и sparql — это не про то, чтоб все быстро летало и экономится трафик, а про то, чтоб совсем не надо было проектировать API для сервисов. Я вообще раньше не задумывался, что использование graphql может привести к сокращению количества запросов, настолько десятый это вопрос.
всё ещё ранние дни
Как это по-английски? И как лучше по-русски?

Автор не осветил важный аспект, который эксплуатирует новый проект Дангласа. Даже два аспекта:
1) GraphQL запросы делает через Post, что уже некомильфо — нельзя использовать многие стратегии кэширования ответов.
2) много компактных структур данных имеют больше шансов на переиспользование. А значит и кэшировать мелкие ответы более выгодно. HTTP/2 снимает многие аргументы против такой стратегии. И уменьшает смысл в генерации монструозных документов на каждый "специальный случай".

Если кто не в курсе, Данглас — автор нового проекта (и спецификации) https://github.com/dunglas/vulcain
А также Mercure и ApiPlatform (который поддерживает и GraphQL и несколько спецификаций классического REST).
Так что в его твитте есть немного желтизны — сам он вовсе не так радикально настроен.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий