- доменные типы internal/catalog лишились json-тегов и MarshalJSON; типы metricWire/layerWire/aggregationWire и перевод catalogWire живут в транспорте, тело отказа тоже получило объявленный тип — байты ответа не изменились - заведён сторож: обход графа типов ответа утверждает, что домен не доезжает до сериализации, плюс требование json-тега на полях транспортных структур и заведомо красные случаи к обоим правилам - решение с ценой обеих сторон записано в architecture.md и ADR; шаг lint в гейте получил свой кеш — общий на машину красил прогон находками из чужого worktree
76 lines
6.4 KiB
Markdown
76 lines
6.4 KiB
Markdown
## Why
|
||
|
||
Публичный контракт единственного читающего маршрута сегодня **выведен из формы
|
||
доменных типов**: `internal/catalog` сам несёт `json`-теги, а
|
||
`internal/httpapi/catalog.go` владеет только оболочкой `{"metrics": …}`.
|
||
Переименование поля в домене, разъединение встроенного `Basis` или добавление
|
||
внутреннего поля меняют байты ответа, не касаясь транспорта; удерживает это один
|
||
литерал в тесте `TestКаталогОтдаётОжидаемыеБайты`, и о том, что он и есть
|
||
контракт, в коде не сказано нигде.
|
||
|
||
Пока маршрут один и потребителей у него нет, цена нулевая. Со следующей задачи
|
||
цели образец копируют точки, тренировки, записи и MCP — четыре маршрута и второй
|
||
транспорт. Решение принимается **до** копирования: потом это будет не решение, а
|
||
археология. Момент выбран ещё и потому, что менять контракт каталога сейчас
|
||
бесплатно — снаружи его не читает никто.
|
||
|
||
## What Changes
|
||
|
||
- **Форма провода объявляется транспортом, а не выводится из домена.** Каждый
|
||
читающий маршрут `internal/httpapi` объявляет собственные типы ответа с
|
||
`json`-тегами; доменные типы `internal/catalog` тегов лишаются, перевод —
|
||
явная функция транспорта.
|
||
- **Доменные типы каталога перестают быть публичным контрактом.** `Metric`,
|
||
`LayerRange`, `Aggregation`, `Basis` остаются формой ответа **use-case**, а не
|
||
формой ответа HTTP. `Style.MarshalJSON` убирается: сериализацию рода кладёт
|
||
транспорт. Значения словаря (`cumulative`/`instant`/`unknown`) при этом
|
||
остаются в домене — провод зовёт `Style.String()`, чтобы не завести второй
|
||
словарь; граница и её цена названы в `design.md`.
|
||
- **Тело отказа тоже получает объявленный тип.** Сегодня `writeError` собирает
|
||
его из `map[string]string`; у читающего маршрута тело отказа — часть того же
|
||
контракта, и оно не должно оставаться единственным местом, куда не смотрит ни
|
||
один сторож. Байты те же.
|
||
- **Байты ответа каталога не меняются.** Правка структурная; контракт из
|
||
требования «Форма ответа каталога» остаётся тем же до символа, и это
|
||
утверждается тестом, а не намерением.
|
||
- **Заводится проверка, прямо утверждающая разделение:** ни один тип домена не
|
||
достигает сериализации ответа — обход графа типов значения ответа, с заведомо
|
||
красным случаем рядом. Отсюда и следует, что переименование поля домена до
|
||
провода не доезжает.
|
||
- **Решение и цена обеих сторон записываются** в `docs/architecture.md`
|
||
(раздел Read API); правило о **механизме** проверки — в
|
||
`docs/conventions/testing.md`, где у правил такого рода дом.
|
||
- Схема не трогается, миграции нет, данные только читаются.
|
||
|
||
## Capabilities
|
||
|
||
### New Capabilities
|
||
|
||
- `read-api`: общие правила читающих маршрутов, поверх которых встают точки,
|
||
тренировки, записи и MCP. Два правила: **публичный контракт меняется только
|
||
правкой транспорта** (форма провода объявляется явно и не выводится из формы
|
||
доменных типов) и **пустая коллекция — список, а не отсутствие**.
|
||
|
||
### Modified Capabilities
|
||
|
||
Нет. Поведение каталога не меняется: байты ответа, заголовки и коды остаются
|
||
прежними, требование «Форма ответа каталога» из `catalog` продолжает описывать
|
||
их без единой правки. Это и есть проверяемое утверждение изменения.
|
||
|
||
## Impact
|
||
|
||
- `internal/catalog/catalog.go` — снятие `json`-тегов с `Basis`, `Aggregation`,
|
||
`LayerRange`, `Metric`; удаление `Style.MarshalJSON`.
|
||
- `internal/catalog/measure_test.go` — `TestStyleСловарь` переезжает с
|
||
`MarshalJSON` на `String()`.
|
||
- `internal/httpapi/catalog.go` — типы провода каталога и перевод из домена.
|
||
- `internal/httpapi/httpapi.go` — объявленный тип тела отказа вместо карты.
|
||
- `internal/httpapi/catalog_test.go` — байтовые утверждения формы (измеренное
|
||
окно, неизмеренное окно, пустая витрина).
|
||
- `internal/httpapi/wire_internal_test.go` — обход графа типов ответа и заведомо
|
||
красный случай к нему.
|
||
- `docs/architecture.md` — раздел Read API: решение с ценой обеих сторон.
|
||
- `docs/conventions/testing.md` — правило о механизме: байты на каждую
|
||
различимую форму ответа, заведомо красный случай для структурной проверки.
|
||
- Схема, витрина, архив, конфиг — не трогаются.
|