Перехід на мікросервіси: власний досвід, переваги та недоліки

Привіт, мене звати Андрій Береза. Я — Architect в SPD Technology, маю 12 років досвіду в IT, працюю з продуктом у галузі фінтех та хочу поділитися досвідом переходу з монолітної архітектури на мікросервісну, зокрема — з монолітного бекенду.

Детальніше про технічні виклики й на що потрібно звернути увагу під час переходу розповім на прикладах у матеріалі. Маю надію, що цей досвід стане у пригоді для команд, які тільки міркують над подібним переходом та хочуть зібрати повну картину вхідних даних, щоб уникнути помилок.

Як цей процес починався. Причини переходу

Шість років тому, коли ми почали процес переходу, продукт складався з моноліту, який називався «платформа». Це був масив даних з майже мільйоном рядків коду, у якому працювали всі команди. Та з часом такий підхід для нас почав приносити низку незручностей.

По-перше, було незрозуміло, хто відповідав за певну частину коду; по-друге, стало важко передбачити наслідки змін у великому моноліті, який має дев’ятирічну історію; по-третє, ми хотіли далі масштабувати продукт та команду, і перші дві проблеми тільки поглиблювалися.

Тому для початку нам треба було вирішувати, як зробити так, аби команди могли працювати незалежно, та як продукт з такою «багатою» історією зміг би, не знижуючи темпи розвитку, витримувати ще більші навантаження і спокійно масштабуватися далі.

Технічні виклики на шляху

Складна доменна сфера

Через те, що наш продукт — це масштабна екосистема, яка має безліч додаткових сервісів та щільну взаємодію між кожним елементом, одним з головних челенджів переходу була складна доменна сфера.

Наприклад, якщо почати ділити на домени проєкт інтернет-магазину, то все доволі легко — є товари, покупки, список бажань користувача тощо. У нашому ж випадку цей процес був і все ще залишається важким, бо перед нами постає складний вибір: що є окремим доменом — бізнес сутність чи бізнес-кейс, і яка команда повинна ним володіти?

Наприклад, є поняття «інвестиції», в якому є компанія, в яку інвестують, і є інвестор, й іноді не зрозуміло, чи це має належати до домену «інвесторів», чи до «компаній», чи взагалі бути окремим доменом «інвестування». І подібних ситуацій безліч.

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

Стандартизація підходів

Додатковим викликом було розповсюдження нових стандартизованих підходів між командами.

Якщо до цього у нас була одна кодова база з однаковими підходами від самого її започаткування, та знання передавалися «з покоління в покоління», коли з’явилися мікросервіси — виникла необхідність описувати нові стандарти.

Наприклад, які фреймворки використовуємо під час роботи, в якому стилі пишемо код тощо. Хоч і однією з особливостей мікросервісів є можливість використання різних технічних стеків, для себе ми визначили, що будемо працювати з уже напрацьованою і стандартною зв’язкою Java + Spring для 90% системи.

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

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

Утворення DevOps команди

Ріст кількості сервісів, які треба деплоїти, призвів до ускладнення всієї інфраструктури, яку необхідно налаштувати, стандартизувати та забезпечити інструментами, а головне — навчити розробників працювати з цим всім.

Це не було проблемою, скоріше доволі складною і тривалою задачею, що затягнулася майже на рік, а може і більше. Тільки тоді основні кроки в напрямі інфраструктури були зроблені. Усім, що описано вище, займалась новостворена команда DevOps.

Налаштування процесу моніторингу

Моніторинг є важливою передумовою для переходу на мікросервіси. Бо для того, щоб зрозуміти, що відбувається в системі з купою рухомих частин, без, як мінімум, метрик і логування неможливо обійтись. В ідеалі б мати ще Distributed Tracing, але навіть просто коли Trace Id в логах — це величезне полегшення життя.

Ця задача також лягла на вже і так навантажені плечі DevOps, але вони з нею чудово впоралися (за стандартами того часу).

Нові технічні рішення: підхід Back-end for Front-end

Декілька років ми ударними темпами нарощували кількість мікросервісів, а з ними паралельно зростала і кількість функціонала на нашому основному вебзастосунку (платформі). Тож через деякий час ми зрозуміли, що навіть прошарок, який просто бере дані з мікросервісів, агрегує і трансформує їх у вигляд, який потрібен для Front-end, дуже розширився, і з’явилася потреба розібратися і з цим.

Спочатку ми пішли шляхом, коли команди комітили в один моноліт з цим вебзастосунком, але дуже швидко всі почали наступати одне одному на ноги, почались непорозуміння, розсинхрон тощо. Ми вирішили розділити його на частини, відповідно до фіч, і кожну частину віддати в ту команду, що володіє відповідною фічою, асоційованою з «доменом».

Це не новий підхід, у світі він називається Back-end for Front-end (BFF). На той момент Front-end в нас ще був монолітний, і для того, щоб зробити цю розбивку непомітною для нього та нічого не зламати, ми вбудували API Gateway поперед усіх BFF: тобто Front-end код робить запити на API Gateway, а той вже знає, на який саме BFF його перенаправляти залежно від url. Наприклад, якщо вона (url) починається з profiles/ — API Gateway передає запит на Profile BFF.

Факапи у процесі переходу

Не все було так гладко під час переходу, як хотілося. Наприклад, в нашому продукті існує один величезний пошуковий engine. Він називається Advanced Search і вміє шукати, фільтрувати, сортувати й агрегувати майже всі бізнес-дані, які є в нашій системі.

