JavaRush /Курси /Модуль 5. Spring /Події як "first-class citizens": структура та життєвий ци...

Події як "first-class citizens": структура та життєвий цикл подій

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

Ми вже встигли познайомитися з різними інструментами роботи з подіями, такими як Apache Kafka, RabbitMQ та ActiveMQ. Тепер настав час заглибитися в деталі подій як ключового елементу архітектури.


Роль подій в архітектурі

В архітектурі Event-Driven події — це не просто повідомлення, які передаються між сервісами. Це повноцінні сутності (або, як кажуть, "first-class citizens"), навколо яких будується логіка додатка. Якщо проводити аналогію, то подія — це як лист, який ви відправляєте, щоб повідомити, що щось сталося. Але це не просто текст повідомлення: вона містить всі важливі деталі, необхідні для того, щоб адресат міг правильно її обробити.

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

Приклад із реального життя

Уявіть, що ви замовляєте каву в улюбленій кав'ярні через додаток.

  1. Ваша дія (замовлення) викликає подію OrderPlaced.
  2. Система обробляє цю подію: бариста бачить замовлення, кавова машина — починає готувати.
  3. Наприкінці ви отримуєте сповіщення, що ваша кава готова (OrderCompleted).

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


Структура події: атрибути, метадані та корисне навантаження

Подія зазвичай складається з наступних частин:

  • Тип (Type): тип події, наприклад, OrderPlaced, PaymentFailed, UserRegistered. Це ідентифікатор дії, яка сталася.
  • Ідентифікатор (ID): унікальний ідентифікатор події. Це важливо для відстеження подій у системі та запобігання повторній обробці.
  • Мітка часу (Timestamp): коли саме подія сталася. У розподілених системах це критично для розуміння порядку обробки.
  • Джерело (Source): хто або що викликало цю подію. Наприклад, order-service, payment-gateway.
  • Корисне навантаження (Payload): дані, які ця подія несе. Наприклад, для OrderPlaced це може бути інформація про замовлення (ID замовлення, ім'я клієнта тощо).
  • Метадані (Metadata): додаткова інформація, наприклад, заголовки повідомлень, контекст виклику тощо.

Приклад JSON-структури події:

{
  "type": "OrderPlaced",
  "id": "123e4567-e89b-12d3-a456-426614174000",
  "timestamp": "2023-10-12T14:37:00Z",
  "source": "order-service",
  "payload": {
    "orderId": "98765",
    "customerId": "45678",
    "orderItems": [
      {
        "productId": "12345",
        "quantity": 2
      },
      {
        "productId": "67890",
        "quantity": 1
      }
    ],
    "totalPrice": 49.99
  },
  "metadata": {
    "traceId": "a1b2c3d4e5",
    "correlationId": "x5y6z7w8v9"
  }
}

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

Корисна порада:

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


Життєвий цикл події

Подія в архітектурі EDA має свій життєвий цикл. Давайте розберемо основні етапи:

Етап 1: Генерація події

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

Код на Java (Spring Boot):


@Component
public class OrderService {

    @Autowired
    private KafkaTemplate<String, OrderEvent> kafkaTemplate;

    public void placeOrder(Order order) {
        // Логіка обробки замовлення
        OrderEvent event = new OrderEvent(
                "OrderPlaced",
                UUID.randomUUID().toString(),
                LocalDateTime.now().toString(),
                "order-service",
                order
        );
        kafkaTemplate.send("orders", event); // Відправка події у Kafka
    }
}

Етап 2: Передача події

Згенерована подія відправляється в систему доставки, наприклад, у брокер повідомлень Kafka. Тут важливо, щоб подія була доставлена в потрібний topic/чергу і оброблена в надійні терміни.

Етап 3: Обробка події

Після отримання події підписники виконують свої задачі. Наприклад:

  • Сервіс оплати списує кошти з картки.
  • Сервіс повідомлень надсилає e-mail клієнту.

Код підписника:


@Service
public class PaymentService {

    @KafkaListener(topics = "orders", groupId = "payment")
    public void handleOrderPlaced(OrderEvent event) {
        if ("OrderPlaced".equals(event.getType())) {
            System.out.println("Processing payment for order: " + event.getPayload().getOrderId());
            // Логіка обробки оплати
        }
    }
}

Завершення життєвого циклу

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


Створення подій як сутностей у коді

Для спрощення роботи з подіями має сенс створити клас, який буде представляти подію:


public class Event<T> {
    private String type;
    private String id;
    private String timestamp;
    private String source;
    private T payload;
    private Map<String, String> metadata;

    public Event(String type, String id, String timestamp, String source, T payload) {
        this.type = type;
        this.id = id;
        this.timestamp = timestamp;
        this.source = source;
        this.payload = payload;
        this.metadata = new HashMap<>();
    }

    // Геттери та сеттери

    public void addMetadata(String key, String value) {
        this.metadata.put(key, value);
    }
}

Приклад створення події:

Order order = new Order(...);
Event<Order> event = new Event<>(
    "OrderPlaced",
    UUID.randomUUID().toString(),
    LocalDateTime.now().toString(),
    "order-service",
    order
);
event.addMetadata("traceId", "abc123");

Потенційні помилки при проєктуванні подій

Якщо події недостатньо чітко описані або в вас відсутній стандарт для їх обробки, можуть виникнути такі проблеми:

  • Крос-командне нерозуміння: розробники додають дані в події без загальної домовленості.
  • Надмірна інформація: події стають громіздкими і складними для обробки.
  • Втрати даних: якщо ви не стежите за унікальністю подій, системи можуть обробляти одні й ті ж події кілька разів.

Щоб цього уникнути, важливо:

  1. Створити єдиний "словник подій" та їхніх структур.
  2. Стандартизувати всі події, аналогічно API.

Рекомендації щодо проєктування та управління подіями

Принципи проєктування:

  1. Мінімалізм: зберігайте в події тільки дані, необхідні для обробки.
  2. Стабільність: структура події не повинна змінюватися без вагомих причин.
  3. Прозорість: подія має однозначно описувати те, що сталося.

Інструменти моніторингу подій

Використовуйте інструменти на кшталт Kafka UI, ELK Stack або Prometheus, щоб відстежувати життєвий цикл подій і їх успішну обробку.

management:
  endpoints:
    web:
      exposure: include[ "prometheus", "health"]

Коли події стають "first-class citizens", ваша система стає більш організованою, передбачуваною і, що важливо, зручнішою для підтримки.

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