Pull to refresh

graphql — подводные камни

Reading time 3 min
Views 8.4K
Наверное, не существует идеальных технологий. Не является исключением и graphql. Если у Вас еще не было опыта работы с этой технологией, то нужно хорошо представлять, какие проблемы могут у Вас возникнуть и заблаговременно к ним подготовиться.

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

Например, для кого-то может быть неожиданным, что каждый объект в graphql придется описывать минимум дважды: один раз в качестве возвращаемого типа объекта, и еще один раз в качестве input типа объекта (см. graphql.org/graphql-js/mutations-and-input-types). Впрочем, это я рассказал для начала и даже не считаю существенным недостатком. Сегодня речь пойдет о таких вопросах, которые, как правило, приходится решать, разрабатывая приложение с применением graphql технологии:

  1. Разделение доступа для пользователей и групп пользователей.
  2. Обработка ошибок.
  3. Проблема 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 года
Tags:
Hubs:
+2
Comments 33
Comments Comments 33

Articles