Коли перед нами стояла задача швидко розширити команду, і ми змогли це зробити лише в Києві (основна частина спеціалістів тоді була — і здебільшого зараз перебуває — в Черкасах), треба було вирішувати, над якою частиною будуть працювати нові колеги.

Зрештою ми віддали їм частину, що відображає результати цього Advanced Search. А частину, що шукає у даних, Search Engine, залишили для команди в основній локації через специфіку експертизи і неможливості її легкої передачі.

І, сюрприз-сюрприз, виявилось, що для внесення змін між ними повинна бути тісна комунікація, бо щойно додаються нові дані в системі, їх треба одночасно додати і в ці два компоненти, а ще тестувати та релізити.

Таке переважно штучне розділення породило тонну надлишкових координацій, конфліктів та знизило загальну ефективність технічного рішення системи пошуку, зокрема внаслідок необхідності введення формального АРІ, на якому втрачається додатковий час як під час розробки, так і під час міжсервісної взаємодії при обміні величезними об’ємами даних.

Додам маленьку ремарку, що на той час не було взагалі такого поняття як «ремоут», усі команди працювали в різних офісах та містах. Зараз це вже одна команда, що володіє обома частинами, а цей компонент пошуку має шанс трансформуватися в більш ефективний уніфікований компонент відповідно до reverse Convey Law.

Насправді хиб та помилок було чимало, але всі вони були дрібними, локальними, бо сам процес переходу від моноліта достатньо непростий і іноді дуже рутинний, але доволі передбачуваний, якщо послідовно дотримуватися критеріїв щодо лінії розділення. Коли я ще був розробником, то, бувало, засідав на декілька місяців, щоб відокремити певний мікросервіс з моноліта.

Переваги мікросервісів

Можливість масштабування. Команди можуть планувати, розробляти та деліверити кожен свою частину, максимально незалежно від інших команд.

Здатність витримувати більше навантаження. Через те, що мікросервіси легко масштабуються, то можна, піднімаючи кількість інстансів, витримати більшу кількість користувачів і даних.

Поєднання різних мов програмування. У мікросервісній архітектурі використання декількох мов програмування не є проблемою. Наприклад, в нашому продукті добре співпрацюють Java, Kotlin та Python, розв’язуючи конкретні задачі найкращим чином.

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

Недоліки мікросервісів

Збільшення когнітивного навантаження. Якщо в моноліті треба лише знати, як додати код в наявний сервіс, то з мікросервісами розробники повинні вміти розгортати та підтримувати нові сервіси, а також пам’ятати про своє оточення і залежності.

Складність інфраструктури. З монолітом стандартизація інфраструктури не дуже потрібна через те, що компонентів дуже мало, і можна їх деплоїти, моніторити, як забажається. Мікросервіси потребують стандартних підходів у всьому — деплойменті, моніторингу, build-процесі тощо.

Підтримка технічного стека в актуальному стані. З ростом кількості сервісів виникає більше репозиторіїв, в яких необхідно оновлювати бібліотеки й технічний стек.

Цього можна не робити, але тоді треба враховувати, що за впровадження якихось загальних бібліотек або підходів варто взяти до уваги весь спектр версій технічного стека. Згадати б просто Log4shell вразливість і 100+ сервісів, де треба проапдейтити ліби.

Ненадійність мережі. Якщо в моноліті ви на 99% впевнені, що за умови виклику методу виконається очікуваний код, то з мікросервісами ви повинні бути на 99% готовими, що рано чи пізно він не виконається:)

Для цього треба підстрахуватися та на всіх викликах мати retry, timeout, а також circuit breaker, щоб в разі чого не перевантажувати додатково сервіс. (І бажано це якось протестувати ще до продакшена).

Необхідність розуміння меж доменів. Це не недолік, але передумова. Коли проєкт тільки починається, не завжди зрозуміло, де закінчується один домен, а де починається інший.

Якщо почати розробку в мікросервісному стилі без цього розуміння і неправильно поділити на домени, можна потрапити в ситуацію, коли розробка потребуватиме величезної кількості мітингів, координації й технічних «милиць».

Висновок

Попри всі технічні виклики на шляху до мікросервісної архітектури, на цей момент 90% нашої системи вже є мікросервісною архітектурою. А 10 % — це все ще моноліти, які поки не до кінця задекаплені.

Їх також можна розділити на окремі мікросервіси, проте ми цього не робимо. Бо, по-перше, це не пріоритет, по-друге, ці шматочки статичні, тобто в них нічого не змінюється і не додається нового.

Взагалі розділення моноліта на мікросервіси технічно нескладне. Єдина проблема визначити, коли це робити та кому це робити. І чи є сенс для бізнесу це робити? Адже монолітна і мікросервісна архітектури це не етапи еволюції, а просто дві архітектурні парадигми, які використовуються для різних організаційних структур і схем масштабування.

👍ПодобаєтьсяСподобалось18
До обраногоВ обраному8
LinkedIn
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

Що толкового з літератури можете порадити почитати по мікросервісам?

Наприклад, якщо почати ділити на домени проєкт інтернет-магазину, то все доволі легко — є товари, покупки, список бажань користувача тощо

