Ajax GET: несколько вызовов, связанных с данными, или меньше вызовов менее конкретных? - PullRequest
4 голосов
/ 05 июля 2019

Я разрабатываю веб-приложение, используя Node.js / express бэкэнд и MongoDB в качестве базы данных.

Приведенный ниже пример относится к странице панели администратора, где я буду отображать карточки с различной информацией, относящейся к пользователям на сайте. Я мог бы хотеть показать - на образце страницы - например:

  1. Количество пользователей каждого типа
  2. Наиболее распространенное местоположение для каждого типа пользователя
  3. Сколько регистраций в месяц
  4. Самые популярные должности

Я мог бы сделать все это по одному маршруту, где у меня есть контроллер, который выполняет все эти задачи и связывает их как объект с URL-адресом, который затем я могу получить из данных с помощью ajax. Или я мог бы разделить каждую задачу на свой собственный маршрут / контроллер с отдельным вызовом ajax для каждой. То, что я пытаюсь решить, - это , каковы лучшие практики для выполнения нескольких вызовов ajax на одной странице .


Пример:

Я создаю страницу, где я создам интерактивную таблицу, используя DataTables для разных типов пользователей (в настоящее время их два: mentors и mentees). В этом примере требуется всего два запроса данных (по одному для каждого типа пользователя), но моя последняя страница будет больше похожа на 10.

Для каждого типа пользователя я выполняю вызов get ajax для каждого типа пользователя и собираю таблицу из возвращаемых данных:

Тип пользователя 1 - Подопечные

$.get('/admin/' + id + '/mentees')
    .done(data => {
        $('#menteeTable').DataTable( {
            data: data,
            "columns": [
                { "data": "username"},
                { "data": "status"}
            ]
        });
    })

Тип пользователя 2 - Наставники

$.get('/admin/' + id + '/mentors')
    .done(data => {
        $('#mentorTable').DataTable( {
            data: data,
            "columns": [
                { "data": "username"},
                { "data": "position"}
            ]
        });
    })

Это требует двух маршрутов в моем бэкэнде Node.js:

router.get("/admin/:id/mentors", getMentors);
router.get("/admin/:id/mentees", getMentees);

И два контроллера, которые структурированы одинаково (но фильтруют для разных типов пользователей):

getMentees(req, res, next){
    console.log("Controller: getMentees");
    let query = { accountType: 'mentee', isAdmin: false };
    Profile.find(query)
        .lean()
        .then(users => {
            return res.json(users);
        })
        .catch(err => {
            console.log(err)
        })
}

Это прекрасно работает. Однако, поскольку мне нужно сделать несколько запросов данных, я хочу убедиться, что я строю это правильно. Я вижу несколько вариантов:

  1. Выполнять отдельные вызовы ajax для каждого типа данных и выполнять любые тяжелые операции с бэкэндом (например, подсчет пользовательских типов и возврата) - как указано выше
  2. Делайте отдельные вызовы ajax для каждого типа данных, но выполняйте тяжелую работу на внешнем интерфейсе . В приведенном выше примере я мог бы так же легко отфильтровать isAdmin пользователей на data, возвращенных из моего вызова ajax
  3. Делайте меньше вызовов ajax, которые запрашивают меньше уточненных данных . В приведенном выше примере я мог бы сделать один вызов (требующий только один маршрут / контроллер) для всех пользователей, а затем отфильтровать data на внешнем интерфейсе, чтобы построить две таблицы

Мне бы хотелось посоветовать, какая стратегия наиболее эффективна с точки зрения времени, затраченного на поиск данных


UPDATE

Чтобы прояснить вопрос, я мог бы достичь того же результата, что и выше, используя настройки контроллера примерно так:

Profile.find(query)
    .lean()
    .then(users => {
        let mentors = [],
        mentees = []

        users.forEach(user => {
            if(user.accountType === 'mentee') {
                mentees.push(user);
            } else if (user.accountType === 'mentor') {
                mentors.push(user);
            }
        });
        return res.json({mentees, mentors});
    })

А затем сделайте один ajax вызов и разбейте данные соответствующим образом. У меня вопрос: какой вариант предпочтительнее?

Ответы [ 6 ]

2 голосов
/ 14 июля 2019

Обычно в таких архитектурах есть 3 вещи:

1. Client 
2. API Gateway
3. Micro services (Servers)

В вашем случае:

