Slide 1

Slide 1 text

Атомарные SPA Александр Китов 1

Slide 2

Slide 2 text

Интернет банк для юр. лиц !2

Slide 3

Slide 3 text

Проблемы монолитных приложений • Сложно делать в параллель • Сложно тестировать • Большая вероятность что-то сломать • Единая точка отказа • Сложно релизить !3

Slide 4

Slide 4 text

SPA = монолит Build Test Deploy Developers Monolith Delivery pipeline !4

Slide 5

Slide 5 text

Микросервисы Build Test Deploy Developers Microservices Delivery pipelines Build Test Deploy Build Test Deploy Build Test Deploy !5

Slide 6

Slide 6 text

Что вообще это такое? • Небольшие, независимые приложения • Имеют собственное состояние, общаются с собственным API • Могут деплоиться/тестироваться независимо • Отказ одного не влияет на другие • С точки зрения пользователя выглядят как единое целое. Единый ux, сквозная общая навигация !6

Slide 7

Slide 7 text

Микросервисы на фронте !7

Slide 8

Slide 8 text

Два подхода !8 balancer main app balancer Встраиваемые приложения Полностью независимые

Slide 9

Slide 9 text

Встраиваемые приложения balancer /app1 /app2 /app3 nginx/haproxy/... main app !9

Slide 10

Slide 10 text

Встраиваемые приложения • iframe • WebComponents • Blackbox React Component • Специальные библиотеки типа single-spa • Свои велосипеды !10

Slide 11

Slide 11 text

Независимые приложения balancer /app1 /app2 /app3 nginx/haproxy/... !11

Slide 12

Slide 12 text

Встраиваемые приложения balancer /app1 /app2 /app3 nginx/haproxy/... main app !12

Slide 13

Slide 13 text

Независимые приложения balancer /app1 /app2 /app3 nginx/haproxy/... !13

Slide 14

Slide 14 text

Как разделять? • Только одна доменная область. Платежи отдельно, тарифы отдельно • Объем кодовой базы • Необходимость распараллелить работу !14

Slide 15

Slide 15 text

Почти независимые приложения • Общие UI компоненты • Общая инфраструктура !15

Slide 16

Slide 16 text

Библиотека компонентов !16

Slide 17

Slide 17 text

Инфраструктура !17

Slide 18

Slide 18 text

Проблемы !18

Slide 19

Slide 19 text

Продуктовые команды !19 Команда А Команда Б

Slide 20

Slide 20 text

Сложно переходить от проекта к проекту !20 App 1 App 2 App 3

Slide 21

Slide 21 text

Stub проекты !21 base retail corporate

Slide 22

Slide 22 text

Синхронизация проектов !22

Slide 23

Slide 23 text

Синхронизация проектов !23

Slide 24

Slide 24 text

Общая конфигурация const struct = [ { title: 'Выписка', url: 'eco/listing', alfaMetricsCode: 'Listing_btn_Menu', featureCode: 'EcoDashboard', isDisabledForBlockedOrganization: false, isNewTab: false }, { title: 'Сервисы', isNewTab: false, alfaMetricsCode: 'Services_btn_Expand', items: [ { title: 'Кредиты', url: 'external/ufr-credit-products', alfaMetricsCode: 'UfrCreditProducts_btn_Menu', featureCode: 'UfrCreditProducts', isDisabledForBlockedOrganization: true, isNewTab: false } ] } ]; !24

Slide 25

Slide 25 text

Меняется и функциональность! !25

Slide 26

Slide 26 text

