ADR 0099: IDE DataBus — типизированные события и проекции состояния¶
Статус: Accepted · Implemented
Дата: 2026-04-25
Связанные ADR¶
| ADR | Роль |
|---|---|
| 0094 | шина доставки и backpressure |
| 0097 | CCU: свёртка в DTO канала |
| 0036 | канал → CDS → композиция поверхности |
| 0095 | уровни Workspace/Solution/IDE |
| 0004 | UI marshaling |
| 0007 | сигналы и связность |
| ## Резюме |
- IDE DataBus: типизированные события в процессе IDE.
- Развязка источников и проекций; не подменяет 0094 и 0097.
Контекст¶
В коде уже есть рабочие элементы шины и свёртки:
- транспорт и батчинг вывода (
ADR 0094); - вычислительные блоки CCU и каналовая композиция (
ADR 0097,ADR 0036).
Но пока нет единого прикладного контракта «событие домена IDE → подписчики → проекция состояния».
Из-за этого часть сигналов продолжает идти через прямые делегаты и ручную склейку в ViewModel.
Решение¶
Ввести в архитектуру слой IDE DataBus как in-process typed event bus.
- Контракт:
IDataBusс минимальным API: Publish<TEvent>(TEvent evt)Subscribe<TEvent>(Action<TEvent> handler)(с disposable-отпиской)
- События типизированные (не
object/string): BuildStateChangedTestsStateChangedDebugStateChangedGitStateChanged- (по мере надобности)
ScopeDecisionChangedи т.д.
- DataBus не подменяет 0094:
Channel<T>/ingestion остаётся транспортом потока; DataBus — слой распространения нормализованных событий домена.
- DataBus не подменяет 0097/0036:
CCU и compositor по-прежнему отвечают за свёртку/проекцию. DataBus доставляет входные события к местам, где строятся снимки/DTO.
- Базовая реализация v1: синхронный in-memory bus в одном процессе IDE, без внешнего брокера/IPC.
- Git в IDE Health: один продуктовый путь — после обновления git-строк в
UiChromeViewModelвызываетсяAfterGitWorkspaceHealthSummaryApplied(в концеRefreshGitSummaryAsyncна UI-потоке), что вMainWindowViewModelпривязано кPublishGitToIdeDataBusAndRebuildIdeHealth(публикацияGitStateChanged+RebuildIdeHealth). Сид начального состояния:SeedIdeHealthDataBus()в конструкторе (startup + первыйGitStateChanged), безPropertyChangedна отдельные поля git.
- Снимок канала и UI:
IdeHealthSnapshotUnit.Buildвызывается только изMainWindowViewModel.IdeHealth(RebuildIdeHealth); результат кэшируется в_lastIdeHealthInputSnapshot, геттеры строк вMainWindowViewModel.Presentationчитают кэш. Roslyn CASCOPE019 фиксирует эту границу.
- Жизненный цикл:
IdeHealthSnapshotUnitреализуетIDisposable(отписка от шины); при закрытии главного окна —ReleaseWorkspaceHealthChannel().
- Порядок для IDE Health (внедрено): прикладной
InMemoryDataBusглавного окна — синхронная диспетчеризация (asynchronousDispatch: false), чтобы подписчикиIdeHealthSnapshotUnitотработали до возврата изPublish, аRebuildIdeHealth()читал согласованный снимок. Сборка из UI: сначалаBuildStateChanged(старт/финиш), затемIsBuilding— чтобыNotifyPropertyChangedFor→RebuildIdeHealthне обходил обновление_buildSnapshot. Публикации с фона MCP — черезUiScheduler.InvokeAsyncвPublishToIdeDataBusAndRebuild(тот же UI-поток, что и свёртка).
Принципы обмена¶
-
Неблокирующий транспорт между слоями:
ни IDS, ни CDS, ни CCU не должны зависеть от синхронного ответа друг друга в runtime-цепочке.
Публикация выполняется как «fire-and-forward» в соответствующий канал/шину, обработка — по готовности потребителя. -
Строгая типизация сообщений:
никакихobject/dynamicв каналах домена.
Используются типизированные события и явные контракты сообщений (record/иерархия типов; discriminated-union-стиль через pattern matching C#). -
Backpressure и политика потерь по классу данных:
- для критичных сигналов (ошибки, жизненный статус IDE, safety/health) — режим без потерь (unbounded, bounded+wait или отдельный приоритетный контур);
-
для тяжёлых/высокочастотных сигналов (например графовые срезы для Skia) —
BoundedChannelс политикой вродеDropOldest/«latest wins», чтобы не копить устаревшие кадры. -
Изоляция доменов:
CCU получает вход из своего typed input-потока (сенсоры/источники) и публикует отдельный typed output-поток (индикация/проекции).
Ошибки в контуре отрисовки/потребления не должны валить анализ/вычисление.
Границы¶
- Можно: использовать DataBus для развязки источников и проекций (UI/MCP/cockpit snapshot).
- Нельзя: смешивать в одном типе транспортные механики (
Channel<T>, backpressure) и бизнес-события. - Нельзя: переносить рендер/UI-логику в обработчики событий шины.
Strangler-план¶
- Пилотный vertical slice:
BuildStateChangedот источника сборки до IdeHealth snapshot. - Затем
TestsStateChangedиDebugStateChanged. - После стабилизации — расширить на Git/прочие доменные сигналы.
- Закрепить границы в
CascadeIDE.ArchitectureAnalyzers: CASCOPE019 — запрет прямого_workspaceHealth.BuildвнеMainWindowViewModel.IdeHealth(и прежние правила pipeline для устаревших API, см. README анализаторов).
Последствия¶
- Меньше связности в
MainWindowViewModel. - Проще тестировать куски по событиям (publish → проверка проекции).
- Проще добавлять новые каналы/снимки без каскадной правки существующих сервисов.
- Появляется риск «event spaghetti» при слабой дисциплине именования/границ — гасится typed-событиями и ADR-гайдлайнами.
Не цели¶
- Внешний message broker, распределённая шина или межпроцессный transport.
- Унификация всех потоков в один универсальный envelope на первом шаге.
- Массовая миграция всех существующих сигналов в один коммит.