Синхронізація даних у додатках реального часу з Theron
Іноді я малюю собі граф того, як має виглядати архітектура сучасних систем та знаходжу ті моменти процесу розробки, які можуть бути покращені та ті практики, які можуть бути застосовані для покращення цих процесів. Після чергової такої ітерації я ще раз переконався, що існують приголомшливі фреймворки та методології для розробки серверної та клієнтської частин, але синхронізація даних між клієнтом, сервером та базою даних працює не так, як того вимагають сучасні реалії: швидке реагування на зміну стану системи, розподіленість та асинхронність обробки даних, повторне використання ранньо оброблених даних.
В останні роки вимоги до сучасних програм та методи їх розробки значно змінилися. Більшість таких програм використовують асинхронну модель, що складається з безлічі слабко пов'язаних компонентів (мікросервісів). Користувачі ж хочуть, щоб програма працювала безвідмовно і завжди була в актуальному стані (дані повинні бути синхронізовані в будь-який момент часу), простіше кажучи, користувачі почуваються комфортніше, коли їм не потрібно щоразу натискати кнопку «Оновити» або повністю перезавантажувати програму, якщо щось пішло негаразд. Під катом трохи теорії та практики та повноцінний додаток з відкритим вихідним кодом зі стіком розробки React, Redux/Saga, Node, TypeScript та нашим проектом Theron.

Результатом моїх досліджень став Theron. Theron – сервіс для створення сучасних програм реального часу. Реактивне сховище даних Theron безперервно транслює зміни в базі даних, виходячи з запиту до неї. Ледве більше ніж за чотири місяці невеликою командою з двох розробників ми реалізували базову архітектуру, основні критерії якої:
- Швидке створення нових програм і безболісна міграція існуючих на Theron.
- Використання сучасних практик при створенні асинхронних, розподілених та відмовостійких систем та ізоморфізм компонентів системи.
- Розподілена інтеграція на низькому рівні з базами даних, такими як Postgres та Mongo.
- Легка інтеграція із сучасними фреймворками, такими як React, Angular та їхніми друзями: ReactiveX, Redux тощо.
- Сфокусованість на вирішення задачі синхронізації даних, а не надання повного стеку розробки та подальшого "вендору локінгу".
- Основна логіка додатків (у тому числі автентифікація та права доступу) має реалізовуватись розробниками на їхньому боці.
Реактивні канали
Мені сподобався функціональний підхід ще тоді, коли я познайомився з однією із найстаріших функціональних мов програмування, орієнтованим на символьні обчислення Рефал. Пізніше, сам того не усвідомлюючи, я почав використовувати реактивну парадигму програмування, і, з часом,Більшість моєї роботи будувалася цих підходах.
Theron побудований на основі ReactiveX. Фундаментальний концепт Theron —реактивні канали, що надають гнучкий спосіб трансляції даних різним сегментам користувачів. Theron використовує класичний Pub/Sub шаблон проектування. Для створення нового каналу (кількість необмежена) та стримінг даних достатньо лише створити нову передплату.
Після встановлення (англ.), імпортуйте Theron та створіть нового клієнта:
Створіть нову передплату:
Коли канал більше не потрібен — відпишіть:
Надсилання даних клієнтам, підписаних на цей канал, з боку сервера (Node.js) також проста:
Реалізація багатьох алгоритмів у рамках реактивного програмування витончена і проста, наприклад, експоненційний бэкофф у клієнтській бібліотеці Theron виглядає приблизно так:
Інтеграція з базою даних
Як було сказано вище, Theron - це реактивне сховище даних: система повідомлень про зміни, яка безперервно транслює оновлення захищеними каналами для вашої програми, виходячи зі звичайного SQL запиту до бази даних. Theron аналізує запит до бази даних та відправляє артефакти даних, за допомогою яких можна відтворити вихідні дані.
Theron інтегрований зараз з Postgres; інтеграція з Mongo у процесі розробки.
Розглянемо, як це працює на прикладі життєвого циклу простого списку, що складається з перших трьох елементів, упорядкованого в алфавітному порядку:
Перед тим як ми продовжимо, підключіть базу даних Theron, ввівши дані для доступу до неї в панелі управління:

