Ускорьте работу приложений с помощью библиотеки Dataloader от Facebook.

Привет. Сегодня я расскажу, как можно улучшить серверную часть своего приложения. Существует библиотека под названием Dataloader, которая помогает нам кэшировать ответы на запросы и использовать их позже. В библиотеке есть хорошая документация и соответствующие примеры, но мы собираемся выяснить, как ее интегрировать и получить от нее максимум.

Прежде чем мы начнем, позвольте мне объяснить, как работает библиотека. Когда вы отправляете запрос из своего приложения, вы можете позже использовать его ответ. Библиотека Dataloader позволяет вам это делать. Он хранит обещание с уникальным ключом, который затем можно использовать, когда вы вызываете библиотеку с тем же ключом. Давайте начнем.

Мы используем GraphQL и Relay в нашем проекте, а под GraphQL мы используем REST API. Иногда мы получаем запрос несколько раз

time: 0.724  URL:  /api/user
time: 0.71   URL:  /api/vacancies
time: 0.772  URL:  /api/user
time: 1.116  URL:  /api/invitations
time: 1.125  URL:  /api/counters/1
time: 1.126  URL:  /api/friends
time: 1.12   URL:  /api/counters/1

это происходит потому, что наш сервер GraphQL не знает, что мы уже получили или не получили.

Итак, я начну с описания наших погрузчиков. Мы создаем новые загрузчики каждый раз, когда делаем новый запрос к серверу GraphQL, потому что это единственный способ быть уверенным, что данные в текущем запросе к серверу GraphQL актуальны. У нас есть функция, которая создает и возвращает нам объект с нашими загрузчиками:

export default function createLoaders(fetchFn) {
 return {
   User: new DataLoader(async (keys) => {
     return Promise.all(keys.map(async () => {
       const response = await fetchFn(‘get’, `/api/user`);
       
       return await response.json(); 
     }));
   }),
   Counters: new DataLoader((keys) => {
     return Promise.all(keys.map(async (id) => {
       const response = await fetchFn('get', `/api/counters/${id}`;
    
       return await response.json();
     });
   })
}

В нашем объявлении сервера GraphQL:

return {
  schema,
  rootValue: {
   id: ‘viewer’,
   loaders: createLoaders(fetchFn),
  },
};

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

Теперь мы посмотрим, как мы можем использовать его в нашем приложении.

...
type: userType,
resolve: async (rootValue) => {
  const {loaders} = rootValue;
  const user = await loaders.UserProfile.load(‘me’);
  const user1 = await loaders.UserProfile.load(‘me’);
  return user;
}
...
...
type: counterType,
resolve: async (rootValue) => {
  const {loaders} = rootValue;
  const counter = await loaders.Counters.load(1);
  const counter1 = await loaders.Counters.load(1);
  
  return counter1;
}
...

У нас может быть много вызовов this loaders во многих местах нашего приложения, но в конечном итоге Dataloader отправит только 1 запрос для каждого загрузчика на наш внутренний сервер.

time: 0.584  URL:  /api/user
time: 0.669  URL:  /api/vacancies
time: 0.982  URL:  /api/invitations
time: 0.622  URL:  /api/counters/1
time: 0.716  URL:  /api/friends

Если вы используете Relay, вам нужно будет отправить только 1 запрос на сервер graphql от ваших клиентов. В этом случае вам нужно будет собрать весь фрагмент вашего Relay в один запрос. Это гарантирует вам максимальную пользу от Dataloader.

В заключение хочу сказать, что библиотека Dataloader решает большую проблему в этом мире, но есть еще много проблем, которые нам нужно решить вместе. Если вам нравится эта библиотека, перейдите в ее репозиторий, где вы можете найти дополнительную информацию о ней.