Всі пишуть, саме цей трешовий приклад, це дуже не мікросервіс.Хто взагалі придумав це? Гарним прикладом — було б генерація зображень, авторизація, відправка до пос.

Хто взагалі придумав це?

Любителі дистрибютед транзакцій в тих місцях де можна було б обійтись без цього трешу😂

Хто взагалі придумав це?

Amazon це придумав, це їх ахітектура — ну як їх, їх реалізація гетерогенного SOA. Так то усе було придумано ще під час проектування такої системи як Multics uk.wikipedia.org/wiki/Multics вченими з MIT та інженерами Bell Labs і має довгу історію розвинення. За великим рахунком Java EE від Sun Microsystems та Bea Technologies (обидві поглинені Oracle) базуються на тих самих засадах.
Потрібно пояснювати чому в книгах техрайтери AWS як то Кріс Рідчардсон скажімо, беруть в якості прикладу e Commerce ?
А так то в цілому цікаві речі теж бувають модними.

Хоча цікавлюсь мікросервісами, і у загальних рисах знаю їхню архітектуру, але не бачу у них потреби, якщо не розробляється проект масштабу інтернет магазина Розетка. Якщо використовується модульний моноліт, то він вирішує майже усі проблеми, які можуть вирішити мікросервіси. У таких монолітах:
1. і домени можна поділити;
2. і багато команд можуть працювати строго над своїми модулями без гіт-конфліктів;
3. і деплой від окремих команд проходить без проблем. Щоправда тут у мікросервісів є хоча й несуттєва, але все ж перевага, бо якщо новий код завалить процес, то упаде весь застосунок, а не окремий сервіс (не знаю як у Java + Spring, а на NodeJS таке може статись). Але якщо код тестується і ловляться помилки, то таке може статись вкрай рідко.

Моноліт не може забезпечити хіба що написання різних сервісів на різних мовах програмування, але то теж навряд чи часто використовується, тому в такому разі можна використовувати просто декілька монолітів.

Щодо «моноліт вижирає гігабайти пам’яті», то використовуйте моноліт на базі NodeJS, який буде споживати мегабайти а не гігабайти.

Хоча цікавлюсь мікросервісами, і у загальних рисах знаю їхню архітектуру, але не бачу у них потреби, якщо не розробляється проект масштабу інтернет магазина Розетка.

Я могу оценивать конечно только приблизительно, но розетка выглядит как маленький проектик по сравнению с теми монолитами с которыми я работал, где даже в пределах 1-2 смежных бизнес модулей разработчик вникает за 2-3 года (а таких модулей до 300).

Ну от над цим маленьким проектом працювало у 2021 50 команд і півтисячи людей в it з загальним штатом 5к людей
ain.ua/...​21/06/21/it-ofis-rozetka

Ваш моноліт це що тоді цікаво os windows, erp sap, oracle database? :D

OS Windows не уся моноліт, там є компоненти COM

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

Наприклад, хтось має щодня релізитись, а хтось інший потребує тиждень тестування через критичність задачі.

Не бачу тут проблем. Різні команди працюють в різних гіт-гілках і, що найважливіше, у різних модулях, які знаходяться у різних каталогах. Потім ті, кому треба часто релізитись, роблять мердж у головну гілку, а ті, кому треба не так часто релізитись, працюють собі в окремій гілці скільки їм треба, і коли прийшов час їхнього релізу без жодних гіт-конфліктів роблять мердж у головну гілку. Гіт-конфліктів ніколи не буде, якщо різні команди працюють з різними модулями, причому їм навіть не обов’язково підтягувати зміни «чужої» команди.

Питання не (лише) в конфліктах.
Один аспект — що перезаливати весь движок на сервері — чи навіть на машинах клієнта — бо хтось десь умовно «галочку пересунув» — не завжди файно.
Інший аспект — щоб змінити інтерфейс центрального модулю в моноліті з прямими викликами треба переписати усе, що його використовує. Отут буде трешачок та конфлікти. Сервіси можуть підтримувати кілька версій інтерфейсу одночасно, і перехід поступовий.
Ще аспект що бага в команди, котра щодня релізиться, покладе усю систему, якщо воно в одному процесі. Навіть коли в них не креш а лік.

Інший аспект — щоб змінити інтерфейс центрального модулю в моноліті з прямими викликами треба переписати усе, що його використовує. Отут буде трешачок та конфлікти. Сервіси можуть підтримувати кілька версій інтерфейсу одночасно, і перехід поступовий.

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

Точно так само як і мікросервіси, модулі можуть мати свої окремі версії, причому не обов’язково весь застосунок повинен змінювати версію, якщо зміна відбулась лише в окремому модулі. Щоправда в такому разі треба буде додатково придумувати якийсь механізм контролю сумісних версій різних модулів, але мікросервісам цей самий механізм також потрібний... ах, он воно що! =) Мікросерсіси тут справді мають перевагу, бо вони можуть використовувати стандартний механізм конфігураційних файлів для різних пакетних менеджерів (наприклад, npm, yarn і т.д. в разі роботи з NodeJS), які їх будуть ставити.

Останній аспект справді має суттєву перевагу на користь мікросервісів.

А от щодо

перезаливати весь движок на сервері — чи навіть на машинах клієнта — бо хтось десь умовно «галочку пересунув» — не завжди файно

то для скриптових мов це не проблема, бо вони можуть перезалити строго змінені файли.

Так, я пішов поїв, і тепер бачу, що я погарячився з останньою перевагою стосовно мікросервісів =). Конфігураційні файли пакетних менеджерів можна використовувати тільки для монолітів.

Припустимо, якщо б сьогодні був реліз v2 певного мікросервіса, який має новий інтерфейс, і разом з ним одночасно працювала v1 цього мікросервіса зі старим інтерфейсом, то залежні сервіси б не змогли використовувати стандарний конфігураційний файл пакетних менеджерів. Таки потрібен окремий механізм, який слідкує за сумісністю.

1. Якщо нема конкретного овнершіпа конкретного репозиторію (а якщо кілька команд комітять в одне репо то саме так і буде) — завжди буде бардак і кожен буде тягнутиме ковдру на себе з часто взаємовиключними цілями (бо одній тімі треба оптимізувати перфоменс, а іншій чимпошвидше релізнути новий функціонал, незважаючи на створення технічного боргу і потенційних проблем). Також неясно що робити з edge cases, коли проблема виникає не через конкретний модуль/команду, але афектить аппку загалом — ресурси на «пошук крайнього» і зайва комунікація.
2. Реліз процес, який вимагає погодження не лише від ПМ/ПО, а організаційно на рівень вище зажди буде гальмувати делівері фіч всім командам.
3. Один пакетний менеджер на весь продукт — через кілька років девелопменту виявиться, що певний легасі модуль хоче бібліотеку 1.x.x , а команда почала девелопити новий модуль і хоче 2.х.х, який ламає легасі модуль — або витрачаєте час на рефакторінг легасі (який і так працює і рефакторінг не має жодного бізнес велю), або розробка нового компоненту починається на застарілому стеку.
4. Раптово в продакшені починають зявлятись проблеми з перфоменсом або непередбачувані падіння апки — додаткові ресурси на локалізацію проблемного модуля.
5. Час онбордінгу нового колеги в девелопмент моноліта завжди буде більшим, чим онбордінг в простіший мікросервіс

1. Якщо нема конкретного овнершіпа конкретного репозиторію (а якщо кілька команд комітять в одне репо то саме так і буде) — завжди буде бардак і кожен буде тягнутиме ковдру на себе з часто взаємовиключними цілями (бо одній тімі треба оптимізувати перфоменс, а іншій чимпошвидше релізнути новий функціонал, незважаючи на створення технічного боргу і потенційних проблем). Також неясно що робити з edge cases, коли проблема виникає не через конкретний модуль/команду, але афектить аппку загалом — ресурси на «пошук крайнього» і зайва комунікація.

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

Не знаю про який саме бардак ви кажете, наведіть конкретні приклади. Бо коли ви кажете «одній тімі треба оптимізувати перфоменс, а іншій чимпошвидше релізнути новий функціонал, незважаючи на створення технічного боргу і потенційних проблем», то не зрозуміло на якому рівні будуть проблеми. Я собі уявляю що дві команди працюють в окремих гіт-гілках і роблять мерджі в головну гілку, коли їм треба. Чому раптом виникають проблеми?

2. Реліз процес, який вимагає погодження не лише від ПМ/ПО, а організаційно на рівень вище зажди буде гальмувати делівері фіч всім командам.

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

3. Один пакетний менеджер на весь продукт — через кілька років девелопменту виявиться, що певний легасі модуль хоче бібліотеку 1.x.x , а команда почала девелопити новий модуль і хоче 2.х.х, який ламає легасі модуль — або витрачаєте час на рефакторінг легасі (який і так працює і рефакторінг не має жодного бізнес велю), або розробка нового компоненту починається на застарілому стеку.

Ось це вже справді суттєвий плюс на користь мікросервісів, погоджуюсь. Але на скільки часто таке буває?

4. Раптово в продакшені починають зявлятись проблеми з перфоменсом або непередбачувані падіння апки — додаткові ресурси на локалізацію проблемного модуля.

Ну якщо саме апка гальмує, то ресурси для локалізації, який мікросервіс винен в цьому, також потрібні.

5. Час онбордінгу нового колеги в девелопмент моноліта завжди буде більшим, чим онбордінг в простіший мікросервіс

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

Не знаю про який саме бардак ви кажете, наведіть конкретні приклади. Бо коли ви кажете «одній тімі треба оптимізувати перфоменс, а іншій чимпошвидше релізнути новий функціонал, незважаючи на створення технічного боргу і потенційних проблем», то не зрозуміло на якому рівні будуть проблеми. Я собі уявляю що дві команди працюють в окремих гіт-гілках і роблять мерджі в головну гілку, коли їм треба. Чому раптом виникають проблеми?

Наприклад, дзвонить Джон Сміт з Legal-відділу і каже, що один з ключових клієнтів занепокоєний, що в модулі Х, який використовує лібу Y версії 1.2.3 у якій виявлено критичний CVE і потрібно терміново фіксити, а щоб пофіксити потірбно оновити лібу до 1.4.0, але 1.4.0 не підтримується у вашій версії платформи/компілятора/фреймворка і вам потрібно апдейтити платформ/компілятор/фреймворк, що, безумовно, заафектить не лише модулі в межах овнершіпа вашої команди, а усю монолітну аппку загалом. А Джесіка Купер з маркетингу панікує, що конкурент анонсував нову фічу і в сусідньої команди приоритет 0 зробити фічу у відповідь у короткий термін і апдейт платформи (навіть якщо це нічого не поламає, як мінімум, потрібно перететстувати весь функціонал) явно не входить в їхні плани. Ось і конфлікт інтересів кількох команд.

