Comments 23
Вторая причина медленной работы GraphQL связана с проблемой N+1. Нас она, к счастью, не коснулась, поскольку реализация запроса от GraphQL в базу данных у нас сделана особым образом. Мы строим по схеме запрос напрямую в БД, без хождений в стороны. Поэтому один запрос в GraphQL — это запросы в одну БД в рамках одной сессии.
А расскажите пожалуйста подробнее как это сделано. Спасибо.
В общих чертах примерно так:
Для удобства, при генерации GQL схемы, мы укладываем в схему при помощи системных кастомных директив информацию о том какой объект хранится в поле и по какой ссылке он ссылает "от-к". Это отсылка к тому, что в БД у нас дерево в двух коллекция links/objects. Но к этим коллекциям есть и системные, аля links settings и objects settings, где хранится информация о полях, типах, о ссылках с направлениями и типом связи.
Далее, когда приходит запрос, мы получаем распарсенный запрос и по нему извлекаем из схемы настройки ссылок.
Выглядит примерно как:queryName: {
rootField: {
link: { ••• }
fields: {
simpleField: { ••• }
objectByLink: {
anotherObjectField: {
link: { ••• }
fields: { ••• }
}
}
}
}
}
И из этого начинаем строить второй запрос, который уже пойдет в БД. Дерево GQL запроса превращается в массив массивов цепочек. Цепочка, по сути - это маршрут по одному пути дерева.
Используя уже этот запрос идем в БД и где-то делаем несколько запросов, где-то один, все выбранные ссылки и объекты кэшируется, на случай если выборка уже производилась, например когда кто-то выбирает путь аля obj1 -> [obj2, obj3 -> obj1]
. Все выполняется в рамках одной сессии. Такой обход дерева конечно же не самый эффективный, поэтому сейчас мы тестируем формирование одного aggregate запроса на lookup`ах, на весь GQL запрос.
А расскажите как вы обеспечивали совместимость gql апи при изменениях структуры данных в хранилище. Если в маппингах я представляю как это делать - под каждый конкретный кейс адаптируем запросы, то в случае больших возможностей gql мне это кажется более сложным
Так же хотелось бы подробнее узнать за счёт чего gql у вас выиграл в скорости по сравнению с маппингами.
Кстати, совместимость - это отдельная боль, конечно. Мы пол года мигрировали и перестраивали структуры и в итоге пришли к одной схеме:
- если возможно, то вводим новое поле и перенаправляем запросы записи/чтения из старого в новый. Т.е. как бы работает оба поля и новое и старое, но в БД это новое поле. Старое объявляем deprecated и после уже чисто работа менеджера - пидалировать всех переходить. После полного перехода удаляем старое поле
- иначе, мы просто создаем новое поле и долго упорно пидалируем всех, а после великие и могучие миграции
А скорость мы выиграли, по сути, чисто на транспорте. Так как GQL уже не требовал все старые сервисы, кроме некоторых, мы как бы "схлопнулись" в монолит и получили очень хороший прирост убрав брокер сообщений.
Давно топлю за то, чтобы человечество ушло от rest в gql.
Объективно gql решает множество проблем и мы должны уйти от доисторического реста и забыть его как страшный сон
Сколько не общаюсь с адептами gql, никак не могу получить внятный ответ на вопрос "чем это лучше restapi+openapi?"
Вопрос без подвоха и стёба. Мне правда интересно.
А Restapi это просто обычное апи или тоже какая-то своя система с таким названием?
Если первое, то как минимум с graphql облегчается работа фронтов, потому что для них уже все есть, им не нужно на каждый чих говорить бэкендам: "а теперь мне нужно еще вот это апи с рюшечками, и вот этим названием", просто потому что graphql позволяет им самим по факту делать запросы в базу (ну не в базу конечно, но для них это выглядит именно так), и выбирать все что требуется.
Из минусов для меня это мониторинг. Когда все ваши базовые инструменты видят один эндпоинт и не могут больше понять что через него проходит.
Простите, но вы описали какой-то мир розовых пони, где бэкенд не надо реализовывать совсем. Даже posgrest не может таким похвастаться.
С graphql возникает очень интересный вопрос с http-кешированием. Так что мониторинг это прям вообще 10ый вопрос.
REST API со схемой тоже позволяет брать данных столько, сколько нужно. Не вижу вообще никаких преимуществ у gql в этом.
Хорошо давайте опишу как это было. Итак, у нас был сервис, не лучше сказать СЕРВИС ну или еще больше. Создание любых новых фич на фронтенде постоянно упиралось в изменения или дополнения бэкенда. Условно, есть апи которое возвращает айди юзера, теперь фронту нужно еще имя и адрес. Надо менять бэк.
Дальше фронту нужно знать а сколько у юзера авто и какие они. Надо менять бэк, добавляем новое апи. Дальше нужно узнать какие-то свойства этих авто - меняем бэк, плодим еще больше апи.
При этом все это апи создавалось достаточно хаотично, кому как хотелось тот так и делал. Это приводило к созданию дубликатов функций, или когда есть три апи возвращающие одно и то же но все это делают по=разному, с разными детялями а может даже с разными названиями одних и тех же полей. Фронты постоянно доставали бэков на тему что и где брать.
Стали внедрять GraphQL. Выносили в него все как только оно было нужно на фронтенде. Структуру держали максимально приближенную в бд. Да конечно весь этот код пришлось написать, но написали мы его 1 раз, а работал он для большого количества разных апи, о которых бэкендам уже и знать не нужно было. Потому что на фронтенде после этого просто могли сказать "дай мне юзера, его авто, а так же вот эти свойства автомобилей". И все, для бэка это было "Опиши схему один раз, и забудь".
При этом все это апи создавалось достаточно хаотично, кому как хотелось тот так и делал.
В этом предложении вы сами говорите, что не использовали никакой спецификации, полностью забили на стандартизацию и вообще на организацию, при этом считаете что виноват в этом REST API. Нее, ну логично, конечно.
Стали внедрять GraphQL.
А могли просто ввести спецификацию OpenAPI и привести REST API в порядок. Работы заняло бы меньше на переделку, и получили бы ещё удобный Swagger UI для интеграции и отладки запросов с документацией.
Структуру держали максимально приближенную в бд.
А что мешало это делать изначально в API? Вот, например, PostgREST делает это прекрасно настолько, что вообще не нужны бэкендеры (DBA только).
Вот об этом и речь, что формат схемы не решает проблемы организации. Только наличие спецификации и стандартизации могут в этом помочь. Какая будет при этом использоваться схема - вообще пофиг! Переписывание REST API на gql это просто смена локации кроватей.
И это я ещё тему ACL и доступа к данным не поднимал. Там вообще мрак...
Вы правы в том что любая стандартизация если бы не решила проблему то уж точно помогла бы. В нашем случае мы просто взяли то что было на слуху, протестировали и поняли что под наши нужды оно ложится идеально.
Насчет того чтобы создавать такое самим на голом ресте, имхо главная сложность это собственно создание непротиворечивого стандарта правил, потом убеждение всех ему следовать, потом исправление незамеченных ошибок.
главная сложность это собственно создание непротиворечивого стандарта правил
Эти правила уже есть: REST API, Swagger/OpenAPI. Это уже набор непротиворечивых правил, которые очень хорошо поддерживаются всеми инфраструктурами (проксирование, балансировка по различным правилам, кеширование и даже условновность). Есть хорошее разделение на действие (метод), сущность (путь и параметры) и контекст (заголовки и query). Всё это прекрасно описывается схемой OpenAPI. До какого-то момента, я даже не видел смысла переходить на 3 версию.
потом убеждение всех ему следовать
Вот здесь соглашусь - вообще люди очень упёртые по натуре, уверены, что делают как проще, но по факту любят всё усложнять. Дисциплина вообще самая сложная часть в любой организации. Но не зависит вообще никак от выбранного стандарта. Поэтому чем вам точно следует гордиться, что приучили хоть к какому-то стандарту людей, пусть даже я не одобряю выбор.
Статические валидаторы, хотя их и ненавидят, очень помогают поддерживать дисциплину. Общий плюс для gql и openapi - возможность использовать принцип Schema First. Это хорошо.
потом исправление незамеченных ошибок
Поэтому хорошо, когда есть помимо стандарта, ещё и какие-то полезные работающие валидаторы. Лично мне инструментарий OpenAPI нравится тем, что я могу разработать схему инфраструктуры, сгенирировать базовый код и наполнить его бизнес-логикой на практически любом ЯП.
Я пошёл дальше и сделал с командой автогенерацию UI на основе схемы. Опять же, gql такого не позволит сделать. Но здесь видна разница в наших с вами подходах: мне интереснее как можно реже обращаться к фронтендерам и получить максимум логики из бэка, а вы с точностью наоборот рассуждаете. Начинает вырисовываться тенденция: фронтендерам больше нравится самим выбирать как работать с данными, даже в ущерб производительности, а бэкендерам нравится больше статический подход, где строгая валидация, высокая скорость за счёт оптимизаций и безопасность.
Я пишу это не ради спора. Просто надеюсь, что наш с вами диалог будет полезен тем, кто будет в будущем выбирать архитектуру и стандарт. К сожалению, лично я нахожу, что GraphQL вносит иллюзию порядка, но на деле потом вносит больше хаоса и проблем. Пока что, не нашёл ничего лучше классического HTTP Rest API и автогененерируемой схемы Swagger/OpenAPI.
У нас в одном проекте graphql
Из плюсов - короче итерации (меньше ожиданий бека, пока они апи запилят). Из минусов - ситуация вообще неконтролируемая. То тормоза, то неожиданные доработки требуются (которые сложнее понять, чем rest). Вопросы безопасности и прав тоже все мутные (может потому, что знаний маловато), в итоге я бы не рекомендовал.
Изначально GQL был создан, чтобы минимизировать время рендера веб/мобильных страниц. Подавляющее большинство более-менее сложных и взрослых приложений может делать десятки REST запросов при открытии страницы. Причем часто часть запросов могут выполняться последовательно, ещё больше увеличивая время ожидания. Очевидное решение - просить все данные разом. Реализация подобного подхода через REST потребует делать по эндпоинту на каждый чих и дичайших накладных расходов на поддержку. Поэтому пришли к концепции "пусть сами просят, что им нужно"
Оно не лучше, оно проще. Доки само гененит и есть морды. Полями рулить можно. Сложные запросы позволяют получить сложные структуры. типа: товар->кто покупал->контакты->почта. С OpenAPI подобное тоже можно сделать, несколько заморочно.
Еще Базу нагружает избыточно. На мелких проектах не заметно.
GQL для меня (c 2016) это способ меньше ругаться в фронтерами.
Менеджеры довольны - этим и лучше!
Фактически оно переносит часть схемы БД куда-то ближе к фронту, чем размазывает ответственность и этим создаёт иллюзию уменьшения затрат человеко-часов.
А можно, пожалуйста, пример тех самых "сложных данных", которые Swagger не отражает?
Сложные данные, как я и говорил в статье - это части дерева. Конечно, для кого то это не сложно, но мы видели, что для многих даже простые, с небольшой вложенностью, выборки вызывали вопросы.
Так например мы можем выбрать объект Profile, у которого есть поле owner - это объект People в БД по ссылке, и для него выбрать адрес регистрации, где выбрать проживающих по этому адресу, у которых выбрать всем машины которыми они владели, и у каждой машины выбрать страховки... и так далее. Все выборки между объектами - по ссылкам, а пользователь в UI видит это как простое поле с типом (owner: People, registration: Place, ownCars: Car[]).
Как понятно отразить такую вложенность, которая может быть абсолютно случайной, с разными нюансами и ограничениями в swagger, и что бы к тебе не приходили без остановки с вопросами - мы такого решения не нашли.
А как СУБД относится к такой вариабельности? Вы случайно не дали фронтендарм возможность сгенерировать 100 джойнов и не насоздавали 10000 индексов по всем комбинациям полей просто потому что "вдруг кому-то понадоибться"? Каждый раз когда я вижу graphql я боюсь такого исхода, как вы его изебгаете?
СУБД чувствует себя довольно хорошо. У нас есть базовые индексы которых вполне достаточно. В большей своей массе, у пользователя не много данных (личные данные, люди (чаще водители), машины и адреса), поэтому запросить это можно с небольшой глубиной.
Но да, нужно понимать, как GQL ляжет в других условиях. Но, например, в apollo server есть встроенные инструменты ограничивать вложенность в запросах.
Один год с GraphQL: как технология работает на длинной дистанции?