Что делать? { "jsFiles": [ "//link.alfabank.ru/shared/main.07fed3df.js" ], "cssFiles": [ "//link.alfabank.ru/shared/main.07fed3df.css" ], "version": "0.1.15", "struct": [ ... // Описание структуры меню ] } /shared/getSharedResources !26

Slide 27

Slide 27 text

Что делать? async function pageLoading() { ... const { jsFiles, cssFiles } = await services.getSharedResources(); return indexTemplate({ js: webpackAssets.js.concat(jsFiles), css: webpackAssets.css.concat(cssFiles), ... }); } !27

Slide 28

Slide 28 text

Server-side render require('http://alfabank.ru/shared.js') !28

Slide 29

Slide 29 text

Server-side render const source = await request('http://alfabank.ru/shared.js'); eval(source); // Oh no, it's eval!! !29

Slide 30

Slide 30 text

Server-side render import vm from 'vm'; const source = await request('http://alfabank.ru/shared.js'); const sandbox = { react: require('react'), reactDOM: require('react-dom'), __CorporateAppHeader: null }; vm.createContext(sandbox); vm.runInContext(source, sandbox); !30

Slide 31

Slide 31 text

Общая точка отказа !31 balancer /app1 /app2 /app3 shared

Slide 32

Slide 32 text

Graceful degradation export default class HeaderFallback extends React.Component { static headerComponent = global.__CorporateAppHeader || (() => ); render() { return React.createElement( HeaderFallback.headerComponent, this.props ); } } !32

Slide 33

Slide 33 text

Еще чуть лучше export default class HeaderFallback extends React.Component { static headerComponent = global.__CorporateAppHeader; async componentDidMount() { if (HeaderFallback.headerComponent) { return; } const loaded = await import( 'arui-private/corporate-app-header' /* webpackChunkName: 'app-header' */ ); HeaderFallback.headerComponent = loaded.default; this.forceUpdate(); // force, because we don't actually change state } render() { return HeaderFallback.headerComponent ? React.createElement(HeaderFallback.headerComponent, this.props) : ; } } !33

Slide 34

Slide 34 text

В чем разница? balancer /app1 /app2 /app3 shared !34

Slide 35

Slide 35 text

А все ли работает? 35

Slide 36

Slide 36 text

Как мониторить? • Следим за состоянием серверов • Следим за состоянием контейнеров • Следим за состоянием баз данных • Анализируем логи !36

Slide 37

Slide 37 text

Все работает? • Сервера • Контейнеры • Базы • Ошибок в логах нет !37

Slide 38

Slide 38 text

Не-а !38

Slide 39

Slide 39 text

Сложноуловимые проблемы • Проблемы с сетью • Косяки в конфигурации сервис-дискавери • Расхождение контрактов фронта/API • ... !39

Slide 40

Slide 40 text

Jest+puppeteer !40

Slide 41

Slide 41 text

Jest+puppeteer test('desktop version', async () => { await page.setViewport({ width: 1280, height: 800 }); await page.goto(`https://link.alfabank.ru/dashboard/?token=${token}`); await page.screenshot({ path: 'screens/dash_desktop.png' }); const companySelector = await page.$('.corporate-app-header__profile'); expect(companySelector).not.toBeNull(); }); !41

Slide 42

Slide 42 text

test runner export async function runTests(): Promise { return new Promise(resolve => { const spawned = childProcess.spawn( 'npm', ['run', 'test'] ); spawned.on('close', async code => { const resultOutput = await readFile( './test-results.json', 'utf8' ); resolve({ code, resultOutput }); }); }); } setInterval(runTests, TIMEOUT) !42

Slide 43

Slide 43 text

slack-bot !43

Slide 44

Slide 44 text

monitor !44

Slide 45

Slide 45 text

Мониторинг • Видим то же, что и пользователь • Не зависит от самих приложений • Можем проверять сложные сценарии • Быстро видим интеграционные проблемы • Можем посмотреть что у нас вообще в бою) !45

Slide 46

Slide 46 text

Итоги !46 • Дублирование загружаемых на клиент данных • Более высокий порог вхождения • Усложнение инфраструктуры • Необходимость синхронизировать вроде как независимые команды • Просто разрабатывать отдельные приложения • Можно деплоить независимо • Больше надежность • Меньше вероятность сломать всё • Проще масштабировать + -

Slide 47

Slide 47 text

Вопросы? !47 https://bit.ly/2HhJZkr