Event is over
Event is over

Building a Robust Integration Architecture with EventStoreDB for No/Low-Code Platform

Talk presentation

I will explore a real-world example in the logistics domain of how we built a robust and scalable integration architecture using .NET Core, Azure, EventStoreDB, and Power Platform. I will discuss how we addressed the challenges of integrating multiple data sources, including third-party systems through event-driven architecture, and also provide insights into using persistent subscriptions for event projections and efficient data synchronization between Dataverse and CosmosDB.

Serhii Kokhan
LogiqApps
  • With a journey spanning over 11 years in the IT industry, Serhii has worked across startups, and product-based and outsourcing companies, wearing different hats such as a backend developer, team lead, and architect.
  • His passion lies in high-load architecture, .NET, Azure, and DevOps. He is particularly interested in startups and cutting-edge technologies, constantly seeking new challenges that allow Serhii to leverage his skills and fuel his growth.
  • As an evangelist in knowledge sharing, he is an active open-source contributor, having spearheaded several projects like Carcass, Plea, and NCredStash on GitHub. Also, he contributes articles on LinkedIn, sharing insights, experiences, and opinions within the tech community.
  • LinkedIn, Twitter, GitHub, Medium

Talk transcription

Привіт всім! Мене звати Сергій, і я дуже радий бути сьогодні з вами, щоб поділитися цікавою інформацією щодо архітектури та розробки одного з проєктів, в якому я брав активну участь. Тема моєї доповіді - "Створення надійної інтеграційної архітектури за допомогою EventStoreDB для No-Code та Low-Code платформи". Перед тим як поглиблюватися в деталі системи, я хочу віддати хвилинку вашої увазі, розповідаючи трошки про себе, мою роботу і заняття в галузі ІТ. Я працюю в ІТ вже більше 11 років, пройшовши через різні компанії та області, включаючи розробку декількох стартапів. Моя основна експертиза - це .NET та Azure.

Також я активно займаюсь відкритим програмним забезпеченням, власник декількох бібліотек з інфраструктури та безпеки, а також інтеграцій .NET додатків, таких як Carcass, Encryptage, Plie та інші. У вільний час я також стараюсь писати статті на LinkedIn та Medium. Останні два роки я працюю в консалтинговій компанії Logic Apps, яка спеціалізується на технологіях Microsoft, таких як Dynamics 365, Power Platform, .NET та Azure. У компанії я є лідом в .NET відділі та виступаю архітектором на етапах Pre-Sales та Discovery.

Моя доповідь сьогодні буде мати такий план: спочатку я розповім про проєкт і його цілі, далі розглянемо проблеми інтеграції даних з різних джерел, архітектуру та розглянемо імплементацію деяких частин системи. В кінці моєї доповіді я розкажу трошки про No-Code, Low-Code платформу, яку ми обрали для клієнта. Тепер дозвольте мені розповісти трошки про сам проєкт. Наша історія починається з клієнта, який управляє великим автопарком і кожен день керує сотнями транспортних засобів. Це складне завдання, яке включає логістику, планування маршрутів та комунікацію між різними сторонами.

У клієнта була вже розроблена система, яка, хоч і в певному розумінні, задовольняла їх потреби, проте з часом вона стала застарілою, важко масштабованою та підтримуваною через монолітну архітектуру, застарілий технічний стек та відсутність відповідної кваліфікації у розробників через легасі-систему. Це призвело до проблем, таких як неефективне планування маршрутів через відсутність GPS-даних, відсутність реального часу в моніторингу автопарку та непослідовна комунікація через різні канали, такі як електронна пошта, телефонні дзвінки, смс та різні месенджери.

До того ж, інтерфейс користувача та його взаємодія із системою залишали бажати кращого. Існувало багато незручностей, зайвостей та витрат часу на аналогічні дії для користувачів. Також відсутність мобільного додатку робила систему менш гнучкою. Це лише огляд того, що ми плануємо розглянути сьогодні в рамках моєї презентації. Буду радий відповісти на будь-які питання в кінці. Дякую за увагу, і почнемо нашу подорож!

