- PVSM.RU - https://www.pvsm.ru -

Amelisa. Оффлайн и реалтайм движок для React и Mongo

Amelisa. Оффлайн и реалтайм движок для React и Mongo - 1
Написал недавно движок [1] для синхронизации данных, имеющий первоклассную поддержку оффлайна. Например, можно уйти в оффлайн, изменять данные, закрыть браузер, открыть браузер, открыть сайт (выйти в онлайн) и данные смержатся без потерь. Также во время онлайна данные между клиентом и сервером синхронизируются в реальном времени. Хочу рассказать, в чём была идея, какие есть подобные решения/технологии и кому это может пригодиться.

Проблема

Требования к веб-приложениям всё время повышаются. Если раньше все были довольны статическими страничками, то сейчас пользователи хотят, чтобы комментарии под картинками котиков обновлялись сразу, лайки накручивались на глазах, уведомления о событиях приходили не дожидаясь перезагрузки страницы. С ростом популярности мобильных устройств, появилась концепция Offline First [2], с идеей, что приложение должно учитывать нестабильность или отсутствие сети.

Веб — это распределенная среда. А синхронизация данных в распределенной асинхронной среде — задача не простая. Всё облегчается тем, что для большинства приложений нету строгих требований к достоверности отображаемых данных. Не так важно, если с сервера не дойдёт какой-то комментарий для картинки с котиком или если два пользователя увидят чуть разное кол-во лайков.
Обычно, в таких случаях, к уже существующему REST-api прикручивается реализация реал-тайма, где сервер каким-то образом знает, какие данные интересуют клиента в данный момент времени (подписки) и при изменении этих данных в бд, шлёт клиенту патчи с обновлениями через web-socket соединение, либо используется какое-то готовое решение. Давайте посмотрм поближе, что сейчас популярно использовать для работы с данными в вебе.

Решения

Flux [3] — методология (и реализация) для работы с данными от Facebook. Главная идея — однонаправленность. Как именно данные достаются с сервера не входит в область интереса Flux. При желании, можно сделать Flux-сторы реалтаймовыми.

Redux [4] — новая и самая популярная Flux-подобная библиотека. Отличается от Flux простотой (нет диспатчера, один стор итп).

Relay [5] — новый фреймворк от Facebook, пришедший на смену методологии Flux. Каждый React компонент, может запросить с сервера данные, которые нужны ему. Делается это с помощью GraphQL [6] языка. Такой абстрактный язык запросов может быть хорошим решением, если у вас много разных источников данных (баз данных), но при этом появляется необходимость руками описывать, как он преобразуется в языки запросов баз данных для всей схемы данных. Relay позволяет запросить только часть документа и разрешает ситуации, когда несколько компонентов запросили одни и те же данные, что полезно, когда у вас большое кол-во компонентов. Подписки должны скоро появиться [7].

Falcor [8] — движок от Netflix. Имеет единый интерфейс для работы с локальными и удалёнными данными. Также интересна концепция paths.

Meteor [9] был своего рода революционером и сильно продвинул идеи изоморфного api для работы с данными и реалтайма. Подписка осуществляется напрямую на монго-запросы, а в качестве патчей при обмене данными выступают операции из монговского oplog'а. Meteor — это даже не фреймворк, а скорее платформа, со своим пакетным менеджером.

Firebase [10] — реалтаймовый BAAS. Интересный и довольно популярный платный сервис, решающий проблему реалтайма в веб-приложениях.

Diffsync [11] использует алгоритм нахождения diff'ов для JSON-объектов, похожий на то, что делает Git для строк. Затем, клиент и сервер обмениваются этими diff'ами. Это может не плохо работать, если у вас в приложении не высокая коллаборативность.

Для того чтобы полностью застраховать себя от недостоверности и от потери данных не достаточно обмениваться патчами, нужен более серьезный подход. Есть две техники разрешения конфликтов для распределенных асинхронных систем — OT [12] и CRDT [13]. Данные представляются в виде состояния и лога операций. Состояние является результатом последовательного применения всех операций из лога и имеет свою версию. Обычно минимальной сущностью состояния является документ и в качестве хранилища используются документо-ориентированные базы данных. Лог операций может храниться в том же или другом хранилище. Помимо этого, вместе с состоянием хранятся определенные метаданные — версия состояния, таймстемп, тип данных и тп. У CRDT есть еще state-based вариант, который используется, например, в Riak [14]. Но передавать, при каждом изменении, весь документ (состояние) не так эффективно, как только одну операцию, по этому обычно в вебе используются op-based CRDT.

ShareJS [15] — самая популярная реализация OT. Я уже писал [16] про нее. Можно добавить, что главными достоинствами ShareJS являются операции над строками, массивами, числами (это полезно, если, например, делаете коллаборационный редактор), а также наличие реализации общего JSON типа данных и возможность подписки на Mongo запросы. Для OT нужен источник правды, где собственно и преобразуются операции. Обычно, это сервер. Реализовать полноценный оффлайн для OT — задача крайне сложная (не знаю ни одной реализации). Надо сказать, что OT активно используется Google в сервисах типа Docs, Wave. Мы, в компании [17], где я работаю, используем ShareJS (как часть фреймворка DerbyJS [18]) уже ни один год и полёт нормальный.

Все описанные выше движки/библиотеки не имеют полноценной поддержки оффлайн, потому что для этого нужны равноправные распределенные реплики данных, как в Git, глобальные версии состояний и тп. Тут есть разные подходы, но наиболее интересным, на мой взгляд, является CRDT. Решения в этой области выглядят так:

