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

Подійне джерело проти CRUD: ключова відмінність

У традиційній CRUD-системі (Create, Read, Update, Delete) стан зберігається як поточне значення кожної сутності. Коли оновлюється позиція треку, попередня позиція перезаписується. Коли SITREP переглядається, попередня версія може зберігатися або ні залежно від того, як розроблено застосунок. Стан системи в будь-який момент часу в минулому не є відновлюваним з бази даних — це вимагає явних архітектурних рішень щодо версіонування.

Подійне джерело інвертує цю модель. Стан ніколи не зберігається безпосередньо. Натомість кожна зміна стану системи зберігається як подія — незмінний, тільки-для-додавання запис. Сховище подій містить: TrackPositionUpdated у момент T1 з координатами X1; TrackPositionUpdated у момент T2 з координатами X2; TrackIdentityAttributed у момент T3 з позначенням підрозділу Y. Поточний стан треку завжди можна обчислити, відтворивши потік подій з початку. Саме сховище подій є журналом тільки-для-додавання — жодна подія ніколи не змінюється і не видаляється.

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

Оборонні випадки використання подійного джерела

Історія SITREP: Ситуаційні зведення часто переглядаються по мірі надходження нової розвідки. У CRUD-системі поточний SITREP відображає останню оцінку, але історія того, як ця оцінка формувалася, втрачається. У системі з подійним джерелом кожна ревізія є подією — SITREPCreated, SITREPRevised, SITREPApproved, SITREPDisseminated — і повна історія завжди доступна для запиту. Після операції аналітики розвідки можуть відтворити точно, що було відомо і коли, що є необхідним для оцінки бойових втрат противника та вдосконалення процесів розвідки.

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

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

Технології сховищ подій

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

Apache Kafka як журнал подій: Архітектура журналу Kafka з тільки-для-додавання та секціонуванням тісно відповідає вимогам подійного джерела. Теми Kafka секціонують події за типом агрегату; у межах теми події впорядковані за секцією та зміщенням. Механізм груп споживачів дозволяє кільком проєкціям споживати той самий потік подій із незалежними зміщеннями. Розподілена конструкція Kafka забезпечує відмовостійкість, яку EventStoreDB вимагає додаткового налаштування для досягнення. Для систем ЗСУ, що вже використовують Kafka для конвеєрів прийому даних, використання Kafka як сховища подій дозволяє уникнути введення другої спеціалізованої бази даних.

PostgreSQL з таблицями тільки-для-додавання JSONB: Для систем, де введення спеціалізованого сховища подій неможливе, PostgreSQL з таблицею тільки-для-додавання та корисними навантаженнями подій JSONB є практичною альтернативою. Схема таблиці: event_id (UUID), aggregate_type (VARCHAR), aggregate_id (UUID), event_type (VARCHAR), event_data (JSONB), occurred_at (TIMESTAMPTZ), recorded_at (TIMESTAMPTZ), recorded_by (VARCHAR). Тригер або обмеження на рівні застосунку запобігають операціям UPDATE та DELETE в таблиці. Це забезпечує семантику подійного джерела без спеціалізованої технології.

Відновлення стану: проєкції, знімки та продуктивність відтворення

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

Стандартним рішенням є знімки (snapshotting): періодична матеріалізація поточного стану сутності та її зберігання разом із потоком подій. При відновленні стану система завантажує останній знімок і відтворює лише події після позначки часу знімка. Трек із 50 000 загальних подій, але знімком після 49 900 подій, вимагає відтворення лише 100 подій. Частота знімків є налаштовуваним параметром: більш часті знімки покращують затримку читання за рахунок більшого сховища.

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

Правові та відповідні виміри

Правові вимоги до зберігання оборонних даних суттєво відрізняються залежно від національної юрисдикції та типу операції. Для систем ЗСУ та підрозділів, що діють у рамках спільних операцій, мінімально необхідне: докази цілісності даних (збережений запис не змінювався з моменту створення), записи ланцюга зберігання (хто отримував доступ і обробляв кожен елемент даних), та відповідність строку зберігання (дані зберігаються протягом необхідного строку та безпечно видаляються після нього). Подійне джерело забезпечує докази цілісності даних власне через свою структуру — сховище тільки-для-додавання є структурно стійким до модифікацій. Ланцюг зберігання забезпечується метаданими подій (поля recorded_by, processing_system_id). Відповідність строку зберігання вимагає явного застосування політики на рівні інфраструктури.

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