Обмежена, іноді повністю відсутня інтеграція зі сторонніми сервісами для постачальників та логістів. Ці проблеми ще більше загострювалися через відсутність інтеграції з системами GPS-трекінгу. Також із даною системою не могли інтегруватися інші провайдери, адже відсутній публічний клієнт для інтеграції. Нашою метою було не тільки вирішити ці проблеми, але й надати клієнту сучасне, гнучке рішення, на базі якого можна було б імплементувати його ідеї. Проєкт передбачав міграцію продакшн-даних з декількох джерел, їх надходження до інших частин майбутнього рішення.

У свою чергу, клієнт хотів рішення, побудоване на No-Code, Low-Code платформі для адаптації до нового рішення, для демонстрації та уніфікації робочих процесів та потоків даних, а також для побудови різноманітної звітності та легкого їх конфігурування. Його бачення передбачало створення мультітенансі Software as a Service CRM, яка задовольняла б потреби в логістичній галузі, охоплюючи системи управління автопарком та управління транспортом. Це CRM також повинно було забезпечувати інтеграцію з GPS-трекінг-сервісами та планування і оптимізацію маршрутів, надаючи можливість моніторингу місцезнаходження, швидкості та використання пального в режимі реального часу. Ми розпочали цей важкий шлях з розуміння проблем, що стояли перед нами, використовуючи можливості сучасних технологій, таких як .NET Core, IWAStoreDB, Power Platform, Azure та інші. Шлях був нелегким, але завдяки багатьом складовим ми в значній мірі подолали ці труднощі, створивши досить ефективне рішення для клієнта.

Сьогодні я з радістю поділюсь з вами архітектурними рішеннями та підходами, які ми використовували, щоб впоратися з цими викликами. Першим великим викликом, з яким ми стикнулися, була інтеграція даних. Дані, як ви всі розумієте, були ключем до інтеграції, і це становило основу для будь-якого бізнесу у будь-якій галузі. Наш клієнт не був винятком, і він використовував різноманітні системи, кожна з яких мала свої правила, схеми та обмеження. Дані, які зберігалися в цих системах, необхідно було консолідувати, трансформувати та інтегрувати в нашу нову систему. Серце міграції та інтеграції даних ми обрали IWAStoreDB, функціональну базу даних з відкритим вихідним кодом. Ця база даних була спеціально розроблена для роботи з даними за моделлю IWASource, і мала купу вбудованих можливостей, таких як потоки, підписки, проекції, добре виглядаючий адміністративний інтерфейс та можливість працювати як в премісі, так і в хмарі.

Тому нашим першим кроком було забезпечити міграцію та інтеграцію всіх даних до IWAStoreDB. Після того, як дані вже надходили туди, ми використали механізм підписок та проекцій для подальшої трансформації та інтеграції даних в інші бази даних, такі як Dataverse і MongoDB. Пізніше MongoDB ми замінили на CosmosDB. Розповім чому. І саме ці механізми дозволили нам реагувати на нові події в режимі реального часу та підтримувати систему в актуальному стані. Давайте зробимо крок назад і подивимося на загальну картину, розглянувши high-level архітектуру. Вона складається з п'яти основних рівень: BackOffice, Integration Middleware, сторона брокера, сторона постачальника та публічний API.

BackOffice інкапсулює в собі легасі системи клієнта, включаючи ті самі FMS, TMS та різні сторонні сервіси. Він слугує відправною точкою для подальших міграцій та інтеграцій даних. Під час розробки нової системи нам довелось розширити функціонал деяких легасі систем BackOffice, додавши інтеграцію з Azure ServiceBus, а також з іншими сервісами. Це дозволило нам надсилати дані безпосередньо до нашого нового рішення для їх подальшої обробки та збереження.

Далі у нас є Integration Middleware, який є проміжним компонентом для інтеграції даних. Це один з найважливіших компонентів системи, який виступає мостом між BackOffice і рештою нашого рішення. Він охоплює ряд сервісів, таких як Azure ServiceBus, Azure Function, а також EventStoreDB та Azure CosmosDB, які ми використовуємо для запису контрольних точок подій, так званих чекпоінтів, і додаткових метаданих для трансформації вхідних даних. Основне завдання Integration Middleware – підписка на черги та отримання повідомлень і подальше перетворення даних у наш канонічний формат за допомогою Rule Engine і зберігання їх в EventStoreDB за допомогою DataPush.

