- Сравнение GraphQL API с REST API, действительно ли GraphQL "убийца" REST?
- Возможные проблемы, сложности и решения при имплементации GraphQL API
- Плюсы и минусы использования GraphQL API
- А вам действительно нужен GraphQL API, стоят ли затраты полученного результата?
3. Kiev 2018GraphQL– удобное API или хайп?
Возможность возвращать разные форматы данных
Только JSON Разные форматы
данных. XML, JSON
4. Kiev 2018GraphQL– удобное API или хайп?
YES NO
Объем и структура
данных жестко
определенна на сервере
Возможность формировать структуру и объем
данных на клиенте
5. Kiev 2018GraphQL– удобное API или хайп?
C R U DR
QUERY
MUTATION
Для операции «Читать»
Для операций «Создать», «Обновить», «Удалить»
GET
POST
PUT
DELETE
7. Kiev 2018GraphQL– удобное API или хайп?
Variables and Parameters
Декларация переменной.
Задается в JSON формате.
Передаем переменную “key” в качестве
аргумента указывая тип данной
переменной, в данном случаи “String ”.
14. Kiev 2018GraphQL– удобное API или хайп?
Date in database: 1522269610866 (timestamp)
@dateFormat(format: “DD-MM-YYYY”)
Query result 28.03.2018
@upperCase
@hasScope(scope: [“read:rating”])
@isAuthenticated
@hasRole(role:[“admin:”])
@log
@price(locale:”en_US”)
26. Kiev 2018GraphQL– удобное API или хайп?
• Конвертирует Postgres схему в GraphQL
• Имеет watch режим, позволяет следить за
изменением в Postgres схеме
• Позволяет считывать комментарии к
таблицам postgres и преобразовывать в поле
description в GraphqQL
27. Kiev 2018GraphQL– удобное API или хайп?
Генерирует CRUD для вашего типа.
Использует NDF формат для импорта
и экспорта в БД
35. Kiev 2018GraphQL– удобное API или хайп?
DB
Queue
Query item
Query item
Query item
Query item
Batching
Single query
Batching использует временные интервалы.
Batching работает на основе «тиков» -
проверяя очередь на наличие ожидающих
запросов каждые N миллисекунд. Если в
очереди есть несколько запросов, они
объединяются в один.
42. Kiev 2018GraphQL– удобное API или хайп?
• Загружать с помощью других API и передавать ссылку на загруженный файл в
мутацию GraphQL.
• Использовать multipart/form-data. Передавая данные в контексте резолвера.
43. Kiev 2018GraphQL– удобное API или хайп?
Библиотеки для загрузки файлов
• NPM graphql-server-express-upload
• Apollo apollo-upload-client
57. Throttling на основе времени выполнения
Kiev 2018GraphQL– удобное API или хайп?
Время выполнения: 100 Ms
Максимальное время выполнения сервером
(Bucket size) = 1000ms
58. Kiev 2018GraphQL– удобное API или хайп?
Throttling на основе сложности запроса.
Complexity: 1
Complexity: 1
Complexity: 1
Query Complexity = 3
Максимальная сложность: 15
62. Kiev 2018GraphQL– удобное API или хайп?
• Limit – максимальное количество баллов в час
• Cost – количество баллов текущего запроса
• Remaining – остаточное количество баллов
• ResetAt – время до сброса состояния
69. Kiev 2018GraphQL– удобное API или хайп?
Дополнительный параметр sort у
которого поля:
”field” – поле по которому будет
выполнятся сортировка.
”order”- порядок сортировки.
Фильтрация выполняется
по sku продукта.
71. Kiev 2018GraphQL– удобное API или хайп?
В каких случаях стоит использовать GraphQL?
• При использовании микросервисной
архитектуры.
• При разработке простого API.
• При разработке платформы/сервиса которая
рассчитан на интеграцию с другими
системами/приложениями.
• Если команды размещены в разных локалях.
(сложность коммуникации)
• При использовании только одного ресурса
предоставления данных.
• При условии что вы разрабатываете
финальный продукт не рассчитанный на
дальнейшее развитие в ближайшем времени.
(Интернет-магазины, промо сайты, т.д)
72. Kiev 2018GraphQL– удобное API или хайп?
• GraphQL Voyager - визуально представляет GraphQL API в
виде интерактивного графа.
https://github.com/APIs-guru/graphql-voyager
• GraphCMS - создание, редактирование GraphQL контента.
https://graphcms.com/
• GraphQL Docs – позволяет создать статическую
документацию на основе вашего API.
https://github.com/2fd/graphdoc
• GraphQL Faker – позволяет подменять данные вашего API.
Полезно для тестирования.
https://github.com/APIs-guru/graphql-faker
• Optics – метрики запросов в GraphQL API.
https://www.apollographql.com/engine/
73. Kiev 2018GraphQL– удобное API или хайп?
Any questions?
The end. Thank You.
Vladimir Zaets
https://github.com/VladimirZaets
https://www.linkedin.com/in/vladimir-zaets-22b2149a/
https://www.facebook.com/vladimir.zaets.75
Editor's Notes
Первая проблема GraphQL это дублирование схемы. Не важно какую базу данных мы используем sql или nosql мы всегда должны декларировать схему для базы данных и для самого Graphql.Основная проблема даже не в том что вам несколько раз придетсяописсывать очень схожие схемы, а в том что вам нужно будет ихпостоянно синхронизировать, поддерживает в консистентном состоянии.
И так какие есть варианты решения данной проблемы. Первый и самый явный это написать свой конвертер/генератор схемы, которыйсформирует схему для вашей БД из схемы Graphql, либо же обратно из схемы GraphQL схему вашей БД. У меня в проекте к примеру мы используем XML для декларации схемы.
Так же вы можете воспользоватся одной из библиотек/фреймворков которые сделают это за вас.
Для начала рассмотрим PostGraphql и PostGraphile.Это одна и та же утилита которые пережила переименование с PostGraphqlв PostGraphile.Данная утилита конвентирует схему Postgres в схему GraphQL.У нее есть довольно много настроек, она может сама запускать Graphql сервер на основе схемы Postgres базы, то есть выступатькак отдельный сервис так может быть внедрена в ваш GraphQL сервер,так же она имеет watch режим, то есть следит за изменением Postgres схемы, считывает комментарии к таблицам постргесс и переносит в поле дескрипшин графкл апи.
Далее Prisma, позиционирует себя как realtime GraphQL database layer,то есть база данных для Graphql которая генерируется в реальном времени.Довольно похожа по своей работе на postgraphql но она работает в обратном направлении. Если postgraphql генерирует graphql схему основываясь на postgres схему то prisma генерирует схему базы данныхосновываясь на схему GraphQL задекларированую в формате SDL (Schema Definition Language).Prisma поднимает БД либо локально используя Docker (он должен быть у вас установлен) либо на своем сервисе, что для меня являетсяминусом, т.к мы не имеем возможность как либо влиять на БД. В качествеБД используется mysql, возможности выбора другой нет. Однако естьвозможность загружать предустановленные данные, импортировать и експортироватьданные с БД используя NDF формат. Так же очень важно то что можно использовать призму для генерации типов.Мы обьявляем тип, а призма генерирует CRUD операции для него.Graphcool является фрейворком построенным на основе призмы.Так же на сколько я слышал призма уже имеет и облачный сервис.
Вторая проблема это "Несоответствие данных на клиенте и сервере"
Допустим мы хотим получить имя автора поста.Для этого нам нужно получить сущность поста.На клиенте обращаясь к GraphqlAPI мы получим обьект пост который содержитинформацию о пользователи.На стороне сервера сущность поста не содержит данных о пользователе кромекак его идентификатор. И соответсвенно из колекции пользователей нам нужно будет выбрать пользователя под таким id и у сущности пользователя взять свойствоимени.В этом и состоит несоответствие данных.С одной стороны это логично, с другой, почти такой же код выполняетсяв резолвере Graphql API, так почему не обращатся напрямую к Graphql APIServer to Server.
Как это делается.Первый вариант это использовать непосредственно функцию GraphQL. В неё нужно передать саму схему и запрос, так же можно передать значение по умолчанию контекст и переменные.
При создании GraphQL сервера вы будете использовать фреймворк или библиотекии велика вероятность того что они будет содержать какой либо врапернад этой функцией для упрощения использования. В качестве примера я добавил функции runQuery и queryOne фреймворка VulcanJS. При их исиользовании вам нужно лишь передать саму квери, и параметрыкоторые нужны для запроса, а если не нужны то просто квери.
Cледущая одна из найболее серезных проблем это избыточность обращенийк БД, то есть проблема N+1
Допустим мы хотим получить имя продавца каждого продукта в определеннойкатегории. Довольно частый кейс. И в таком довольно простом примеремы уже имеем N+1 проблему.
Давайте посмотрим результат выполнения запроса и как он формировался.Мы получаем коллекции продуктов и имя продавца для каждого из них, вполне ожидаемый вариант. Но как собирался данный ответ? происходило одно обращение в БД для полученияколекции продуктов, и еще 3 для получения имени продавца для каждого продукта.В данном случаи для того что бы собрать этот ответ потребовалось 4 обращения в бд.Cобственно N это количество айтемов в колекции. Если бы в нашем случаи было100 продуктов, то мы обратились бы раз что бы получить колекцию и 100 раз для получения имени продавца для каждого продукта, и того произошло б 101 обращениек базе данных.
Как это решается? А решается это используя Batching. Batching работает на основе «тиков» - проверяя очередь на наличие ожидающих запросов каждые N миллисекунд. Если в очереди есть несколько запросов, они объединяются в один.В нашем случаи как мы видим, запрос на получения имени продавка каждого продукта складываются в очередь, и через N миллисекунд, когда отработаеточередной Batching тик, все что находится в очереди обьединяется и происходит всего одного обращения к БД.
Реализация батчинга довольно сложная задача, особенно на некоторыхязыках, к счастью фейсбук позоботился об этом, и выпустил библиотекуdataLoader на плечи которой и перекладыватся Batching и частично кэширование.Реализация библиотеки есть на многих языках и работает она так же хорошо.Было проверено помимо NodeJS само собой, на PHP в которой посмтроенана колбеках что не свойственно для пхп.
Следущая проблема это низкая производительность, а именно проблемыс кешированием.
Сначала поговорим про сетевое кэширование.Сетевое кэширование с GraphQL невозможно.Используя Graphql API как мы уже знаем у нас фактически одна точка вхождени,один ендпоинт, таким образом мы не можем использовать кэширование на уровне сети, то есть использовать такие сервисы как варниш, фастли и другие,т.к они работают по принципу перехвата запроса на сетевом уровне,проверяют есть ли данные в кэше по текущей точке вхождения и если естьи возвращают данные, так и не проксируя запрос на сервер.соответсвенно это не подходит для GraphQL.
С стороны сервера мы должны кешировать результаты обращений к самой БД. Сохраняя в виде плоского списка запрос как ключ и ответ как значение,ну это в самой простой имплементации.
Теперь про кеширование на клиентеС Graphql API нужно кешировать каждое поле отдельно отражая графо-подобную структуру в виде списка. Давайте рассмотрим пример, у нас есть запрос у которого есть поле "product" у него есть поле "created_by" и у него поле нейм. Простой grqphql запрос. Мы кешируем все поля корневого типа в виде списка. В данном случаи product(id: "1").Первый айтем в списке содержит поле тайтл которое имеет скалярное значение и ссылку на второй аймет который представляет собой поле created_by. Поле created_by сохраняется как отдельный айтем в листе т.к это поле содержит значение сложно типа.
Следущая проблема это загрузка файлов с помощу GraphQL API.Про загрузку файлов через GraphQL API в документации ничего не сказано, как же её реализовать?
Первый самый простой способ это загружать файлы через другиетипы API, например REST и передавать ссылку на загруженый файлв мутацию Graphql.Второй способ, который используют большенство библиотек и фреймворковэто используя multipart/form-data. Мы считываем мультипарт дату с реквеста и передаем в качестве контекста в резолвер. Ну и с резолвера мы уже можем производить любые манипуляции с файлом, загрузить на CDN или что то еще.
Как я уже говорит, что второй подход реализованы во многих библиотеках и фреймворках, например в apollo, либо же использовать midlware для NodeJS.
Следущая проблема которую мы рассмотрим это версионирование API.
В REST API мы привыкли добавлять версию непосредственно в url ендпойта.GraphQL как таковой не пропогандирует версионирование своего API,обьясняя это тем что клиент сам запрашивает нужные ему данныеи мы можем без учета версий расширять существующие типы.И многие считают это огромным плюсом что GraphQL API не нужно версионировать.Но вполне реальные случаи когда API меняется по 30 раз в неделю, и помимо разширения нам может быть нужно помунять интерфейссуществующих полей, например изменмить набор входящих аргументов,либо возвращаеймые данные или что угодно еще. Кейс возможно не частый но вполне возможный.Что мы можем сделать? Все что нам позволяет GraphQL это пометитьполе как deprecated написать причину, и разширить тип добавляя новое практически аналогичное поле с новым интерфейсом.Но с таким подходом API будет безгранично разширятся практически аналогичными полями. Далеко даже не факт что пользователи API заметят,что поле которые они используют стало депрекейтед, не смотря на точто в возвращаеймые данные будет добавлены свойства isDeprecated и deprecatedReason. С стандартным версионированием в REST такой проблемы нет, пользователи API могут быть уверены что версия API которую они используют останется без изменений.
Далее давайте поговорим про security в graphql API.
Далее давайте поговорим про security в graphql API.
дальше и поговори про блемлему с максимальной глубиной запроса.
Тк структура запроса формируется на стороне клиента то мы можем написать подобный запрос с не ограниченой вложеностю.
Конечно такого допускать мы не должны.
Для этого нам нужно указать, какую глубино вложености можно использовать.
В данном случаи, допустим что мы указали maximym query depth 3, это значит что если у нас будет такой запрос как представлен выше уже не валидный, все что отмечено красным уже не валидная часть.
Реализовать ограничение вложености мы можем использую библиотеку graphql-depth-limit. Конкретно эта библиотека работает с express и Koa graphql, но я уверен наверняка что существуют и другие и не только для NodeJS.
Использовать в данном случаи её довольно просто, мы передаем в массив validationRules, функцию dephLimit которая предоставляет библиотека, и указываем какую глубино вложености мы позволяем. Так же она конечно содержить более гибкие настройки, можете посмотреть её API на гитхабе
Но как мы понимаем, проблемными запросы могут быть не только глубокого уровня вложености, но если у нас и на одном уровне обьявлено много тяжелых запросов.
Как нам дать понять приложению что запрос тяжелый. Допустим у нас такой простой запрос, давайте скажем что по умолчанию что получения любого поля в API это complexity 1. При этом мы указываем максимальную комплексити для одного запроса, допустим 100. Здесь конечно тяжело подобрать оптимальную complexity для вашего приложения, это зависит от многих факторов, если интерестно есть достаточно много статей по расчету сложности запроса.
И далее, у нас представлено поле operations, и допустим мы знаем что для получения этого поля нам нужно достаточно много ресурсов, и конкретно для этого поля мы можем указать complexity вы, в данном случаи 5 и общая сложность запроса у нас будет являться 7.
Соответсвенно при отправке такого запроса, будет произведена статическая проверка, то есть запросы в БД не будут отправлятся до того момента как не пройдет успешно статическая проверка, в данном случаи она пройдет успешно, т.к максимальное комплексити указано 100, а у нашем запросе всего 7, и только после этого будут происходить обращения к БД для получения данных по полям.
Основной проблемой данного подхода является что что значения complexeti являются абстрактными, их практически непозможно просчитать точно, можно к примеру отталкиватся на время выполнения запроса, но это тоже довольно абстрактно.
Для того что бы это имплементировать вы опять таки можете воспользоватся библиотекой.Мы указываем максимальную комплексити запроса, и так же мы можем указать значение по умолчанию как для скалярных полей,
Так и для обьектов и списков. Далее передаем результат выполнения в массив валидейшн рулс.И используем директивы в определении типов для того что бы указать значение комплексити для определенного поля.
Подходы с ограничением вложености и ограничением по сложности запроса смогут оберечь нас от слишком сложных и большых запросов, но тем не мениее не сможет уберечь нас от количества запросов которые отправляются на наш сервер. И для того что бы уберечся от слишком большого количества запросов мы можем использовать тротлинг подход.
Данный подход позволит ограничит количество запросов опираясь либо на времени выполнения либо на сложности выполнения запросов.
Давайте посмотрим пример на основе времени. Допустим у нас есть простой запрос.
Предположим что время выполнения запроса равняется 100мс. Далее мы указываем какое количество серверного времени в опреленное время мы отдаем на обработку запросов для клиента.
Допустим мы говорим что мы позволяем использовать 1000мс серверного времени в минуту.Таким образом клиент может отправить 10 таких запросов в минуту, после чего сервер будет отвергать все последущие запросы.
Основная проблема что время выполнения запроса еще более относительная величина.
По этому мы можем построить тротлинг на основе сложности запроса.
В таком случаи мы ограничиваем по определенному комплексити сложность одного запроса, опредилив максимальное значение и так же мы определим значение максимальной сложности запросов относительно времени. К примеру, максимальная комлексити это 15 в минуту, таким образом клиент сможет отправить только 5 таких запросов за одну минуту, дальнейшие будут блокироватся.
Таким образом мы мы миксуем подход с ограничение сложности для одного запроса и ограничеим по сложности относительно времени.
И первое что вам конечно нужно реализовать это ограничения по таймауту. То есть если запрос выполняется слишком длительное количество времени мы должны оборвать его выполнения и вернуть ошибку. С этим думаю понятно, давайте пойдем
Давайте немного посмотрим на GraphQL API, наверное вы знаете что гитхаб имеет graphql api, это 4 версия, но они одновременно поддержуют и активно обновляют 3тию REST версию API. Давайте посмотрим какие ограничение накладывает Github на свое API, для безопасности.
И первое ограничение это ограничение на возможность получать более 100 айтемов из коллекции. Если мы попросим 1000 елементов то как видим мы получаем ошибку.
Гитхаб апи иммет ограничение по комплексити, 5000 в час.Вместе с запросом на поучения данных мы можем запросить данные о текущем состоянии.
Деларируем поле rateLimit
Который содержит поля limit – которое говорит про комплексити в час
Кост – количество баллов сложность текущего запроса
Remaining – остаточное количество баллов сложности
ResetAt – время до сброса состояния, когда нам гитхаб снова выдаст очки сложности
Вот например такой запрос будет иметь цену 6.
Но давайте разберемся как гитхаб считает. Гитхаб считает по 1 балу за запрос. То есть для получения полекции оранизаций мы получим мы используем всего 1 бал.
Далее мы хотим получить мемберов из каждой организации, и того у нас получается 30 вариаций, и мы тратим 30 балов. Далее мы хотим получить фоловером, для каждого из 20 мемберов из каждой организации. И того 30 организаций мы умножаем на 20 мемберов для того что бы учесть все вариации, и того мы получим 600 балов. Сумируем их и получаем 631, затем эту сумму делим на 100 и получаем 6, это и есть стоймость текущего запроса. Значения округляются до целого числа. Таким образом мы можем отправить около 830 таких запросов в час.
Ну и вот к примеру реальный запрос, который отправляет одна из наших утилит которая работает с гитхаб апи. В данном случаи гитхаб отпадает по таймауту. Выполняется этот запрос успешно только если мы будем запрашивать не 100 а 50 айтемов.