Микросервисный підхід у веб-розробці: micro frontends
До недавнього часу JavaScript використовувався для таких примітивних завдань, як зміни кольору тексту на сторінці. Веб почав стрімко розвиватися, і, як наслідок, складність веб-додатків збільшилася. За останні 10 років у веб перекочувало більшість програм, які ми використовуємо щодня. Зараз вже важко уявити своє життя без Google Drive, Google Docs, YouTube і т. д.
У цій статті поговоримо про микросервисном підході в веб-розробки користувальницьких інтерфейсів.
Типове веб-додаток складається з HTML-верстки, CSS-стилів і JavaScript-коду, який дозволяє досягти максимального рівня інтерактивності і чуйності. Чим вище складність програми, тим складніше користувальницький інтерфейс, а внаслідок цього — і інструменти, які потрібні для його розробки. Саме тому фронтенд-розробка перетворилася з простого набору додатків для користувача інтерфейсу в складну екосистему з великою кількістю інструментів і високим порогом входу.
Проблема і рішення
Користувальницькі інтерфейси в інтернеті продовжують розвиватися. Підтвердження цьому можна побачити і на сайті :
Згідно зі статистикою, кожен місяць обсяг JavaScript-коду в веб-додатках зростає, що на більшості проектів веде до збільшення наступних параметрів:
- час розробки у зв'язку з високим рівнем складності коду;
- час тестування;
- часовий інтервал між релізами.
Великі додатки, у яких такі проблеми, часто називають монолітами із-за використовуваної архітектурного підходу.
Монолітна архітектура — це архітектурний підхід, в якому вся основна логіка програми зібрана в одному місці. Монолітне додаток складається з одношарового об'єднання різних компонентів в одне ціле.
Розробка нової функціональності в такому додатку, де великий обсяг застарілого коду, з кожним роком стає все дорожче для бізнесу. Зі схожою проблемою вже стикалися бекенд-розробники. Одне з рішень більшості проблем монолітної архітектури отримало назву «микросервисы» (Microservices).
Микросервисная архітектура — це повна протилежність монолітної архітектури. Використовуючи такий підхід замість одного великого програми, ми створюємо набір невеликих слабосвязанних і легко замінних модулів, які взаємодіють один з одним. Одне з головних достоїнств микросервисной архітектури — можливість використовувати найкращий технічний стек для кожної окремої задачі.
Крім цього, можна виділити і інші достоїнства микросервисной архітектури в порівнянні з монолітною:
- модульність;
- зменшення часу на тестування;
- скорочення часу на деплой і можливість робити це паралельно;
- можливість горизонтального масштабування команди.
А чи можна використовувати микросервисный підхід у веб-розробки користувальницьких інтерфейсів? Відповідь: да! Можна і потрібно! Саме такий підхід і називають микрофронтенд (micro frontends).
Микрофронтенд (Micro frontends)
Микрофронтенд (micro frontend) — архітектурний підхід, в якому незалежні додатки зібрані в одне велике додаток. Він дає можливість об'єднати в одному додатку різні віджети або сторінки, написані різними командами з використанням різних фреймворків (див. рис. нижче).
Головні переваги микрофронтенд-підходу — у розробці великих ентерпрайз-додатків:
- модульна архітектура. Окремі віджети або сторінки — це повністю незалежні програми;
- швидкість тестування. Зміни в одному віджеті або сторінці можна протестувати ізольовано і тільки в цьому додатку, не витрачаючи часу на тестування всього іншого функціоналу;
- паралельні деплойменты. Окремі віджети або сторінки можуть і повинні деплоиться незалежно.
Крім очевидних переваг такого підходу, у нього є і суттєві недоліки:
- збільшення загальної складності програми;
- дублювання коду. Кожна програма розробляється окремою командою, яка бере свої технічні рішення. Це веде до повторному завантаженні однакових фреймворків, бібліотек і загальним дублювання коду, який міг бути використаним повторно;
- JS-бандл монолітного програми завжди буде менше, ніж сукупність бандлів в микрофронтенд-архітектурі;
- можливі проблеми з кешування і версионностью додатків;
- глобальні змінні або CSS-стилі — це речі, про які варто забути в микрофронтенд-архітектурі, якщо програми повністю не ізольовані.
Використання такого архітектурного підходу на маленьких проектах і в маленьких командах несе більше проблем і додаткової складності в розробці, ніж переваг. Але великі проекти разом з розподіленими командами, навпаки, отримують більший виграш від створення микрофронтенд-додатків. Саме тому сьогодні микрофронтенд-архітектура вже широко використовується багатьма великими компаніями в своїх веб-додатках:
На жаль, поки що не існує конкретної специфікації для побудови микрофронтенд-архітектури. Ось, мабуть, найбільш доступні та прості способи і техніки для побудови микрофронтенд-додатків:
- IFrames;
- бібліотека Tailor.js;
- фреймворк single-spa.
IFrames
IFrame — це давня технологія, яка, незважаючи на всю свою неактуальність, дає можливість побудувати микрофронтенд-архітектуру. Використовуючи IFrame, кожен окремий віджет можна помістити в IFrame, який завантажує потрібну програму. При використанні такого підходу ви, швидше за все, зіткнетеся з наступними проблемами:
- продуктивність;
- складність підтримки.
Бібліотека Tailor.js
Тут ви можете прочитати більше про самій бібліотеці. Компанія Zalando створила цілу екосистему для побудови микрофронтенд-архітектури, і Tailor.js — це частина екосистеми. Особливість Tailor.js — те, що це пакет для Node.js, і орієнтований він на побудову микрофронтенд-архітектури з серверним рендерингом.
Для мене додаткова особливість цього пакета — недолік документації. Живі приклади проектів я зміг знайти тільки в окремих топіках GitHub, де звичайні користувачі запитують рада і прикріплюють посилання на свої репозиторії з кодом. Якщо вам потрібен серверний рендеринг, то ця бібліотека точно стане в нагоді.
Single-spa
Основною і, на мою думку, найкращий підхід у побудові микрофронтенд-архітектури — це фреймворк single-spa. Ось основні причини, по яких я раджу вибрати single-spa:
- робочі приклади з використанням сучасних фреймворків та бібліотек;
- хороша документація;
- можливість нишпорити залежності між окремими віджетами;
- підтримка незалежних деплойментов;
- складання програми на стороні клієнта;
- готова екосистема врапперов для швидкої інтеграції існуючих додатків в микрофронтенд-архітектуру.
Single-spa — це фреймворк, який дає можливість об'єднати різні додатки, незалежно від використовуваної бібліотеки або фреймворку, в одне ціле. Під капотом single-spa набір існуючих інструментів разом з власними рішеннями:
- SystemJS — завантажувач модулів, який потрібен для асинхронної завантаження окремих програм;
- врапперы — single-spa надає окремі врапперы під кожен фреймворк, який створює обгортку над додатком, потрібну для інтеграції і підключення окремого додатка в загальне single-spa;
- API — single-spa надає набір інструментів, які потрібні для комунікації між окремими додатками, підписку на події і т. д.
Типове додаток з використанням single-spa виглядає так:
А ось так виглядає комунікація між окремими елементами микрофронтенд-архітектури, побудованої з використанням single-spa:
Root Application — це корінь додатки. Саме тут відбувається підключення sigle-spa як основного фреймворку, а також конфігурація SystemJS для коректного завантаження зовнішніх додатків.
Кожне дочірнє додаток для коректної інтеграції повинно надавати публічні методи bootstrap, mount, unmount, які використовуються фреймворком single-spa для мануального бутстрэппинга програми. Майже кожен сучасний фреймворк існує готовий врапперов, який спрощує цю задачу і автоматизує якусь частину процесу. На сайті single-spa можна знайти список усіх фреймворків, під які існують готові врапперы.
Побудувати набір додатків з використанням микрофронтенд-підходу і single-spa можна як шляхом створення повністю всієї інфраструктури і додатків з нуля, так і на основі наявної програми. Розглянемо приклади того, як це виглядає, створюючи набір повністю нових програм з використанням React.js і Angular 8.
Конфігурація для білду React.js під single-spa представлена тут .
import React from 'react' import ReactDOM from 'react-dom' import singleSpaReact from 'single-spa-react' import { property } from 'lodash' import setPublicPath from './set-public-path.js' const reactLifecycles = singleSpaReact({ React, ReactDOM, loadRootComponent: () => import(/* webpackChunkName: "react-app" */'./App.js').then(property('default')), domElementGetter, }) export const bootstrap = [ () => { return setPublicPath() }, reactLifecycles.bootstrap, ] export const mount = [ reactLifecycles.mount, ] export const unmount = [ reactLifecycles.unmount, ] export const unload = [ reactLifecycles.unload, ] function domElementGetter() { let el = document.getElementById("react-app"); if (!el) { el = document.createElement('div'); el.id = 'react-app'; document.body.appendChild(el); } return el; }
Використовуючи існуючий врапперов single-spa для React.js ми створюємо інтерфейс з методами bootstrap, mount, unmount, де відповідно описуємо, як повинно бутстрэппиться наш додаток. Врапперов допомагає инкапсулировать внутрішню імплементацію та створити API для правильного підключення single-spa фреймворка.
Схожим чином це виглядає і для Angular 8.
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { Router } from '@angular/router'; import { AppModule } from './app/app.module'; import { environment } from './environments/environment'; import singleSpaAngular from 'single-spa-angular'; import { singleSpaPropsSubject } from './single-spa/single-spa-props'; if (environment.production) { enableProdMode(); } const lifecycles = singleSpaAngular({ bootstrapFunction: singleSpaProps => { singleSpaPropsSubject.next(singleSpaProps); return platformBrowserDynamic().bootstrapModule(AppModule); }, template: '<app-root />', Router, NgZone: NgZone, }); export const bootstrap = lifecycles.bootstrap; export const mount = lifecycles.mount; export const unmount = lifecycles.unmount;
Так само, як і у випадку з React.js ми надаємо інтерфейс для ручного бутстрэппинга програми на Angular 8.
Крім використання специфічних врапперов, додаток має бути зібрано як amd-модуль. Кожен такий модуль асинхронно підключається в корінь всієї микрофронтенд-архітектури — Root Application. Нижче приклад елементарної імплементації.
<!DOCTYPE html> <html> <head> <title>Root application</title> <meta name="importmap-type" content="systemjs-importmap"> <script type="systemjs-importmap"> { "imports": { "angular-app": "http://localhost:4201/main.js", "react-app": "http://localhost:4202/main.js", "single-spa": "https://unpkg.com/[email protected]/lib/system/single-spa.min.js" } } </script> <!-- Loading of required libraries shouyld be added --> </head> <body> <header class="header-fixed"> <nav> <a href="/angular-app">Angular app</a> <a href="/react-app">React app</a> </nav> </header> <script> System.import('single-spa').then(function (singleSpa) { singleSpa.registerApplication( 'angular-app', () => System.import('angular-app'), location => location.pathname.startsWith('/angular-app') singleSpa.registerApplication( 'react-app', () => System.import('react-app'), location => location.pathname.startsWith('/react-app') ) singleSpa.start(); }) </script> </body> </html>
Дві головні частини, на які варто звернути увагу:
- script-тег, в якому потрібно описати маппінг назви програми, на адресу, з якої single-spa буде довантажувати amd-модуль для цього додатка;
- script-тег, де потрібно безпосередньо зареєструвати додаток з допомогою методу registerApplication. Тут потрібно вказати адресу, при переході на який system.js буде завантажувати відповідний модуль.
Як можна побачити, Root Application — простий HTML-файл з основними конфігураціями для завантаження інших додатків. В одному микрофронтенд-додатку можна зареєструвати безліч микроприложений. Якщо при реєстрації програми в 3-му параметрі методу registerApplication просто вказати /, такий додаток буде завантажуватися для кожного доступного адреси. Саме такий підхід бажано використовувати для створення навігаційної панелі або частин програми, які є загальними і не повинні завантажуватися повторно при переході між додатками.
Повністю робочий приклад того, як це працює, можна знайти в моєму GitHub-репозиторії .
У цьому репозиторії представлена покрокова імплементація микрофронтенд-архітектури, фінальна робоча версія — в гілці з назвою step-7.
Нам, розробникам, далеко не завжди доводиться створювати додатки з нуля. Single-spa дає можливість повністю використовувати існуючі програми як елементи микрофронтенд-архітектури, будь то Root Application або асинхронно завантажувані микроприложения.
Якщо з існуючої програми потрібно створити микроприложение, сумісний з single-spa і має можливість завантажуватися асинхронно, в першу чергу потрібно шукати відповідний врапперов. Для більшості сучасних фреймворків та бібліотек single-spa надає врапперы, готові до завантаження через npm, а також документацію, що описує, як їх правильно налаштувати. Якщо ж для використовуваного фреймворку не існує готового враппера, то завжди можна написати його самостійно. Налаштувавши врапперов і зібравши amd-модуль, ви отримаєте готове до підключення микроприложение; все, що залишиться, це додати відповідну конфігурацію Root Application. З власного досвіду скажу, що для сучасних фреймворків та бібліотек, таких як Angular 2+, React, Vue, які збираються з допомогою Webpack, конвертація в микроприложение проходить швидко і без додаткових танців з бубнами».
Якщо з існуючої програми потрібно створити Root Application, тоді в першу чергу потрібно index.html підключити бібліотеки для single-spa і додати всі необхідні для завантаження інших микроприложений конфігурації. Після того як ваш index.html став Root Application в single-spa, можна приступити до налаштування іншої частини програми для коректної роботи в single-spa-архітектурі так, як це описано в попередньому абзаці.
Проблеми та недоліки
Велика проблема для обох завдань — це старі (legacy) додатки, написані на старих фреймворках і стягуються з використанням старих інструментів. Наприклад, додаток на AngularJS або Backbone, яка збирається за допомогою gulp або grunt, може «з'їсти» дуже багато вашого часу, перш ніж буде коректно налаштовано. З власного досвіду можу виділити наступні проблемні місця:
- залежно — дуже багато залежить від того, яким чином у вашому додатку завантажуються залежності. Все, що не import, потенційно може викликати проблеми на етапі складання чи виконання;
- стилі, зображення, шрифти — швидше за все, все це буде поламано або з багами після першої успішної складання вашого микроприложения.
На етапі конфігурації проблеми, з якими ви зіткнетеся, будуть найрізноманітніші, тому запасіться терпінням ;)
Крім складної конфігурації, виділимо проблеми, які можуть виникнути при побудові микрофронтенд-архітектури з використанням single-spa:
- кешування микроприложений. Без правильної стратегії кешированию микроприложения, як і Root Application, будуть кешуватись в браузері і ігнорувати будь-які нові зміни, які будуть релизнуты;
- дебаггинг. Якщо щось не працює, дебаггинг без додаткових конфігурацій та надбудов може бути досить важким процесом, так як вам доведеться дебажити не програми, а окремі amd-модулі без сорс мап;
- загальний розмір програми разом з усіма асинхронними микроприложениями збільшиться, в порівнянні з монолітним підходом;
- загальна продуктивність микрофронтенд-додатка буде нижче, ніж у моноліту;
- повторення коду. Повторна завантаження коду — як бібліотек, так і цілих фреймворків — веде до погіршення швидкодії всього додатки, а не тільки окремої сторінки. Single-spa дає можливість нишпорити залежності між додатками, але пам'ятайте: чим більше у вас залежностей, бібліотек, компонент, які вештаються між додатками, тим більше ваша микрофронтенд-архітектура схожа на монолітну;
- SEO-оптимізація. Завантаження окремих додатків разом з бутстрэппингом повністю проходить на клієнта, що значно ускладнює SEO-оптимізацію. Single-spa не підтримує серверний рендеринг, для додавання підтримки можна використати зв'язку single-spa + Tailor.js.
Висновки
Микрофронтенд-архітектура, як і будь-яка інша архітектура, вирішує одні проблеми, створюючи інші. Микрофронтенд-архітектура ускладнить ваше життя і життя вашої команди, якщо:
- над проектом працює менше 10 осіб і немає планів до розширення;
- ваш додаток не складається з абстрактних ізольованих модулів, які в перспективі можуть бути окремими додатками;
- багфиксинг разом з розробкою нових фіч проходить швидко і без труднощів, пов'язаних з розміром вашого додатка.
Опубліковано: 05/12/19 @ 11:00
Розділ Сервіси
Рекомендуємо:
Поради сеньйорів: як прокачати знання junior security specialist
Зарплатне опитування
«Я просто роблю те, що мені подобається». Як 18-річний студент навчає дітей програмуванню та видає підручники з Python
Go дайджест #11: мови 10 років, новинки в Go 1.14
PM дайджест #22: engineering mentorship, як проводять code review Google