Что происходит, если вдруг на клиенте пропадает интернет? Возможно, на долю секунды, а может быть, на более ощутимый период? Все мы как пользователи сталкиваемся с нестабильным сигналом, плавающим качеством связи. Иногда это неважно, ведь хочется посмотреть какое-то весёлое видео, иногда от этого может зависеть очень многое – представьте, что вам срочно надо купить билет на поезд или оплатить тот же самый интернет.

Те сферы, где очень важен конечный пользователь – например, СМИ, говорят, что уже 13% пользователей уходят, если ваш сайт открывается больше четырёх секунд, не разбираясь в причинах. А теперь давайте представим такого пользователя, который еще пробует отправить комментарий, и он постоянно «отваливается» из-за проблем со связью?

Процент уходов и отказов будет заведомо больше. Как этого избежать? Что можно сделать в ситуации, когда данные должны быть гарантированно отправлены как от клиента, так и со стороны сервера?

На этот и другие вопросы отвечает Андрей Ситник – автор PostCSS и Автопрефиксера, ведущий фронтендер в «Злых Марсианах».









– Почему мы вообще говорим о проблемах связи? Разве это не вопрос, исключительно связанный с физическими/сетевыми возможностями?

– OSI тут не совсем в тему. Logux заменяет REST и AJAX. То есть это чисто прикладной уровень. Logux решает следующие проблемы:

  1. Сейчас требуется много кода для простых запросов.
  2. Живое обновление данных с сервера писать на порядок сложнее.
  3. Хорошую поддержку оффлайн вообще ад написать. Но оффлайн нужен всегда, так как у нас всегда есть «500 мс оффлайн» — эти постоянные крутилки на кнопках.

Для этого Logux как бы создаёт особый виртуальный канал событий между клиентом и сервером.

Клиент может положить в этот канал события — Logux сам их отправит, когда можно будет. И то же самое на сервер. Давай разберём это на техническом уровне пошагово:

Разбор полетов


1. На клиенте это будет библиотека с API один-в-один как у Redux (к Реакт не привязана, есть и более низкоуровневый API). Ты точно так же создаёшь action. Но у некоторых action можешь выставить ключ sync: true — тогда Logux доставит их сам на сервер. Эта библиотека держит веб-сокет, посылает пинг, проверяя, что связь есть.

– Так, а чем хорошо то, что эта библиотека не привязана к Реакту?

Не все разрабатывают на Реакте (и это очень хорошо для разнообразия среды). Кто-то может использовать Vue.js, Angular. Или просто иметь JS-приложение, где нет HTML, так что Реакт будет не так нужен. Идём дальше:

2. Дополнительная библиотека, которая при потере связи покажет спец. бейджик «Нет связи» вверху страницы и изменит фавикон. Если в канале на отправку окажутся события, а связи не будет — она тоже скажет пользователю, что не все данные сохранились.

– Зачем пользователю это знать? Для какого формата взаимодействия это может быть критически важным?

– Если пользователь передал нам какие-то данные, то мы обязаны его уведомить, что они не сохранились. Я бы сказал, что это поведение должно быть по умолчанию. Библиотека опциональна во многом, чтобы написать самому такое уведомление и лучше встроить его в дизайн. Хотя есть случаи, когда можно не показывать проблемы со связью — например, если Logux используется для сбора действий пользователя для аналитики.

Следующий шаг.

3. Когда связь появится, Logux-клиент и сервер отправят друг другу те события, которые добавились за время отсутствия связи.

– Понятно, логично, сколько действий может быть в очереди?

– Обычно событий 10-20. Но у нас нет жёстких ограничений — сколько поместится в памяти клиента и сервера.

– Порядок выполнения этих событий?

– Порядок событий контролируется очень строго. Тут же проблема не только в порядке событий одного пользователя. Но и при работе нескольких пользователей — порядок всех их событий должен быть одинаковым на всех системах (чтобы итоговое состояние было одинаковым). Поэтому в Logux каждому событию присваивается время создания. Оно довольно хитрое — например, учитывается и разница времени между клиентом и сервера.

– Не будет ли так, что какие-то события уже не надо выполнять? Взаимная проверка зависимости действий?

– Клиент может чистить лог от уже ненужных событий. Это уже определяется бизнес-логикой.

Следующий шаг: пользователь может закрыть браузер — неотправленные события сохранятся в localStorage. Но перед попыткой закрытия библиотека попросит пользователя сначала выйти в интернет, так как есть неотправленные данные.

– Опять возвращаемся к вопросу ёмкости и важности, и что произойдёт, если пользователь принудительно закроет браузер?

