Что делать с утратившими актуальность данными в localstorage при обновлении версии приложения?

Есть приложение на Vue.js, оно хранит свое состояние и пользовательские настройки в виде сериализованного объекта в localStorage примерно таким образом:

export default {
  data() {
    return {
      config: {
                someKey1: "someValue",
                someKey2: "someValue2",
                somekey3: ["one", "two", "three"],
                //...
              },
      appname: "myApp",
    };
  },
    mounted() { //при загрузке
        let savedConfig = localStorage.getItem(this.appname . '_config');
        if (savedConfig !== null) {
            this.config = JSON.parse(savedConfig);
        }
    },
    updated() { //при изменении состояния
        let savingConfig = JSON.stringify(this.config);
        localStorage.setItem(this.appname . '_config', savingConfig);
    },
    //Проверки, валидации и весь не относящийся к вопросу код опущен
};

И это работает.

Но в случае мажорного обновления приложения (добавления/изменения логики и т.д.) объект config может сильно поменяется. Тогда лежащая у пользователя в браузере копия станет неактуальна.

Как грамотно заложить условие того, что поменялась версия приложения и как пересобрать в этом случае этот объект на клиенте в идеале с сохранением всех/большинства настроек сделанных пользователем? Есть ли какие-то устоявшиеся подходы по этому поводу?


Ответы (1 шт):

Автор решения: Total Pusher

Я рассуждаю так:

  • вначале необходимо продумать структуру конфигурации (модуль/аспект/домен/модель - есть разные названия для этого)
  • предусмотреть версионирование
  • localstorage - это key-value хранилище, запрос WHERE domain="user" AND version="1.0.0" написать нельзя. Нет типизации, значение всегда string
  • если какая-то настройка переезжает из одного места в другое, нужна миграция
  • нет смысла поддерживать 100 миграций, достаточно ~3 последних, кто не успел обновиться - получает дефолтный конфиг

Теперь как это можно сделать

Структура

Допустим, нужно хранить данные о пользователе (user).

Получить имя пользователя можно так:

localStorage.getItem('v1:user:personal_data:name')

Тут есть версия (я для упрощения не стал делать семантическое версионирование 1.0.0.0-..., просто v1), домен пользователь user, аспект/модель персональные данные personal_data и собственно имя.

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

Со структурой разобрались, далее будем эволюционировать код.

Фасад для localstorage

Писать localStorage.getItem... не удобно. Поэтому делаем фасад. Получится так:

settings.get("user", "personal_data", "name"). А может быть, используем dot notation: user.personal_data.name.

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

Как раз JSON.parse и JSON.stringify. Во Vue для этого подходит Vuex, он сюда очень хорошо вписывается, я бы использовал именно его.

хранит свое состояние и пользовательские настройки в виде сериализованного объекта

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

Версионирование и миграция

Допустим, name в новом релизе v2 теперь называется firstName.

В скрипте установлена переменная releaseVersion = 'v2', а в localStorage userVersion = 'v1'.

Скрипт видит, что версия отличается, и выполняет миграцию:

let name = settings.get('v1:user:personal_data:name')
settings.set('v2:user:personal_data:firstName', name)
settings.remove('v1:user:personal_data:name')
// в конце делаем userVersion = releaseVersion

На этом этапе рекомендую использовать TDD (разработку через тесты). Первый тест должен проверять, что имя было перенесено.

После этого начинаем усложнять логику:

  • если значения нет, используем какое-то дефолтное значение
  • вместо простыней с названиями переменных - используем маппинг "user:personal_data:name": "user:personal_data:firstName", ...
  • предусматриваем колбек, если простым маппингом нельзя перенести (например, делаем верхний регистр в имени)
  • делаем цепочки миграций v1 => v2 => v3
  • если вы дошли до этого этапа, у вас получилась магия

Без TDD реализовать это практически невозможно, запутаетесь и плюнете.

PS

Также я посмотрел готовые JS-пакеты, но ничего подходящего не нашел.

Если у вас legacy-код, сделайте фасад, который будет работать как с legacy, так и с modern реализациями. Когда от legacy избавитесь, фасад можно грохнуть.

→ Ссылка