Build 060.3: add REST trade schema validation

This commit is contained in:
2026-07-18 14:16:47 +03:00
parent 013efd8a97
commit c0f69ee94a
4 changed files with 896 additions and 1 deletions

View File

@@ -0,0 +1,640 @@
# Build 060.3 — REST Trade Schema Validation
## Статус
✅ Завершён
---
# Цель Build
После завершения Build 060.2 в системе уже существовал transport-контракт отдельной сделки:
```
REST JSON
DzengiRestAggTrade
```
Однако отсутствовал обязательный архитектурный слой Schema Validation.
В соответствии с архитектурой Acquisition Pipeline каждый внешний документ обязан проходить структурную проверку до начала парсинга.
Для REST Trades это означало необходимость добавить отдельный слой:
```
REST JSON
Schema Validation
ValidatedRestAggTradesDocument
REST Trade Parser
DzengiRestAggTrade
```
Именно этот слой реализован в Build 060.3.
---
# Причины появления Build
REST endpoint `/api/v2/aggTrades` возвращает JSON-массив объектов.
Пример ответа:
```json
[
{
"a": 2134857062,
"p": "64497.25",
"q": "0.005",
"T": 1784218066823,
"m": false
},
{
"a": 2134857063,
"p": "64498.10",
"q": "0.002",
"T": 1784218067000,
"m": true
}
]
```
До Build 060.3 никакой проверки структуры документа не существовало.
Parser был вынужден бы принимать произвольный объект.
Это нарушало принятую архитектуру Dzentra.
---
# Архитектурное решение
Во время проектирования рассматривались два варианта.
## Вариант A
Schema Validation получает весь REST-документ.
```
REST response
ValidatedRestAggTradesDocument
parse_rest_agg_trades()
tuple[DzengiRestAggTrade]
```
---
## Вариант B
Schema Validation работает с одной сделкой.
```
REST response
trade
ValidatedRestAggTradeDocument
```
После анализа существующей архитектуры проекта был выбран вариант A.
Причины:
- ExchangeInfo валидируется целиком;
- Candles валидируются целиком;
- WebSocket сообщения валидируются целиком;
- Parser всегда получает уже структурно проверенный документ.
Trades Feed должен следовать тем же правилам.
---
# Итоговая архитектура
После Build 060.3 pipeline выглядит следующим образом.
```
REST JSON
validate_rest_agg_trades_schema()
ValidatedRestAggTradesDocument
parse_rest_agg_trades()
tuple[DzengiRestAggTrade]
Value Validation
Canonical Trade
```
Каждый слой отвечает только за собственную область ответственности.
---
# Ответственность Schema Validation
Schema Validation проверяет исключительно структуру документа.
Проверяется:
- корневой JSON-массив;
- каждый элемент массива является JSON-объектом;
- все ключи являются строками;
- обязательные поля присутствуют;
- документ переводится в immutable-представление.
Schema Validation не занимается:
- проверкой типов значений;
- преобразованием данных;
- проверкой диапазонов;
- проверкой бизнес-ограничений;
- созданием transport-моделей.
Все перечисленные обязанности принадлежат последующим слоям Acquisition Pipeline.
---
# Новое исключение
В Build добавлено новое исключение.
```python
class TradeSchemaError(MarketDataAcquisitionError):
pass
```
Исключение используется исключительно для ошибок структуры REST Trade документа.
Build сознательно не добавляет:
- TradeParseError;
- TradeValueError;
- TradeMappingError.
Они будут появляться только в соответствующих Build.
Это позволяет сохранить строгую границу ответственности каждого этапа разработки.
---
# Новый контракт Schema Layer
Добавлен immutable-контракт.
```python
@dataclass(frozen=True, slots=True)
class ValidatedRestAggTradesDocument:
items: tuple[Mapping[str, object], ...]
```
Контракт хранит уже структурно проверенный REST-документ.
Каждый элемент массива:
- является Mapping;
- является immutable;
- сохраняет все поля документа;
- ещё не преобразован в transport model.
Parser получает именно этот контракт.
# Реализация Schema Validation
В Build реализована новая функция.
```python
validate_rest_agg_trades_schema(
document: object,
) -> ValidatedRestAggTradesDocument
```
Функция является единственной точкой входа для проверки структуры REST Trade документа.
Parser больше не должен принимать произвольный JSON.
Он обязан получать только результат работы Schema Validation.
---
# Проверка корневого объекта
Первым этапом валидируется корень документа.
Ожидается исключительно JSON-массив.
Допустимый пример:
```json
[
{
"a": 1,
"p": "1.25",
"q": "5",
"T": 123,
"m": false
}
]
```
Недопустимые варианты:
```json
{}
```
```json
123
```
```json
"text"
```
```json
null
```
Во всех подобных случаях возбуждается
```python
TradeSchemaError
```
Parser никогда не увидит подобный документ.
---
# Проверка элементов массива
После проверки корня валидируется каждый элемент массива.
Каждый элемент обязан быть JSON-объектом.
Допустимо:
```json
[
{
"a": 1
}
]
```
Недопустимо:
```json
[
1
]
```
```json
[
[]
]
```
```json
[
null
]
```
```json
[
"trade"
]
```
Подобные документы отклоняются ещё до Parser.
---
# Проверка ключей
После проверки структуры объекта производится проверка ключей.
Каждый ключ обязан быть строкой.
Например,
допустимо:
```json
{
"a": 1
}
```
недопустимо:
```python
{
1: "value"
}
```
Подобная ситуация невозможна для корректного JSON, однако тест присутствует специально.
Причины:
- защита от программного формирования словаря;
- единообразие со всеми остальными Schema Validator;
- гарантированный контракт Parser.
---
# Проверка обязательных полей
Для каждого Trade объекта проверяется наличие обязательных полей.
Обязательными являются:
```
a
p
q
T
m
```
Отсутствие любого поля считается ошибкой структуры документа.
Например,
```json
{
"a": 1,
"p": "10"
}
```
будет отклонён ещё на этапе Schema Validation.
---
# Дополнительные поля
Schema Validation не запрещает дополнительные поля.
Например,
```json
{
"a": 1,
"p": "100",
"q": "5",
"T": 123,
"m": false,
"exchange": "spot",
"custom": "value"
}
```
считается корректным документом.
Дополнительные поля полностью сохраняются.
Это решение принято намеренно.
Schema Layer отвечает только за минимальный контракт документа.
Любые дополнительные поля могут использоваться последующими Build без изменения существующей Schema Validation.
---
# Отсутствие проверки типов
Build принципиально не проверяет типы значений.
Следующий документ считается структурно корректным.
```python
[
{
"a": "invalid",
"p": None,
"q": [],
"T": False,
"m": 123,
}
]
```
Причина заключается в разделении ответственности Acquisition Pipeline.
Schema Validation отвечает только за структуру документа.
Проверка:
- int;
- bool;
- Decimal;
- строки;
- диапазоны значений;
- корректность timestamp;
будет выполняться исключительно Parser и Value Validation.
Это полностью соответствует архитектуре проекта.
---
# Immutable представление
После успешной проверки структура документа преобразуется в immutable-контракт.
Каждый элемент копируется в новый словарь.
После этого словарь оборачивается в
```python
MappingProxyType
```
Коллекция объектов преобразуется в
```python
tuple
```
Итоговый контракт становится полностью неизменяемым.
Это гарантирует:
- невозможность случайного изменения Parser;
- защиту между слоями Pipeline;
- детерминированность обработки;
- отсутствие побочных эффектов.
Подобная схема уже используется ExchangeInfo и Candles Schema Validation.
Trades Feed теперь полностью ей соответствует.
---
# Поведение при пустом ответе
REST endpoint может вернуть пустой массив.
Например,
```json
[]
```
Такой ответ считается полностью корректным.
После проверки возвращается
```python
ValidatedRestAggTradesDocument(
items=(),
)
```
Пустой документ не считается ошибкой.
Это позволяет корректно обрабатывать участки истории, где сделки отсутствуют.
---
# Unit-тесты
Для новой функциональности добавлен отдельный набор тестов.
Проверяются:
- корректный REST-документ;
- пустой массив;
- сохранение дополнительных полей;
- отсутствие проверки типов значений;
- отклонение некорректного корня документа;
- отклонение элементов, не являющихся JSON-объектами;
- отсутствие обязательных полей;
- нестроковые ключи.
Таким образом фиксируется полный публичный контракт нового Schema Validator.
Любое изменение поведения в будущем будет обнаружено автоматически.
---
# Результаты Build
После завершения реализации были выполнены проверки проекта.
Schema Tests
```
38 passed
```
Полный набор unit-тестов
```
1011 passed
```
Дополнительно успешно выполнены
```
python -m compileall src
```
и
```
git diff --check
```
Ошибок форматирования обнаружено не было.
---
# Изменённые файлы
В рамках Build изменены только три файла.
```
src/market_data/acquisition/exceptions.py
```
Добавлено исключение
```
TradeSchemaError
```
---
```
src/market_data/acquisition/validation/schema.py
```
Добавлены:
- ValidatedRestAggTradesDocument;
- validate_rest_agg_trades_schema();
- вспомогательные функции проверки структуры.
---
```
tests/unit/market_data/acquisition/validation/test_schema.py
```
Добавлен полный набор unit-тестов нового Schema Validator.
Никакие другие части проекта не изменялись.
---
# Итог
Build 060.3 завершает формирование первого уровня REST Trades Acquisition Pipeline.
После его завершения система получила полноценный слой структурной проверки документов.
Pipeline теперь имеет следующий вид.
```
REST JSON
Schema Validation
ValidatedRestAggTradesDocument
REST Trade Parser
DzengiRestAggTrade
Value Validation
Canonical Trade
```
Следующим этапом серии станет **Build 060.4 — REST Trade Parser**, который будет отвечать за преобразование структурно проверенного документа в transport-модель `DzengiRestAggTrade`, не нарушая границ ответственности, установленных данным Build.