Files
dzentra_bot/docs/migrations/build_007.md

24 KiB
Raw Blame History

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 тестов.

Проверены:

  1. соответствие корректного source объекта InstrumentDocumentSource;
  2. соответствие корректного handler объекта InstrumentDocumentHandler;
  3. соответствие корректного feed объекта InstrumentFeedProtocol;
  4. отклонение объектов без обязательных методов;
  5. structural typing без явного наследования;
  6. наследование InstrumentReferenceTransportError от MarketDataAcquisitionError;
  7. наличие общего базового типа у всех ошибок 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 008012 предполагаемая полная цепочка будет выглядеть так:

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-подключение.