Наверное, не существует идеальных технологий. Не является исключением и graphql. Если у Вас еще не было опыта работы с этой технологией, то нужно хорошо представлять, какие проблемы могут у Вас возникнуть и заблаговременно к ним подготовиться.
Для начала скажу, что я скорее сторонник, чем противник, применения graphql кругом, где это только возможно. И совершенно не собираюсь разубеждать кого-нибудь в целесообразности применения этой технологии. И именно поэтому поднимаю вопросы, которые относятся к нерешенным вопросам в рамках технологии graphql.
Например, для кого-то может быть неожиданным, что каждый объект в graphql придется описывать минимум дважды: один раз в качестве возвращаемого типа объекта, и еще один раз в качестве input типа объекта (см. graphql.org/graphql-js/mutations-and-input-types). Впрочем, это я рассказал для начала и даже не считаю существенным недостатком. Сегодня речь пойдет о таких вопросах, которые, как правило, приходится решать, разрабатывая приложение с применением graphql технологии:
graphql вообще ничего не знает о разделении доступа для пользователей и групп. Таким образом, вся работа по разделению доступа на ответственности разработчика приложения. В функцию-резольвер третьим параметром передается объект контекста приложения. Поэтому, если Вы, например, работаете с реализацией graphql JavaScript+express, то в параметре контекста Вы можете получить текущего пользователя из объекта request express.js. Но дальнейшая работа по разграничению доступа должна проводиться непосредственно в каждом резольвере:
Естественно, такой подход усложняет контроль прав доступа, т.к. нет возможности задавать права доступа в декларативной манере и контроль прав рассредоточен по десяткам (для некоторых больших систем по тысячам) функциям-резольверам. Поэтому существует целый ряд библиотек, которые решают эту проблему. Некоторые из них достаточно популярны (судя по количеству звезд на github.com), например github.com/maticzav/graphql-shield.
Если Ваш фронтенд требует валидацию ввода и формирование подробных сообщений для каждого поля, не прошедшего валидацию, то обработка ошибок в graphql, скорее всего, покажется Вам недостаточно гибкой. Например, если входной параметр был описан как строка, а пришло числовое значение, то сообщение об ошибке будет мало подходящим для этого:
Если есть грубая ошибка в типе входного параметра, то сообщение об ошибке будет генерироваться автоматически и контролировать это процесс нет возможности. Если валидация по типу входного параметра прошла успешно, то есть возможность отправить клиенту кастомное сообщение об ошибке, выбросив объект
Эта проблема была подробно рассмотрена в сообщении.
graphql построен на функциях-резольверах. Это означает, что выборка данных из базы данных может порождать проблему, которая называется SELECT N+1. Предположим что в функции-резольвере был получен список объектов, в котором связанные с этим объектом данные представлены идентификаторами (внешними ключами). Для каждого такого идентификатора будет вызвана своя функиця-резольвер, в которой (в каждой) будет дополнительно сделан запрос к базе данных. Таким образом, вместо одного запроса к базе данных (с SQL JOIN) будет выполнено много запросов, что перегружает базу данных запросами.
Для решения этой проблемы facebook разработал библиотеку github.com/graphql/dataloader, которая использует стратегию отложенного запроса. Вместо выполнения запроса непосредственно в фунции-резольвере, предлагается накапливать идентификаторы (вторичные ключи) в массиве, после чего получать их сразу одним запросом.
apapacy@gmail.com
13 мая 2019 года
Для начала скажу, что я скорее сторонник, чем противник, применения graphql кругом, где это только возможно. И совершенно не собираюсь разубеждать кого-нибудь в целесообразности применения этой технологии. И именно поэтому поднимаю вопросы, которые относятся к нерешенным вопросам в рамках технологии graphql.
Например, для кого-то может быть неожиданным, что каждый объект в graphql придется описывать минимум дважды: один раз в качестве возвращаемого типа объекта, и еще один раз в качестве input типа объекта (см. graphql.org/graphql-js/mutations-and-input-types). Впрочем, это я рассказал для начала и даже не считаю существенным недостатком. Сегодня речь пойдет о таких вопросах, которые, как правило, приходится решать, разрабатывая приложение с применением graphql технологии:
- Разделение доступа для пользователей и групп пользователей.
- Обработка ошибок.
- Проблема SELECT N + 1
Разделение доступа для пользователей и групп пользователей
graphql вообще ничего не знает о разделении доступа для пользователей и групп. Таким образом, вся работа по разделению доступа на ответственности разработчика приложения. В функцию-резольвер третьим параметром передается объект контекста приложения. Поэтому, если Вы, например, работаете с реализацией graphql JavaScript+express, то в параметре контекста Вы можете получить текущего пользователя из объекта request express.js. Но дальнейшая работа по разграничению доступа должна проводиться непосредственно в каждом резольвере:
function(root, {id}, ctx) {
return DB.Lists.get(id)
.then( list => {
if(list.owner_id && list.owner_id != ctx.userId){
throw new Error("Not authorized to see this list");
} else {
return list;
}
});
}
Естественно, такой подход усложняет контроль прав доступа, т.к. нет возможности задавать права доступа в декларативной манере и контроль прав рассредоточен по десяткам (для некоторых больших систем по тысячам) функциям-резольверам. Поэтому существует целый ряд библиотек, которые решают эту проблему. Некоторые из них достаточно популярны (судя по количеству звезд на github.com), например github.com/maticzav/graphql-shield.
Обработка ошибок
Если Ваш фронтенд требует валидацию ввода и формирование подробных сообщений для каждого поля, не прошедшего валидацию, то обработка ошибок в graphql, скорее всего, покажется Вам недостаточно гибкой. Например, если входной параметр был описан как строка, а пришло числовое значение, то сообщение об ошибке будет мало подходящим для этого:
{
"errors": [
{
"message": "Expected type String, found 1.",
"locations": [
{
"line": 2,
"column": 15
}
]
}
]
}
Если есть грубая ошибка в типе входного параметра, то сообщение об ошибке будет генерироваться автоматически и контролировать это процесс нет возможности. Если валидация по типу входного параметра прошла успешно, то есть возможность отправить клиенту кастомное сообщение об ошибке, выбросив объект
new Error ('custom message ...')
. Добавить кастомные поля к объекту ошибки не получится (кастомизация ошибки реализована в библиотеках apollo-server-express и apollo-errors при совместном их использовании). Разумеется, всегда есть возможность сериализовать объект в строку message
на сервере и десериализовать на клиенте. Но нужно ли так поступать?Проблема SELECT N + 1
Эта проблема была подробно рассмотрена в сообщении.
graphql построен на функциях-резольверах. Это означает, что выборка данных из базы данных может порождать проблему, которая называется SELECT N+1. Предположим что в функции-резольвере был получен список объектов, в котором связанные с этим объектом данные представлены идентификаторами (внешними ключами). Для каждого такого идентификатора будет вызвана своя функиця-резольвер, в которой (в каждой) будет дополнительно сделан запрос к базе данных. Таким образом, вместо одного запроса к базе данных (с SQL JOIN) будет выполнено много запросов, что перегружает базу данных запросами.
Для решения этой проблемы facebook разработал библиотеку github.com/graphql/dataloader, которая использует стратегию отложенного запроса. Вместо выполнения запроса непосредственно в фунции-резольвере, предлагается накапливать идентификаторы (вторичные ключи) в массиве, после чего получать их сразу одним запросом.
apapacy@gmail.com
13 мая 2019 года