[Разработка веб-сайтов] Кешируем CRUD в IndexedDB
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Допустим, у нас есть бекенд, который умеет хранить какие-то сущности. И у него есть апи для создания, чтения, изменения и удаления этих сущностей, сокращенно CRUD. Но апи на сервере, а пользователь забрался куда-то глубоко и половина запросов валится по таймауту. Не хотелось бы показывать бесконечный прелоадер и вообще блокировать действия пользователя. Offline first предполагает загрузку приложения из кеша, так может быть и данные брать оттуда?Предлагается хранить все данные в IndexedDB (допустим, что их не очень много), и по возможности синхронизировать с сервером. Возникает несколько проблем:
- Если Id сущности генерится на сервере, в базе, то как жить без Id, пока сервер недоступен?
- При синхронизации с сервером, как отличать сущности созданные на клиенте от удаленных на сервере другим пользователем?
- Как разрешать конфликты?
ИдентификацияИдентификатор нужен, так что будем его создавать сами. Для этого прекрасно подходит GUID или `+new Date()` с некоторыми оговорками. Только когда придет ответ от сервера с настоящим Id, надо везде его заменить. Если на эту свежесозданную сущность уже ссылаются другие, то эти ссылки тоже надо поправить.СинхронизацияИзобретать велосипед не будем, посмотрим на репликацию баз данных. Смотреть на нее можно бесконечно, как на пожар, но вкратце, один из вариантов выглядит так: помимо сохранения сущности в IndexedDB, будем писать лог изменений: [time, 'update', Id=3, Name='Иван'], [time, 'create', Name='Иван', Surname='Петров'], [time, 'delete', Id=3]...При получении с сервера свежих данных, мы можем сравнить их с существующими и вычислить аналогичный лог изменений на сервере. После этого объединить логи и отправить необходимые изменения на сервер, если интернет еще не пропал, и только после этого записывать изменения в IndexedDB. И не забыть обновить Id. КонфликтыКонфликт - это не спор между двумя пользователями, чья точка зрения правильная, и поочередное исправление одной и той же записи до посинения. А вот ситуация, когда пользователи довольны и видят каждый свою версию - конфликт, а конкретно неконсистентность. Непрерывной консистентности в веб приложениях достичь несложно - при каждом изменении блокировать всех клиентов, кого это изменение касается, пока они все не подтвердят получение. Это никому не нравится, поэтому приходится идти на компромис: ладно, пусть иногда пользователи видят разное, но если все замрут и перестанут вносить изменения, то через некоторое время у всех будет одно и то же. Для этого придумали термин Eventual Consistency. Оказалось, что ее можно достичь незаметно для пользователя, но не так просто. Можно использовать Operational Transformations (OT) или Conflict-free Replicated Data Types (CRDT) но для них придется довольно радикально поменять формат обмена данных с сервером. Если это невозможно, то можно на коленке сделать CRDT на минималках: добавить в сущность поле UpdatedAt и записывать в него время последнего изменения. Это не избавит от всех конфликтов, но снизит их количество на порядок.Итак, при объединении двух логов группируем их по Id сущности и дальше работаем в каждой группе отдельно. Если в одном из логов есть операция удаления, то оставляем только ее. Пользователь, удаливший запись наверняка имел на это веские основания и не хотел бы, чтобы запись вдруг возродилась. Никто не любит зомби кроме зомби. Если в одном из логов есть операция создания сущности, то в другом логе должно быть пусто, ведь Id уникальный, ага. С изменениями немного сложнее - нужно посмотреть на время последнего изменения сущности в каждом из логов. Сравнить. И выбрать тот лог, в который изменение пришло позднее. Last write win. Проверим Eventual Consistency: если все пользователи перестанут вносить изменения и подключатся к интернету, у всех будут сущности последней версии. Отлично.
function mergeLogs(left, right){
const ids = new Set([
...left.map(x => x.id),
...right.map(x => x.id)
]);
return [...ids].map(id => mergeIdLogs(
left.filter(x => x.id == id),
right.filter(x => x.id ==id)
)).reduce((a,b) => ({
left: [...a.left, ...b.left],
right: [...a.right, ...b.right]
}), {left: [], right: []});
}
function mergeIdLogs(left,right){
const isWin = log => log.some(x => ['create','delete'].includes(x.type));
const getMaxUpdate = log => Math.max(...log.map(x => +x.updatedAt));
if (isWin(left))
return {left: [], right: left};
if (isWin(right))
return {left: right, right: []};
if (getMaxUpdate(left) > getMaxUpdate(right))
return {left: [], right: left};
else
return {left: right, right: []};
}
ЭпилогРеализации не будет, потому что в каждом конкретном случае есть свой дьявол в деталях, да и реализовывать тут по большому счету нечего - генерацию идентификатора и запись в indexedDB. Конечно, CRDT или OT будут лучше, но если нужно сделать быстро, а на бекенд не пускают, то сгодится и это поделие.
===========
Источник:
habr.com
===========
Похожие новости:
- [Разработка веб-сайтов, JavaScript, VueJS] VueUse — обязательная библиотека для Vue 3
- [Разработка веб-сайтов, CSS, HTML] Как обеспечить глассморфизм с помощью HTML и CSS (перевод)
- [Разработка веб-сайтов, JavaScript, Совершенный код, HTML] Целительная сила JavaScript (перевод)
- [Разработка веб-сайтов, Open source, Программирование, .NET] Блеск и нищета open source платформы RawCMS. Причины провала и выводы (перевод)
- [Разработка веб-сайтов, PHP, Проектирование и рефакторинг] Ты приходишь в проект, а там легаси…
- [Firefox, Google Chrome, Расширения для браузеров, Браузеры] Почему uBlock Origin лучше работает в Firefox
- [Разработка веб-сайтов, JavaScript, Анализ и проектирование систем, Проектирование и рефакторинг, Лайфхаки для гиков] Трёхпроходный алгоритм рефакторинга Front End
- [Разработка веб-сайтов, Управление разработкой, Управление проектами, Управление персоналом] Записки юного TeamLead: Ошибки, о которых не стыдно говорить
- [Разработка веб-сайтов, CSS, Программирование, Java] От студента до учителя: как разобраться в веб-разработке, если это не твой профиль
- [Разработка веб-сайтов, Управление проектами, Управление медиа] Google и Ростуризм запустили проект «Раскуси Россию» о русской кухне
Теги для поиска: #_razrabotka_vebsajtov (Разработка веб-сайтов), #_crud, #_indexeddb, #_keshirovanie (кеширование), #_offlinefirst, #_razrabotka_vebsajtov (
Разработка веб-сайтов
)
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 18:45
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Допустим, у нас есть бекенд, который умеет хранить какие-то сущности. И у него есть апи для создания, чтения, изменения и удаления этих сущностей, сокращенно CRUD. Но апи на сервере, а пользователь забрался куда-то глубоко и половина запросов валится по таймауту. Не хотелось бы показывать бесконечный прелоадер и вообще блокировать действия пользователя. Offline first предполагает загрузку приложения из кеша, так может быть и данные брать оттуда?Предлагается хранить все данные в IndexedDB (допустим, что их не очень много), и по возможности синхронизировать с сервером. Возникает несколько проблем:
function mergeLogs(left, right){
const ids = new Set([ ...left.map(x => x.id), ...right.map(x => x.id) ]); return [...ids].map(id => mergeIdLogs( left.filter(x => x.id == id), right.filter(x => x.id ==id) )).reduce((a,b) => ({ left: [...a.left, ...b.left], right: [...a.right, ...b.right] }), {left: [], right: []}); } function mergeIdLogs(left,right){ const isWin = log => log.some(x => ['create','delete'].includes(x.type)); const getMaxUpdate = log => Math.max(...log.map(x => +x.updatedAt)); if (isWin(left)) return {left: [], right: left}; if (isWin(right)) return {left: right, right: []}; if (getMaxUpdate(left) > getMaxUpdate(right)) return {left: [], right: left}; else return {left: right, right: []}; } =========== Источник: habr.com =========== Похожие новости:
Разработка веб-сайтов ) |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 18:45
Часовой пояс: UTC + 5