JavaRush /Курси /Модуль 5. Spring /Лекція 210: Рефакторинг монолітних застосунків у подійно-...

Лекція 210: Рефакторинг монолітних застосунків у подійно-орієнтовану архітектуру

Модуль 5. Spring
Рівень 13 , Лекція 9
Відкрита

Монолітні застосунки добрі й прості — як склянка молока. Але у молока є термін придатності, і рано чи пізно ваша монолітна система може зіткнутися з низкою проблем:

  1. Складність масштабування: якщо потрібно додати потужностей тільки для одного модуля (наприклад, обробки замовлень), доведеться масштабувати весь моноліт.
  2. Відмовостійкість: помилка в одному модулі може призвести до падіння всього застосунку.
  3. Залежності між командами: зміни в одному модулі вимагають узгодження з іншими командами і можуть вплинути на всю систему.
  4. Складність впровадження нових технологій: весь моноліт прив'язаний до однієї технологічної бази, що ускладнює експерименти з новими інструментами.

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


Основні етапи переходу від моноліту до подійно-орієнтованої архітектури

1. Аналіз існуючого моноліту

Перш ніж братися за сокиру і розрубувати моноліт на частини, треба зрозуміти, що саме ви збираєтесь рубати:

  • Ідентифікуйте модулі та їхню відповідальність: розберіться, які частини вашого застосунку виконують яку бізнес-логіку.
  • Проаналізуйте залежності: вивчіть, які модулі взаємодіють між собою, які дані передаються між ними.
  • Виясніть критичні точки: визначте, де найчастіше виникають вузькі місця (bottlenecks), помилки або проблеми з продуктивністю.

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

Порада: використовуйте візуальні інструменти для аналізу, такі як діаграми залежностей або навіть прості таблиці в Excel. Чітке розуміння поточного стану полегшить подальший перехід.

2. Визначення меж відповідальності сервісів

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

  • Розділіть бізнес-логіку за принципом єдиної відповідальності (Single Responsibility Principle): кожен сервіс має займатися лише своєю справою.
  • Ізолюйте дані: переконайтеся, що кожен сервіс працює тільки зі своєю частиною даних.

Наприклад, сервіс обробки замовлень (Orders) може відповідати за створення, оновлення та скасування замовлень, але не повинен знати, як розраховуються знижки або які товари є на складі. Ці задачі можуть бути передані іншим сервісам, таким як Discounts і Inventory.

3. Впровадження подій

Тепер починається магія подій. Замість того щоб модулі напряму викликали методи один одного, ми використовуємо брокер повідомлень (наприклад, Kafka) для публікації і підписки на події. Приклад:

  1. Користувач оформлює замовлення.
  2. Сервіс Orders публікує подію OrderCreated.
  3. Сервіс Inventory підписаний на цю подію і перевіряє, чи є потрібні товари на складі.
  4. Сервіс Notifications підписаний на ту ж подію і відправляє лист із підтвердженням замовлення.

Приклад реалізації класу події:


public class OrderCreatedEvent {
    private String orderId;
    private String userId;
    private List<String> itemIds;

    // Конструктори, геттери та сеттери
}

Видавець події:


@Component
public class OrderService {
    private final KafkaTemplate<String, OrderCreatedEvent> kafkaTemplate;

    public OrderService(KafkaTemplate<String, OrderCreatedEvent> kafkaTemplate) {
        this.kafkaTemplate = kafkaTemplate;
    }

    public void createOrder(Order order) {
        OrderCreatedEvent event = new OrderCreatedEvent(order.getId(), order.getUserId(), order.getItemIds());
        kafkaTemplate.send("orders-topic", event); // Публікація події в Kafka
    }
}

Обробник події:


@Component
public class InventoryService {

    @KafkaListener(topics = "orders-topic", groupId = "inventory-group")
    public void handleOrderCreated(OrderCreatedEvent event) {
        // Перевірка наявності товарів на складі
        System.out.println("Processing order: " + event.getOrderId());
    }
}

4. Крок за кроком: ізолюйте сервіси

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

  1. Notifications — сервіс для відправки повідомлень.
  2. Payments — сервіс для обробки платежів.
  3. Catalog — сервіс для управління каталогом товарів.

5. Обробка даних та міграція клієнтів

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

  • Якщо запити стосуються старої бізнес-логіки в моноліті, вони обробляються там.
  • Якщо запити потрапляють у виділені мікросервіси, API Gateway перенаправляє їх безпосередньо.

6. Додавання моніторингу та тестування

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

  • Використовуйте Kafka Metrics для моніторингу стану брокера повідомлень.
  • Налаштуйте розподілене логування за допомогою ELK (Elasticsearch, Logstash, Kibana) або Splunk.
  • Увімкніть трасування запитів (наприклад, з допомогою Zipkin).

Проблеми, з якими ви зіткнетесь (і як їх вирішити)

Перехід на подійно-орієнтовану архітектуру рідко проходить гладко. Серед найпоширеніших проблем:

  1. Узгодженість даних: в асинхронній архітектурі складно гарантувати, що всі дані будуть оброблені одночасно. Використовуйте паттерн "Eventual Consistency" (поступова узгодженість).
  2. Обробка помилок: що робити, якщо одне із ланок ланцюжка падає? Налаштуйте retry (повторні спроби) і fallback (резервні дії).
  3. Зростання складності: взаємодія великої кількості сервісів через події може стати складнішою, ніж очікувалося. Регулярно переглядайте архітектуру.

Показовий приклад рефакторингу

Візьмемо простий монолітний застосунок для інтернет-магазину. Рефакторингом ми вирішуємо виділити два основні сервіси:

  1. Orders (обробка замовлень).
  2. Notifications (повідомлення).

Стартова конфігурація (моноліт):


public class OrderController {
    public void placeOrder(Order order) {
        // Логіка замовлення
        notifyUser(order);
    }

    private void notifyUser(Order order) {
        // Відправка повідомлення
    }
}

Після рефакторингу:

  1. Сервіс Orders публікує подію OrderPlaced, замість безпосереднього виклику методу для повідомлення.
  2. Сервіс Notifications слухає подію OrderPlaced і відправляє лист користувачу.

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

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