Hoodie [19] — Offline First фреймворк, завязанный на CouchDB. В новой версии на клиенте будет использовать PouchDB [20]. CouchDB хранит всю историю состояний документов, это и используется для оффлайна. Можно провести аналогию с Git — при оффлайне, история состояний разделяется на две ветки: серверную и клиентскую, а при онлайне они мержатся. Чем-то похоже на state-based CRDT. CouchDB — по большому счёту key-value хранилище, есть так же базовая реализация запросов, но не такая богатая, как, например, в Mongo.

Swarm [21] — CRDT движок, разработанный нашими сибирскими [22] учёными [23]. Имеет много типов данных — key-value, строки, массивы и тп. Swarm не завязан ни на какую базу данных и, соответственно, не поддерживает подписку за запросы. Реализвация подписок на запросы в общем виде (без завязки на конкретную базу данных) дело не тривиальное.
У Виктора очень интересные [24], доклады [25] и интервью [26].

Общий момент для всех решений является то, что если есть серверная часть, то она написана на Javascript и требует NodeJS. Это объясняется тем, что существенная часть кода между клиентом и сервером переиспользуется.

Наверняка есть еще интересные решения, про которые я не знаю или не вспомнил. Делитесь в комментариях, будем обсуждать.

Amelisa

Идея заключалась в том, чтобы совместить CRDT оффлайн возможности и подписки на Mongo-запросы. Обернуть это в изоморфный Racer [27]-подобный api и интегрировать с React. Добавить наработки из ShareJS по контролю доступа, масштабированию и тп.
По сравнению с SwarmJS пришлось пожертвовать разнообразием типов данных. В отличие от ShareJS и его общего JSON типа данных (включающиего операции над объектами, массивами, строками и числами), в Amelisa каждый документ — это обычный key-value (операции над объектами).
Из Transmit [28] взята идея как реализовать серверный рендеринг для дерева компонентов, каждый из которых подписывается на данные изолированно. Сложность тут в том, что не известны входящие данные для нижележащих компонентов, до момента пока вышележащие не получат данные из базы и не отрендерятся.
Также есть подобие join'ов и возможность смешивать подписки на Mongo-запросы с фетчем данных с обычных url (REST-api, сторонние сервисы и тп.).
Более подробно о возможностях читайте в документации [1]. Хотя в данный момент она оставляет желать лучшего и, возможно, проще посмотреть на пример CRUD-приложения [29], в котором есть авторизация на основе модуля Amelisa Auth [30] и базовый access control. Для самых смелых — исходники [31].

Скорее всего Amelisa будет интересна в тех проектах, где нужен оффлайн и синхронизация данных, но при этом нету требования делать коллаборационные редакторы и тп. Хорошим use-кэйсом может быть todo-list приложение, которые работает на телефоне в виде нативного или веб-приложения и на десктопе в виде веб-приложения. При этом пользователь не хочет думать в онлайне он или в оффлайне, чтобы посмотреть свой список задач на сегодня, отметить законченные или добавить новые. А в момент, когда он окажется в онлайне, данные синхронизируются между всеми устройствами.
Также в требования для Amelisa можно включить React, Mongo, NodeJS.

В данный момент работаю над стабилизацией и интеграцией c React Native [32]. Также мы пишем мобильное приложение, где используется Amelisa и совсем скоро оно должно пойти в продакшен. Лучший способ следить за новостями — подписаться на Twitter Amelisa [33].

Автор: vmakhaev

Источник [34]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/javascript/112892

Ссылки в тексте:

[1] движок: http://amelisajs.com

[2] Offline First: http://offlinefirst.org/

[3] Flux: https://facebook.github.io/flux

[4] Redux: https://github.com/reactjs/redux

[5] Relay: https://facebook.github.io/relay

[6] GraphQL: https://facebook.github.io/graphql

[7] появиться: https://github.com/facebook/relay/issues/541

[8] Falcor: http://netflix.github.io/falcor

[9] Meteor: https://www.meteor.com/

[10] Firebase: https://www.firebase.com

[11] Diffsync: https://github.com/janmonschke/diffsync

[12] OT: https://en.wikipedia.org/wiki/Operational_transformation

[13] CRDT: https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type

[14] Riak: http://docs.basho.com/riak/latest

[15] ShareJS: https://github.com/share/ShareJS

[16] писал: https://habrahabr.ru/post/206324

[17] компании: http://decisionmapper.com

[18] DerbyJS: http://derbyjs.com

[19] Hoodie: http://hood.ie

[20] PouchDB: http://pouchdb.com

[21] Swarm: https://github.com/gritzko/swarm

[22] сибирскими: https://twitter.com/gritzko

[23] учёными: https://github.com/abalandin

[24] интересные: https://www.youtube.com/watch?v=BfzjuhX4wJ0&feature=youtu.be&t=6h31m10s

[25] доклады: https://www.youtube.com/watch?v=uyZKWyciSXY

[26] интервью: https://radiojs.ru/2015/11/radiojs-33

[27] Racer: https://github.com/derbyjs/racer

[28] Transmit: https://github.com/RickWong/react-transmit

[29] пример CRUD-приложения: https://github.com/amelisa/amelisa-crud-example

[30] Amelisa Auth: https://github.com/amelisa/amelisa-auth

[31] исходники: https://github.com/amelisa/amelisa

[32] React Native: https://facebook.github.io/react-native

[33] Twitter Amelisa: https://twitter.com/amelisajs

[34] Источник: https://habrahabr.ru/post/277645/