Внутрішній пристрій захвату (locking) бази даних – велика тема для окремої статті у майбутньому. Theron - розподілена система, томупул підключень до бази даних обмежений 10 (з можливістю збільшення до 20) загальними підключеннями.
1. Створення нової підписки
Theron працює із SQL запитами, тому ваш сервер повинен повертати не результат виконання запиту, а вихідний SQL запит. Наприклад, у нашому випадку JSON відповідь сервера може виглядати так:
На стороні клієнта почнемо трансляцію даних для нашого SQL запиту, створивши нову передплату:
Theron відправить GET запит '/todos' вашому серверу, перевірить валідність поверненого SQL запиту і почне трансляцію початкових інструкцій з необхідними даними, якщо цей запит не був скешований на стороні клієнта.
- ROW_ADDED — додано новий елемент.
- ROW_REMOVED - елемент видалено.
- ROW_MOVED — елемент змінено.
- ROW_CHANGED — елемент змінено.
- BEGIN_TRANSACTION – новий блок синхронізації.
- COMMIT_TRANSACTION — синхронізація успішно завершена.
- ROLLBACK_TRANSACTION – при синхронізації виникла помилка.
| 1 | A |
| 2 | B |
| 3 | C |
- < type: ROW_ADDED , payload: < row: < id: 1, name: 'A' >, prevRowId: null > >
- < type: ROW_ADDED , payload: < row: < id: 2, name: 'B' >, prevRowId: 1 >
- < type: ROW_ADDED , payload: < row: < id: 3, name: 'C' >, prevRowId: 2 > > Кожен блок синхронізаціїпочинається та закінчуються інструкціями BEGIN_TRANSACTION та COMMIT_TRANSACTION. Для коректного сортування елементів на стороні клієнта Theron додатково надсилає дані попереднього елемента.
2. Користувач перейменовує елемент A (1) на D (1)
Припустимо, що користувач перейменовує елементA(1)наD(1). Оскільки SQL запит упорядковує елементи в алфавітному порядку, то відбудеться сортування елементів, і стан клієнта зміниться так:
| 1 | A | 2 | B |
| 2 | B | 3 | C |
| 3 | C | 1 | D |
Припустимо, що користувач створює новий елементA(4). Оскільки наш SQL запит обмежує дані першими трьома елементами, то стороні клієнта відбудеться видалення елементаD(1), і стан клієнта зміниться так:
| 2 | B | 4 | A |
| 3 | C | 2 | B |
| 1 | D | 3 | C |
| 1 | D |
Припустимо, що користувач видаляє елементD(1)з бази даних. Theron в цьому випадку не надішле нових інструкцій, оскільки ця зміна в базі даних не впливає на дані, що повертаються нашим SQL запитом, і не впливає на стан клієнта:
| 4 | A | 4 | A |
| 2 | B | 2 | B |
| 3 | C | 3 | C |
Тепер, знаючи як Theron працює з даними, ми можемо реалізувати логіку відтворення даних на стороні клієнта. Алгоритм досить простий: ми будемо використовувати тип інструкції та метадані попереднього елемента для коректного позиціонування елементів у масиві. У реальному додатку потрібно використовувати, наприклад, бібліотеку Immutable.js для роботи з масивами та оператор scan приклад.
Час прикладів

Висновок
Крім виправлення гіпотетичної тонни помилок та класичного написання клієнтських бібліотек під популярні платформи, ми працюємо над виділенням у незалежний компонент зворотного проксі-сервера та балансувальника. Ідея полягає в тому, щоб можна було створювати за сервера API, до якого можна звертатися як через звичайні запити HTTP, так і через постійне підключення WebSocket. У наступній статті про архітектуру Theron я напишу про це детальніше.
Команда у нас невелика, але енергійна, і ми любимо експериментувати. Theron знаходиться в активній розробці: є безліч ідей та моментів, які потрібно реалізувати та покращити. Із задоволенням вислухаємо будь-яку критику, приймемо поради та конструктивно це обговоримо.