«Offline first» подход к созданию веб-приложений

В этом году на конференции Full Frontal, оффлайн-приложения были популярной темой. Пол Кинлан сделал отличный доклад «Строим веб-приложения будущего. Завтра, сегодня и вчера» (вот его слайды), в котором он сравнивал ощущения пользователей от работы с 50 популярными мобильными приложениями для iOS и Android с ощущениями от веб-сайтов и приложений.

Стоит ли говорить, что нативные приложения зарекомендовали себя с гораздо лучшей стороны, когда соединение с интернетом было недоступно. Оффлайн-режим — очень важная вещь, и стоит думать о нем с самого начала работы над приложением, а не рассчитывать добавить его потом, когда будет время. Работая над сайтом Rareloop, мы с первого дня помнили об оффлайн-режиме. Мобильные клиенты FormAgent тоже были изначально спроектированы для работы в оффлайне, чтобы пользователь мог продолжать работу в отсутствие интернета и прозрачно синхронизироваться, когда связь появляется. В этой статье я описываю принципы и практики, которые, на мой взгляд, очень помогают разрабатывать такие приложения.

Обратите внимание! Я не рассматриваю вопросы кэширования ресурсов приложения — вы можете использовать App Cache или гибридное решение (вроде PhoneGap), это не принципиально [От переводчика: на Хабре есть подробная статья про особенности работы с Application Cache API]. Это руководство посвящено скорее тому, как спроектировать архитектуру веб-приложения для работы в оффлайн-режиме, а не тому, какие механизмы использовать для его реализации.

Базовые принципы

Максимально отвяжите приложение от сервера

Исторически большую часть работы над веб-страницей брал на себя сервер. Данные хранились в БД, доступ к ним осуществлялся через толстый слой кода на серверном языке врде PHP или Ruby, данные обрабатывались и рендерились в HTML с помощью шаблонов. Большинство современных фреймворков используют архитектуру MVC для разделения этих задач, но вся тяжёлая работа по-прежнему делается на сервере. Хранение, обработка и отображение информации требуют постоянной связи с сервером.

Подход offline first предполагает перемещение всего стека MVC на сторону клиента. На стороне сервера остаётся только лёгкий JSON API для доступа к БД. Благодаря этому серверный код становится намного меньше, проще, и его легче тестировать.

Джеймс Пирс тоже говорил об этом на Full Frontal (слайды), в несколько шутливой форме:

Никаких угловых скобок на линии — только фигурные!

Резюме:

  1. Убедитесь, что клиентское приложение способно обойтись без сервера, предоставляя минимальный функционал. В крайнем случае — хотя бы сообщение о том, что данные не доступны.
  2. Используйте JSON.
Создайте объект-обёртку для серверного API на стороне клиента

Не загрязняйте код приложения вызовами AJAX с вложенными колбэками. Создайте объект, который будет представлять функциональность сервера внутри приложения. Это способствует разделению кода и облегчает тестирование и отладку, позволяет использовать удобные заглушки на месте ещё не реализованных серверных функций. Внутри этот объект может использовать AJAX, но с точки зрения остального приложения не должно быть видно, как именно он связывается с сервером.

Резюме:

  1. Абстрагируйте JSON API в отдельном объекте.
  2. Не засоряйте код приложения вызовами AJAX.

Отвяжите обновление данных от хранилища данных

Не стоит поддаваться искушению просто запрашивать данные напрямую у объекта, абстрагирующего API сервера, и сразу использовать их для рендеринга шаблонов. Лучше создайте объект данных, который будет служить прокси между объектом API и остальным приложением. Этот объект данных будет отвечать за запросы обновлений данных и обрабатывать ситуации, когда связь обрывается — синхронизировать данные, изменённые во время работы в оффлайне.

Объект данных может опрашивать сервер на предмет наличия обновлений, когда пользователь нажмёт кнопку «обновить», или по таймеру, или по событию браузера "online" — как угодно, а отсутствие прямых обращений к серверу позволяет легче управлять кэшированием данных.

Объект данных также должен отвечать за сериализацию и сохранение своего состояния в постоянном хранилище, в Local Storage или WebSQL/IndexedDB, и уметь восстанавливать эти данные.

Резюме:

  1. Используйте отдельный объект данных для хранения и синхронизации состояния.
  2. Вся работа с данными должна идти через это прокси-объект.

Пример

В качестве простого пример возьмём приложение для управления контактами. Сначала сделаем серверный 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/

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *