Что такое безопасный и масштабируемый способ полного выбора всех пользователей из Amazon Cognito API в JavaScript? - PullRequest
0 голосов
/ 15 января 2019

Я являюсь частью небольшой команды, работающей на довольно небольшом веб-сайте с учетными записями пользователей; сейчас около 100 пользователей. И мы используем Amazon Cognito для управления пользователями. На нашем сайте есть сводная страница, которая отображает список / таблицу всех пользователей и различные атрибуты. Однако существует жесткое ограничение на количество элементов, возвращаемых вызовом API Amazon Cognito listUsers, в данном случае 60.

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

Кроме того, наш веб-сайт использует реагирующий-редукс и библиотеку javascript bluebird. В контексте этого вопроса, компонент, запрашивающий список всех пользователей, также отправляет действия (кусок избыточности), и CognitoIdentityServiceProvider из aws-sdk Amazon передается bluebird Promise.promisfyAll (добавляет асинхронный постфикс к вызову listUser). Я изменяю этот компонент, чтобы получить полный список пользователей.

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

Это работает, но я не очень доволен реализацией; в нашем случае все будет хорошо, поскольку у нас около 100 пользователей, но я не знаю, будет ли это хорошо масштабироваться до тысяч пользователей или более. Я понимаю, что рекурсия настолько опасна, что я не знаю, может ли эта техника вызвать проблемы, когда число обещаний / вызовов возрастает. Вопросы о накладных расходах и управлении памятью приходят на ум. Тем не менее, нам не нужно беспокоиться о количестве пользователей, стремительно растущих в тысячах, хотя бы некоторое время, но я все еще хочу узнать о потенциально предпочтительных и / или более безопасных методах достижения того же самого.

Следующие фрагменты взяты из компонента, запрашивающего список пользователей:

import Promise from 'bluebird';
import { CognitoIdentityServiceProvider } from './services/aws';

export const fetchRegisteredUsers = () => (dispatch) => {
...

  let allUsersTemp = [];
  let paginationToken;

  const getUserList = () => {
    let tempUserTest = CognitoIdentityServiceProvider.listUsersAsync({
      UserPoolId: process.env.COGNITO_USER_POOL_ID,
      PaginationToken: paginationToken
    });
    return tempUserTest.then((tempUser) => {
      allUsersTemp = allUsersTemp.concat(tempUser.Users);
      paginationToken = tempUser.PaginationToken;
      if(paginationToken) {
        return getUserList();
      } else {
        return;
      }
    })
  }

  const adminUsers = CognitoIdentityServiceProvider.listUsersInGroupAsync(adminParams);

  return Promise.all([adminUsers]).then(
    ([{ Users: adminUsers }]) => {
      getUserList().then((item) => {
        let allUsers = allUsersTemp;
        const adminUsernames = adminUsers.map(user => user.Username);

        allUsers.forEach(user => {
          let mappedAttributes = {};
          user.Attributes.forEach(attribute => mappedAttributes[attribute.Name] = attribute.Value);
          user.Attributes = mappedAttributes;
          user.isAdmin = adminUsernames.includes(user.Username);
        });

        dispatch({
          type: FETCH_REGISTERED_USERS_SUCCESS,
          payload: allUsers
        });
      });
    }, ...



Из того, что я понимаю, вызов bluebird Promise.all ждет, пока каждый переданный ему элемент не сможет разрешиться, прежде чем выполнить то, что находится в .then; до внесения каких-либо изменений в код было два списка: один для пользователей и один для администраторов. Promise.all дождался выполнения обоих обещаний, выполнил некоторую базовую обработку данных и возвратил данные в полезной нагрузке диспетчеризации действий.

После моих изменений логика для обработки и возврата списка пользователей выполняется после завершения рекурсивной цепочки обещаний (getUserList).

Мой вопрос: С этим методом все в порядке, и могу ли я использовать его как есть? Или это небезопасно, и если да, то в чем конкретно проблема? И есть ли лучший способ вообще?

Мои слабые места - рекурсия и обещания / bluebird, поэтому, пожалуйста, не стесняйтесь критиковать код на все, что связано с этими темами

1 Ответ

0 голосов
/ 16 января 2019

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

Рекурсивные подходы к выполнению обещаний, таких как getUserList, в порядке .

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

Что касается стиля кода, я бы рекомендовал не объявлять allUsersTemp и paginationToken как изменяемые переменные более высокого уровня. Вместо этого задайте им параметры вашей рекурсивной функции и выполните обещание с результатом:

function getUserList (paginationToken, allUsersTemp = []) => {
//                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  return tempUserTest = CognitoIdentityServiceProvider.listUsersAsync({
    UserPoolId: process.env.COGNITO_USER_POOL_ID,
    PaginationToken: paginationToken
  }).then(tempUser => {
    const allUsers = allUsersTemp.concat(tempUser.Users);
    const nextToken = tempUser.PaginationToken;
    if (nextToken) {
      return getUserList(nextToken, allUsers);
//                       ^^^^^^^^^^^^^^^^^^^
    } else {
      return allUsers;
//           ^^^^^^^^
    }
  });
}

Promise.all дождался выполнения обоих обещаний

Нет, вы передавали массив только с одним обещанием Promise.all и вызывали вторую функцию после завершения первой. Чтобы запустить одновременно, он должен выглядеть как

return Promise.all([
  CognitoIdentityServiceProvider.listUsersInGroupAsync(adminParams),
  getUserList()
]).then(([{ Users: adminUsers }, allUsers]) => {
  …
});
...