– Ничего страшного. Как только пользователь снова вернётся на сайт — данные сами отправятся.

Теперь все оставшиеся шаги:

5. Для написания Logux-сервера есть специальный серверный фреймворк. То есть это как express.js. Ты сам описываешь, как сервер реагирует на то или иное событие с клиента. При этом логика чуть хитрее — то, что событие пришло сейчас, не значит, что оно было создано сейчас. Поэтому у каждого события есть время создания. При соединении клиент и сервер определяют разницу времени и синхронизируют часы (и время создания старых событий).

6. Фреймворк для сервера пока на node.js. Потом сделаем для Elixir и Go. Но можно использовать и Ruby, Python и PHP — просто поставить node.js сервер как прокси, чтобы держать сокет. А уже этот прокси-сервер будет посылать старый REST-запрос в ruby-сервер.

7. Поскольку у нас есть этот лог событий и точное время, поверх него можно сделать CRDT как в Swarm.js — и автоматически разрешать конфликты во многих простых случаях.

8. Вообще, в большинстве случаев можно не рисовать крутилку — клиент может сразу отрисовывать, будто событие уже выполнилось (например, комментарий отправлен), Logux сам покажет пользователю, что данные не сохранились на сервер. Само собой, будут случаи, где так не получится — например, оплата. Там можно делать старую логику с крутилкой.

Logux и альтернативы


– Спасибо, а чем Logux тогда лучше уже существующих решений?

– Есть Relay/GraphQL — они сокращают количество кода при запросе данных. Но изменение самих данных тут тоже реализовано не очень просто. Живое обновление решено плохо. Оффлайн тоже не проработан.

Есть изоморфные базы данных — они работают на клиенте и на сервере и синхронизируют данные между собой. Например, CouchDB и Firebase. Там хорошо решено живое обновление данных. Готовый CRDT сейчас мало у кого есть, но, в целом, реализовать его можно. Но их довольно сложно расширять, многие разработчики боятся такого странного подхода к синхронизации, поэтому такие базы данных и не стали индустриальным стандартом.

Есть ещё CRDT-библиотеки — например, Swarm.js. Logux копирует много идей у Swarm.js. Но Swarm.js сложно подружить с Реактом и Редаксом. Также сложно передавать в нём не-CRDT операции.

– В чём сильные и слабые стороны?

– Меньше кода, живое обновление данных из коробки, легко сделать приложение для работы в оффлайн. И всё это со знакомой семантикой Редакса.

Главная слабая сторона — надо запускать дополнительный сервер. Но это есть у всех таких решений. Плюс этот сервер можно сделать прокси-сервером и всю логику продолжать хранить в PHP или Ruby on Rails.

– Откуда вообще появилась потребность в этом выделенном решении?

– На Амплифере нужно было показывать живую статистику публикаций и обновлять страницу с ошибками сразу же при появлении проблемы. И мы поняли, что хорошего решения не нашлось. Попробовали внедрить Swarm.js, но легко развернуть его не получилось. Так что я начал думать, как подружить Swarm.js и Редакс. В Питере в это время как раз был Дэн Абрамов, и в разговоре с ним родилась идея отдельной библиотеки.

– Имеет ли это шансы на то, чтобы стать индустриальным стандартом?

– Я стараюсь сделать Logux, как универсальное решение для всех веб-приложений. Но, мне кажется, в 2017 мы увидим много ещё попыток переделать AJAX/REST — сейчас это главная проблема веб-разработки, на мой взгляд. Кто победит в этой борьбе, узнаем только в 2018.

Примеры из жизни


– Есть ли 1-2 интересных практических кейса?

– Да, даже TODO MVC — вам же хочется продолжить работать со списком задач даже, если связи нет. Или если вы добавили задание на компьютере, то хотите, чтобы оно появилось тут же и на телефоне, без перезагрузки страниц.

Или комментарии — живое обновление, как в Фейсбуке или ВК, полезно для вовлечения людей.

Есть ещё одно преимущество Logux для любого сайта — это «оптимистичный интерфейс» (о нём как раз рассказывал другой спикер HolyJS). Сейчас при каждом действии мы показываем пользователю «крутилку». Каждое сохранение блокирует интерфейс и нарушает поток пользователя. С помощью Logux на порядок проще делать оптимистичный интерфейс, где сохранение будет происходить в фоне, отвлекая пользователя, только если ошибка действительно произошла. Например, так работает Google Inbox. Любое веб-приложение выиграет от оптимистичного интерфейса, так как пользователи всегда любят быстрые интерфейсы.

– Большое спасибо за интервью, и до встречи на конференции.