Ось це вже справді суттєвий плюс на користь мікросервісів, погоджуюсь. Але на скільки часто таке буває?

Якщо це проект не на першому-другому році життя, а вже почали депрекейтитись його залежності — це прямо коммон кейс.

Ну якщо саме апка гальмує, то ресурси для локалізації, який мікросервіс винен в цьому, також потрібні.

На порядок менше ресурсу потрібно. Як мінімум видно спайки в моніторингу конкретних мікросервісів. Навіть якщо в моноліті є і працює APM це не завжди панацея, бо це удже відносні метрики. А якщо, наприклад, апка починає текти памяттю і вбивається OOM Killer’ом без адекватного кор дампу і стек трейсів — веселі години на інцедент менеджмент мітингу з десятком коман, де всі показують пальцем одні на других.

Думаю що ви це говорите в теорії, а не зі своєї практики.

Саме з досвіду експлуатації як монолітів, так і мікросервісів, яким 5+ років і вони досі в стані активної розробки. Так от траблшутінг мікросервісів значно простіший (принаймі в пошуку овнера баги)

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

Чим саме легше розібратись в модулі, чим в мікросервісі? Модуль в межах моноліту не є атомарним і, як мінімум, має shared-частину, яку не можна чіпати, бо це афектить інших, і це не завжди очевидно для нових колег. А весь проект знати середньостатистичному мідлу і не потрібно для того, щоб почати перформити в межах свого скоупу

Цікаво як вирішили проблеми подвійного запису, та опрацювання від збоїв ? Які патерни використовували : distributed modular monolit, database per service, SAGA — orchestration (з опису схоже що саме окестрація), SAGA — choreography, parallel pipelines ? Чи є в системі API gateway , elastic load balancing, двофакторна аунтифікація та single sign on — на базі яких інструментів і т.д. ?

Проблеми подвійного запису не було так як міграція відбувалась так — пишемо окремий сервіс, переписуємо моноліт щоб він використовував під капотом цей сервіс. На вихідних все стопаємо, мігруємо дані з монолітної бази в сервісну і деплоїмо це все. В сучасному світі це не дуже підхід, але на той час ми мали таку можливість і мали окремі довгоживучі гілки де ми ці всі рефакторінги робили. Зараз в нас вже більше scaled trunk based підхід де довгоживучі гілки це антипатерн :). АПІ гейтвей є, але тільки для наших BFF, бо в нас є по факту величезний веб апп як розподілений моноліт який для фронт-енда за допомогою гейтвея виглядає як одне ціле, а за самими БФФами вже стоять кор сервіси з даними. Еластік лоад балансінга немає поки, та і не треба було потреби, в нас особливість продукту що юзерів не так багато, але дуже багато даних, тому ботлнек зазвичай десь в стореджі, а не в сервісі. SSO поки тільки самописне на SAML (в нас корпорації це основні клієнти), але мігруємо на Auth0.

Ну ясно, ми так теж робили у 18-19, це звичайний для індустрії підхід, як писав Дейкстера чудово — що програмісти не проектують будівлі інакше цивілізації настав би кінець. Насправді проблема подвійного запису у вас є по переході на мікросервісну архітектуру, в незалежності від того вирішували її, або ні. Щоправда при деяких підходах — вона не є проблемою.

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

Там дещо ширше питання, швидше кину посилання на статтю Білджима Ібрама з Red-Hat developers.redhat.com/...​ns-microservices-compared

Скажіть — кожен сервіс в окремому репозиторії живе, чи якесь монорепо? Скоріш за все що в окремому, просто цікавлюся. Чи не поступають жалоби від розробників що тепер їм замість одної репи доводиться переключатися між різними?

В чому суть проблеми то? Ніби лопатою щось треба копати щоб переключитись...

мені теж ОК але є люди яким «незручно»

Так у чому це полягає? Що вони переключають?
Один раз викачав потрібні репозиторії і потім відкриваєш собі в ІДЕ потрібний сервіс.
При чому репозиторії викачую при потребі, а не всі відразу.

У нас мікросервіси і декілька різних людей, коли приходили на проект, хотіли «це все підняти на своїй машині». Мені така просьба дивує бо.. навіщо? Я не уявляю, що люди, які працюють в google — невже вони собі на локалхості весь google піднімають?

Але чомусь людям хочеться «все підняти у себе» і «все відкрити в IDE». Моя IDE взагалі поляже, якщо я весь проект відкрию одночасно. Я так і роблю — відкриваю/піднімаю ті з якими працюю. Зазвичай я не працюю більше ніж з 2 мікросервісами одночасно.

Я з локалі піднімаю один сервіс і на дев направляю те чого не вистачає 😂

Але чомусь людям хочеться «все підняти у себе» і «все відкрити в IDE»

Як тестувати, якщо нема енва, а зламаєш дадуть по попі ? Так чи інакше є наприклад localstack і справді можна підняти, при умові наявності відповідного потужної робочої станції. У GCP є Google App Engine.

Для локастеку не треба потужної. В тестконтейнерах ще можна тестувати

Зазвичай, коли на тобі якась задача, тобі треба міняти 1 чи 2 мікросервіса. Дуже рідко буває що три. Піднімаєш ці три і тестуєш їх.

