Мікросервіси в Java, або Як пройшов мій перший воркшоп у ролі спікера

Привіт, світе! Мене звати Єгор, і я радий представити свою першу статтю на DOU. В мене більше 6-ти років комерційного досвіду, а наразі працюю в компанії DataArt на посаді Senior Software Engineer. Моя діяльність у компанії не обмежується лише розробкою: я є ментором, проводжу співбесіди як для підняття кваліфікації колег, так і зовнішні, активно беру участь у розвитку нашої Java-ком’юніті і влаштовую вебінари на різноманітні теми. У травні я мав нагоду побувати на DataArt Summer IT Camp.

DataArt Summer IT Camp — це двотижневий онлайн-інтенсив, який відбувся у липні. Учасники дізнались про новітні технології в ІТ, розвивали soft skills та поглиблювали знання під час практичних воркшопів. Усі ресурси були доступні для перегляду в асинхронному форматі, що дозволяло зручно підлаштуватися під різницю в часових поясах. Однак для практичних воркшопів учасникам потрібно було долучитися онлайн, а в Тбілісі та Львові навіть була можливість відвідати офіси DataArt для офлайн-семінарів.

Цей Camp зібрав 1662 учасники з 50 країн, і я пишаюсь тим, що став частиною грандіозного заходу. Я погодився провести воркшоп з Java, проте через інтенсивний графік роботи на проєкті, ми зібрали команду з 4-х людей для підготовки. Роботу поділили швидко: Ростислав Кузьмич та Андрій Прачов писали код, Максим Леськів готував квізи та допомагав мені з проведенням воркшопу офлайн, а я готував теоретичну частину, робив код ревʼю та був англомовним спікером для онлайн-авдиторії. Забігаючи наперед скажу, що кожен впорався на відмінно! Хлопці, дякую за допомогу — ви круті! Без вас воркшопу та цієї статті не було б.

А тепер більш детально про сам воркшоп — план був такий:

  1. Вступний квіз на знання Java, для того, щоб створити всім робочий настрій.
  2. Презентація про еволюцію архітектури програмного забезпечення.
  3. Реалізація мікросервісів на Java.
  4. Фінальний тест із засвоєного матеріалу.

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

Вступний квіз мав розважити слухачів та налаштувати на продуктивний вечір. Тут були питання на кшталт «Хто розробив Java або який клас є батьківським для всіх обʼєктів в Java?». Також були глибші питання, наприклад: що таке Optionals, Records, як працює GC тощо. Результати цього тесту допомогли мені зрозуміти, наскільки ґрунтовно авдиторія розуміє мову програмування, і скажу завчасно: мені пощастило з учасниками, оскільки більшість із них добре розбиралася в темі.

Щодо презентації, то тут я вирішив зосередитися на еволюції архітектури в програмуванні та пояснити різницю між основними архітектурними підходами від моноліту до наносервісів.

Моноліт

Розпочнемо з Моноліту (Monolith) — це найбільш древній та простий архітектурний підхід, оскільки тут особливо нічого вигадувати не потрібно: уся логіка в одному місці, усі компоненти, разом із БД та UI, є взаємоповʼязаними та взаємозалежними, а процеси розробки, тестування та розгортання відбуваються в одній уніфікованій системі. Загалом зобразити моноліт досить легко:

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

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

З плюсів я тут можу виділити хіба:

  • простоту розробки та розгортання;
  • легкість у тестуванні.

Отож, де використання моноліту є доцільним? Попри те, що цей підхід є застарілим, все-таки трапляються випадки, коли краще використати його, адже:

  1. Підходить для невеликих додатків, де всі функції тісно пов’язані між собою.
  2. Ідеально підходить для швидкого створення прототипу додатку.
  3. Ефективний у додатках, де потрібна висока продуктивність, оскільки внутрішні виклики відбуваються швидше, ніж міжсервісні.

Сервіс-орієнтована архітектура (SOA)

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

Схематично цей архітектурний підхід ми можемо зобразити так:

Як ми можемо побачити зі схеми, бізнес-логіка в цьому підході розбита на декілька сервісів, кожен з яких використовується для конкретних бізнес-задач, але ми все ще маємо спільну БД. Також ми бачимо новий компонент ESB. Enterprise Service Bus — це програмне забезпечення для інтеграції застосунків у системі. ESB забезпечує спільне середовище, яке дозволяє різним застосункам взаємодіяти між собою, спрощуючи обмін повідомленнями та координацію процесів.

Такий підхід має певні переваги над монолітом:

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

Проте тут є певні недоліки:

  • складно керувати міжсервісними звʼязками;
  • можуть виникнути проблеми з продуктивністю через затримку в мережі;
  • система залишається жорстко звʼязаною через наявність спільної БД — будь-які зміни в БД можуть вплинути на роботу інших програм.