Брокерська частина системи, яку ми розробили, є No-Code рішення на базі Power Platform. Цей компонент орієнтований на брокерські команди та їх бізнес-підрозділи, надає рішення для їхніх індивідуальних процесів, потоків даних та потреб у різноманітній звітності. Сторона постачальника задовольняє потреби клієнтів та логістів і виконання їх повсякденних операцій. Ця частина включає в себе різноманітні рішення, які можуть бути використовувані на базі Power Platform. Це можуть бути Azure Cosmos DB, Back-End, Front-End та мобільні додатки. Нарешті, публічне API має надавати доступ до даних системи ззовні за моделью підписок, але команда почала його тільки розробляти.

Integration Middleware, як я вже казав, є вхідною точкою для міграції та інтеграції даних. Головну роль в цьому відіграє Azure ServiceBus. Він слугує центральним каналам зв'язку між BackOffice на базі Power Platform та нашим новим рішенням. Як видно на схемі, продюсери відправляють повідомлення, де вони спрямовуються в певну чергу. Після надходження повідомлень в черги, спрацьовують Azure Functions по триггерам і обробляють їх. Кожна функція призначена для реагування на певні типи подій в певному агрегаті, наприклад, оновлення статусу транспортного засобу чи реєстрація нового маршруту.

Ми додали певний механізм обробки вхідних повідомлень за певними правилами та критеріями і подальшим трансформуванням їх в канонічні структури, які наша система може зрозуміти і ефективно обробити. Цей механізм ми назвали Rule Engine. Правила трансформування можуть включати перейменування полів, зміну типів даних або агрегацію декількох полів в одне поле, і багато інших. Після того, як наші дані трансформовані, вони потрапляють у потоки EventStoreDB за допомогою механізму DataPusher для подальшої інтеграції з іншими частинами системи.

Давайте розглянемо абстракцію агрегата та приклади його імплементації. Кожен агрегат має ID для його ідентифікації, Version, який відображає версію змін над агрегатом, та History, де зберігаються усі зміни по даному агрегату. Метод WAN слугує роутером для вхідних подій агрегата, викликаючи відповідний метод агрегата в залежності від типу події. Метод Apply викликається в методах дочірнього класу кожен раз, коли нова подія надходить до агрегату, додаючи цю зміну до колекції History.

Метод Load призначений для побудови агрегата і прогонки подій від певної версії. Ось приклад агрегата BrokerJob, який відповідає за задачі, які назначаються на певного брокера в компанії певним клієнтам. Всі агрегати системи мають унаслідувати клас Aggregate, а також на кожен агрегат навішується атрибут AggregateVersion для вказання версії схеми агрегата. Метод WAN слугує роутером для вхідних подій агрегата. Якщо прийшла подія типу appendedBrokerJobEvent, тоді ми викликаємо метод onAppended і передаємо в нього цю подію. Метод onAppended відповідає за логіку Absert даних з вхідної події в Massive Jobs і додає її до агрегату. Метод Append є контрактом для роботи з агрегатом ззовні і має викликатися клієнтським кодом для зміни стану агрегату. EventStoreDB дозволяє не тільки відстежувати поточний стан даних, але також забезпечує сторічний запис усіх змін, що дозволяє аудитувати їх та робити запити та відтворювати події в певному проміжку часу, якщо це необхідно.

EventStoreDB, хоч і є супершвидкою базою даних, але при зростанні обсягу даних та кількості подій читання цілого потоку подій може стати менш продуктивним. Тут з'являється концепція знімків (SnipShots). Знімок - це збережений стан потоку в певний момент часу. Коли ми хочемо прочитати потік, ми спочатку вичитуємо найсвіжіший знімок, а потім лише події після нього. Це дозволяє значно зменшити навантаження на базу даних і оптимізує читання. Спочатку ми вирішили зберігати ці знімки в MongoDB, який потім мігрував на Azure Cosmos DB через його простоту. Однак зберігання знімків в окремій базі даних створює додатковий рівень складності та витрат на керування іншою базою. Також існує ризик неузгодженості даних у разі збоїв, оскільки знімки не завжди є транзакційно узгодженими з потоками EventStoreDB.

