Целевая раскладка модулей
NOTE
Статус: Target design. Документ описывает целевую архитектуру. Сервисы, модули и контракты, упомянутые ниже, могут ещё не существовать в
backend/. Правила маркировки — в50-processes/documentation-standard.md.
Этот документ описывает целевую структуру backend-кода и архитектурные границы между модулями. Это не буквальное описание текущего дерева файлов.
Сейчас в репозитории уже есть минимальный scaffold:
backend/cmd/api-server/— текущая точка входа HTTP API.backend/internal/httpserver/— простой handler с health/readiness endpoint’ами.docs/docs/иdeploy/— канонические docs и локальный контур.
Ниже описано, во что этот scaffold должен вырасти по мере реализации целевой архитектуры.
Планируемая раскладка backend
backend/
├── cmd/ # точки входа процессов
│ ├── api-server/ # public/admin HTTP API
│ ├── ingestor/ # orchestration коннекторов и ingestion jobs
│ ├── matcher/ # matching engine
│ └── estimate-builder/ # meta-search / estimate pipeline
│
├── internal/
│ ├── core/ # доменные и прикладные модули
│ │ ├── catalog/
│ │ ├── offers/
│ │ ├── pricing/
│ │ ├── search/
│ │ ├── matching/
│ │ ├── enrichment/
│ │ └── meta-search/
│ │
│ ├── connectors/ # интеграции с поставщиками
│ │ ├── etm/
│ │ └── ...
│ │
│ └── platform/ # общая инфраструктура
│ ├── config/
│ ├── errors/
│ ├── observability/
│ ├── messaging/
│ ├── rate-limiter/
│ ├── storage/
│ └── auth/
│
├── go.mod
└── ...Структура одного модуля (bounded context)
Полное описание решения — ADR-0031.
Каждый модуль в internal/core/<bc>/ — это тонкое ядро плюс набор суб-модулей, где каждый суб-модуль соответствует одному агрегату (или use-case-bounded группе агрегатов).
internal/core/<bc>/
├── AGENTS.md
├── README.md
├── di.go # fx.Module() BC, собирает суб-модули
├── kernel/
│ ├── domain/ # BC-wide VO / IDs / shared errors (по необходимости)
│ └── infra/ # shared pg helpers и прочие адаптеры
└── <submodule>/ # = агрегат или use-case-bounded группа
├── AGENTS.md
├── README.md
├── di.go # fx.Module() суб-модуля
├── domain/ # плоский layout
├── app/
├── infra/
│ └── postgres/
└── api/
└── http/Внутри одного суб-модуля слои (domain, app, infra, api) держатся плоскими. Дополнительное дробление (domain/model/, app/command/, app/query/, …) вводится только если суб-модуль действительно перерос плоскую раскладку — это указывается в его README.md.
Правила зависимостей
Разрешённый граф зависимостей внутри суб-модуля:
api ──▶ app ──▶ domain
▲
│
infraОбязательные правила:
domainсуб-модуля не знает о PG, Kafka, HTTP, Elasticsearch и других внешних технологиях. Разрешено:core/<bc>/kernel/domainи whitelistinternal/platform/{clock,errors,money,ids}.appимпортирует только свойdomain+ тот же whitelist платформенных пакетов. Он не видитinfra,apiи тем более пакеты соседних суб-модулей.infraреализуетdomain-порты и может импортироватьkernel/infra+internal/platform/**.api— тонкий слой; импортируетappсвоего суб-модуля +platform/httpx+platform/errors.- Соседние суб-модули (
<bc>/<sub-a>↔<bc>/<sub-b>) не импортируют пакеты друг друга напрямую. Если суб-модулю A нужна логика B, A объявляет consumer-owned порт в своёмdomain/, B-ориентированный адаптер — в своёмinfra/, проводка — вcore/<bc>/di.go. - Доменные модули в
internal/core/*не импортируют конкретные поставщиковые реализации изinternal/connectors/*. - Межмодульное (между BC) взаимодействие — только через consumer-owned контракты + события + проводку в
cmd/*/main.go.
Эти правила машинно проверяются линтером go-arch-lint — конфиг backend/.go-arch-lint.yml генерируется из дерева (инструмент backend/cmd/archlint-gen), и CI-job backend-archlint запускается в каждом merge request.
Как читать этот документ вместе с репозиторием
- Если нужно понять, что уже можно запустить, смотрите
../40-operations/deployment.mdи текущее деревоbackend/. - Если нужно понять, как должен быть организован код при дальнейшем росте, используйте этот документ.
- Если нужно понять, почему выбрана именно такая структура, смотрите ADR-001 и связанные документы в
adr/.
Регистрация коннекторов
Ядро не знает о конкретных поставщиках заранее. В целевом состоянии registration/wiring коннекторов происходит в composition root процесса ingestion.
Принцип такой:
- доменный ingestion-модуль работает только с интерфейсом
Connector; - конкретный connector (
etm, другой supplier) реализует этот интерфейс в своём модуле; - composition root регистрирует нужные реализации в зависимости от конфигурации окружения.
Внедрение зависимостей
Композиционный корень — fx.App (см. ADR-0030 и P25 в principles.md).
cmd/<process>/main.go= толькоfx.New(...)+app.Run();- платформенные модули —
backend/internal/platform/di(Core,Observability,Postgres,Kafka,Redis,OpenAPI,Health,HTTP); - каждый BC предоставляет
Module()вbackend/internal/core/<bc>/di.go; - ресурсы с внешним соединением регистрируют
fx.Lifecyclehooks; graceful shutdown — черезfx; - тестовая композиция —
fxtest.New+fx.Replaceсmockery-моками. Unit-тесты на доменную/app-логику в DI не нуждаются и продолжают работать через обычные конструкторы.
Межмодульное и межсервисное взаимодействие
- синхронные вызовы: internal HTTP/Connect endpoint’ы;
- асинхронные вызовы: Kafka;
- локально и в production-like окружениях сервисы общаются через сеть
docker-compose, а не черезlocalhostодного контейнера.
Общие библиотеки
internal/platform/* содержит только общую инфраструктуру:
config— конфигурация из env и файлов;errors— общий registry кодов ошибок, typed error model, correlation helpers и policy/lint правила против raw string errors;observability— OTel и logging;messaging— Kafka adapters;rate-limiter— Redis token bucket;storage— helpers для PG, ES, ClickHouse, S3;auth— shared auth utilities.
Бизнес-логика живёт не здесь, а в доменных модулях internal/core/*.