Фото Xavi Cabrera на Unsplash
Вступ
Протягом моєї кар'єри мені довелося проєктувати та створювати кілька великих і складних систем. Однією з ключових стратегій для вирішення цих задач було розбивання їх на менші, більш керовані компоненти, при цьому процес створення інтерфейсів користувача (UI) робився максимально безшовним.
У цій статті ми розглянемо ці виклики на прикладі створення розподіленого інтерфейсу користувача за допомогою Mateu. Наш підхід полягає у складанні інтерфейсу з кількох мікрофронтендів, кожен з яких визначається за допомогою Mateu, що спрощує процес розробки для розробників бекенду.
Для демонстрації цього методу ми розглянемо простий випадок: створення системи бронювання, де інтерфейс складається з кількох мікрофронтендів.
Що таке мікрофронтенди?
Мікрофронтенд — це модульний компонент інтерфейсу користувача, який можна вбудовувати в більший інтерфейс та розгортати незалежно один від одного. Оновлення мікрофронтенду не потребує змін або перезавантаження основного інтерфейсу.
Крім можливості повторного використання, особливо цінним є те, що мікрофронтенди дозволяють командам повністю володіти своїми функціями, від розробки до розгортання. Замість того, щоб одна центральна команда фронтенду відповідала за весь інтерфейс і споживала REST-ендпоінти від різних бекенд-сервісів, кожна команда може керувати та доставляти повну функціональність, включаючи як фронтенд, так і бекенд компоненти. Для того, щоб додати нову функцію до вашого додатку, потрібно лише вбудувати відповідний мікрофронтенд.
Мікрофронтенди дають змогу командам розробників зменшити залежності та мінімізувати бюрократію, що допомагає усунути вузькі місця та оптимізувати процес розробки.
Що таке Mateu?
Mateu — це бібліотека для Java, яка призначена для створення бекенд-орієнтованих інтерфейсів користувача. Використовуючи прості анотації та інтерфейси, Mateu пропонує домен-специфічну мову (DSL), що дозволяє визначати інтерфейс користувача за допомогою простих класів Java, в той час як її runtime піклується про його функціональність.
Frontend-частина Mateu — це фактично веб-компонент, що взаємодіє з простим REST API. Цей веб-компонент надсилає запити до API і відображає отримані відповіді. Оскільки фронтенд і бекенд повністю розділені, можна використовувати різні реалізації фронтенду та технології бекенду, якщо вони відповідають вимогам API-контракту.
На даний момент єдиним доступним бекендом є Java, але я планую розширити бібліотеки для C# та Go в майбутньому (сподіваюся, до 2025 року).
Frontend-компонент Mateu базується на Vaadin design system, що забезпечує елегантний і послідовний користувацький досвід.
Проблематика
Нашим завданням є створення простої системи бронювання. Зокрема, ми хочемо побудувати платформу для колл-центру, що дозволяє керувати бронюваннями (включаючи створення нових бронювань, зміни, скасування), виставленням рахунків та оплатою.
Нижче наведена схема, яка висвітлює наші функціональні вимоги та показує потік користувача:
Потік користувача / вайрфрейм
З точки зору нефункціональних вимог, інтерфейс користувача, представлений для користувачів колл-центру, повинен бути єдиним, навіть якщо кожна команда розробників буде відповідати за власні інтерфейси. Крім того, у наших командах немає розробників фронтенду.
Наша кінцева мета — створити інтерфейс, який інтегрує меню та екрани з різних підсистем (або мікросервісів, у нашому випадку). Ці екрани також будуть включати компоненти з інших підсистем. Ми хочемо уникнути використання Iframe та забезпечити безшовну інтеграцію по всій системі. Крім того, ми не хочемо мати кілька вебсайтів з різними URL, навіть якщо вони підключені через одноразовий вхід (SSO) і використовують одну й ту саму дизайн-систему.
Натомість ми хочемо один URL з єдиним, інтегрованим меню.
Простір рішень
Для нашої системи бронювання припустимо, що ми визначили два окремих піддомені: бронювання та фінанси. Кожен піддомен відповідає за окремий обмежений контекст, де концепти мають сталі значення в межах цього контексту.
Наша схема контекстів
Контекст бронювання охоплює всі аспекти життєвого циклу бронювання, в той час як контекст фінансів зосереджений на виставленні рахунків та оплаті. Для полегшення комунікації між ними обидва контексти мають спільну кодову базу, зокрема визначення подій.
Мікросервіси
Для кожного обмеженого контексту ми створимо мікросервіс, яким керуватиме окрема команда, відповідальна за весь набір функцій — як фронтенд, так і бекенд.
Загалом ми визначимо п’ять мікросервісів:
- Один мікросервіс для кореневого інтерфейсу користувача.
- Один мікросервіс для контексту бронювання, який надаватиме свої власні компоненти UI.
- Один мікросервіс для контексту фінансів, який надаватиме свої власні компоненти UI.
- Один мікросервіс для API Gateway, який маршрутизуватиме запити до відповідного мікросервісу.
- Один мікросервіс для збирання даних в запитну базу даних, яка агрегує дані з різних мікросервісів.
Інфраструктура
Наступна схема допомагає нам візуалізувати кінцеву картину:
Оглядова схема
Коли користувач вводить URL нашої системи в браузері, запит спочатку проходить через CDN (наприклад, Cloudflare), потім потрапляє до балансувальника навантаження, який направляє його до одного з наших вузлів Kubernetes. Звідти запит проксіюється до API Gateway, який, врешті-решт, передає його до відповідного мікросервісу. Лише початковий HTTP-запит з браузера повертає HTML, тоді як усі наступні запити — це виклики REST API, які здійснюються веб-компонентами Mateu.
Бази даних
Кожен мікросервіс має свою базу даних, і, додатково, ми дозволяємо мікросервісу бронювання виконувати запити до спеціалізованої бази даних для читання, яка агрегує дані з обох мікросервісів: бронювання та фінансів. Зв'язок між мікросервісами та їх базами даних ілюструється на наступній схемі:
Потік даних між мікросервісами
Мікросервіси бронювання та фінанси читають і записують у свої власні бази даних, при цьому генерують події в загальний Kafka-топік. Водночас мікросервіс CQRS-sink споживає події з мікросервісів бронювання та фінанси, щоб заповнити базу даних для читання. Ця база даних для читання потім використовується мікросервісом бронювання для побудови списку бронювань, що містить як оперативні, так і фінансові дані. З іншого боку, мікросервіс фінансів слухає події, що виникають у мікросервісі бронювання, для оновлення своєї бази даних.
Те, що ми вибрали різні системи управління базами даних для кожного мікросервісу, підкреслює автономність кожного мікросервісу.
Проєкт
Для побудови нашої системи ми створимо монорепозиторій, і структура проєкту виглядатиме наступним чином:
Структура проєкту
Тут нема багато що пояснювати. Кожен мікросервіс має свою власну папку, яка видна на кореневому рівні.
Від цього моменту ми відкладемо бізнес-логіку та деталі реалізації піддоменів і зосередимося виключно на розробці інтерфейсу користувача, зокрема на аспекті композиції.
Інтерфейс користувача
Наш інтерфейс користувача матиме меню з різних обмежених контекстів: одне, пов'язане з життєвим циклом бронювання, і інше, пов'язане з фінансами.
Існує кілька способів визначити кореневий інтерфейс користувача, але оскільки у наших командах немає розробників фронтенду, ми будемо використовувати Mateu для цієї мети.
Ми оберемо складання на стороні клієнта (замість складання на рівні edge чи сервера), оскільки це найпростіший підхід для інтеграції різних мікрофронтендів. Крім того, ми не покладаємося на залежності Maven для складання макро UI. Натомість ми хочемо справжні мікрофронтенди, які можна розгортати незалежно один від одного разом із відповідними мікросервісами, на їх власний розсуд. Цей підхід також допомагає уникнути необхідності створювати API, спеціально призначені для підтримки UI.
Тепер давайте подивимося, як UI визначається в різних мікросервісах.
UI бронювання
UI бронювання створюється для того, щоб представити меню та різні компоненти, визначені та керовані мікросервісом бронювання.
Наступний код можна використовувати для визначення UI бронювання:
// ЦЕ Є В МІКРОСЕРВІСІ БРОНЮВАННЯ
class BookingMenu {
@MenuOption // (2)
BookingsCrud bookings;
}
@MateuUI("/booking") // (1)
public class BookingUI {
@Submenu
BookingMenu booking; // "booking" буде ідентифікатором меню
}
Отже, нам потрібно лише визначити клас, де ми:
- Оголошуємо його як UI та прив’язуємо до маршруту “/booking”.
- Додаємо функціональність CRUD для бронювань до меню.
Щоб поділитися меню бронювання, ми можемо відкрити UI бронювання, вибрати пункт меню і взяти частину URL, яка нас цікавить.
UI бронювання після вибору одного з пунктів меню бронювань
На скріншоті вище ми повинні зосередитися на URL (https://article2.mateu.io/booking#bookingbookings_). Ключова частина URL — booking#booking
, оскільки ми будемо споживати меню з того самого домену. Усередині booking#booking
перша частина ідентифікує UI бронювання, а друга частина — меню.
UI фінансів
UI фінансів створюється для того, щоб представити меню та різні компоненти, визначені та керовані мікросервісом фінансів.
Ось код для визначення UI фінансів:
// ЦЕ Є В МІКРОСЕРВІСІ ФІНАНСІВ
class FinancialMenu {
@MenuOption
InvoiceCrud invoices; // (2)
@MenuOption
PaymentCrud payments; // (3)
}
@MateuUI("/financial") // (1)
public class FinancialUI {
@Submenu
FinancialMenu financial; // "financial" буде ідентифікатором меню
}
Знову ж таки, нам потрібно лише визначити клас, де ми:
- Оголошуємо його як UI та прив’язуємо до маршруту “/financial”.
- Додаємо CRUD для рахунків до меню.
- Додаємо CRUD для платежів до меню.
Щоб поділитися меню фінансів, ми можемо відкрити UI фінансів, вибрати пункт меню і взяти частину URL, яка нас цікавить.
UI бронювання після вибору одного з пунктів меню бронювань
На скріншоті вище ми повинні зосередитися на URL (https://article2.mateu.io/financial#financialinvoices_). Ключова частина URL — financial#financial
, оскільки ми будемо споживати меню з того самого домену. Усередині financial#financial
перша частина ідентифікує UI фінансів, а друга частина — меню.
UI оболонки
Єдина мета нашого UI оболонки — це надання домашньої сторінки та агрегування меню з різних мікросервісів.
Для цього ми вже визначили UI в кожному мікросервісі (Бронювання та Фінанси), кожен з яких має своє меню.
Тепер, в окремому мікросервісі, ми визначимо кореневий (або оболонковий) UI, який буде агрегувати ці меню.
Використовуючи Mateu, нам потрібно всього лише один клас Java для визначення кореневого UI:
// ЦЕ Є В МІКРОСЕРВІСІ КОРЕНЕВОГО UI
@Title("Home")
public class Home { // контент домашньої сторінки
@RawContent
String content = """
Привіт! :)
Це система бронювання, побудована за допомогою Mateu!
"""; } @MateuUI("") // (1) @PageTitle("Система бронювання Mateu") @Title("Home") @AppTitle("Моя система бронювання") @FavIcon("/mateu-favicon.png") public class ShellUI extends Home implements HasLogo { @MenuOption(remote=true) // (2) String booking = "booking#booking"; @MenuOption(remote=true) // (3) String financial = "financial#financial"; // ... } ```
В цьому коді ми просто:
1. Оголошуємо його як UI та асоціюємо з маршрутом `/`.
2. Додаємо меню бронювання, яке буде динамічно завантажене браузером під час виконання з URL `booking#booking`.
3. Додаємо меню фінансів, яке буде динамічно завантажене браузером під час виконання з URL `financial#financial`.
Зверніть увагу, що ми створили клас `Home` для визначення контенту домашньої сторінки, а клас `ShellUI` відповідає за визначення меню, дотримуючись принципу розділення обов'язків.
Вищенаведений код призведе до наступного контенту, що буде відображений, коли користувач відкриє вебсайт:
```
Зверніть увагу, що для вбудовування нашого UI в будь-яку HTML-сторінку потрібен лише один скрипт (той, що з src="/dist/assets/mateu.js"
) та теги mateu-ui
.
HTML вище буде відображатися наступним чином:
Скріншот домашньої сторінки
Як будується меню?
Ця діаграма послідовностей показує, як меню програми динамічно конструюється під час виконання:
Діаграма послідовності побудови меню
Діаграма вище підкреслює, що це складання на стороні клієнта. Це веб-компонент Mateu, який працює в браузері, запитує необхідну інформацію з різних мікросервісів і потім конструює меню.
Як показано на наступних скріншотах, користувач сприймає меню як єдине, об'єднане ціле, хоча підменю визначаються різними мікросервісами.
Меню, що надходить з мікросервісу бронювання
Меню, що надходить з мікросервісу фінансів
Зверніть увагу, що коли будь-який з цих пунктів меню вибирається, мікрофронтенд з відповідного мікросервісу відображається користувачу. Додаючи ці меню до нашого додатку, ми ефективно підключаємо всі базові компоненти.
Crud для бронювань у кореневому додатку
Перегляди, що містять мікрофронтенди
Тепер давайте подивимося, як створити перегляд, що включає мікрофронтенд, визначений в іншому мікросервісі.
Ми використаємо перегляд бронювання як приклад складеного перегляду, який включає мікрофронтенди з інших піддоменів.
Перегляд бронювання відображатиме інформацію безпосередньо з бронювання, а також фінансове резюме у вигляді мікрофронтенду з фінансового обмеженого контексту.
Фінансовий Мікрофронтенд Резюме
Фінансовий мікрофронтенд резюме буде визначений за допомогою класу Java в фінансовому мікросервісі, як показано нижче:
// ЦЕ Є В ФІНАНСОВОМУ МІКРОСЕРВІСІ
@MateuUI("/financial/bookingreport")
@Service
@Title("")
public class BookingReport implements ConsumesContextData {
//...
@Output@Money
double value;
@Output@Money
double invoiced;
@Output@Money
double paid;
@Output@Money@Bold
double pending;
//...
@Button(type = ActionType.Secondary, target = ActionTarget.NewModal)
@Width("100%")
Callable createInvoice;
@Button(type = ActionType.Secondary, target = ActionTarget.NewModal)
@Width("100%")
Callable registerPayment;
/...
private void load() {
var data = getBookingReportDataUseCase
.handle(new GetBookingReportDataRequest(id));
this.value = data.value();
this.invoiced = data.invoiced();
this.paid = data.paid();
this.pending = data.value() - data.paid();
}
@Override
public void consume(Map context,
ServerHttpRequest serverHttpRequest) {
id = (String) context.getOrDefault("bookingId", "");
load();
}
}
Код вище визначає форму, яка відображає фінансову інформацію для бронювання, а нижче ви можете побачити, як вона виглядатиме для користувача, коли буде вбудована в UI.
Фінансова інформація по бронюванню в мікрофронтенді
ID бронювання отримується з даних контексту, які, як ми побачимо пізніше, будуть встановлені в BookingView
, коли ми визначимо об'єкт MicroFrontend
. Також зверніть увагу на маршрут, вказаний в анотації @MateuUI
(/financial/bookingreport
), який буде використаний пізніше, коли ми будемо визначати об'єкт MicroFrontend
.
Додавання Фінансового Мікрофронтенду Резюме до Перегляду Бронювання
Наш код для перегляду бронювання виглядатиме приблизно так:
// ЦЕ Є В МІКРОСЕРВІСІ БРОНЮВАННЯ
@Title("")
@Service
@Scope("prototype")
class BookingInfoSection implements HasStatus {
//...
@NotEmpty
String leadName;
@NotEmpty
String service;
@NotNull
LocalDate serviceStartDate;
@NotNull
LocalDate serviceEndDate;
@NotNull
BigDecimal value;
@MainAction // ...
BookingsCrud back() {
// ...
}
@MainAction // ...
Mono cancelBooking(BigDecimal newValue) {
// ...
}
@MainAction
Mono save() {
// ...
}
//...
}
@Service
@Scope("prototype")
public class BookingView implements Container {
//...
@HorizontalLayouted
List content;
//...
public Mono load(String id) {
this.id = id;
var financialSection = ;
main = new MyHorizontalLayout(bookingInfoSection, financialSection);
return findBookingUseCase.handle(
new FindBookingRequest(new BookingId(id)))
.map(i -> {
content = List.of(
new BookingInfoSection(
i.id(),
i.leadName(),
// ...
),
new MicroFrontend(
"/financial/bookingreport",
Map.of("bookingId", id))
);
// ...
return i;
}).then(Mono.just(this));
}
//...
}
У цьому фрагменті коду ми визначаємо клас Java під назвою BookingView
, який містить два компоненти, розташовані в горизонтальному лейауті. Перший компонент, BookingInfoSection
, є формою, яка дозволяє переглядати та редагувати деталі бронювання. Другий компонент, визначений за допомогою MicroFrontend
, представляє мікрофронтенд, що надходить з фінансового мікросервісу, як ми бачили раніше. Зверніть увагу, що значення, яке використовується при визначенні мікрофронтенду, є відносним URL для фінансового резюме: /financial/bookingreport
.
Додатково, ID бронювання передається через дані контексту.
Мікрофронтенд перегляду бронювання
В результаті, коли користувач переходить до перегляду деталей бронювання, він сприймає це як єдиний, об'єднаний вигляд, хоча насправді це композиція різних мікрофронтендів з різних мікросервісів.
Мікрофронтенди бронювання та фінансів разом
Зверніть увагу, що кнопки Створити рахунок та Зареєструвати платіж праворуч відкриватимуть модальні вікна та виконуватимуть дії, повністю контрольовані фінансовим мікросервісом, тоді як кнопки Скасувати бронювання та Зберегти зліва визначаються та управляються мікросервісом бронювання.
Давайте переглянемо, як це все працює
Як згадувалося раніше, середовище виконання Mateu — це фактично веб-компонент, що споживає простий REST API. Існують два основні веб-компоненти в Mateu на стороні фронтенду: mateu-ui та mateu-ux. Обидва компоненти приймають базовий URL для API як атрибут, що дозволяє підключати їх до різних кінцевих точок, сумісних з API.
Важливо зазначити, що це завжди екземпляри одного і того ж веб-компонента, які рендерять різний контент, залежно від даних, що отримуються з REST API, що надаються різними мікросервісами.
Як ми бачили, веб-компонент mateu-ui включає логіку для отримання віддалених меню, що дозволяє об'єднувати різні підменю з різних кінцевих точок, наданих різними мікросервісами. З іншого боку, веб-компонент mateu-ux може містити інші компоненти mateu-ux, кожен з яких має різні базові URL API, тим самим дозволяючи комбінувати кілька мікрофронтендів.
Огляд мікрофронтендів
Зверніть увагу, що бекенди Mateu не зберігають стан, що робить їх ідеальним вибором для нашої архітектури мікросервісів. Такий підхід дозволяє запускати кілька реплік кожного поду з можливістю перезапуску в будь-який час без впливу на функціональність системи.
Примітка: Зверніть увагу на Cross-Origin Resource Sharing (CORS). Оскільки ми використовуємо композицію на стороні клієнта, браузер блокуватиме будь-які HTTP-запити до URL-адрес за межами нашого домену. Якщо вам потрібно отримувати мікрофронтенди з різних доменів, вам необхідно додати відповідні заголовки Access-Control-Allow-Origin
до кінцевих точок.
Висновок
Ми успішно створили складний UI, розподілений по кількох мікрофронтендах, кожен з яких потенційно керується різними командами бекенду, які володіють своїми функціями від початку до кінця, з незалежними темпами розгортання.
Важливо відзначити мінімальну кількість коду, яку ми написали. Ми не використовували HTML або JavaScript і не писали жодних API для підтримки нашого UI.
Додатково, фронтенди Mateu — це, по суті, просто звичайні веб-компоненти. Ви можете вбудовувати їх куди завгодно — чи то в просту HTML-сторінку, Jamstack-додаток, побудований за допомогою React, Vue, Angular тощо, чи навіть в сайт на WordPress — як справжні мікрофронтенди.
Подяки
Щира подяка моїй дружині, Антонії, за рецензування цієї статті та зроблення її зрозумілою і доступною для всіх.
Джерела
Перекладено з: Mateu: a different approach to micro frontends