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

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

В этом посте мы собираемся показать, как использовать Firebase Realtime Database, чтобы сохранить этот очищенный ответ, используя его в качестве кеша. Это предотвратит слишком частый вызов исходного серверного API, а также ненужные преобразования.

Зачем вообще использовать кеш?

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

Что кэшировать и как долго будет зависеть от нескольких факторов, уникальных для вашего случая:

  • Бизнес-правила: возможно, ваша серверная часть производит новые данные только в определенное известное время. Например, в некоторых газетах есть только утренние, полуденные и вечерние выпуски. Или, может быть, вы знаете, что ваш бэкэнд генерирует только обновленные данные каждый час.
  • Затраты: сбор данных, которые запрашивает мобильный клиент, может оказаться дорогостоящей операцией для вашей серверной части.
  • Время: это связано с предыдущим моментом. Если создание запрошенных данных стоит дорого, то это наверняка будет медленным. И вы не хотите тратить время пользователей на ожидание.

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

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

Сохранять очищенную модель в Firebase

Продолжая пример, представленный в первой части, мы собираемся сохранить очищенную ленту от The Guardian. Корм после очистки выглядит так:

В этом примере нужно учитывать три вещи:

  1. Сохранение очищенных данных в базе данных Firebase нашего проекта.
  2. Проверка наличия действительных кэшированных данных перед загрузкой новых.
  3. Политика аннулирования кеша: определение того, когда этот кеш больше не действителен.

1. Сохранение преобразованных данных в базу данных Firebase.

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

Давайте начнем с преобразования этого кода во что-то более многообещающее.

exports.fetchGuardian = functions.https.onRequest((req, res) => {
    return request(URL_THE_GUARDIAN)
        .then(data => cleanUp(data))
        .then(items => response(res, items, 201))
});
function request(url) {
    return new Promise(function (fulfill, reject) {
        client.get(url, function (data, response) {
            fulfill(data)
        })
    })
}
function response(res, items, code) {
    return Promise.resolve(res.status(code)
        .type('application / json')
        .send(items))
}

Этот код эквивалентен тому, что был в предыдущем посте, но с помощью Promises поток легче понять и изменить.

Я опускаю здесь функцию cleanUp, поскольку она не имеет отношения к делу, но посмотрите предыдущий пост, если он вам интересен.

Первое, что мы собираемся сделать, это сохранить items в базе данных Firebase, прежде чем возвращать их клиенту. Это можно сделать, изменив предыдущий код и добавив вызов save(items) в цепочке обещаний:

exports.fetchGuardian = functions.https.onRequest((req, res) => {
    return request(URL_THE_GUARDIAN)
        .then(data => cleanUp(data))
        .then(items => save(items))
        .then(items => response(res, items, 201))
});
function save(items) {
    return admin.database().ref('/feed/guardian')
        .set({ items: items })
        .then(() => {
            return Promise.resolve(items);
        })
}

С помощью admin.database().ref('feed/guardian') мы получаем ссылку на путь в нашей базе данных.
Затем set({items: items}) подталкивает массив items к этому пути в базе данных с помощью ключа "items" и возвращает пустое обещание.

Наконец, когда обещание выполнено (то есть процесс записи завершен), мы возвращаем новое обещание с исходным массивом items, чтобы продолжить цепочку.

В Firebase это сгенерирует:

2. Проверка кешированных данных перед загрузкой новых

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

Следующим шагом будет проверка наличия сохраненных данных в базе данных Firebase перед отправкой HTTP-запроса на наш сервер.

exports.fetchGuardian = functions.https.onRequest((req, res) => {
    return admin.database().ref('/feed/guardian')
        .once('value')
        .then(snapshot => {
            if (isCacheValid(snapshot)) {
                return response(res, snapshot.val(), 200)
            } else {
                return request(URL_THE_GUARDIAN)
                    .then(data => cleanUp(data))
                    .then(items => save(items))
                    .then(items => response(res, items, 201))
            }
        })
});
function isCacheValid(snapshot) {
    return (snapshot.exists())
}

Что изменилось с шага 1, так это то, что мы читаем из базы данных перед загрузкой фида.

Как мы уже знаем, admin.database().ref('feed/guardian') - это путь в базе данных. С .once('value') мы считываем значения по этому пути один раз и возвращаем с ними Promise.

Что мы делаем потом, просто:

  • Если данные, прочитанные из /feed/guardian, действительны, мы возвращаем их (в этот момент действительные означает просто существующие)
  • Если данные недействительны (они не существуют), мы делаем то же самое, что и на шаге 1: считываем исходный канал, сохраняем его в базе данных Firebase и возвращаемся.

3. Политика аннулирования кеша.

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

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

function save(items) {
    return admin.database().ref('/feed/guardian')
        .set({
            date: new Date(Date.now()).toISOString(),
            items: items
        })
        .then(() => {
            return Promise.resolve(items);
        })
}

Это создаст новое поле в нашей базе данных:

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

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

function isCacheValid(snapshot) {
    return (
        snapshot.exists() &&
        elapsed(snapshot.val().date) < ONE_HOUR
    )
}
function elapsed(date) {
    const then = new Date(date)
    const now = new Date(Date.now())
    return now.getTime() — then.getTime()
}

Все вместе

Код также доступен здесь как основной фрагмент

Будьте на связи!

Это вторая часть серии из трех статей о Firebase.

В последней части мы узнаем, как использовать Google Cloud Natural Language API из нашей облачной функции Firebase для улучшения ответа серверной части, например, для добавления анализа настроений.

— -

Найдите меня в Twitter @lgvalle, я бы хотел поговорить об Android, Firebase и облачных функциях.