SOA — це перевірений часом архітектурний підхід, який ідеально підійде:

  1. Для великих та складних систем, які вимагають високої гнучкості та адаптивності.
  2. Для організацій із широким спектром розрізнених систем, які потребують обміну даними.
  3. Коли певні бізнес-функції потрібні в декількох додатках.

Мікросервіси

Ось ми дійшли до найбільш популярного архітектурного рішення сьогодення — мікросервісів. Цей підхід зараз використовують усюди, де треба і не треба. Архітектура мікросервісів розробляє додаток як набір невеликих, слабко пов’язаних між собою сервісів. Кожен мікросервіс є незалежною сутністю з певною роллю і може розроблятися, розгортатися і масштабуватися незалежно. Мікросервіси взаємодіють один з одним через API, часто використовуючи HTTP / REST або асинхронний обмін повідомленнями. Головною відмінністю між мікросервісною архітектурою та SOA є наявність окремих баз даних для кожного сервісу. Це можна проілюструвати так:

На схемі цього не зображено, проте, як і в SOA, перед мікросервісами може бути ще один компонент, який часто називають Backend for Frontend (BFF). По суті, цей паттерн робить обгортку над API для кожного мікросервісу та створює єдину уніфіковану API для UI.

Серед переваг можна виокремити:

  • висока масштабованість та гнучкість;
  • стійкість;
  • можливість використання різних технологій для кожного мікросервісу.

З мінусів:

  • складний у керуванні;
  • проблеми при узгодженні даних;
  • проблеми з продуктивністю через затримки в мережі.

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

Наносервіси

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

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

Здебільшого наносервіси асоціюються з безсерверною (serverless) архітектурою — це підхід до розробки та розгортання додатків, де розробники не потребують управляти серверами. Замість цього вони лише розробляють та розгортають код. Важливо розуміти, що serverless не означає відсутність серверів. Сервери справді існують, але їхнє управління, обслуговування та масштабування відбувається постачальником хмарних послуг за кулісами.

Коли говорять про наносервіси, то мають на увазі «функції як сервіс» (Function as a Service, FaaS), такі як AWS Lambda, Google Cloud Functions або Azure Functions. Оскільки я маю досвід тільки з AWS Lambda, тому на схемі я вказував їх та API Gateway, який виконує функції роутинга.

З переваг можна зазначити:

  • автоматична масштабованість;
  • відсутність управління серверами;
  • сплачуєте тільки тоді, коли користуєтесь.

Мінуси наступні:

  • підвищена складність в управлінні, моніторингу та налагодженні;
  • затримка під час холодного старту, тобто під час першого виклику функції, вона виконуватиметься довше, оскільки потрібно підняти environment;
  • потенційна привʼязка до постачальника, тобто якщо ми обрали AWS, то переїхати на Azure або GCP буде важко.

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

Практичне завдання

Для практичного завдання ми вирішили реалізувати мікросервісну архітектуру на прикладі старої-доброї онлайн-книгарні. Ми створили три мікросервіси на Java + SpringBoot + H2 (embedded DB) і ще один discovery-сервіс, який використовував Spring Eureka. Увесь код можна знайти за посиланням. А тут я розповім коротко про кожен мікросервіс, а також про те, що б ще можна було покращити в нашому завданні:

  1. server-eureka-java — інфраструктурний сервіс, який дозволяє виявляти інші мікросервіси нашої книгарні.
  2. service-authority-java — мікросервіс, за допомогою якого відбувається реєстрація та авторизація юзерів.
  3. service-bookmanager-java — тут відбуваються CRUD-операції над книжками, також цей сервіс може виконати виклик до service-ordercontroller-java, щоб отримати всі замовлення для конкретної книги.
  4. service-ordercontroller-java — тут створюються замовлення і робляться запити до service-bookmanager-java для отримання інформації про книжки.

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

  1. Ініціалізація проєкту.
  2. Необхідні DTO.
  3. Створення репозиторіїв.
  4. Створення сервісів.
  5. Створення контролерів.

Перевірку коректності роботи мікросервісів ми здійснювали за допомогою викликів у Postman. Для кожного мікросервісу ми підготували колекцію Postman запитів, щоб учасники не витрачали час на їхнє створення. Ви можете їх знайти в папці docs під назвою «назва_сервісу.postman_collection.json».

Висновки

І хоч я та учасники воркшопу залишилися задоволеним практичною частиною, а більшості вдалось підняти мікросервіси та побавитись із запитами, проте ідеалу не існує, і наш код — не виняток. Десь посеред воркшопу я зрозумів, що наш service-authority-java вийшов «відірваним» від інших мікросервісів, оскільки ми зовсім забули про те, що JWT токен, який генерується там, мав би прокидатись і в інші мікросервіси та перевірятися на валідність. А так склалося, що хоч у нас і є сервіс, який здійснює авторизацію, проте вона працює тільки в середині цього сервісу, а інші два сервіси могли приймати команди від будь-кого, що є неправильним. В ідеалі, кожен мікросервіс має мати секʼюріті та робити запити до service-authority-java для перевірки, чи валідний токен. У кінці виступу мені навіть поставили питання щодо цього: я відповів, що це сплановане рішення, щоб не заплутувати учасників-початківців ще більше. Звісно, що я лукавив.

