Parent
Часть плана модульной декомпозиции: #489
Цель
Заменить прямые импорты и вызовы между несвязанными доменами на события через EventBus. Модули публикуют события, другие модули подписываются. Нет прямых зависимостей между доменами одного уровня.
События для реализации
Каждое событие — отдельный PR:
| Событие |
Публикует |
Подписчик |
Что заменяет |
KnowledgeUpdated |
knowledge |
llm (reload FAQ cache) |
Прямой вызов из wiki_rag роутера |
WidgetSessionCreated |
channels/widget |
crm (create amoCRM lead) |
create_task(_widget_create_amocrm_lead) в orchestrator.py |
WidgetMessageSent |
channels/widget |
crm (append note to lead) |
Прямой вызов в widget endpoint |
WidgetContactSubmitted |
channels/widget |
crm (link contact to lead) |
Прямой вызов в widget endpoint |
UserRoleChanged |
core/auth |
core/cache (invalidation) |
Ручной cache.invalidate() |
SessionRevoked |
core/auth |
core/cache (invalidation) |
Ручной _session_cache.remove() |
DatasetSynced |
crm, ecommerce |
knowledge (reindex collection) |
Прямой вызов reindex в amocrm/woocommerce роутерах |
ConfigChanged |
core/config |
affected modules (reload) |
Нет (сейчас требует рестарт сервиса) |
BotProcessDied |
channels/telegram, /whatsapp |
monitoring (alert), self (auto-restart) |
Нет (сейчас бот падает молча) |
Паттерн
# modules/channels/widget/events.py
@dataclass
class WidgetSessionCreated(BaseEvent):
session_id: str
widget_instance_id: str
visitor_metadata: dict
# modules/channels/widget/router.py — публикация
await bus.publish(WidgetSessionCreated(
session_id=session.id,
widget_instance_id=widget_id,
visitor_metadata=metadata,
))
# modules/crm/service.py — подписка
class CRMService:
def setup_events(self, bus: EventBus):
bus.subscribe(WidgetSessionCreated, self._on_widget_session)
async def _on_widget_session(self, event: WidgetSessionCreated):
"""Создать lead в amoCRM при создании виджет-сессии."""
...
Порядок работы
Начать с самых простых и изолированных событий:
UserRoleChanged, SessionRevoked — внутри core, самый безопасный
KnowledgeUpdated — простая связь knowledge → llm
DatasetSynced — crm/ecommerce → knowledge
WidgetSessionCreated/MessageSent/ContactSubmitted — widget → crm
BotProcessDied — channels → monitoring (новая функциональность)
ConfigChanged — последним (широкий blast radius)
Критерии готовности
Риск: низкий-средний
Каждое событие меняет ровно один поток данных. Если обработчик упал — publisher продолжает работать (fire-and-forget с логированием).
Зависимости
- Фаза 0 (EventBus должен существовать)
- Фаза 4 (EventBus должен быть интегрирован в startup — модули регистрируют подписки)
Оценка: M (5-6 PR, по одному событию)
Parent
Часть плана модульной декомпозиции: #489
Цель
Заменить прямые импорты и вызовы между несвязанными доменами на события через EventBus. Модули публикуют события, другие модули подписываются. Нет прямых зависимостей между доменами одного уровня.
События для реализации
Каждое событие — отдельный PR:
KnowledgeUpdatedWidgetSessionCreatedcreate_task(_widget_create_amocrm_lead)в orchestrator.pyWidgetMessageSentWidgetContactSubmittedUserRoleChangedcache.invalidate()SessionRevoked_session_cache.remove()DatasetSyncedConfigChangedBotProcessDiedПаттерн
Порядок работы
Начать с самых простых и изолированных событий:
UserRoleChanged,SessionRevoked— внутри core, самый безопасныйKnowledgeUpdated— простая связь knowledge → llmDatasetSynced— crm/ecommerce → knowledgeWidgetSessionCreated/MessageSent/ContactSubmitted— widget → crmBotProcessDied— channels → monitoring (новая функциональность)ConfigChanged— последним (широкий blast radius)Критерии готовности
events.pysetup_events()при инициализации модуляРиск: низкий-средний
Каждое событие меняет ровно один поток данных. Если обработчик упал — publisher продолжает работать (fire-and-forget с логированием).
Зависимости
Оценка: M (5-6 PR, по одному событию)