Стоит ли говорить, что нативные приложения зарекомендовали себя с гораздо лучшей стороны, когда соединение с интернетом было недоступно. Оффлайн-режим — очень важная вещь, и стоит думать о нем с самого начала работы над приложением, а не рассчитывать добавить его потом, когда будет время. Работая над сайтом Rareloop, мы с первого дня помнили об оффлайн-режиме. Мобильные клиенты FormAgent тоже были изначально спроектированы для работы в оффлайне, чтобы пользователь мог продолжать работу в отсутствие интернета и прозрачно синхронизироваться, когда связь появляется. В этой статье я описываю принципы и практики, которые, на мой взгляд, очень помогают разрабатывать такие приложения.
Обратите внимание! Я не рассматриваю вопросы кэширования ресурсов приложения — вы можете использовать App Cache или гибридное решение (вроде PhoneGap), это не принципиально [От переводчика: на Хабре есть подробная статья про особенности работы с Application Cache API]. Это руководство посвящено скорее тому, как спроектировать архитектуру веб-приложения для работы в оффлайн-режиме, а не тому, какие механизмы использовать для его реализации.
Базовые принципы
Максимально отвяжите приложение от сервера
Исторически большую часть работы над веб-страницей брал на себя сервер. Данные хранились в БД, доступ к ним осуществлялся через толстый слой кода на серверном языке врде PHP или Ruby, данные обрабатывались и рендерились в HTML с помощью шаблонов. Большинство современных фреймворков используют архитектуру MVC для разделения этих задач, но вся тяжёлая работа по-прежнему делается на сервере. Хранение, обработка и отображение информации требуют постоянной связи с сервером.
Подход offline first предполагает перемещение всего стека MVC на сторону клиента. На стороне сервера остаётся только лёгкий JSON API для доступа к БД. Благодаря этому серверный код становится намного меньше, проще, и его легче тестировать.
Джеймс Пирс тоже говорил об этом на Full Frontal (слайды), в несколько шутливой форме:
Никаких угловых скобок на линии — только фигурные!
Резюме:
- Убедитесь, что клиентское приложение способно обойтись без сервера, предоставляя минимальный функционал. В крайнем случае — хотя бы сообщение о том, что данные не доступны.
- Используйте JSON.
Создайте объект-обёртку для серверного API на стороне клиента
Не загрязняйте код приложения вызовами AJAX с вложенными колбэками. Создайте объект, который будет представлять функциональность сервера внутри приложения. Это способствует разделению кода и облегчает тестирование и отладку, позволяет использовать удобные заглушки на месте ещё не реализованных серверных функций. Внутри этот объект может использовать AJAX, но с точки зрения остального приложения не должно быть видно, как именно он связывается с сервером.
Резюме:
- Абстрагируйте JSON API в отдельном объекте.
- Не засоряйте код приложения вызовами AJAX.
Отвяжите обновление данных от хранилища данных
Не стоит поддаваться искушению просто запрашивать данные напрямую у объекта, абстрагирующего API сервера, и сразу использовать их для рендеринга шаблонов. Лучше создайте объект данных, который будет служить прокси между объектом API и остальным приложением. Этот объект данных будет отвечать за запросы обновлений данных и обрабатывать ситуации, когда связь обрывается — синхронизировать данные, изменённые во время работы в оффлайне.
Объект данных может опрашивать сервер на предмет наличия обновлений, когда пользователь нажмёт кнопку «обновить», или по таймеру, или по событию браузера "online
" — как угодно, а отсутствие прямых обращений к серверу позволяет легче управлять кэшированием данных.
Объект данных также должен отвечать за сериализацию и сохранение своего состояния в постоянном хранилище, в Local Storage или WebSQL/IndexedDB, и уметь восстанавливать эти данные.
Резюме:
- Используйте отдельный объект данных для хранения и синхронизации состояния.
- Вся работа с данными должна идти через это прокси-объект.
Пример
В качестве простого пример возьмём приложение для управления контактами. Сначала сделаем серверный API, который позволит нам получать сырые данные кнтактов. Предположим, что мы создали RESTful API, где URI /contacts
возвращает список всех записей контактов. В каждой записи есть поля id
, firstName
, lastName
и email
.
Затем напишем объект-обёртку над этим API:
var API = function() { }; API.prototype.getContacts = function(success, failure) { var win = function(data) { if(success) success(data); }; var fail = function() { if(failure) failure() }; $.ajax('http://myserver.com/contacts', { success: win, failure: fail }); };
Теперь нам нужен объект данных, который станет интерфейсом между нашим приложением и хранилищем данных. Он может выглядеть так:
var Data = function() { this.api = new API(); this.contacts = this.readFromStorage(); this.indexData(); }; Data.prototype.indexData = function() { // Выполняем индексирование (например, по email) }; /* -- API апдейтов-- */ Data.prototype.updateFromServer = function(callback) { var _this = this; var win = function(data) { _this.contacts = data; _this.indexData(); if(callback) callback(); }; var fail = function() { if(callback) callback(); }; this.api.getContacts(win, fail); }; /* -- Сериализация данных -- */ Data.prototype.readFromStorage = function() { var c = JSON.parse(window.localStorage.getItem('appData')); // позаботимся о результате по умолчанию return c || []; }; Data.prototype.writeToStorage = function() { window.localStorage.setItem('appData', JSON.stringify(this.contacts)); }; /* -- Стандартные геттеры/сеттеры -- */ Data.prototype.getContacts = function() { return this.contacts; }; // Запрос данных, специфичный для приложения Data.prototype.getContactWithEmail = function(email) { // Поиск контактов с помощью механизмов индексирования... return contact; };
Теперь у нас есть объект данных, через который мы можем запросить обновление у серверного API, но по умолчанию он будет возвращать данные, сохранённые локально. В остальном приложении можно использовать вот такой код:
var App = function() { this.data = new Data(); this.template = '...'; this.render(); this.setupListeners(); }; App.prototype.render = function() { // Используем this.template и this.data.getContacts() для рендеринга HTML return html; } App.prototype.setupListeners = function() { var _this = this; // Обновляем данные с сервера $('button.refresh').on('click', function(event) { _this.refresh(); }); }; App.prototype.refresh = function () { _this.showLoadingSpinner(); _this.data.updateFromServer(function() { // Данные пришли с сервера _this.render(); _this.hideLoadingSpinner(); }); }; App.prototype.showLoadingSpinner = function() { // показываем крутилку }; App.prototype.hideLoadingSpinner = function() { // прячем крутилку };
Это очень простой пример, в реальном приложении, вы, вероятно, захотите реализовать объект данных, как singleton, но для илююстрации того, как надо структурировать код для работы в режиме оффлайн этого достаточно.
ссылка на оригинал статьи http://habrahabr.ru/post/160477/
Добавить комментарий