Перейти к содержимому

Архитектура

Enterium — модульный монолит: один FastAPI-бэкенд, разрезанный на bounded contexts с принудительными границами в CI, плюс React web UI, набор неизменяемых нативных пайплайнов, исполняемых in-process, и два небольших вендорнутых сервиса для публикации и медиа.

MCP + RESTrender + deployweekly metricsOwner / teamWeb UILLM agentsPlatform APIPostgreSQL+ pgvectorRedisExternal providersLLM · search · images ·storagePublishing hubFleet of static sitesSearch consoles

Всё состояние принадлежит API. Внешние провайдеры доступны только через контекст integrations (единый аудируемый канал); публикуемые сайты — статические, их собирает и деплоит publishing hub; поисковые метрики возвращаются по расписанию.

platform FastAPI, Python 3.12Bounded contextsintegrations · runs ·research · retrieval · quality· publishanalytics · competitors ·cockpit · ops · tickets ·tasks · chatlog · specsNative pipelinesimmutable versioned dirs:collect · generate · localizeShared layersauth · lifecycle engine ·ORM · API routerswebuiReact 19 + TypeScript, Vitecontractsshared OpenAPI + JSONschemaswp_deployerpublishing hub serviceimagegenmedia generation service

Правила, которые держат монолит монолитом, а не клубком

Заголовок раздела «Правила, которые держат монолит монолитом, а не клубком»
  1. Bounded context — вертикальный срез одной бизнес-способности: его логика, схемы, роутер и тесты живут в одной папке (platform/app/contexts/<name>/).
  2. Контексты общаются только через контракты. Контекст вызывает другой только через его публичный contracts.py (типизированный интерфейс) или событие — никогда импортом внутренностей, никогда запросом к чужим таблицам. Это принуждает import-linter в CI: нарушение — красный билд, а не замечание в ревью.
  3. Одна таблица — один контекст-владелец. Никаких cross-context JOIN.
  4. Новая способность = новый модуль, не новый сервис. Отдельный сервис — исключение, требующее нескольких жёстких триггеров (другой рантайм, изоляция секретов, независимое масштабирование). Два вендорнутых сервиса существуют ровно потому, что эти триггеры выполнили.
  5. Shared kernel остаётся тонким — конфигурация, база, примитивы аутентификации, логирование, событийная обвязка. Без бизнес-логики.

Полная карта контекстов с зонами ответственности — на странице Bounded contexts.

Постоянно работающего оркестратора-демона нет. Прод живёт на ночном таймере, который прогоняет цикл Collect → Generate → publish по флоту контрактов; между прогонами платформа простаивает по дизайну (статус orchestrator.present: false — норма, см. Как интерпретировать статус). Каждое исполнение записывается как pipeline_run с построчными run_step — история запрашивается, а не выводится.

Сами пайплайны — нативные и in-process: каждая версия — неизменяемая директория Python-шагов, резолвящаяся через каталог пайплайнов. Отказы громкие: ошибка шага роняет прогон и фиксируется в поверхности ошибок; тихих пошаговых ретраев нет (транзиентные ошибки провайдера ретраит только LLM-адаптер внутри себя).