Після практичної частини учасників чекав ще один фінальний тест — на цей раз складніший, що стосувався теми воркшопу та ще деяких вебінарів, які проходили раніше. Усі учасники, які успішно склали цей тест (набрали більше 65 %), отримали від нас сертифікати про успішне закінчення навчання.

Наша компанія вже розпочала підготовку до наступного DataArt Winter IT Camp. Поки я ще не отримував запрошення на участь, проте якщо запропонують, то вже маю ідеї щодо наступного воркшопу. Дякую всім за увагу і до наступних зустрічей, вони ще будуть.

👍ПодобаєтьсяСподобалось5
До обраногоВ обраному1
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
Головною відмінністю між мікросервісною архітектурою та SOA є наявність окремих баз даних для кожного сервісу

Чоому раптом має бути database per service. Це тільки один з підходів, зовсім не обов’язковий.

Може бути таке, що для деяких сервісів база не потрібна, але якщо 2 або більше мікросервісів пишуть в одну базу, то це така собі мікросервісна архітектура :)

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

Ну, ви вже придираєтесь :) Все залежить від потреб системи і я переконаний, що існують системи, де необхідно підняти і 10, а може і більше mongodb кластерів. Але можна обійтись різними колекціями, схемами, або просто різними таблицями. Головна ідея, щоб дані були інкапсульовані і якщо сервісу А потрібні дані з сервісу B, то він їх може отримати тільки через сервіс B і ніяк інакше. Просто як показує практика, якщо дані не інкапсульовані, то рано чи пізно знайдеться «розумник», який вирішить полізти в БД напряму. Для кращого розуміння принципів мікросервісної архітектури ми вирішили використовувати embedded H2 DB для кожного мікросервісу.

окрім інкапсуляції даних є й інші фактори. Наприклад проблеми з джойнами і розподіленими транзакціями

та робити запити до service-authority-java для перевірки, чи валідний токен.

Ні. Ідея JWT як раз в тому, щоб не робити цього

Так, ви праві. Токен перевіряється секретним ключем, який використовувався для генерації jwt. І запити до service-authority-java робити не потрібно.

Вибачте, що виправляю. Токен перевіряється публічним ключем.

Дякую! Дурна помилка :)

SOA не обов’язково мати ESB, так само як необов’язково мати спільну БД.

Якщо сервіси мають спільну БД то фактично це навіть не SOA а декомпозований моноліт.

А ще в вашій діаграмі мікросервісів немає натяку на те що мікросервіси можуть спілкуватись між собою.

перед мікросервісами може бути ще один компонент, який часто називають

Це і є API Gateway. У вас він тільки на схемі мікросервісів.

або асинхронний обмін повідомленнями.

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

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

Якщо ви не деплоїтесь на різних континентах то затримка в мережі це 1% часу виконання розподіленого запиту.

Дякую за коментарі та уточнення :)
1) Щодо ESB, то так, це не є обовʼязковими, але good practice, особливо якщо система велика.
2) Про спільну БД малось на увазі, що писати двом або більше сервісам в одну базу не є забороненим, навідміну, від мікросервісів, хоча погоджуюсь, що краще мати хоча б різні схеми, але як показує практика так роблять не завжди.
3) Навіть не можу згадати, чому я в схемі про мікросервіси не вказав про BFF, напевно не хотів, щоб вона була максимально схожа на схему NS. Але погоджуюсь, що варто було б зазначити.
4) Асинхроність оминав навмисно, оскільки на цю тему можна окремий воркшоп проводити, а ми і так ледь вписались в 4 години :)
5) Я думаю, що існують системи де навіть 1% матиме значення, але їх не багато, і для більшості систем це не важливо. Але те, що моноліт не матиме цієї затримки, то факт.

Роботу поділили швидко: Ростислав Кузьмич та Андрій Прачов писали код, Максим Леськів готував квізи та допомагав мені з проведенням воркшопу офлайн, а я готував теоретичну частину, робив код ревʼю та був англомовним спікером для онлайн-авдиторії.

Далеко підеш з таким підходом=)

Не зовсім зрозумів, що ви маєте на увазі під словом «такий»?

1) Судячи із картинок наведених вами вище SOA від MS архітектури відрізняється наявністю API Gateway. Без API Gateway ви наврядчи побудуєте MS архітектуру (судячи з того що ви використовуєте netflix oss то це може бути zuul)
2) Для того щоб розуміти MS архітектуру слід почати із DDD та такого визначення як `bounded context`
Дякую за статтю

Бачу ви поправили картинки — тепер це має сенс. Я як раз хотів виправити коментар із питанням «до чого тут ESB до наносервісів та lamda до SOA ?»

Оу, дуже дякую, що зауважили! Тут були переплутані картинки в SOA та NS. Вже поміняв місцями :)

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