ADR-0052: Backend conventions hardening — DI naming, HTTP envelope, submodule docs

Status: accepted Date: 2026-05-09 Deciders: команда проекта

Контекст

ADR-0030 ввёл uber-go/fx как обязательный DI runtime и зафиксировал правило «каждый BC поставляет <bc>.Module() через internal/core/<bc>/di.go». ADR-0031 ввёл микроядро суб-модулей и обязал каждый суб-модуль иметь AGENTS.md и README.md. На уровне HTTP в internal/platform/httpx существует общий пакет с WriteJSON, DecodeJSON, HandleError и ErrorEnvelope, и в public-api.openapi.yaml зафиксирован canonical формат ошибки Error / ErrorEnvelope.

На практике дерево разъехалось:

  1. Имя DI-файла. 18 BC использовали di.go, 15 — fx.go (cabinet/*, pricing, stock, delivery, search/proposal и др.). Технически это неважно для работы fx, но создаёт ложное впечатление, что fx.go и di.go — разные сорта файлов. Новый инженер каждый раз тратит время, проверяя, не делают ли они разное.
  2. HTTP-helpers. 12+ handler’ов в core/* имели локальные приватные копии writeJSON/writeError/writeErrorJSON. Часть возвращала {error: "string"}, часть — {error: {code, message}}. Контракт ошибки в публичном API объявлен один (ErrorEnvelope), на практике клиент получал две-три различных формы. Спека сама зафиксировала это рассогласование, держа параллельную схему ErrorResponse для одного endpoint’а.
  3. Документация суб-модулей. Большинство суб-модулей (на момент фиксации — порядка 22) не имели AGENTS.md и README.md, несмотря на формальное требование ADR-0031. Шаблоны лежат в docs/docs/20-architecture/templates/{bc,submodule}-{AGENTS,README}.md, но обязательность была неформальной.

ADR-0030 и ADR-0031 формально иммутабельны (правила из самих ADR), поэтому уточнения вводятся отдельным ADR. Этот документ затягивает гайки и ставит CI-проверки.

Решение

Уточняем три конвенции backend-уровня. Все три проверяются автоматически.

1. Имя DI-файла = di.go. Без исключений.

В каждом пакете-композиционном корне (BC, sub-module, platform-подсистема) файл, который объявляет fx.Module(...) либо Module()/Module-export поверх fx.Option, называется di.go. Имя fx.go запрещено.

platform/di/ остаётся самим собой как пакет di (платформа уровня).

CI-проверка: новый шаг в Makefile / GitLab pipeline отвергает любой файл **/fx.go в backend/.

2. HTTP-ответы — только через internal/platform/httpx.

  • httpx.WriteJSON(w, status, payload) — единственный путь сериализации тела ответа.
  • httpx.HandleError(w, r, err) — единственный путь рендеринга ошибки.
  • httpx.ErrorEnvelope ({error: {code, title, message, category, retryable, request_id, trace_id, details}}) — единственный shape ошибки в JSON-ответе.
  • httpx.DecodeJSON(r, target) — единственный путь декодирования request body.

Запрещено объявлять локальные writeJSON / writeError / writeErrorJSON функции в handler-пакетах. Запрещено использовать json.NewEncoder(w).Encode(...) напрямую — пиши через httpx.WriteJSON.

В public-api.openapi.yaml остаётся единственный envelope — ErrorEnvelope со схемой Error. Альтернативная схема ErrorResponse ({error: string, detail: string, ...}) удаляется как deprecated.

CI-проверка: regexp grep по func writeJSON\b|func writeError\b|func writeErrorJSON\b|json\.NewEncoder\(w\)\.Encode в backend/internal/core/**/*.go падает с ошибкой при ненулевом совпадении.

3. AGENTS.md + README.md обязательны для каждого BC и суб-модуля.

Каждый каталог backend/internal/core/<bc>/ и backend/internal/core/<bc>/<submodule>/ обязан содержать оба файла, заполненных по шаблонам из docs/docs/20-architecture/templates/.

Layer-каталоги (domain/, app/, infra/, api/) внутри суб-модуля не обязаны иметь AGENTS.md / README.md.

CI-проверка: новый шаг отвергает PR, который добавляет каталог BC или суб-модуля без обоих файлов.

Последствия

Плюсы

  • Один взгляд на дерево — одна консистентная картина: di.go везде, одна форма ошибки везде, одна структура папки везде.
  • Новый инженер видит в любом suspended BC всё, что ему нужно знать: AGENTS.md (правила), README.md (что и зачем), di.go (точка сборки).
  • Контракт ошибок наконец совпадает с тем, что объявлено в OpenAPI; клиент пишет один парсер.
  • Линтерные правила предотвращают регресс: невозможно «случайно» внести локальный writeJSON или забыть AGENTS.md в новом суб-модуле.

Минусы

  • Перевод всех существующих handler’ов на httpx.HandleError — breaking change для тестов, которые ассертили старый {error: "string"} shape. Решается одной волной: тесты адаптируются на body.error.code в той же миграции.
  • Новые CI-проверки увеличивают цикл feedback на ~5 секунд.
  • Не все суб-модули одинаково описаны — авторы устают писать «формальные» AGENTS.md. Митигация: шаблоны достаточно компактны (≤30 строк), и качество растёт по мере того, как кто-то реально работает с модулем.

Нейтральные последствия

  • ADR-0030 и ADR-0031 не редактируются (иммутабельны). Этот ADR трактуется как «уточнение исполнения» поверх обоих.
  • Renamed файлы сохраняют git history через git mv.
  • Существующие spec-описания, упоминавшие ErrorResponse, мигрируют на ErrorEnvelope. Внешних клиентов на старом shape нет (см. сессию миграции 2026-05-09).

Рассмотренные альтернативы

Оставить fx.go как валидное имя

Не решает проблему когнитивной нагрузки: на смеси двух имён каждый новый инженер заглядывает оба, чтобы понять, не разное ли. Один канонический выбор дешевле. di.go выигрывает потому, что ADR-0030 §2 уже его прямо называет.

Сохранить локальные writeJSON / writeError ради «гибкости»

Гибкость иллюзорна: handler’ы её не используют осознанно, разные формы ошибки появились случайно. Каждый раз, когда формат ошибки нужно расширить (добавить trace_id, например), его правят в одном месте — а двенадцать локальных копий остаются на старом shape, и контракт плывёт.

Документация по необходимости

Уже пробовали: 22 суб-модуля без AGENTS.md/README.md на момент этого ADR. «По необходимости» в реальности означает «никогда».

Ссылки

  • ADR-0030 — Runtime DI через uber-go/fx.
  • ADR-0031 — Microkernel sub-modules per bounded context.
  • ADR-0051 — Public API auth и API-key RBAC.
  • internal/platform/httpx/ — целевой HTTP-helper пакет.
  • docs/docs/20-architecture/templates/{bc,submodule}-{AGENTS,README}.md — шаблоны документации.