httpapi: форма провода читающих маршрутов объявлена транспортом
- доменные типы internal/catalog лишились json-тегов и MarshalJSON; типы metricWire/layerWire/aggregationWire и перевод catalogWire живут в транспорте, тело отказа тоже получило объявленный тип — байты ответа не изменились - заведён сторож: обход графа типов ответа утверждает, что домен не доезжает до сериализации, плюс требование json-тега на полях транспортных структур и заведомо красные случаи к обоим правилам - решение с ценой обеих сторон записано в architecture.md и ADR; шаг lint в гейте получил свой кеш — общий на машину красил прогон находками из чужого worktree
This commit is contained in:
+33
-21
@@ -63,12 +63,10 @@ func (s Style) String() string {
|
||||
}
|
||||
}
|
||||
|
||||
// MarshalJSON отдаёт род строкой. Нулевое значение уезжает как `unknown`, а не
|
||||
// как пустая строка: клиент не должен видеть в ответе состояние, которого в
|
||||
// словаре нет.
|
||||
func (s Style) MarshalJSON() ([]byte, error) {
|
||||
return []byte(`"` + s.String() + `"`), nil
|
||||
}
|
||||
// Собственной сериализации у Style нет намеренно: строку в ответ кладёт
|
||||
// транспорт (`internal/httpapi`, форма провода). Домен владеет ЗНАЧЕНИЯМИ
|
||||
// словаря, а не их видом на проводе; `String()` при этом нужен и логам, и
|
||||
// сообщениям тестов, и проводу — второго словаря заводить незачем.
|
||||
|
||||
// Параметры измерения. Оба названы числами, а не оставлены на усмотрение вызова,
|
||||
// потому что от них зависят счётчики основания в ответе.
|
||||
@@ -156,6 +154,16 @@ func clipMetric(metric string) string {
|
||||
return metric[:maxMetricInLog] + "…"
|
||||
}
|
||||
|
||||
// ФОРМЫ ПРОВОДА В ЭТОМ ПАКЕТЕ НЕТ, и это решение, а не упущение.
|
||||
//
|
||||
// Типы ниже — форма ответа use-case, а не форма ответа HTTP: `json`-тегов они
|
||||
// не несут и до сериализации не доезжают. Публичный контракт чтения объявляет
|
||||
// транспорт (`internal/httpapi`), поэтому переименование поля здесь байты
|
||||
// ответа клиенту не меняет — оно ломает компиляцию перевода. Обратная цена
|
||||
// названа вслух: новое поле само в ответ не попадёт, его обязан перечислить
|
||||
// транспорт. Решение и цена обеих сторон — `docs/architecture.md`, раздел
|
||||
// «Read API», подраздел «Форма провода».
|
||||
|
||||
// Basis — основание, на котором объявлен род. Числа подобраны так, чтобы их
|
||||
// разности были осмысленны: `Hours − Compared` — часы, отброшенные проверкой
|
||||
// пригодности, `Compared − Agreeing − Conflicting` — часы, не сошедшиеся ни с
|
||||
@@ -165,17 +173,21 @@ func clipMetric(metric string) string {
|
||||
// и «часов не было вовсе» — разные события, и клиент обязан различать их без
|
||||
// второго запроса.
|
||||
type Basis struct {
|
||||
Hours int `json:"hours"`
|
||||
Compared int `json:"compared"`
|
||||
Agreeing int `json:"agreeing"`
|
||||
Conflicting int `json:"conflicting"`
|
||||
FirstHour *time.Time `json:"first_hour"`
|
||||
LastHour *time.Time `json:"last_hour"`
|
||||
Hours int
|
||||
Compared int
|
||||
Agreeing int
|
||||
Conflicting int
|
||||
FirstHour *time.Time
|
||||
LastHour *time.Time
|
||||
}
|
||||
|
||||
// Aggregation — род вместе с основанием.
|
||||
//
|
||||
// Встраивание здесь — удобство домена, а не форма ответа: плоскость объекта
|
||||
// `aggregation` на проводе объявлена транспортом поимённо и от этого
|
||||
// встраивания не зависит.
|
||||
type Aggregation struct {
|
||||
Style Style `json:"style"`
|
||||
Style Style
|
||||
Basis
|
||||
}
|
||||
|
||||
@@ -186,22 +198,22 @@ type Aggregation struct {
|
||||
// часовых объектов здесь нет: объект — деталь хранения, клиент про него не
|
||||
// знает.
|
||||
type LayerRange struct {
|
||||
Layer string `json:"layer"`
|
||||
From time.Time `json:"from"`
|
||||
To time.Time `json:"to"`
|
||||
Points int `json:"points"`
|
||||
Layer string
|
||||
From time.Time
|
||||
To time.Time
|
||||
Points int
|
||||
}
|
||||
|
||||
// Metric — запись каталога.
|
||||
type Metric struct {
|
||||
Metric string `json:"metric"`
|
||||
Metric string
|
||||
// Units — множество различных единиц метрики, отсортированное. Массив, а не
|
||||
// строка: на живом потоке единицы не менялись ни разу, но одна форма поля
|
||||
// для обоих случаев честнее строки, которая при расхождении молча выберет
|
||||
// одно из двух.
|
||||
Units []string `json:"units"`
|
||||
Aggregation Aggregation `json:"aggregation"`
|
||||
Layers []LayerRange `json:"layers"`
|
||||
Units []string
|
||||
Aggregation Aggregation
|
||||
Layers []LayerRange
|
||||
}
|
||||
|
||||
// Snapshot — каталог вместе с версией ответа.
|
||||
|
||||
Reference in New Issue
Block a user