Комментарии 79
JSON-RPC
Остальное от лукавого
SOAP (WSDL, XML) ...
Остальное от лукавого
Graphql и Json-RPC это немного разные вещи. Graphql про снижение зависимости разработчиков клиента от разработчиков сервера (graphql - query language). Json-RPC про оптимизацию коммуникации (remote procedure call protocol).
Для разных клиентов (например разные экраны в приложении) одни и те же данные нужны в разном виде. И если с обычными полями всё просто (можно сразу все отсылать), то со связанными сущностями сложнее, т.к. нельзя их загружать все, на всякий случай.
Поэтому либо бекенд на каждый чих клиента должен делать новый api (например, api: getOrderWithProducts, getOrderWithCustomers, getOrderWithProductsAndCustomers). Либо бекенд делает только "одноуровневый" API, но тогда клиент должен это всё склеивать у себя. И если у вас несколько разных клиентов (например, другие сервисы, интеграция с другой стороной и frontend), то значит и склейка будет реализована несколько раз.
У нас frontend очень радуется graphql, потому что это сильно упрощает им работу. Бекенд не рад, т.к. это много работы по созданию резолверов и не самая тривиальная реализация, для изначальной схемы. А то, что бекендеров не дёргают на каждый чих, они и не замечают.
Я могу из своего опыта сказать (соответственно, моя предметная область - малые и средние сайты c2b, скажем так. Несколько десятков эндпойнтов/методов на backend API)
80% запросов на бэк - кастомные. Нужно писать контроллеры со своей логикой.
20% - из разряда CRUD хоть с сущностями хоть с нет (которые типа ложатся на REST или GraphQL - ваш getOrderWithProductsAndCustomers ).
Если нет вложенных сущностей - все ок. Если есть- написать свой универсальный функционал добавления одного вложенного уровня в зависимости от параметра запроса - несложно. Свой маленький удобный QL
80% запросов - кастомные. Иначе если логику переносить на фронт (бэк - типа просто доступ к БД), то будет огромный оверхед по запросам
GraphQL - это невероятное усложнение коммуникации фронта с бэком, подходит наверно для каких-то очень редких случаев public API
REST - это зоопарк без удобных стандартов и даже особо бест практик.
JSON-RPC дает строгость, стандарты и порядок, который ты сам и задаешь. Всё гениальное - просто
Вот моя статья о нем - https://habr.com/ru/articles/709362/
Честно говоря не знаю, почему это невероятное усложнение коммуникаций. Graphql клиент не очень сложный, оверхед помимо реста, это только указать список необходимых полей.
Да, он даёт нехилый оверхед на бекенде, если речь идёт о нескольких десятках несвязанных запросов (типа один для заказов, другой для заказчиков, третий для продуктов).
Однако, если одни и те же данные нужны в десятке разных срезов, то оверхед на бойлерплейт и поддержку гораздо выше.
Не уверен насчёт вашей предметной области, но из того, что встречал на практике, кастомные ендпоинты нужны только для модификации данных (ака мутации в графкл), но эти эндпоинты могут возвращать одинаковые данные. По крайней мере из одной центральной модели. А не так, что у нас есть UseCase1OrderDto, UseCase2OrderDto, итд.
Ну и опять же, это вопрос масштаба. Если у вас требуется только один уровень вложенности, и то не всегда, то конечно, графкл не нужен. Но и категорично отвергать другие подходы не стоит, т.к. у всех разные случаи.
Либо бекенд делает только "одноуровневый" API, но тогда клиент должен это всё склеивать у себя. И если у вас несколько разных клиентов (например, другие сервисы, интеграция с другой стороной и frontend), то значит и склейка будет реализована несколько раз.
В чём проблема склейки несколько раз? Каждый клиент - это отдельная банановая республика, у каждого может быть своя разная стратегия склейки. Это его обязанность, сам пусть и решает как склеивать. Подумаешь, пару раз повторится, но на третий раз - появится исключение какое-то (ещё и в рантайме на том же клиенте, например).
А OData пробовали/рассмативали?
ресурсы - конечные точки,
навигация от одних ресурсов к другим через связи, возможность запросить или запостить вложенные объекты.
Интроспекция и самоописание.
Поддержка действий над объектами (ресурсами) и пагинации =)
Всегда считал, что использование GraphQL для проектов масштаба меньше Facebook будет оверкилом. По факту, нарушается KISS-принцип. Поэтому даже пробовать GraphQL не стал на практике, всегда обходился простым REST.
Верно. GraphQL проявляет себя на достаточно больших проектах. На которых несколько каналов, условно веб, андроид, ios и один backend. И активная поставка изменений в ui на все каналы.
забыл добавить, один backend не означает, что физически один backend. там может быть пачка микросервисов, которые пилит пачка backend-команд. и все данные бесшовно склеиваются через федерацию. как там распилины данные по микросервисам, фронтам знать неинтересно, они видят схему и запрашивают данные.
да, все эти удобства не достаются бесплатно. расплачиваться приходится всем, что упомянуто в статье в качестве недостатков: проблема N+1, сложностью отладки при отсутствии детального логирования и трейсинга, вопросы по безопасности
GraphQL вынуждает возвращать код 200 с сообщением об ошибке в полезной нагрузке ответа. Чтобы понять, в какой конечной точке произошёл сбой, нужно проверить каждую полезную нагрузку.
Это нормально и правильно. Разделяет канал и данные. В результате GraphQL не обязан работать исключительно поверх http.
мы думали о них с точки зрения графа объектов
а чем это лучше обычного подхода?
Автор сам себе гемора насоздавал и жалуется... Никто не мешает не делать такую большую схему, сделай ограничение на каком то уровне и все, не давай ходить дальше и не будет проблем.
Пагинация - вообще без проблем, откуда там проблемы?
Запросы видишь ли в браузере одинаковые... Дык сделай разные. Можно тупо послать /graphql/nameOfMyRequest и сервер отлично это отработает, а nameOfMyRequest игнорирует, хотя в браузере все будет видно.
И т.д. В общем, надо уметь пользоваться инструментом. Мы используем 3 года на небольшом проекте и только рады...
А как вы боретесь с дублированием связанных данных из-за денормализации? У нас денормализация давала раздутие выдачи на несколько порядков.
А какая проблема с раздутием выдачи? Если выдача несколько сот килобайт то современные браузеры это скушают и нет проблем, если у вас дерево выдачи очень большое то в этом и фишка... Вы сами решаете внутренний тип будете выдавать полностью или выдадите его как другой тип (обрезанный) и не дадите ходить дальше. Надо просто понять, что нет одного правильного пути. Для каждого проекта свое... Мы например выдаем 2 уровня максимум и на втором уровне не выдаем уже сущность вложенную, а выдаем ее ID. Хочешь ее получить, делай следующий запрос...
В этом и смысл что никто не обязывает вас выдавать все дерево объектов на 5 уровней например. Вы сами определяете как вы считаете правильным. Обычно 2-3 уровня хватает (я бы даже про третий уровень подумал уже). Это в любом случае уже лучше чем прсто rest но при этом и решает все проблемы что он тут описал
Пара десятков килобайт легко раздувается до пары десятков мегабайт даже на паре уровней вложенности, если граф сильно связный.
А вам, кажется, GQL вообще не нужен, если полтора уровня хватает для всего.
Кажется не должно раздуваться, если у нас корректно определены запрашиваемые поля.
Да, "контрагент"/"договоры" дадут на третий уровень у "договора" массив "стороны" типа "контрагент" у которого снова "договоры"(четвертый уровень) и потом снова "Стороны" (пятый)
Но обычно вы не будете разворачивать второй уровень до контрагентов, а если и будете, то их снова до договоров точно не станете.
Конкретно у нас была проблема с обычным иерархическим меню, которое, разумеется, должно было загружаться целиком, а не на 2 уровня.
Но если в меню нет циклических ссылок "наверх", то вам вернется только то, что запрошено, сверху вниз. То есть или никаких мегабайт не будет, или они все равно должны быть, так как их и запросили.
Откуда в меню может лезть денормализация? (ну и за меню, показываемое пользователю как один объект с больше чем 3 уровнями уже кажется надо лишать премии, а если объект не один, то можно и подгружать по мере углубления)
Я сейчас открыл в браузере меню "другие закладки", добрался до 5 уровня вложенности. Это страшно даже на 4к в 100%. На телефоне это вообще не возможно.
Меню не дерево, а DAG. При денормализации в дерево происходит комбинаторный взрыв.
а кто вам мешал выдавать ссылки на ноды вложенные, а не снова ноды?
Необходимость работы с данными всего меню, а не только корневыми пунктами. Собственно данных там как правило было в районе килобайта. У некоторых пользователей доходило до нескольких десятков кб. Но из-за адского дублирования при денормализации, объём раздумался так, что IE падал в попытке распарсить огромный JSON.
Если я верно понял, то в чем проблема работать с данными всего меню, если все ноды в корне по id, а все ссылки на ноды везде - это id этих нод? Да, это не совсем по GQL, но сделать вполне можно.
а что нельзя ввести спец. обьекты в выдаче graphql которые бы указывали что они рефы. Клиент бы сам ресолвил такое, причем оффлайн и очень тривиально.
{id: 1, name: test, items: [{ref_id: 1}, ... ]}
Ну конечно, куда нам до вас... Не надо передергивать, никто не писал что хватает. Мы сами решаем на каком уровне не выдавать дальше (если надо дальше то уже надо будет посылать новый запрос). Это зависит от ситуации, но главное, что это возможно и мы сами это контролируем. Ножом можно и хлеб резать и вены... Надо самому решать как использовать инструмент.
Можете пжл пояснить на примере? В т.ч. "сильно связный граф"
Я не знаю как получить пару десятков мегабайт на фронте на паре уровней вложенности. И более того, не знаю как должен выглядеть UI для отображения всех этих данных. Разумеется если мы не рисуем таблицу на десятки тысяч строк и 100 столбцов.
Юзеры и лейблы записались для каждой ишью полным текстом возможно по нескольку раз? Так мелочь это дало.
Место там боди сожрало, которое собственно основная полезная нагрузка и не дублируется.
Не "возможно" и не "по нескольку". Боди можете вырезать, оно там вообще не нужно.
"возможно" в том плане, что теоретически 140 (вроде примерно столько) ишью могли создать 140 пользователей, разных. Ну и с метками аналогично. Выкинуть боди — размер упал в 10 раз, сколько процентов дают выкинутые неуникальные юзеры и метки я искать не буду, башка уже не варит.
С произвольным DAG конечно хуже, но если меню — произвольный сильносвязный DAG, то проблемы с меню не только в размере ответа GRAPHQL
Ваша попытка подобрать условия, когда проблема себя не проявляет, не избавляет от самой проблемы. Если вы будете проектировать систему, думая лишь об удобных кейсах, то вам будет очень больно прилетать с прода. Смотрите не на сам палец, а туда, куда он указывает.
Ну так проблема дедубликации решается известно как в общем виде, а значит она не прооблема. Я приводил схему из 1С, вы привели схему из GraphQL.
Единственно, я не знаю если в с клиента запрашивать 2 раза одну и ту же таблицу с разным количеством полей, то это считается одним типом или двумя. Возможно это потребует небольших дополнительных плясок, или можно решать "в лоб" через 2 типа и дублирование (уже гораздо меньшее)
И это не "денормализовывать-нормализовывать" в терминах РСУБД, это скорее безсхемная объектная форма записи, вариации на тему NoSql, а то и HEAP. но так как задача не в хранении, а в передаче ответа с бека на клиент, то никаких сложностей в кодировании, кэшировании и прочих ускорениях поиска это не создает. (до тех пор, пока вы не поймаете таки цикл, но кажется и это можно успешно обойти)
Не совсем понятно, при чём тут graphql тогда, если данные так же раздуваются через REST или через большинство других протоколов.
И, как вам справедливо отметили, в представленном примере порядок увеличен за счёт body, а не дупликации.
Более того, в отличии от стандартных подходов, graphql вам позволяет снизить ущерб от дупликации, тем что вы гораздо меньше полей будете доставать
В реальном UI для списка из issues, у вас несколько полей всего.
Но в целом проблема дубликации существует, тут вы правы. Но то что у вас это всё раздувалось до десятков мегабайт мне кажется очень странным.
данные так же раздуваются через REST
Не раздуются, если не делать (бессмысленную) денормализацию.
graphql вам позволяет снизить ущерб от дупликации, тем что вы гораздо меньше полей будете доставать
Это есть в большинстве обобщённых REST протоколов, начиная с древней OData, заканчивая новомодным HARP.
то что у вас это всё раздувалось до десятков мегабайт мне кажется очень странным
Вот так всегда и бывает. Ты думаешь "да такого не моет быть", а потом прилетают багрепорты от пользователей, которые слишком любят твой сервис, и используют его на полную катушку, а потом у них бац и у них приложение вообще не открывается из-за кривого протокола.
А кстати типовых средств не предлагается для нормализации?
Ну типа в в ответе давать ответ на вопрос, но сложные поля возвращать как ссылку (ИД объекта, ИД состава полей) + таблица соответствия (ИД объекта, ИД состава полей) — представление объекта. И пусть уже на стороне клиента собирают рекурсивный денормализованный ужас.
Ну да, не совсем то, что я имел в виду, но почти.
Не совсем понятно как работать дедубликатору со схемой
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С — там тоже развесистая структура переплетенных справочников
Какая-то странная схема дедупликации там предлагается. Зачем хранить первый элемент полностью, а последующие ссылкой? Чтобы обработчик усложнить втыканием двух веток кода «если встретился полный набор данных, работает с ним, в противном случае ищем, где он встречался ранее»? Почему бы не всегда хранить только ссылки, а сами данные в отдельной, индексируемой по ключу, карте? Обработка данных сразу станет единообразной: достали ключ, сходили в карту.
Поздравляю, вы изобрели нормализованную выдачу. Но это не про GQL.
Тут как бы вся ветка о том, как убрать дублирование в GraphQL, зачем вы её тогда начали, если «это не про GQL»? Ну и комментировал я решение по ссылке для дедупликации, где «почти» это и предлагают, только в какой-то извращённой манере.
Ну а я комментировал прекрасный тезис "надо уметь пользоваться инструментом. Мы используем 3 года на небольшом проекте и только рады" и уточнил, как правильно пользоваться этим инструментом, чтобы не приходилось нормализованную выдачу СУБД денормализовывать в GQL резолверах, чтобы потом нормализовывать для передачи по сети, чтобы потом опять денормализовыват для GQL клиента, чтобы потом опять нормализовывать, чтобы разные части UI не противоречили друг другу.
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
Видимо оригинальный автор 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 МП, веб, десктоп, да еще и сторонние подключаются).
Вот эта аргументация, что если несколько типов клиентов, то gql лучше rest или json-rpc - она откуда берется?
В чем именно лучше?
Лучше реста, так как можно за один запрос получить что нужно и не терять на сетевом взаимодействии, лучше RPC тем, что специфика клиента лежит в клиенте, а не на бэке.
специфика клиента лежит в клиенте, а не на бэке
Это о чем?
Пример
Ну вот нам на десктопе (в браузере) нужен по результатам запроса один набор полей, а на андроид другой.
В случае РПЦ мы делаем 2 процедуры (специфика клиента пролезла в бэк) или одну, которая возвращает все. А клиенты потом половину выкидывают. (зря гоняем трафик и бэк)
В случае GQL мы делаем с разных клиентов разные запросы, бэк единообразно выбирает только то, что запросили и все довольны. Специфичные для клиентов вещи лежать в кодовой базе клиентов, лишние данные по сети не ходят.
То есть вы считаете, что указать в селекте / отфильтровать на бэке нужные клиенту поля это бОльший оверхед, чем весь тот код, который gql привносит на бэк своим существованием?
Ну смотрите, если будете делать разные конкретные процедуры для разных клиентов — будет протечка абстракций, она имхо хуже, чем работающий черный ящик gql. А если будете использовать одну процедуру, с указанием ей что там и как фильтровать/возвращать, то в итоге изобретете несовместимый gql
Вопрос ведь в другом, оверхед есть, вопрос тех проблем, что решает gql. Любая технология решает одни проблемы добавляя другие.
Лучше брать то, что подходит.
Если мучаемся от просьб добавлять, изменять атрибутный состав, то с рестом точно жить несколько не удобно. Тут даже не стоит вопрос об экономии сетевого трафика, тут вопрос про удобство предоставления апи и да, клиенты, кто не любит gql, могут смотреть на него как на простые рест запросы (чем оно и является).
Во всей этой истории есть главный момент: брать нужно то, с чем может команда совладать, будет готова к минусам подхода, ну и закроет свои проблемы.
В небольших проектах и небольших командах принесет скорее боль из-за сложности входа и решения проблем. В. Больших (особенно с развесистыми струткрами) скорее даст гибкость и независимость команд разработки (кстати всякие там api first ложиься достаточно не плохо в gql). Опять же большой и нагруженный проект, ну тут что, много денег, много команд, хоть рест, хоть json-rpc (я думал он rip), хоть grpc.
Подходить к любой технологии с энтузиазмом, потом разочаровываться и не находить до конца путей решения проблем - очень не конструктивный подход. Потом и рождаются статьи такого типа, по прочтению которой приходишь к выводу, что gql прям не очень.
Пока что единственный прозвучавший обоснованный аргумент в пользу gql - он удобен на больших проектах
Чем? Тем, что там очень много вызовов, и фронтэндеры могут сразу реализовывать новые вызовы на затрудняя бэкэндеров, архитекторов и менеджеров, что на больших проектах скорей всего довольно заморочистая история.
Другими словами, gql отвязывает фронт от бэка на уровне разработки.
Но ведь это означает, что фронт напрямую работает с БД. Бэк - просто прослойка к базе, без бизнес логики. Транскриптор GQL в SQL.
Это правильно? Вопрос сложный. Но пока что всё, что я вижу - люди хотят работать с БД "напрямую". Как я написал выше, на моих проектах таких вызовов 20%-30%. И они прекрасно реализуются кастомно-шаблонно без подключения фейсбуковских сложных конструкций.
Ну на самом деле мы этим и занимаемся на бэке, просто затягивая технологии для повышения своего JSI (шутка, с долей правды).
Вопрос не в затруднении бэкэндеров, вопрос в том что бэкэнд один, апи один, клиентов много и разных, причем если на фронте зачастую одна версия с заточкой под 2 режима (исключаем канарейку, тут может быть больше), то на мп минимум с десяток (не выкидывать же всех), но давайте другие стороны немного посмотрим.
Обязательность полей: застолбив контракт как not null, то вы гарантировано получите из бэка консистентные данные, да, есть конечно в open spec required - но это больше соглашение, чем обязанность, в gql бэк сам обделается и ответит ошибкой.
Строгая типизация: тут немного с другой стороны попробую показать. Вот у нас есть условно банковские счета и метод, который их возвращает. Как нам в 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!
}
Может быть неочевидным, но второй вариант и может быть расширен еще другими типами и будет нести гарантии в контракте, что удобно и клиентам и бэку.
Ошибки и graceful degradation: допустим бэк написал BFF на ресте и джоинит сам dto от других сервисов (конечно это такой себе вариант и лучше бы тогда CQRS использовать, НО реальность такова, что это достаточно дешевый и быстрый подход), как можно сообщить клиенту, что какую-то часть данных он не получит? Можно допустим кинуть ошибку и отдать, то что есть, можно ничего не отдавать, можно не отдавать данные, которые достать не получилось. В общем, доводить клиентов до истерии.
Как решено в gql? те данные, при выборке которых произошел фейл - отдается ошибка, оставшиеся приходят клиенту, т.е. клиент осведомлен о том, что чего-то не хватает и принимает решение, как с этим работать, т.е. может отобразить ту часть данных, которую можно -> graceful degradation возможен без приседаний (пользуются ли этим? не всегда, клиенты привыкли получать 4хх или 5хх)
Фильтрация и пагинация в долбанутых кейсах: допустим у нас есть замечательная структура:
{
"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.
Это backward compatible.
Нет, клиент ожидает полный список, а вы ему молча обрезали всё после 10 элемента - это сломает его логику. Давайте я вам лучше покажу, как это выглядит на HARP.
До:
profile(accounts(title);recomendations(description;date))
После:
profile(accounts(title;_num=0@10=);recomendations(description;date;_num=0@10=))
Да, схема даже не поменялась. Кто хочет - грузит всё. Кто не хочет всё - фильтрует.
С харп не знаком, возможно кто-то его использует и применимость очень интересная, но на рынке найти готовых спецов будет сложно, с gql не очень, но он хоть маркетингово раскачан и на том им спасибо.
По примеру: если изначально не заложена пагинация и фильирация, то дальнейшее добавление не будет ломать старых клиентов? Не будет имплицитно резать историю?
Ох уж этот Marketing Driven Development..
Не будет, конечно, ибо на уровне языка запросов она есть всегда и везде, но если не реализована, то просто не фильтрует.
кто не любит gql, могут смотреть на него как на простые рест запросы (чем оно и является)
Вообще-то GQL - это RPC. От того, что вы завернули все GQL вызовы в один HTTP метод, он не становится REST.
Кастомные типы - в json та же трабла, только в gql хотя бы можно подчеркнуть на уровне схемы, что тут нужно что-то делать с этим типом, в json вообще может прийти что угодно.
Странное противопоставление, учитывая, что GQL возвращает как раз JSON. Ну и, если вы не в курсе: https://json-schema.org/
Самая большая проблема любой абстракции по типу GraphQL что на вообще не учитывает особенности хранения данных, т.е. не учитывает проблемы и узкие места хранилища. И это исходит из одной из первоначальных задач технологии — универсальность. Отсюда и проблема N+1 и все остальное.
Как результат часто в итоге все забывают что данные должны храниться в какой-то конкретной БД у которой есть свои узкие места, которые GQL в принципе не может учитывать, ибо не знает о ничего (а если учитывать что большинство фронтэндеров к сожалению очень далеки от бекенда и особенностей хранения данных, то вообще становится очень грустно). В результате появляются костыли-комбайны-оптимизаторы которые пытаются решить проблемы на уровне абстракций значительно выше, чем они должны решаться.
По-факту любая абстрация (и база данных не исключение) - ставит трейд-офф перед выбором ее применения и тут либо соглашаемся, либо отказываемся. Оптимизировать gql бывает достаточно проблемно, но это цена, которую можно заплатить, ну и оптимизировать можно
В том то и дело, что часто оптимизацию невозможно сделать без переписывания бека / без довольно сильного изменения схемы данных. Самая большая боль в таких абстракциях и в таком подходе (дадим прямой доступ к базе фронту), что к огромному сожалению бОльшая часть фронтендеров очень далека от понимания как работает БД — для большинства это магия, которая обязана отдать данные которые они запросили (таких непонимающих много и среди бекендеров, но если быть честным, то процент значительно меньше).
Т.е. с одной стороны все узкие места конкретного хранилища обычно вылазят таблицах от несколько лямов записей, а с другой GQL из-за дополнительной абстракции имеет смысл только на очень больших проектах (это не мои слова, это многие здесь в топике писали не раз). Но только вот проблема больших проектов — это большой объем данных и значительно бОльший шанс, что фронтенд приложит прод каким нибудь N+1 запросом.
И это не камень в сторону GQL, а просто проблема любой подобной абстракции поверх конкретного хранилища (аналогично когда юзают какой-либо навороченный ORM для сложных выборок, а не для записи и выборок по id т.д.)
В целом согласен с вашей точной зрения, что ещё один уровень абстракции снижает производительность.
Но не согласен с логикой про БД и N+1 (и очень большие проекты).
Графкл это абстракция над сервисами (а не над БД). Соответственно все оптимизации с БД ложатся на плечи соответствующих сервисов. Задача графкл это их склеить.
Если сервисы предоставляют убогий и не оптимальный АПИ, то это проблема не графкл, вам ни одна технология не решит эту проблему.
По факту, кто-то всегда должен склеивать данные для UI:
Либо это делает бэк в кастомных запросах и отдает толстые ДТО (увеличение времени разработки + дорогой maintenance)
Либо это делает фронт, дёргая десяток ендпоинтов (много работы на фронте + никаких возможностей для оптимизации)
Либо это делает дополнительная прослойка, например графкл (сложность поддержки этой прослойки + нет возможностей для оптимизации, только страховка от больших запросов)
Каждый выбирает сам. По мне, если мы говорим о команде из нескольких человек и долгоживущем продукте, первый и последний пункты несостоятельны. Инвестиция в графкл + оптимизацию АПИ сервисов на большом отрезке себя окупает. Потому что доменная модель меняется гораздо реже, чем UI.
Если вы разрабатываете в одиночку проект за полгода и забываете о нём, то разумеется такая инвестиция ничего не даёт.
Графкл это абстракция над сервисами (а не над БД). Соответственно все оптимизации с БД ложатся на плечи соответствующих сервисов. Задача графкл это их склеить.
Это в идеальном мире. А по факту куда ни глянь GQL — это транспортная абстракция к БД для фронта, когда никто вообще не думает о том как это все выполняется на беке. Тут же все очень логично если есть возможность сделать просто (и не важно как это будет тупить на беке прода), то именно так просто и будут делать. Ну действительно много ли вы знаете из чистых фронтендеров (без бекенд опыта), кто знает особенности разных БД?
Чтобы не показалось что я только о GQL говорю, то точно так же тормознуто можно сделать и рестом (привет тупая пагинация на обычных БД), но GQL имеет значительно больше способов выстрелить себе в ногу (следствие универсальности и полного абстрагирования от способа хранения конкретных данных)
Я считаю, что неправильно осуждать технологию за то, что её неправильно используют.
И по моему мнению, бекенд для графкл должны разрабатывать бекендеры, так у них будет больше мотивации и возможностей сделать адекватную реализацию. И лучше понимание бизнес доменов.
Иначе и приходим к ситуации, что бедные фронты, в погоне за экономией своих трудозатрат, пытаются интегрировать непростую технологию поверх неподготовленного фундамента.
Графкл это надстройка над АПИ. Если ваш АПИ это БД или плохой рест, то проблема не в надстройке.
Если люди идут по пути "сделать проще", то это их осознанный, и в каких то случаях правильный, выбор. Но они должны понимать, что это костыль, и у каждого костыля есть цена.
Я не говорю, что графкл идеален и его должны использовать все. Скорее, он подойдёт для меньшинства проектов. Я лишь за объективную оценку технологии.
Скорее, он подойдёт для меньшинства проектов.
Так и я о чем же. Только вот GQL везде продвигается как универсальное средство, как серебряная пуля для фронта. Типа юзайте GQL и бекендеры вам будут почти не нужны, сделаем автогенерацию кода по спеке/схеме или будем вообще юзать какой либо облачный SaaS. Что дергать и как будут решать чисто на фронте. И это как раз в данный момент печальная реальность (ну очень много где считается за норму).
Когда я писал что кто-то сделает проще без понимания бека я имел примеры типа тупящей пагинации при использовании большинства бекендов (и апи переписывание бека тут никак не поможет на определенных типах хранилища)
Самый типичный пример. Если брать доку:
https://graphql.org/learn/pagination/#pagination-and-edges
- We could do something like friends(first:2 offset:2) to ask for the next two in the list.
- We could do something like friends(first:2 after:$friendId), to ask for the next two after the last friend we fetched.
- We could do something like friends(first:2 after:$friendCursor), where we get a cursor from the last item and use that to paginate.
Как думаете какой шанс что фронтенд разработчик выберет 2-й или 3-й вариант, а не первый? И если вдруг будет выбран первый вариант (ну он же первый в доке), то он в любом случае будет тупить на больших значениях offset на большинстве баз данных. И не важно какие ухищрения делать в коде, на уровне бека это нельзя будет исправить, и единственное решение — так не делать, а делать вариант 2 или 3 если возможен. Это как раз и есть пример что если фронтенд разработчик не знает особенности хранения данных и узкие места — он гарантированно наступит на эти грабли. И это самый простой случай и пример из документации. Я не говорю уже о чем-то более сложном типа агрегатов и т.п.
И это еще раз как раз проблема абстрагирования от особенности реализации хранения этих данных.
6.2. Пагинация.
Вот прямо в спецификации по той ссылке описана: https://graphql.org/learn/pagination/.
Библиотека Hot Chocolate для .NET умеет это из коробки навешиванием всего одного атрибута.
https://chillicream.com/docs/hotchocolate/v13/fetching-data/pagination
6.4. N+1
Реально решается через dataloader без проблем. В той же библиотеке всё для этого есть.
Ну да, несколько строчек кода надо написать для каждого случая.
https://chillicream.com/docs/hotchocolate/v13/fetching-data/dataloader
GraphQL: от восторга до разочарования