Конечно, в интервью удалось обсудить только общий взгляд на библиотеку и ее философию, на HolyJS Андрей выступит с подробным часовым докладом о разработке и работе с Logux. Кроме того, можно будет сразу посмотреть доклады, посвященные и другим обсуждаемым технологиям:
Поделиться с друзьями
-->

Комментарии (12)


  1. eugef
    02.12.2016 18:49
    +1

    А как Logus подружить с Service Workers?

    Так как SW и служат для решения проблемы оффлайна, то логично всю логику Логуса исполнять там.


    1. Iskin
      02.12.2016 19:09
      +1

      Есть две разные задачи в поддержке оффлайна:

      1. Кеширование HTML/JS/CSS-файлов приложение.
      2. Организация работы с данными приложения без связи.

      Эти задачи очень разные. Сервис-воркеры созданы для решения только первой задачи. Для решения второй задачи они не созданы.

      Можно конечно перенести работу с логом в сервис-воркер, но это только усложнит код и уменьшит поддержку браузеров.


      1. eugef
        02.12.2016 23:43

        Если запросы к API идут по HTTP(s), то в принципе можно кешировать их на уровне Service Workers.
        Когда связи нет, то выполнять запросы отложенно а клиенту возращать, например, что все ок.
        Как бонус — это прозрачно для приложения. Из минусов — не получится перехватить WebSockets, ну и меньше поддержка браузером


        1. Iskin
          03.12.2016 01:50

          Да, так можно — но смысла мало. На клиенте всё равно больше кода для AJAX-запроса. Непонятно как понятно и прозрачно интегрировать получение таких же событий с сервера.

          Плюс запуск и обновление сервис-воркера — хитрая задача. В спеке есть не очень очевидные моменты (например, с адресом воркер-файла).

          Ну и преимуществ не будет никаких (в фоне воркер сокет не может держать).


  1. 23derevo
    02.12.2016 21:56
    +4

    Каким образом гарантируется доставка данных от клиента серверу и наоборот? Как бороться с повторной отправкой данных в случае, если сломались клиент, сервер или интернет между ними? Через идемпотентные операции?


    1. Iskin
      02.12.2016 22:07
      +2

      Можно сказать, что и идемпотентные. Каждое событие имеет уникальный ID (он же время создания). Когда событие добавляется в лог, то проверяется, нет ли уже такого ID. Так что всё проблема повторной доставки сильно упрощается.


      1. 23derevo
        02.12.2016 22:11
        +1

        ну да, это и есть идемпотентность в чистом виде. Просто синтетическая — за счет синтетического ID события.


  1. Vestild
    02.12.2016 23:44
    +1

    Я правильно понимаю, что Logux работает на WebSocket?
    Это не создаёт проблем? Старые брауззеры, прокси?


    1. Iskin
      03.12.2016 01:53

      Прокси большая проблема для обычных веб-сокетов, но если перейти на сокеты поверх SSL (WSS), то большинство проблем решится. Мы стараемся везде в интерфейсе форсить WSS.

      Сокеты — IE 10+, это нормально. Логакс ориентирован на SPA, а для IE <10 SPA создают редко.

      Но, сам Логакс не ограничивает как передаются данные. Просто стандартный драйвер использует веб-сокеты. Можно заменить на AJAX + keep-alive. Или внедрить протокол Логакса внутрь какого-то нибудь корпоративного XML.


  1. bioforge
    02.12.2016 23:46
    +1

    Может задам тупой вопрос, но почему бы в БД и на клиенте не хранить дату последнего изменения каждого поля?
    При синхронизации сверять даты и обновлять поля с датой меньше присланной. Побеждает тот, кто последний менял данные.


    1. Iskin
      03.12.2016 01:55

      Правильно мыслите — собственно в CRDT примерно так и сделано.

      Но разделение по полям не идеальный способ. Есть типы данных, которые так мержить не получится — например, счётчики или длинный текст (где хочется параллельное редактирование). Для этого и придумали более абстрактный способ — хранить действие.

      Но сборщик мусора в логе и так удаляет все старые значения поля (если это CRDT Map). Так что хранится очень близко к вашей идей. Просто чуть универсальнее для других типов.


    1. Iskin
      03.12.2016 01:57

      Кстати, как хранить конечные данные на сервере решаете вы. Так что даты полей можете не хранить, а использовать обычное хранение. Или хранить именно с датами, чтобы мерж работал, даже если клиента не было онлайн более месяца.

      В общем Логакс никак не ограничивает БД на сервер. Свой лог он хранит в отдельном файле и этот лог лишь дополнительная вещь. Его можно смело чистить через неделю.