Це як казати «я у табуретки зламану ножку підклеїв, тепер мені треба побудувати дім, щоб в ньому протестувати табуретку, перш ніж приносити іі собі в хату» — табуретка це окремий самодостатній обʼєкт, він може бути протестований в ізоляціі (сядь, ляж, попригай на ній), вона не працює по-різному в тому чи іншому домі (хіба що один з домів в космосі чи під водою)

Бувають обʼєкти що залежать один від одного. Наприклад висота столу і табуретки має підходити. Але для перевірки цього все одно не треба весь дім будувати.

Мені така просьба дивує бо.. навіщо?

є тисяча і один випадок, коли таке потрібно). наприклад, програмний продукт з виробництва любих салатів із будь-яких інгредієнтів, подаємо на вхід ковбасу і зелений горошок, — а на виході салат із крабових паличок. оскільки продукти на вході будь-які, то, очевидно, під невідомі вхідні продукти ніяких тестів ще немає (вони будуть написані після ремонту цього конкретного випадку, але поки що ні). продукт на мікросервісах, їх 27 штук, там і бази, і черги, і сраний оркестратор, і аі-модулі прийняття рішень, і власне рутинні сервіси обробки, і статистика, оплата, авторизації і усе інше гуано. і шсх майже любий може перетворити ковбасу в сурімі-палки, а горошок в кукурудзу. і найефективніше буде саме розгорнути всю шарманку локально, якщо це хоч скільки-небудь можливо, по черзі чіплятися і дивитись де полягло. у мене є чарівний скрипт, який черріпікає цар-коміт, в якому зроблена підміна конфігурацій, після чого розгортає всі контейнери локально, і вуаля — шукай проблему швидко і без перешкод, на все про все 10 хвилин. на відміну від сценарію, де б я по черзі чи невеликими пакетиками розгортав окремі сервіси локально, зламав би мозок від кількості варіантів заміни конфігурації і остаточно заплутався де що і якої версії в даний момент виконується.

А там логу нема, щоб відслідкувати дані, котрі бігали в даному запиті між сервісами? Тоді й зрозуміло стане, на кого баг вішати.

Так, я на мікросервісному проекті четвертий рік, завжди по логам знаходжу де баг і фіксиш 1 мікросервіс де поламалося. Тобто ще ні разу не було нужди підняти _все_. У мене є відчуття шо це якийсь code smell — якшо є потреба піднімати на локалості всі 27 мікросервісів — шось в архітектурі зроблено неправильно. Ніби це не мікросервіси, а моноліт просто розкиданий по репозиторіям.

логи звичайно є, якісь). в стартапах своєрідна атмосфера, може бути зоопарк логгерів, наприклад, чотири штуки, і кожен щось пише, але не факт, що саме те, що зараз треба. проблема звичайно системна і із заматюрілістю компаніі плавно вирішиться, але ж стартапи бувають або швидкі або мертві.

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

to make it definite: я не закликаю до повного локального розгортання і сам хотів би жити в прекрасному світлому майбутньому, де усі сервіси повністю заізольовані, з усіх боків обкладені тестами, і тричі на день автоматично перевіряються усі інтергаційні маршрути. але на жаль в реальному житті це майже завжди не так.

До стартапів старе питання — чи реально швидкому стартапу треба ті мікросервіси martinfowler.com/bliki/MonolithFirst.html ?
В гіршому випадку там буде один оркестратор (інтегратор) котрий можна підняти локально під дебагером, а конектиться він до службових сервісів на проді чи в шареному тест енві. І дебаг оркестратора покаже, на котрому кроці зламались дані.

в нас по різним репо, так, спочатку людей трохи напрягало на перехідному етапі, але коли все розділилося до кінця і команди почали ізольовано деліверити і кодити, то навпаки всі зраділи що не треба піднімати величезний моноліт який вижирає останні краплини оперативної пам’яті на локальній машині :)

У нас одному чуваку незручно було юніт тести писати. Звільнили бідолаху, коли сказав, що не буде їх писати в принципі.

Ми телефонію робили без юніт-тестів і майже без автотестів (сусідня команда їх трохи пописала на систему в цілому).
За результатми (6.5 років розробки, з котрих 4+ в проді) скажу:
* Асерти + code ownership + талановитий мануальщик + довгий релізний цикл заміняють юніт- та автотести.
* Без юніт-тестів система гнучка і легко змінюються внутрішні інтерфейси. Розробка без тормозів. Ось навіть хтось за таке зробив презентацію herbertograca.com/...​here-did-it-all-go-wrong
* Але на усякі алгоритмічні речі юніт-тести мали б сенс — наприклад, в движках кастомного регекспу та телефонної книги без юніт-тестів баги лізли кілька місяців.

так, а де саме цікаве? процесс міграції даних в сервісні дб? яка коммунікація, синхронна/асинхронна, який сервіс меш, чи взагалі він є, де хоститься своє залізо/google cloud/AWS, як рахунки виросли і тд? :)

Це вже буде у наступній статті для performace review :)

performance review в кінці і середині року зазвичай, тому не вгадали ;)