У майбутньому ми плануємо перейти від зберігання знімків в Azure Cosmos DB до EventStoreDB. Підписки та проекції є важливими механізмами інтеграційної архітектури, що полегшують синхронізацію та трансформацію даних у реальному часі між частинами системи. Persistent Subscriptions в EventStoreDB дозволяють клієнтам підписатися на потік і отримувати події по мірі їх надходження. Однак впорядкування подій в потоці не гарантується через можливість наявності декількох консюмерів. Ми використовуємо Persistent Subscriptions як механізм сповіщення про нові події в потоці, оброблюючи їх для забезпечення цілісності даних та гарантованого порядку обробки. Схема вичитування подій з потоку та їх обробки включає кілька етапів. До подальшого детального розгляду цих етапів.

Контрольна точка в даному випадку представляє собою позицію останньої обробленої події у потоці подій. Якщо порядковий номер вхідної події менший за збережену позицію, то ця подія вже була прочитана, і ми пропускаємо її, очікуючи наступну. Якщо це не так, ми генеруємо нову позицію для зчитування подій, інкрементуючи поточну позицію на одиницю. Після цього ми читаємо події з потоку, починаючи з нової позиції, гарантуючи правильний порядок обробки подій. У випадку, якщо виникає помилка читання подій з потоку (наприклад, якщо потік вже видалено або назва потоку неправильна), метод toListAsync просто викине виняток streamNotFoundException. У даному випадку це є нормальною штатною ситуацією.

В EventStoreDB потоки можуть бути користувацькими або системними. Системні потоки мають префікс $, наприклад, $Settings. Також існує концепція метаданих для потоків, які починаються з префіксу $. Наприклад, для потоку $Settings відповідний потік метаданих матиме назву $$$Settings. Потоки метаданих для користувацьких потоків мають префікс $. Важливим етапом є оновлення кожної події до останньої версії схеми. Це важливо, оскільки схеми подій можуть змінюватися з часом. Під час цього оновлення застосовуються зміни до агрегату, і процес проекції поточної події в інші сховища даних гарантується.

Останнім етапом є зберігання контрольної точки, що дозволяє відстежувати, де було зупинено обробку подій, забезпечуючи обробку кожної події рівно один раз. Рядок 1 коду, при виклику метода SaveCheckPointAsync, зберігає контрольну точку, використовуючи останню позицію події. Це важливо для відстеження прогресу та забезпечення обробки кожної події рівно один раз. Щодо функціоналу, пов'язаного з роботою з агрегатами, доменними подіями, обробниками оновлень, чекпоінтами, снапшот-репозиторіями та іншими аспектами, ви можете знайти його на нашому GitHub-проєкті Carcass, який є у вільно доступним. Щодо проекцій даних, ми визначили два типи обробників: синхронні та асинхронні. Кожен з них має свої переваги та недоліки.

Синхронні обробники проекцій корисні, коли потрібно зберігати дані в єдиній базі та коли важливо забезпечити сильну узгодженість даних. Вони також спрощують обробку помилок, а їх retry policy механізм полегшує виявлення та обробку помилок. Проте, вони можуть бути повільнішими, особливо якщо виникають блокування через чекання завершення кожної операції. Масштабування таких обробників може бути складним, і вони можуть втрачати прогрес при перериванні або виході з ладу. Асинхронні обробники проекцій корисні, коли є кілька цільових сховищ даних, і коли продуктивність та масштабованість важливі. Проте вони можуть ускладнити управління та координацію завдань. Вибір між цими двома підходами включає в себе баланс між узгодженістю, продуктивністю, масштабованістю та складністю, враховуючи конкретні вимоги вашої системи.

Наш згаданий використаний Microsoft Power Platform є No-Code та Low-Code платформою, що дозволяє як технічним спеціалістам, так і бізнес-користувачам швидко створювати рішення за допомогою мінімізації написання коду та використання готових компонентів та інтеграцій. Це є однією з технологій, що допомагає оптимізувати операційну діяльність, підвищити ефективність та сприяти інноваціям.