1. Client is JS application code
2. API Gateway + Server is Nodejs/express (Dual responsibility)

Точка 1, которую следует отметить

Серверы предоставляют только основные API. Таким образом, этот API для сервера должен быть только пользовательским API, например:

/users?type={mentor/mentee/*}&limit=10&pageNo=8

Т.е. любой может запросить все данные или отфильтрованные данные, используя строку запроса типа.

Точка 2, которую следует отметить

Поскольку веб-страницы состоят из нескольких точек данных, и выполнение вызова для каждой точки данных на одном и том же сервере увеличивает двустороннюю передачу и ухудшает работу UX, шлюзы API есть. Таким образом, в этом случае JS не будет напрямую взаимодействовать с главным сервером, он взаимодействует с API-шлюзом и такими API, как:

/home

Приведенный выше API-интерфейс внутренне вызывает API-интерфейсы и объединяет данные в один json с наставником и списком учеников

/users?type={mentor/mentee/*}&limit=10&pageNo=8

Этот API просто передает вызов на главный сервер с атрибутами запроса

Теперь, поскольку в вашем коде шлюз API и сервер Core объединены в один слой, вам следует настроить код:

getHome(req, res, next){
    console.log("Controller: /home");
    let queryMentees = { accountType: 'mentee', isAdmin: false };
    let queryMentors = { accountType: 'mentor', isAdmin: true };

    mentes  = getProfileData(queryMentees);
    mentors = getProfileData(queryMentors);
    return res.json({mentes,mentors});
}

getUsers(req, res, next){
    console.log("Controller: /users");
    let query = {accountType:request.query.type,isAdmin:request.query.isAdmin};
    return res.json(getProfileData(query));
}

И общий класс ProfileService.js с такой функцией, как:

getProfileData(query){
  Profile.find(query)
        .lean()
        .then(users => {
            return users;
        })
        .catch(err => {
            console.log(err)
        })
}

Подробнее о API Gateway Pattern здесь

2 голосов
/ 05 июля 2019

Это действительно хороший вопрос.Прежде всего вы должны понять, как ваше приложение будет управлять полученными данными.Если это огромный объем данных, которые не изменяются во внешнем интерфейсе, но имеют разные представления и для этих представлений требуются целые данные, они могут быть кэшированы во внешний интерфейс (например, данные пользовательских настроек - приложение всегда их читает, но редкие изменения), тогда вы можете использовать ваши вторые варианты .В другом случае, если веб-интерфейс работает только с небольшой частью огромного объема данных базы данных (например, данных журнала для конкретного пользователя), предпочтительно выполнить предварительную обработку (фильтрацию) на стороне сервера ваш первый и третий параметры .На самом деле второй вариант предпочтительнее только для кэширования неизмененных данных на веб-интерфейсе, как по мне.

После уточнения вопроса вы можете использовать группировку для запроса и библиотеку lodash:

Profile.find(query)
    .lean()
    .then(users => {
        let result = [];    

       result = _(users)
        .groupBy((elem) => elem.accountType)
        .map((vals, key) => ({accountType: key, users: vals}))
        .value(); 
        });
        return res.json(result);
    });

Конечно, вы можете отобразить свои данные так, как вам удобно.Этот способ позволяет получить все типы учетных записей (не только «mentee» и «mentor»)

2 голосов
/ 05 июля 2019

TL; DR: вариант 1

IMO Я бы не передавал необработанные данные во внешний интерфейс, все может пойти не так, вы можете обнаружить слишком много, это может занять много времени для обработки неопределенной клиентской машины (это может быть устройство с низким энергопотреблением и ограниченной пропускной способностью и, например, заряд батареи), вы хотите плавного взаимодействия с пользователем, и javascript на клиенте, собирающий информацию из массы данных, отвлекает от этого. Я использую серверную часть для обработки (подготавливаю информацию так, как вам нужно), JS для извлечения и размещения информации (AJAX) на странице и такие вещи, как переключение состояний элементов, и CSS для всего, что движется (анимации и переходы и т. Д.). ) как можно больше, прежде чем прибегать к JS. Также для маршрутов мой подход заключается в том, что каждый отдельный пакет информации (dataTable) имеет маршрут, поэтому вы не перегружаете метод слишком многими целями, сохраняйте его простым и обслуживаемым. Вы всегда можете абстрагироваться от всего, что идентично и часто повторяется.

Итак, чтобы ответить на ваш вопрос, я бы выбрал вариант 1. Вы также можете предложить одну конечную точку «загрузки страницы», а затем, если что-то изменится, обновить отдельные таблицы позже, используя их различные конечные точки. Этот начальный вызов 'page-load' может сопоставить информацию от конечных точек на сервере и служить одним пакетом данных для начального заполнения всех таблиц. Один начальный запрос с одной партией четко определенных данных, а затем возможность обновить отдельную таблицу, если пользователь ее запрашивает (или если вы получаете в нее толчок).

1 голос
/ 14 июля 2019
  1. от производительности и плавности пользовательского интерфейса на пользовательском устройстве: Конечно, было бы лучше сделать 1 запрос Ajax для всех основных данных (что важно показать как можно скорее) и, возможно, выполнить больше запросов с меньшим приоритетомданные с небольшой задержкой.Или выполните 2 запроса: один для «быстрых» данных и другой для «медленных» (если это применимо), потому что:

С одной стороны, многие запросы Ajax могут замедлять пользовательский интерфейс, может быть ограничение дляколичество ajax-запросов выполняется одновременно (это зависит от браузера и может быть от 2 до 10), так что если для ex.в т. е. будет ограничение 2, тогда при 10 ajaxes будет очередь ожидающих запросов ajax

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

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

Говоря о фильтрующих пользователях:

Profile.find(query)
    .lean()
    .then(users => {
        let mentors = [],
        mentees = []

        users.forEach(user => {
            if(user.accountType === 'mentee') {
                mentees.push(user);
            } else if (user.accountType === 'mentor') {
                mentors.push(user);
            }
        });
        return res.json({mentees, mentors});
    })

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

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

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

1 голос
/ 13 июля 2019

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

Клиентская сторона вот так

let getQuery = (id,userType)=>{
$.get('/admin/' + id + '/userType/'+userType)
.done(data => {
    let dataTable = null;
    switch(userType){
        case "mentee":
        dataTable = $('#menteeTable');
        break;
        case "mentor":
        dataTable = $('#mentorTable');
        break;
        //.. you can add more selector for datatables but I wouldn't prefer this way you can generate "columns" property on server like "data" so meaning that you can just use one datatable object on client side
    }
    dataTable.DataTable( {
        data: data,
        "columns": [
            { "data": "username"},
            { "data": "status"}
        ]
    });
})
}

Я предпочитаю клиентскую сторону

let getQuery = (id,userType)=>{
$.get('/admin/' + id + '/userType/'+userType)
.done(data => {
    $('#dataTable').DataTable( {
        data: data.rows,
        "columns": data.columns
        ]
    });
})
}

Ответ сервера должен поддерживать {data: [{} ...], столбцы: [{} ....]} вот так в этом сценарии Примеры таблиц

На стороне сервера, как этот Маршрутизатор, всего один

router.get("/admin/:id/userType/:userType", getQueryFromDB);

Контроллер

getQueryFromDB(req, res, next){

let query = { accountType: req.params.userType, isAdmin: false };
Profile.find(query)
    .lean()
    .then(users => {
        return res.json(users);
    })
    .catch(err => {
        console.log(err)
    })
}

Итак, основное значение вашего вопроса для меня в том, что подопечные, наставники и т. Д. - это параметры, подобные «id»

. Убедитесь, что ваша аутентификация проверила, какие пользователи имеют доступ к данным userType для обоих кодов.Примеры моего и вашего кода, кто-то может получить ваши данные, просто изменив маршрут

Хороших выходных

0 голосов
/ 11 июля 2019

На заднем конце просто сделайте один динамический параметрический метод API.Вы можете передать наставника, ученика, администратора и т. д. в качестве роли. У вас должен быть какой-либо тип аутентификации и авторизации пользователя, чтобы проверить, может ли пользователь a видеть пользователей в роли B или нет.Что касается пользовательского интерфейса, то пользователь может выбрать одну страницу с раскрывающимся фильтром или добавить URL в закладки.Например, несколько URL / администратор / наставник и т. Д. Или один URL со строкой запроса и выпадающим списком ./user?role=mentor,/user?role=admin.На основе URL вы должны сделать контроллеры.Я обычно предпочитаю выпадать и получать данные (по умолчанию все наставники могут быть выбраны).Это специальное приглашение, подходящее для приглашений романтического характера (например, свидания или помолвки).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...