Что делать с утратившими актуальность данными в 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 шт):
Я рассуждаю так:
- вначале необходимо продумать структуру конфигурации (модуль/аспект/домен/модель - есть разные названия для этого)
- предусмотреть версионирование
- 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 избавитесь, фасад можно грохнуть.