Процес міграції даних не такий складний в нашому кейсі, бо у нас є час коли ми можемо взагалі даунтайм робити (вихідні), того це просто був стоп зе ворлд. Комунікація 90% синхронна — OpenAPI contract-first підхід з генерацією кода на сервері і клієнті, зараз по-троху додаємо асинхронної, сервіс меша нема, він поки тільки в планах, коли ми розпилювали моноліт (бо це було 6, може навіть 7 років назад), то орендували залізо просто на Rackspace, потім вже перейшли на віртуалки на гугл клауді, зараз вже GKE. Рахунки виросли більше коли ми перейшли з он-преміс баз даних на клаудні, і з он-преміс кафки на менеджед, там в пару раз все виросло :)

Мікросервіс без unit тестування — гроші на вітер.
По суті це must вимога до них

будь-який код без unit тестування — гроші на вітер, не тільки мікросервіси

На вас Haskel-лісти вже точать сокиру, щоб з нею ганятись. Скільки років вони за Дейксторою будують мови які побудовані на доказовості алгоритму математичним, а не науковим методом. Модульне тестування як і в цілому тестування від модульного до end-to-end просто похідні від найбільш розповсюдженого наукового підхода в доказовості вірності алгоритму.
BTW Само буває так що покриття тестами — 97%, бо то була вимога замовника так зробити. Але тести написані після розробки коду і до дизайну і виявляється, що софт взагалі то виконує алгоритм який не відповідає бізнес вимогам, коротше кажучи неправильно працює бо робить зовсім не те що треба. Але тести покривають саме цей не вірний алгоритм :) Тобто рефакторинг коштує так само як і повна переробка — бо треба переробляти : як дизайн, так код, так і тести. От де справді виявляється — гроші на вітер, коли робимо зовсім не те, що треба би було робити — бо точити нема коли, пиляти ж треба і відрепортити замовнику за кількість зроблених сторіпойнтів за спринт, а не робочий софт показати.
XP та Кента Бека усім в карму.

а еще интеграционные, е2е тесты, без них не то что на ветер, а вообще опасно деплоиться. Юнит тесты это только самая начальная стадия тестирования)

Микросервисы не нужны. Хватит себя же на*бывать.

А якщо моноліт в одну машину трохи не влазить?

Во первых влезет. Во вторых разобьешь на 2 монолита если что.

Ще 2-3 ітерації і прийде світла ідея, навіщо закидувати на кожну ноду всі 5 гб коду, давайте деплоїти тільки потрібне, у нас на сервері Сатурн і так тільки репорти крутяться. Oh shi...
2-3 ітерації потому — у нас код, що запускає код завеликий, та й усі джуни його ламають, давайте винесемо і закриємо доступ усім крім архітектів.
Пішов у відпустку, гіт пулл, 1488 файлів змінено, я не хочу то всьо дивитися, словами розкажіть що тут робилося.
Мікросервіси — то найкраще, що придумали для поганих інженерів і поганих менеджерів, бо аппа не впаде вся, а половина проблем продакшену вирішується розгортанням минулої версії контейнеру.

ну так в розподілений моноліт...

Запустити на N машинах, та балансування зробити.

Запустити на N машинах, та балансування зробити.

Не забудьте про важливі припущення:
1) Система здатна одночасно працювати на кількох машинах. Умовно одна нода не почне змінювати дані паралельно з іншою, тут варто нагадати, що не всі сценарії закриваються транзакціями в РСУБД. Або простіший приклад — бекграудн процеси зможуть працювати в такому сетапі.
2) Існує можливість балансування між різними нодами. Умовно система стейтлес.
3) Система достатньо гарно модуляризованаа в рамках єдиної кодової бази, щоб внесення змін в 1 модуль не вимагало регресії всієї системи.

UPD.
Щу класна тема, коли навантаження на 1 процес (умовна генерація репортів) викликає голодування ресурсів на іншому (умовна активність користувачів)

Ну тут макросервіси просяться)

Я те розумію, бо штук 10 так маштабував. Головне shared nothing, щось трохи змінити в коді може треба, але складність цього залежить від абстракцій роботи з кешами, сесіями, юзерськими файлами (якщо аплоад є). Зазвичай роботи не супер багато, а часто налаштування в конфігах на інші хости прописати.

Ну тобто, перехід на мікро-сервіси лише заради маштабування навантаження, не дуже аргумент. Чи взагалі, коли часто приводять в приклад: корзина — мікросервіс, список замовлень — мікросервіс, каталог товарів — мікросервіс. А потім, так, нам же треба всюди інформація про товари, ладно, будемо реплікацію робити в базу кожного мікросервіса щоб по феншую, чи ладно, один раз не п*дорас, хай вони в одну базу з продуктами лазять. При тому що кількість заказів в 1000 разів менша ніж на rozetka. І нахіба все це. Коли моноліт підтримувати було б простіше. А специфічні речі — пошук, конвертація відео/зображень, так цілком логічно винести в окремі сервіси і даже не мікро.

Не кажу що мікросервісний підхід не потрібен, чи автор теми щось не те каже. Але і не завжди він виправданий, бо можна простіше вирішити проблеми.

Так, цілком згоден що мікросервіси це далеко не завжди виправдано, тому я останнє речення і додав «Адже монолітна і мікросервісна архітектури це не етапи еволюції, а просто дві архітектурні парадигми, які використовуються для різних організаційних структур і схем масштабування.». Працювати 30 командам на одному, двох, трьох монолітах це не зручно (як мінімум 6-7 років назад так було). Ми не масштабували навантаження, а більше девелопмент :). В будь якому випадку треба завжди переходити на дістрібьютед архітектуру тільки вже коли немає іншого більш простого виходу.

