24 KiB
Build 007 — Protocol и Exceptions
Статус: Завершён
Подсистема: market_data/acquisition
Область: Instrument Reference Data
Тип изменения: Изолированное расширение новой архитектуры без подключения к production runtime
Результат полного набора тестов: 113 passed
1. Цель Build 007
Цель Build 007 — зафиксировать минимальные публичные контракты взаимодействия между следующими слоями новой подсистемы Instrument Reference Data:
Build 008 — Dzengi REST Adapter
Build 009 — Instrument Handler
Build 010 — Instrument Feed
Build 011 — Registry
Build 012 — Acquisition Service
Также добавлена специализированная ошибка транспортного уровня для будущего REST adapter.
После завершения Build 007 архитектурная последовательность выглядит следующим образом:
InstrumentDocumentSource
↓
InstrumentDocumentHandler
↓
InstrumentFeedProtocol
↓
Registry
↓
Acquisition Service
Build 007 не реализует получение или обработку данных и не подключает новую подсистему к существующему ExchangeService.
2. Почему Build 007 выполняется именно сейчас
До начала Build 007 были завершены предыдущие этапы:
Build 001 — внутренняя модель Instrument Reference Data
Build 002 — raw-модели ответа Dzengi
Build 003 — структурная валидация exchangeInfo
Build 004 — parser exchangeInfo
Build 005 — value validation
Build 006 — mapper Dzengi → Instrument
К началу Build 007 уже существовал полный pipeline преобразования заранее полученного JSON-документа:
raw JSON document
↓
Schema Validation
↓
Parser
↓
Dzengi Raw Models
↓
Value Validation
↓
Mapper
↓
tuple[Instrument, ...]
Следующие Build должны добавить транспортный источник, handler, feed, registry и acquisition service.
Перед их реализацией необходимо было определить минимальные интерфейсы взаимодействия между этими слоями и добавить специализированную ошибку транспортного уровня.
3. Архитектурная граница Build 007
Build 007 отвечает только за:
определение контракта источника сырого документа;
определение контракта обработчика сырого документа;
определение контракта источника готовых Instrument;
добавление ошибки транспортного уровня.
Build 007 не выполняет:
HTTP-запросы;
получение exchangeInfo;
schema validation;
parsing;
value validation;
mapping;
создание конкретного Handler;
создание конкретного Feed;
создание Registry;
создание Acquisition Service;
кэширование;
изменение ExchangeService;
изменение runtime;
изменение Telegram UI;
изменение автоторговли.
4. Изменённые файлы
В рамках Build 007 изменены:
app/src/market_data/acquisition/protocol.py
app/src/market_data/acquisition/exceptions.py
Создан тестовый файл:
app/tests/unit/market_data/acquisition/test_protocol.py
Не изменялись:
app/src/market_data/acquisition/adapters/dzengi/rest.py
app/src/market_data/acquisition/handlers/instrument_handler.py
app/src/market_data/acquisition/feeds/instrument_feed.py
app/src/market_data/acquisition/registry.py
app/src/market_data/acquisition/service.py
app/src/market_data/acquisition/adapters/dzengi/models.py
app/src/market_data/acquisition/adapters/dzengi/parser.py
app/src/market_data/acquisition/adapters/dzengi/mapper.py
app/src/market_data/acquisition/validation/schema.py
app/src/market_data/acquisition/validation/values.py
app/src/integrations/exchange/*
app/src/telegram/*
app/src/trading/*
Работающий legacy-код бота не изменён.
5. Созданные Protocol
В файле:
app/src/market_data/acquisition/protocol.py
созданы три минимальных протокола:
InstrumentDocumentSource
InstrumentDocumentHandler
InstrumentFeedProtocol
Все протоколы основаны на структурной типизации Python:
typing.Protocol
и объявлены как:
@runtime_checkable
Это позволяет использовать их как для статической типизации, так и для ограниченной runtime-проверки через isinstance().
6. InstrumentDocumentSource
Контракт:
@runtime_checkable
class InstrumentDocumentSource(Protocol):
def fetch_instrument_document(self) -> object:
...
Назначение:
получить декодированный транспортный документ Instrument Reference Data.
Предполагаемый конкретный потребитель этого контракта появится в:
Build 008 — Dzengi REST Adapter
Будущий REST adapter должен реализовать метод:
fetch_instrument_document()
и вернуть сырой декодированный документ.
7. Почему InstrumentDocumentSource возвращает object
Возвращаемый тип:
object
выбран сознательно.
Транспортный источник не должен выполнять:
schema validation;
parsing;
value validation;
mapping.
На транспортной границе REST adapter может получить произвольное декодированное JSON-значение:
dict
list
str
int
float
bool
None
Проверка структуры является обязанностью:
Schema Validation
Поэтому транспортный слой не должен преждевременно утверждать, что полученный документ является корректным JSON-объектом нужной структуры.
Архитектурная граница остаётся следующей:
REST Adapter
↓
object
↓
Schema Validation
↓
ValidatedExchangeInfoDocument
8. InstrumentDocumentHandler
Контракт:
@runtime_checkable
class InstrumentDocumentHandler(Protocol):
def handle_instrument_document(
self,
document: object,
) -> tuple[Instrument, ...]:
...
Назначение:
преобразовать сырой документ в проверенные внутренние модели Instrument.
Конкретная реализация появится в:
Build 009 — Instrument Handler
Handler должен объединить уже существующий pipeline:
object
↓
validate_exchange_info_schema()
↓
ValidatedExchangeInfoDocument
↓
parse_exchange_info()
↓
DzengiExchangeInfoResponse
↓
validate_exchange_info_values()
↓
map_dzengi_exchange_info_to_instruments()
↓
tuple[Instrument, ...]
При этом Protocol не знает:
какая биржа является источником;
какой parser используется;
какой mapper используется;
какие source-specific raw-модели существуют.
9. InstrumentFeedProtocol
Контракт:
@runtime_checkable
class InstrumentFeedProtocol(Protocol):
def load_instruments(self) -> tuple[Instrument, ...]:
...
Назначение:
получить полный immutable-набор внутренних моделей Instrument.
Конкретный Feed появится в:
Build 010 — Instrument Feed
Предполагаемая композиция:
InstrumentFeed
├── InstrumentDocumentSource
└── InstrumentDocumentHandler
Рабочая последовательность:
InstrumentFeed.load_instruments()
↓
InstrumentDocumentSource.fetch_instrument_document()
↓
object
↓
InstrumentDocumentHandler.handle_instrument_document()
↓
tuple[Instrument, ...]
10. Почему протокол называется InstrumentFeedProtocol
Будущий файл:
app/src/market_data/acquisition/feeds/instrument_feed.py
предназначен для конкретной реализации Feed.
Чтобы избежать конфликта между интерфейсом и конкретным классом, протокол получил имя:
InstrumentFeedProtocol
Конкретная реализация в Build 010 сможет называться:
InstrumentFeed
Таким образом:
InstrumentFeedProtocol
контракт
InstrumentFeed
конкретная реализация
11. Structural Typing
Реализации не обязаны наследоваться от Protocol напрямую.
Например:
class StubInstrumentDocumentSource:
def fetch_instrument_document(self) -> object:
return {
"symbols": [],
}
Такой объект удовлетворяет контракту:
InstrumentDocumentSource
без явного наследования:
class StubInstrumentDocumentSource(InstrumentDocumentSource):
...
Это уменьшает связанность между конкретными реализациями и интерфейсами.
12. Runtime Checkable
Все три Protocol объявлены с:
@runtime_checkable
Благодаря этому допустима проверка:
isinstance(source, InstrumentDocumentSource)
Тестами подтверждено:
объект с требуемым методом
→ соответствует Protocol
объект без требуемого метода
→ не соответствует Protocol
Важно: runtime-проверка Protocol подтверждает структурное наличие требуемых атрибутов и методов, но не выполняет полную глубокую проверку всех аннотаций типов и фактических возвращаемых значений.
13. Новая транспортная ошибка
В файл:
app/src/market_data/acquisition/exceptions.py
добавлена:
class InstrumentReferenceTransportError(
MarketDataAcquisitionError
):
pass
Она предназначена для будущего:
Build 008 — Dzengi REST Adapter
14. Назначение InstrumentReferenceTransportError
Ошибка предназначена для проблем получения Instrument Reference Data от внешнего источника.
Потенциальные случаи:
ошибка соединения;
timeout;
HTTP error;
ошибка JSON decoding;
непредвиденная ошибка REST-клиента.
Она не используется для:
неверной структуры документа;
ошибки parsing;
недопустимых значений;
ошибки mapping.
Эти случаи уже имеют специализированные типы исключений.
15. Итоговая иерархия ошибок
После Build 007 иерархия выглядит следующим образом:
MarketDataAcquisitionError
├── InstrumentReferenceTransportError
├── InstrumentReferenceSchemaError
├── InstrumentReferenceParseError
├── InstrumentReferenceValueError
└── InstrumentReferenceMappingError
Назначение ошибок:
| Ошибка | Ответственность |
|---|---|
InstrumentReferenceTransportError |
Получение данных от внешнего источника |
InstrumentReferenceSchemaError |
Структура исходного документа |
InstrumentReferenceParseError |
Преобразование проверенного документа в raw-модели |
InstrumentReferenceValueError |
Допустимость значений |
InstrumentReferenceMappingError |
Преобразование raw-модели во внутреннюю модель Instrument |
16. Какие дополнительные Protocol не создавались
В Build 007 сознательно не создавались отдельные Protocol для:
Schema Validator
Parser
Value Validator
Mapper
Registry
Acquisition Service
Причины:
validators, parser и mapper уже реализованы как чистые функции;
Registry и Acquisition Service пока не имеют нескольких реализаций;
дополнительные интерфейсы сейчас не используются;
создание таких контрактов было бы преждевременной абстракцией.
Это соответствует принципу:
не создавать абстракции «на будущее» без конкретного потребителя.
17. Какие дополнительные Exceptions не создавались
В Build 007 сознательно не добавлялись:
InstrumentReferenceHandlerError
InstrumentReferenceFeedError
InstrumentReferenceRegistryError
InstrumentReferenceServiceError
Причины:
Handler может передавать точные ошибки Schema, Parser, Value и Mapping;
Feed может передавать точные ошибки Transport и Processing;
Registry ещё не реализован;
Acquisition Service ещё не реализован;
новые типы ошибок следует вводить только там, где появляется реальная новая категория отказа.
18. Реализованные тестовые сценарии
Создан файл:
app/tests/unit/market_data/acquisition/test_protocol.py
Реализовано 7 тестов.
Проверены:
- соответствие корректного source объекта
InstrumentDocumentSource; - соответствие корректного handler объекта
InstrumentDocumentHandler; - соответствие корректного feed объекта
InstrumentFeedProtocol; - отклонение объектов без обязательных методов;
- structural typing без явного наследования;
- наследование
InstrumentReferenceTransportErrorотMarketDataAcquisitionError; - наличие общего базового типа у всех ошибок Instrument Reference Data.
19. Выполненные проверки
Проверка 1 — unit-тесты Protocol и Exceptions
Команда:
python -m pytest \
tests/unit/market_data/acquisition/test_protocol.py \
-q
Результат:
....... [100%]
7 passed in 0.01s
Статус:
PASSED
Проверка 2 — Python compilation
Команда:
python -m py_compile \
src/market_data/acquisition/protocol.py \
src/market_data/acquisition/exceptions.py \
tests/unit/market_data/acquisition/test_protocol.py
Результат:
Команда завершилась без ошибок и без вывода.
Статус:
PASSED
Проверка 3 — полный набор тестов проекта
Команда:
python -m pytest -q
Результат:
................................................................................................................. [100%]
113 passed in 0.06s
Статус:
PASSED
Проверка 4 — отсутствие преждевременной production-интеграции
Команда:
grep -RIn \
--exclude-dir="__pycache__" \
--exclude="*.pyc" \
-E "InstrumentDocumentSource|InstrumentDocumentHandler|InstrumentFeedProtocol|InstrumentReferenceTransportError" \
src tests
Подтверждено:
InstrumentDocumentSource
используется только в protocol.py и unit-тестах
InstrumentDocumentHandler
используется только в protocol.py и unit-тестах
InstrumentFeedProtocol
используется только в protocol.py и unit-тестах
InstrumentReferenceTransportError
объявлена в exceptions.py и используется только в unit-тестах
Не обнаружено подключения к:
src/integrations/exchange/*
src/telegram/*
src/trading/*
Статус:
PASSED
20. Архитектура после Build 007
После завершения Build 007 новая часть подсистемы имеет следующую структуру:
market_data/
└── acquisition/
├── exceptions.py
│ ├── MarketDataAcquisitionError
│ ├── InstrumentReferenceTransportError
│ ├── InstrumentReferenceSchemaError
│ ├── InstrumentReferenceParseError
│ ├── InstrumentReferenceValueError
│ └── InstrumentReferenceMappingError
│
├── protocol.py
│ ├── InstrumentDocumentSource
│ ├── InstrumentDocumentHandler
│ └── InstrumentFeedProtocol
│
├── models/
│ └── instrument.py
│ └── Instrument
│
├── validation/
│ ├── schema.py
│ └── values.py
│
└── adapters/
└── dzengi/
├── models.py
├── parser.py
├── mapper.py
└── rest.py
На текущем этапе:
rest.py
ещё не реализован
instrument_handler.py
ещё не реализован
instrument_feed.py
ещё не реализован
registry.py
ещё не реализован
service.py
ещё не реализован
21. Полная архитектурная цепочка после Build 007
Уже реализовано:
raw JSON document
↓
validate_exchange_info_schema()
↓
ValidatedExchangeInfoDocument
↓
parse_exchange_info()
↓
DzengiExchangeInfoResponse
↓
validate_exchange_info_values()
↓
map_dzengi_exchange_info_to_instruments()
↓
tuple[Instrument, ...]
Зафиксированы контракты для будущей orchestration-цепочки:
InstrumentDocumentSource
↓
InstrumentDocumentHandler
↓
InstrumentFeedProtocol
↓
Registry
↓
Acquisition Service
После реализации Build 008–012 предполагаемая полная цепочка будет выглядеть так:
Dzengi REST API
↓
Dzengi REST Adapter
implements InstrumentDocumentSource
↓
object
↓
Instrument Handler
implements InstrumentDocumentHandler
↓
tuple[Instrument, ...]
↓
Instrument Feed
implements InstrumentFeedProtocol
↓
Registry
↓
Acquisition Service
22. Влияние на legacy-систему
Build 007 не подключён к существующим компонентам:
ExchangeService
ExchangeSymbol
SymbolValidationResult
Telegram UI
AutoTrade
Market Stream
Market Data Runner
Execution Quality
Не изменены:
ExchangeService.get_exchange_symbols()
ExchangeService.validate_symbol()
ExchangeService.get_symbol_runtime_status()
normalize_symbol()
symbol_candidates()
Старый production-путь продолжает работать без изменений.
Новая подсистема развивается параллельно.
23. Обратная совместимость
Подтверждено сохранение:
сигнатур существующих методов;
старых импортов;
существующего формата ошибок;
Telegram UI;
автоторговли;
runtime-поведения;
legacy ExchangeSymbol;
legacy-кэша.
Build 007 имеет полную обратную совместимость.
24. Классификация изменений
| Изменение | Классификация |
|---|---|
InstrumentDocumentSource |
Обязательное архитектурное изменение |
InstrumentDocumentHandler |
Обязательное архитектурное изменение |
InstrumentFeedProtocol |
Обязательное архитектурное изменение |
InstrumentReferenceTransportError |
Обязательное архитектурное изменение |
| Structural typing | Улучшение архитектурной независимости |
runtime_checkable |
Улучшение тестируемости и проверяемости контрактов |
| Дополнительные преждевременные Protocol | Не создавались |
| Дополнительные преждевременные Exceptions | Не создавались |
| Изменение production-поведения | Отсутствует |
25. Итог Build 007
Build 007 завершён успешно.
Реализовано:
InstrumentDocumentSource
InstrumentDocumentHandler
InstrumentFeedProtocol
InstrumentReferenceTransportError
Подтверждено:
7 protocol tests passed
113 total project tests passed
Python compilation passed
No production integration detected
Legacy bot behavior unchanged
Итоговый статус:
BUILD 007 — COMPLETE
26. Следующий этап
Следующий этап утверждённого плана:
Build 008 — Dzengi REST Adapter
Его задача — реализовать конкретный транспортный источник, соответствующий контракту:
InstrumentDocumentSource
Будущая граница Build 008:
Dzengi REST API
↓
Dzengi REST Adapter
↓
object
Build 008 не должен выполнять:
schema validation;
parsing;
value validation;
mapping;
создание Instrument;
создание Handler;
создание Feed;
создание Registry;
создание Acquisition Service;
изменение ExchangeService;
production-подключение.