Event-Driven ядро
Що таке Event-Driven ядро
Event-Driven ядро (EDC) - це «хребет» архітектури, в якому бізнес-факти фіксуються і поширюються як незмінні події, а інша функціональність (читання, інтеграції, аналітика, кеші, нотифікації) будується поверх потоку цих подій. Ядро задає контракт подій, правила доставки та інваріанти порядку/ідемпотентності, забезпечуючи слабку зв'язність і масштабованість.
Ключова ідея: спочатку записати факт (ядро), а потім незалежно збагачувати і проектувати його в потрібні моделі. Це зменшує зв'язність і підвищує стійкість до часткових збоїв.
Цілі та властивості EDC
Істинність фактів: кожна подія - незмінний запис «що сталося».
Слабка зв'язність: продюсери не знають споживачів; розширення системи - додаванням передплатників.
Масштабування: горизонтальне зростання по партіях/топіках, незалежні споживачі.
Спостережуваність і аудит: наскрізні ідентифікатори, відтворюваність, ретенції та репрогравання.
Керована еволюція: версії схем, сумісність, deprecation.
Архітектурні компоненти
1. Шина/брокер подій: Kafka/NATS/Pulsar/SNS + SQS - канали, партії, ретенції.
2. Реєстр схем: JSON Schema/Avro/Protobuf для сумісності та еволюції.
3. Outbox/CDC-контур: атомарна фіксація факту + публікація без «подвійного запису».
4. Проекції/читання (CQRS): матеріалізовані уявлення для швидких запитів.
5. Саги/оркестрація: координація довгоживучих процесів через події/команди.
6. Збагачення: окремі топіки'.enriched '/' .derived'без впливу на критичний шлях.
7. Обсервабіліті: трасування, логування, метрики по подіям і лагам.
Модель подій
Типи подій
Domain Events: бізнес-факти ('payment. authorized`, `kyc. approved`).
Integration Events: орієнтовані на зовнішні системи (стабільні, повільно змінюються).
Change Data Capture (CDC): технічні зміни запису (використовуйте для міграцій/інтеграцій).
Audit/Telemetry: дії акторів, безпека, SLA.
Обов'язкові атрибути (ядро)
json
{
"event_id": "uuid",
"event_type": "payment. authorized. v1",
"occurred_at": "2025-10-31T11:34:52Z",
"producer": "payments-service",
"subject": { "type": "payment", "id": "pay_123" },
"payload": { "amount": 1000, "currency": "EUR", "method": "card" },
"schema_version": 1,
"trace_id": "abc123",
"partition_key": "pay_123"
}
Рекомендації: 'event _ id'глобально унікальний,'partition _ key'задає порядок для сутності,'trace _ id'забезпечує кореляцію.
Семантика доставки та ідемпотентність
At-least-once (за замовчуванням у багатьох брокерів): споживачі зобов'язані бути ідемпотентними.
At-most-once: прийнятно лише для другорядних телеметрій.
Exactly-once: досягається на рівні потоку і сховища через транзакції/ідемпотентні ключі/лейки (дорожче, потрібна вагома причина).
Шаблон ідемпотентності споживача
Дедуп-таблиця по'event _ id '/' (event_id, consumer_id)'з TTL ≥ ретенції топіка.
Upsert замість insert; версіонування проекцій за «sequence »/« occurred _ at».
Операції в рамках транзакції: позначка «бачив» + зміна стану.
Порядок і партіонування
Гарантований порядок у межах партії.
Вибирайте'partition _ key'так, щоб всі події однієї агрегат-сутності потрапляли в одну партію ('user _ id','payment _ id').
Уникайте «гарячих ключів»: хеш з сіллю/під-ключі, якщо потрібно розподілити навантаження.
Схеми та еволюція
Additive-first: нові опціональні поля, заборона на зміну типів/семантики без major-версії.
Сумісність: BACKWARD/FORWARD в реєстрі схем; CI блокує несумісні зміни.
Іменування: `domain. action. v{major}` (`payment. authorized. v1`).
Міграції: публікуйте пари «v1» і «v2» паралельно, забезпечте подвійне випромінювання (dual-write через outbox), знімайте «v1» після переходу.
Outbox и CDC
Outbox (рекомендується для транзакційних сервісів)
1. В одній БД-транзакції: зберігаємо доменний запис і подію в outbox.
2. Фоновий паблішер читає outbox, публікує в брокер, позначає «відправлено».
3. Гарантії: немає «втрати» факту при падіннях, немає розсинхронізації.
CDC (Change Data Capture)
Підходить для існуючих систем/міграцій; джерело - лог реплікації БД.
Вимагає фільтрації/перекодування в доменні події (не транслюйте «сирі» таблиці зовні).
CQRS і проекції
Команди змінюють стан (часто синхронно), події - формують проекції (асинхронно).
Проекції розраховані на запити (пошук, списки, звіти), оновлюються передплатниками.
Тимчасова розсинхронізація - норма: показуйте стійкий UX («дані оновляться через кілька секунд»).
Саги: координація процесів
Оркестрація: один координатор посилає команди і чекає подій.
Хореографія: учасники реагують на події один одного (простіше, але вимагає дисципліни в контрактах).
Правила: чіткі компенсації і тайм-аути, повторювані кроки, ідемпотентні обробники.
Спостережуваність
Trace/Span: прокидайте'trace _ id '/' span _ id'через заголовки при породженні подій.
Метрики: лаг споживачів, швидкість публікації/споживання, dead-letter rate, частка дедуплікацій.
DLQ/parking lot: неуспішні повідомлення - в окремий топік з алертом; забезпечте переобробку.
Безпека та відповідність
Класифікація даних: ядро містить тільки необхідний мінімум PII/фінданих (модель зворотної піраміди), деталі - у збагаченнях.
Підпис/хеш критичних атрибутів, контроль цілісності.
Шифрування in-flight та at-rest, секціонування прав за темами/консюмерами (IAM/ACL).
Політики ретенції та права на забуття: чітко визначені для кожного топіка.
Продуктивність і стійкість
Backpressure: у споживачів - обмеження конкурентності, у брокера - квоти/ліміти.
Batch-обробка і компресія: групуйте записи для зниження накладних витрат.
Ретраї з джиттером і DLQ замість нескінченних спроб.
Rebalance-стійкість: зберігайте офсети транзакційно/зовні, прискорюйте холодний старт снапшотами.
Типові шаблони подій
Ядро платежів
`payment. initiated. v1` → `payment. authorized. v1` → `payment. captured. v1` → `payment. settled. v1`
Відмови: `payment. declined. v1`, `payment. refunded. v1`
Партіонування: `payment_id`
SLA: лаг ядра ≤ 2с p95; ідемпотентність споживачів обов'язкова.
КУС/верифікації
`kyc. started. v1` → `kyc. document. received. v1` → `kyc. approved. v1`/`kyc. rejected. v1`
PII - мінімально; деталі документа - в'kyc. enriched. v1'з обмеженим доступом.
Аудит/безпека
`audit. recorded. v1'з атрибутами'actor','subject','action','occurred _ at','trace _ id'.
Безперервна ретенція/архівування; підвищена цілісність (WORM-сховища).
Антипатерни
Fat Event: перевантажені payload'и без потреби, витоку PII.
Hidden RPC через події: очікування синхронних відповідей «тут і зараз».
Сирі CDC назовні: тісна зв'язність зі схемою БД.
Немає ідемпотентності у споживачів: дублі призводять до подвійних побічних ефектів.
Одна загальна топіка «на все»: конфлікт інтересів, проблемний порядок, складна еволюція.
Покрокове впровадження EDC
1. Картування домену: виділіть ключові агрегати і життєві цикли.
2. Каталог подій: назви, смисли, інваріанти, обов'язкові поля.
3. Схеми та реєстр: виберіть формат, увімкніть правила сумісності.
4. Outbox/CDC: для кожного продюсера визначте механізм публікації фактів.
5. Партіонування: виберіть ключі і оцініть гарячі ключі/перерозбиття.
6. Ідемпотентність: шаблон дедупа + транзакційність споживачів.
7. Проекції: визначте матеріалізовані моделі та SLA оновлення.
8. Обсервабіліті: трасування, лаги, DLQ, алерти.
9. Security/PII: класифікація даних, шифрування, ACL.
10. Гайд по еволюції: політика версій, депрекейт, dual-write для міграцій.
Чек-лист продакшену
- У кожної події є'event _ id','trace _ id','occurred _ at','partition _ key'.
- Схеми в реєстрі, включені перевірки сумісності.
- Ідемпотентність споживачів реалізована і протестована.
- Налаштовані DLQ/parking lot і алерти на помилки публікації/споживання.
- Проекції пересоздаются з логу (replay) з прийнятним часом.
- Обмежений доступ до PII; мінімальні payload'и в ядрі.
- SLA по лагах/доставці заміряються і видно на дашбордах.
- Є план міграції версій подій і вікон депрекейту.
FAQ
Чим EDC відрізняється від «просто шини»?
Ядро - це не тільки брокер, але і контракт подій, правила порядку/ідемпотентності, процеси еволюції і спостережуваність.
Чи можна будувати тільки на CDC?
CDC підходить для інтеграцій/міграцій, але доменні події ясніше виражають сенс і стабільніше переживають зміни БД.
Як бути з узгодженістю?
Приймаємо eventual consistency і проектуємо UX/процеси під неї (індикатори оновлення, ретраї, компенсації).
Коли потрібен exactly-once?
Рідко: коли подвоєння строго неприпустимо і неможливо компенсувати. Частіше досить at-least-once + ідемпотентність.
Підсумок
Event-Driven ядро перетворює потік бізнес-фактів в надійний фундамент системи. Чіткі контракти подій, дисципліна доставки і спостережуваність дають масштабованість, стійкість і швидкість еволюції - без крихких синхронних зв'язків і «шторму» регресій при змінах.