Я не пробовал, но думаю, что там больше геморроя, чем выгоды.

Не скажи. Если микросервис это кусок домена на 4-8 человек постоянно работающих над этим куском и не вылазящим из него, то идея нормальная.

Скорее 50/50. Основной геморрой — очень сложная девопсия, обеспечить согласованность интерфейсов и версий можно нормально только при наличии хорошей Сlaud Ops автоматизации и билд-иженерии, а времени и людей на нее никогда нет поэтому часто закрывается «иванушкой дурачком» который в екселе записывает что и куда задеплоено по версиям и тикетам. Можно себе представить как это весло и весло мониторить, если таких сервисов 17-18. Еще сами принципы stateless очень плохо стыкуются с legacy системами, которые заказчики постоянно хотят интегрировать — а они в массе своей statefull.
Зато нет проблем которые оборачиваются лютым гемороем и проблемами с руководством, когда сайт не выдерживает нагрузку в черную пятницу и кибр понедельник. Реально может выдержать очень большую нагрузку, от которой монолиты падают.

Це саме можна на вікіпедії прочитати

Щось враження що робили щоб зробити.

Стосовно меж сервісів:
The purpose of service boundaries in microservices is to capture a domain or workflow. In some applications, those natural boundaries might be large for some parts of the system — some business processes are more coupled than others. Here are some guidelines architects can use to help find the appropriate boundaries:
Purpose
The most obvious boundary relies on the inspiration for the architecture style, a domain. Ideally, each microservice should be extremely functionally cohesive, contributing one significant behavior on behalf of the overall application.
Transactions
Bounded contexts are business workflows, and often the entities that need to cooperate in a transaction show architects a good service boundary. Because transactions cause issues in distributed architectures, if architects can design their system to avoid them, they generate better designs.
Choreography
If an architect builds a set of services that offer excellent domain isolation yet require extensive communication to function, the architect may consider bundling these services back into a larger service to avoid the communication overhead.
Iteration is the only way to ensure good service design. Architects rarely discover the perfect granularity, data dependencies, and communication styles on their first pass. However, after iterating over the options, an architect has a good chance of refining their design.

Стосовно BFF для мікросервісів:
While an API layer may be used for variety of things, it should not be used as a mediator or orchestration tool if the architect wants to stay true to the underlying philosophy of this architecture: all interesting logic in this architecture should occur inside a bounded context, and putting orchestration or other logic in a mediator violates that rule. This also illustrates the difference between technical and domain partitioning in architecture: architects typically use mediators in technically partitioned architectures, whereas microservices is firmly domain partitioned.

Стосовно стандартів кодування:
A well-known architect who was a pioneer in the microservices style was the chief architecture at a personal information manager startup for mobile devices. Because they had a fast-moving problem domain, the architect wanted to ensure that none of the development teams accidentally created coupling points between each other, hindering the teams’ ability to move independently. It turned out that this architect had a wide mix of technical skills on the teams, thus mandating that each development team use a different technology stack. If one team was using Java and the other was using .NET, it was impossible to accidentally share classes!
This approach is the polar opposite of most enterprise governance policies, which insist on standardizing on a single technology stack. The goal in the microservices world isn’t to create the most complex ecosystem possible, but rather to choose the correct scale technology for the narrow scope of the problem. Not every service needs an industrial-strength relational database, and forcing it on small teams slows them rather than benefitting them. This concept leverages the highly decoupled nature of microservices.

Стосовно retry та timeout:
the common operational concerns appear within each service as a separate component, which can be owned by either individual teams or a shared infrastructure team. The sidecar component handles all the operational concerns that teams benefit from coupling together. Thus, when it comes time to upgrade the monitoring tool, the shared infrastructure team can update the sidecar, and each microservices receives that new functionality.
Once teams know that each service includes a common sidecar, they can build a service mesh, allowing unified control across the architecture for concerns like logging and monitoring. The common sidecar components connect to form a consistent operational interface across all microservices

Стосовно єдиного пошукового движка:
Another requirement of microservices, driven by the bounded context concept, is data isolation. Many other architecture styles use a single database for persistence. However, microservices tries to avoid all kinds of coupling, including shared schemas and databases used as integration points.
Data isolation is another factor an architect must consider when looking at service granularity. Architects must be wary of the entity trap (discussed in “Entity trap”) and not simply model their services to resemble single entities in a database. Architects are accustomed to using relational databases to unify values within a system, creating a single source of truth, which is no longer an option when distributing data across the architecture. Thus, architects must decide how they want to handle this problem: either identifying one domain as the source of truth for some fact and coordinating with it to retrieve values or using database replication or caching to distribute information.
While this level of data isolation creates headaches, it also provides opportunities. Now that teams aren’t forced to unify around a single database, each service can choose the most appropriate tool, based on price, type of storage, or a host of other factors. Teams have the advantage in a highly decoupled system to change their mind and choose a more suitable database (or other dependency) without affecting other teams, which aren’t allowed to couple to implementation details.

Цитати з Fundamantals of Software Architecture by Mark Richards

Щось враження що робили щоб зробити

Зазвичай клієнт десь почув — що модно і роблять. Те що це не так вже то воно і просто з’ясовується значно пізніше, по ходу роботи чи вже в експлуатації. Коротше як і з усім в IT.

Підписатись на коментарі