Отже, перший – це PowerApps. Він дозволяє швидко створювати застосунки, не потребуючи знань програмування, так як з коробки поставляється широкий набір вбудованих функцій та UAC-компонентів. За допомогою drag-and-drop, binding, викликів інших компонентів системи, PowerApps допомагає дуже швидко побудувати інтерфейс з робочим функціоналом. Далі у нас є Power Automate, раніше відомий як Microsoft Flow. Він дозволяє автоматизувати рутинні задачі, повторювані завдання та бізнес-процеси. Power BI – це інструмент бізнес-аналітики для побудови різноманітних звітів та візуалізації даних. Power Virtual Agents дозволяє створювати чат-ботів за допомогою UAC-компонентів без написання коду.

Ну і база даних Dataverse, про яку ми вже згадували не один раз, вище є серцем всієї Power Platform, так як в ній зберігається велика кількість інформації для функціонування усієї платформи. Використовуючи Power Platform, ми змогли створити досить гнучке рішення, яке спростило та модернізувало частину, пов'язану з брокерами, надавши їм інструменти для швидкої адаптації, до змін в бізнес-процесах, автоматизації завдань візуалізації та аналізу даних.

Але при використанні будь-якої платформи, сервісу чи фреймворку, у вас в тій чи іншій мірі будуть якісь обмеження. Power Platform не є винятком. Далі піде мова саме про ці обмеження і їх можливі вирішення. Проблема перша, з якою ми зіткнулися, це було щитування і запис даних у великих об'ємах. Обмеження на запис і щитування в Power Platform становить 6000 запитів на 5 грудня. Після перевищення цього ліміту API вертає HTTP 429 Too Many Requests. Рішенням може бути імплементація механізму retry policy, використання batch requests для оптимізації записів та імплементація механізму використання мультиаккаунтів в запитах.

Проблема друга полягає в тому, що за замовченням Dataverse може зберігати близько 3 гігабайт даних з логфайлом приблизно 1 гігабайта. Рішення є досить простим: докупити і розширити об'єми даних на стільки, наскільки ви потребуєте. Проблема третя пов'язана з типами даних в Dataverse. Так як вони мають свою специфіку, і напряму записати значення в поле деякого типу не вийде, треба чітко розуміти специфіку типів даних, з якими ви працюєте, та імплементувати preprocessing.

Проблема четверта пов'язана з SDK. Дуже довгий час він був написаний під .NET 4, і використання його в .NET Core не було можливим. Як варіант, можна використовувати і хрест типів. Але десь 3 роки тому Microsoft почала міграцію SDK під .NET Core. На жаль, в ньому є обмеження. І одне з них — це неможливість розробки плагінів. Тут якби немає чіткого рішення. Потрібно розуміти, що ви можете імплементувати за допомогою нового SDK, а що прийдеться імплементувати на старому.

Ну і остання проблема — це непідтримка з коробки сторонніх identity провайдерів для авторизації в Canvas. Наприклад, у вас є API, який закритий авторизацією через хмару. Якщо вам потрібно в Canvas App викликати цей API, передавши бар'єрний токен користувача, це можуть бути деякі проблеми. Так як стандартний механізм авторизації побудований на Azure AD, і плюс там ще є своя специфіка, як Power Platform працює з цією авторизацією. Простого рішення немає. І потрібно буде писати логіку на Power Automate. Додавати кастомний коннектор з авторизацією. А також це все якимось чином вбудовувати в Canvas App.

Ну що, ось така у мене вийшла доповідь. Що б хотів ще сказати на останок. Успіх проєкту дуже сильно залежить від людей, команди, технологій, клієнта, процесів, планування, архітектури, інтеграції. Якщо в усьому цьому знайти правильний баланс. Виклики, з якими нам вдавалося зіткнутися, і уроки, отримані на цьому шляху, дали нам цінну інформацію для наших майбутніх проєктів. Дякую організаторам за можливість виступити на даній конференції. І всім вам за витрачений час на прослуховування моєї доповіді. Зараз відповім на всі ваші запитання.

Sign in
Or by mail
Sign in
Or by mail
Register with email
Register with email
Forgot password?