Контекст
План поэтапной миграции от текущей монолитной архитектуры к модульной декомпозиции, описанной в #481.
Стратегия: Strangler Fig — новая структура создаётся рядом со старой, импорты перенаправляются постепенно, старые файлы становятся тонкими фасадами-реэкспортами, удаляются только когда все потребители мигрированы.
Основано на аудите: #480, целевая архитектура: #481.
Текущее состояние (три монолита)
| Монолит |
Размер |
Проблема |
orchestrator.py |
4140 строк |
Startup (365 строк), 8 глобальных сервисов, ~100 legacy endpoints, 5 background tasks, регистрация 28 роутеров |
db/models.py |
3667 строк, 54 модели |
Все домены в одном файле |
db/integration.py |
2688 строк, 29 менеджеров |
Все менеджеры — синглтоны на уровне модуля |
Что уже хорошо (не нужно трогать)
db/repositories/ — 45 файлов, чистая изоляция, не импортируют ничего из orchestrator/routers/services
- Бот-подпроцессы (telegram_bot/, whatsapp_bot/) — уже общаются через HTTP API, не импортируют
db/
- Роутеры — уже 28 отдельных файлов в
app/routers/
Ключевые ограничения и риски
1. Alembic-миграции
Существующие миграции делают from db.models import User, ChatSession, .... Перенос моделей без фасада-реэкспорта ломает alembic upgrade head.
Решение: db/models.py остаётся как фасад — импортирует из доменных модулей и реэкспортирует всё. Старые миграции работают без изменений.
2. SQLAlchemy Base.metadata
Все модели должны быть зарегистрированы в одном Base для:
Base.metadata.create_all() (автосоздание таблиц)
- Relationship-ы между моделями разных доменов
- Alembic autogenerate
Решение: Base остаётся в db/database.py. Доменные models.py импортируют оттуда. При старте все модели "видны" через явные импорты в точке входа.
3. Cross-domain FK
14 таблиц имеют workspace_id FK, owner_id → User.id повсеместно. Убирание FK ради "чистоты модулей" = потеря referential integrity без выигрыша.
Решение: Принять, что доменные модели знают о core-моделях (User, Workspace) — это нормальная зависимость "все зависят от core". Не убирать FK из существующих таблиц.
4. Параллельная разработка (local + server)
Массовый рефакторинг файловой структуры = merge-ад. Переносы файлов + правки в старых путях = конфликты на каждом PR.
Решение: Каждую фазу делает одна машина. Мелкие PR с чёткими границами. Вторая машина в это время не трогает мигрируемые файлы.
Фазы миграции
✅ Фаза 0: Инфраструктура core (нулевой риск)
Чисто аддитивно — создаём новые модули, не трогая существующий код
Создать modules/core/:
events.py — EventBus (in-process pub/sub)
health.py — HealthRegistry (модули регистрируют свои health checks)
tasks.py — TaskRegistry (named background tasks, cancel_all)
base.py — BaseService (если нужен общий интерфейс)
Критерий готовности: Новые модули импортируемы и покрыты тестами. Существующий код не изменён.
Зависимости: Нет.
Фаза 1: Разделение db/models.py (низкий риск)
Модели переезжают в доменные файлы, db/models.py становится фасадом
Целевая структура:
modules/
├── core/models.py ← User, Role, RolePermission, UserSession,
│ Workspace, WorkspaceMember, WorkspaceInvite,
│ UserIdentity, SystemConfig
├── chat/models.py ← ChatSession, ChatMessage, ChatSessionShare, ResourceShare
├── channels/
│ ├── telegram/models.py ← BotInstance, TelegramSession + Bot* (12 sales-моделей)
│ ├── whatsapp/models.py ← WhatsAppInstance
│ └── widget/models.py ← WidgetInstance
├── llm/models.py ← CloudLLMProvider, LLMPreset
├── knowledge/models.py ← KnowledgeCollection, KnowledgeDocument, FAQEntry
├── speech/models.py ← TTSPreset
├── crm/models.py ← AmoCRMConfig, AmoCRMSyncLog
├── ecommerce/models.py ← WooCommerceConfig
├── kanban/models.py ← KanbanProject, KanbanTask, KanbanTaskDependency,
│ KanbanChecklistItem, KanbanTaskStatus
├── claude_code/models.py ← ClaudeCodeSession, ClaudeCodeProject
├── monitoring/models.py ← AuditLog, UsageLog, UsageLimits
├── sales/models.py ← PaymentLog
├── admin/models.py ← UserConsent
└── telephony/models.py ← GSMCallLog, GSMSMSLog, GitHubRepoProject
db/models.py остаётся — реэкспорт:
# db/models.py — фасад (backward compat для Alembic и старого кода)
from modules.core.models import User, Role, RolePermission, ...
from modules.chat.models import ChatSession, ChatMessage, ...
from modules.kanban.models import KanbanProject, KanbanTask, ...
# ... все 54 модели
Порядок: По одному домену за коммит. Начать с самых изолированных (kanban, claude_code, monitoring), заканчивать core (от которого все зависят).
Критерий готовности: alembic upgrade head работает. alembic revision --autogenerate видит все модели. Все тесты проходят. Все from db.models import X продолжают работать.
Зависимости: Нет (можно параллельно с Фазой 0).
Фаза 2: Разделение db/integration.py (низкий-средний риск)
Менеджеры переезжают в доменные service.py, db/integration.py становится фасадом
Паттерн:
# modules/chat/service.py
class ChatService: # бывший AsyncChatManager
...
# db/integration.py — фасад
from modules.chat.service import ChatService as AsyncChatManager
async_chat_manager = AsyncChatManager()
29 менеджеров → ~14 доменных service.py (некоторые домены объединяют несколько менеджеров):
| Домен |
Менеджеры → service.py |
| core |
AsyncUserManager, AsyncUserSessionManager, AsyncRoleManager, AsyncWorkspaceManager, AsyncUserIdentityManager, AsyncConfigManager |
| chat |
AsyncChatManager, AsyncChatShareManager |
| channels/telegram |
AsyncBotInstanceManager, AsyncTelegramSessionManager |
| channels/whatsapp |
AsyncWhatsAppInstanceManager |
| channels/widget |
AsyncWidgetInstanceManager |
| llm |
AsyncCloudProviderManager |
| knowledge |
AsyncKnowledgeDocManager, AsyncKnowledgeCollectionManager, AsyncFAQManager |
| speech |
AsyncPresetManager |
| crm |
AsyncAmoCRMManager |
| ecommerce |
AsyncWooCommerceManager |
| kanban |
AsyncKanbanManager, AsyncKanbanProjectManager |
| claude_code |
AsyncClaudeCodeManager, AsyncClaudeCodeProjectManager |
| monitoring |
AsyncAuditLogger, AsyncPaymentManager, DatabaseManager |
| admin |
AsyncResourceShareManager |
Порядок: Начать с листовых доменов (ecommerce, claude_code, kanban), заканчивать core.
Критерий готовности: Все роутеры работают без изменений (импорт из db.integration по-прежнему валиден). Тесты проходят.
Зависимости: Фаза 1 (модели должны быть в доменах, чтобы service.py импортировал из своего домена).
Фаза 3: Перенос роутеров (средний риск)
app/routers/*.py → modules/{domain}/router.py, фасад в app/routers/__init__.py
Паттерн:
# modules/chat/router.py — реальный код
router = APIRouter(prefix="/admin/chat", tags=["chat"])
...
# app/routers/chat.py — фасад
from modules.chat.router import router # noqa: F401
28 роутеров → ~14 доменов:
| Домен |
Роутеры |
| core |
auth.py, roles.py, workspace.py |
| chat |
chat.py |
| channels/telegram |
telegram.py |
| channels/whatsapp |
whatsapp.py |
| channels/widget |
widget.py |
| llm |
llm.py |
| knowledge |
wiki_rag.py |
| speech |
tts.py, stt.py, services.py |
| crm |
amocrm.py |
| ecommerce |
woocommerce.py |
| kanban |
kanban.py, github_repos.py |
| claude_code |
claude_code.py |
| monitoring |
audit.py, usage.py, monitor.py |
| sales |
bot_sales.py, yoomoney_webhook.py |
| admin |
backup.py, legal.py, faq.py, github_webhook.py |
| telephony |
gsm.py |
На этом этапе каждый домен имеет: models.py, service.py, router.py — базовая структура из #481.
Параллельно: Обновить импорты в роутерах — вместо from db.integration import X → from modules.{domain}.service import X.
Критерий готовности: orchestrator.py регистрирует роутеры через ту же точку входа (app/routers/__init__.py). Все endpoints работают.
Зависимости: Фаза 2.
Фаза 4: Разборка orchestrator.py (высокий риск)
Самая сложная фаза — разбить 4140 строк на управляемые части
Подэтапы:
4a. Вынести legacy endpoints
~100 legacy endpoints (OpenAI-compatible /v1/*, widget endpoints, helper functions) → отдельные роутеры:
/v1/* → modules/compat/router.py
- Widget endpoints (из orchestrator.py) → уже должны быть в
modules/channels/widget/router.py (после Фазы 3)
StreamingTTSManager → modules/speech/streaming.py
4b. Модульный startup
Заменить 365-строчный startup_event() на:
for module in enabled_modules:
svc = module.create_service(db, **deps)
app.include_router(module.router)
Каждый модуль получает create_service() → возвращает инициализированный сервис.
4c. Background tasks → TaskRegistry
5 create_task() без сохранения references → TaskRegistry из Фазы 0:
tasks.register("session_cleanup", chat_svc.periodic_cleanup, interval=3600)
tasks.register("vacuum", db.periodic_vacuum, interval=7*24*3600)
tasks.register("kanban_sync", kanban_svc.periodic_sync, interval=900)
4d. Graceful shutdown
shutdown_event() → tasks.cancel_all() → channels.stop_all_bots() → bridge.stop() → db.close()
4e. Deployment modes через модульную загрузку
MODULES = {
"full": [..., speech, telephony],
"cloud": [...], # speech и telephony не загружаются
}
Целевой orchestrator.py (или app.py): ~100-150 строк.
Критерий готовности: Health check работает. Все endpoints доступны. Боты стартуют. Graceful shutdown корректен.
Зависимости: Фазы 0-3.
Фаза 5: EventBus для cross-module коммуникации (низкий-средний риск)
Заменить прямые импорты между доменами на события
Приоритетные события:
| Событие |
Публикует |
Подписчик |
Что заменяет |
KnowledgeUpdated |
knowledge |
llm (reload FAQ cache) |
Прямой вызов из wiki_rag роутера |
WidgetSessionCreated |
channels/widget |
crm (create lead) |
create_task(_widget_create_amocrm_lead) в orchestrator.py |
WidgetMessageSent |
channels/widget |
crm (append note) |
Прямой вызов в widget endpoint |
UserRoleChanged |
core/auth |
core/cache (invalidation) |
Ручной cache.invalidate() |
DatasetSynced |
crm, ecommerce |
knowledge (reindex) |
Прямой вызов reindex в amocrm/woocommerce роутерах |
ConfigChanged |
core/config |
affected modules |
Нет (сейчас требует рестарт) |
По одному событию за PR. Каждое — отдельная дочерняя issue.
Критерий готовности: Прямые импорты между несвязанными доменами заменены на подписки. Граф зависимостей соответствует #481.
Зависимости: Фаза 4 (EventBus должен быть интегрирован в startup).
Фаза 6: Protocol interfaces (низкий риск, можно делать в любой момент после Фазы 2)
Типизация контрактов между модулями
Определить Protocol-классы для:
KnowledgeService (search, search_multi, get_collections)
LLMService (generate, stream, resolve_backend)
ChatService (create_session, send_message, stream_message)
Это не ломает код — просто добавляет типизацию для mypy и документации.
Граф зависимостей между фазами
Фаза 0 (core infra) ─────────────────────────────────┐
│ │
Фаза 1 (split models) ─── можно параллельно ──────────┤
│ │
Фаза 2 (split integration) ───────────────────────────┤
│ │
Фаза 3 (move routers) ────────────────────────────────┤
│ │
Фаза 4 (разборка orchestrator) ───────────────────────┤
│ │
Фаза 5 (EventBus) ────────────────────────────────────┘
│
Фаза 6 (Protocols) ── можно в любой момент после Ф2 ──┘
Оценка объёма
| Фаза |
Риск |
Оценка |
Кол-во PR |
| 0: Core infra |
Нулевой |
S |
1 |
| 1: Split models |
Низкий |
M |
3-5 (по группам доменов) |
| 2: Split integration |
Низкий-средний |
L |
5-7 (по доменам) |
| 3: Move routers |
Средний |
M |
3-5 |
| 4: Разборка orchestrator |
Высокий |
XL |
5-8 (по подэтапам) |
| 5: EventBus |
Низкий-средний |
M |
5-6 (по событиям) |
| 6: Protocols |
Низкий |
S |
1-2 |
Итого: ~25-35 PR, каждый — небольшой и ревьюабельный.
Правила миграции
- Никогда не ломать
from db.models import X — фасад-реэкспорт обязателен
- Никогда не ломать
from db.integration import X — фасад-реэкспорт обязателен
- Один домен за PR — не мигрировать несколько доменов в одном PR
- Тесты + lint на каждом PR — CI должен быть зелёным
alembic upgrade head — обязательная проверка на каждом PR с моделями
- Новые миграции импортируют из
modules/ — старые остаются с db.models
- Фасады удаляются только когда ВСЕ потребители мигрированы (отдельный PR, в конце)
Дочерние issues
Для каждой фазы и подэтапа будут созданы отдельные issues с конкретными задачами, чеклистами и acceptance criteria. Они будут привязаны к этому issue как parent.
Контекст
План поэтапной миграции от текущей монолитной архитектуры к модульной декомпозиции, описанной в #481.
Стратегия: Strangler Fig — новая структура создаётся рядом со старой, импорты перенаправляются постепенно, старые файлы становятся тонкими фасадами-реэкспортами, удаляются только когда все потребители мигрированы.
Основано на аудите: #480, целевая архитектура: #481.
Текущее состояние (три монолита)
orchestrator.pydb/models.pydb/integration.pyЧто уже хорошо (не нужно трогать)
db/repositories/— 45 файлов, чистая изоляция, не импортируют ничего из orchestrator/routers/servicesdb/app/routers/Ключевые ограничения и риски
1. Alembic-миграции
Существующие миграции делают
from db.models import User, ChatSession, .... Перенос моделей без фасада-реэкспорта ломаетalembic upgrade head.Решение:
db/models.pyостаётся как фасад — импортирует из доменных модулей и реэкспортирует всё. Старые миграции работают без изменений.2. SQLAlchemy Base.metadata
Все модели должны быть зарегистрированы в одном
Baseдля:Base.metadata.create_all()(автосоздание таблиц)Решение:
Baseостаётся вdb/database.py. Доменныеmodels.pyимпортируют оттуда. При старте все модели "видны" через явные импорты в точке входа.3. Cross-domain FK
14 таблиц имеют
workspace_idFK,owner_id → User.idповсеместно. Убирание FK ради "чистоты модулей" = потеря referential integrity без выигрыша.Решение: Принять, что доменные модели знают о
core-моделях (User, Workspace) — это нормальная зависимость "все зависят от core". Не убирать FK из существующих таблиц.4. Параллельная разработка (local + server)
Массовый рефакторинг файловой структуры = merge-ад. Переносы файлов + правки в старых путях = конфликты на каждом PR.
Решение: Каждую фазу делает одна машина. Мелкие PR с чёткими границами. Вторая машина в это время не трогает мигрируемые файлы.
Фазы миграции
✅ Фаза 0: Инфраструктура core (нулевой риск)
Создать
modules/core/:events.py— EventBus (in-process pub/sub)health.py— HealthRegistry (модули регистрируют свои health checks)tasks.py— TaskRegistry (named background tasks, cancel_all)base.py— BaseService (если нужен общий интерфейс)Критерий готовности: Новые модули импортируемы и покрыты тестами. Существующий код не изменён.
Зависимости: Нет.
Фаза 1: Разделение
db/models.py(низкий риск)Целевая структура:
db/models.pyостаётся — реэкспорт:Порядок: По одному домену за коммит. Начать с самых изолированных (kanban, claude_code, monitoring), заканчивать core (от которого все зависят).
Критерий готовности:
alembic upgrade headработает.alembic revision --autogenerateвидит все модели. Все тесты проходят. Всеfrom db.models import Xпродолжают работать.Зависимости: Нет (можно параллельно с Фазой 0).
Фаза 2: Разделение
db/integration.py(низкий-средний риск)Паттерн:
29 менеджеров → ~14 доменных service.py (некоторые домены объединяют несколько менеджеров):
Порядок: Начать с листовых доменов (ecommerce, claude_code, kanban), заканчивать core.
Критерий готовности: Все роутеры работают без изменений (импорт из
db.integrationпо-прежнему валиден). Тесты проходят.Зависимости: Фаза 1 (модели должны быть в доменах, чтобы service.py импортировал из своего домена).
Фаза 3: Перенос роутеров (средний риск)
Паттерн:
28 роутеров → ~14 доменов:
На этом этапе каждый домен имеет:
models.py,service.py,router.py— базовая структура из #481.Параллельно: Обновить импорты в роутерах — вместо
from db.integration import X→from modules.{domain}.service import X.Критерий готовности:
orchestrator.pyрегистрирует роутеры через ту же точку входа (app/routers/__init__.py). Все endpoints работают.Зависимости: Фаза 2.
Фаза 4: Разборка
orchestrator.py(высокий риск)Подэтапы:
4a. Вынести legacy endpoints
~100 legacy endpoints (OpenAI-compatible
/v1/*, widget endpoints, helper functions) → отдельные роутеры:/v1/*→modules/compat/router.pymodules/channels/widget/router.py(после Фазы 3)StreamingTTSManager→modules/speech/streaming.py4b. Модульный startup
Заменить 365-строчный
startup_event()на:Каждый модуль получает
create_service()→ возвращает инициализированный сервис.4c. Background tasks → TaskRegistry
5
create_task()без сохранения references →TaskRegistryиз Фазы 0:4d. Graceful shutdown
shutdown_event()→tasks.cancel_all()→ channels.stop_all_bots() → bridge.stop() → db.close()4e. Deployment modes через модульную загрузку
Целевой
orchestrator.py(илиapp.py): ~100-150 строк.Критерий готовности: Health check работает. Все endpoints доступны. Боты стартуют. Graceful shutdown корректен.
Зависимости: Фазы 0-3.
Фаза 5: EventBus для cross-module коммуникации (низкий-средний риск)
Приоритетные события:
KnowledgeUpdatedWidgetSessionCreatedcreate_task(_widget_create_amocrm_lead)в orchestrator.pyWidgetMessageSentUserRoleChangedcache.invalidate()DatasetSyncedConfigChangedПо одному событию за PR. Каждое — отдельная дочерняя issue.
Критерий готовности: Прямые импорты между несвязанными доменами заменены на подписки. Граф зависимостей соответствует #481.
Зависимости: Фаза 4 (EventBus должен быть интегрирован в startup).
Фаза 6: Protocol interfaces (низкий риск, можно делать в любой момент после Фазы 2)
Определить Protocol-классы для:
KnowledgeService(search, search_multi, get_collections)LLMService(generate, stream, resolve_backend)ChatService(create_session, send_message, stream_message)Это не ломает код — просто добавляет типизацию для mypy и документации.
Граф зависимостей между фазами
Оценка объёма
Итого: ~25-35 PR, каждый — небольшой и ревьюабельный.
Правила миграции
from db.models import X— фасад-реэкспорт обязателенfrom db.integration import X— фасад-реэкспорт обязателенalembic upgrade head— обязательная проверка на каждом PR с моделямиmodules/— старые остаются сdb.modelsДочерние issues
Для каждой фазы и подэтапа будут созданы отдельные issues с конкретными задачами, чеклистами и acceptance criteria. Они будут привязаны к этому issue как parent.