40 KiB
Build 060.13 — WebSocket Trade Mapper
Engineering Migration Report
Контроль документа
| Свойство | Значение |
|---|---|
| Build | 060.13 |
| Название | WebSocket Trade Mapper |
| Статус | Completed |
| Проект | Dzentra |
| Подсистема | Market Data Acquisition |
| Компонент | Trades Feed |
| Версия | 1.0 |
Цель Build
После завершения Build 060.12 система получила полностью реализованный уровень WebSocket Trade Value Validation, подтверждающий корректность всех значений транспортной модели
DzengiWebSocketTradeEvent
На этом этапе Pipeline гарантирует:
- корректную структуру транспортного документа;
- успешное построение transport model;
- корректность всех обязательных значений;
- отсутствие недопустимых числовых представлений;
- корректность строковых идентификаторов.
Однако даже после успешного прохождения всех перечисленных этапов транспортная модель ещё не может использоваться остальной частью системы.
Несмотря на корректность структуры и значений, объект
DzengiWebSocketTradeEvent
по-прежнему остаётся транспортной моделью биржи Dzengi.
Он содержит особенности конкретного источника данных:
- транспортные числовые представления;
- транспортное поле
size; - транспортное поле
buyer; - транспортное поле
order_id; - транспортное представление времени в миллисекундах Unix Epoch.
Использование подобных моделей за пределами слоя адаптера противоречит базовым архитектурным принципам Dzentra.
Внутренние компоненты системы не должны зависеть от особенностей API конкретной биржи.
Для решения данной задачи архитектура Dzentra предусматривает следующий обязательный уровень Pipeline —
Mapper.
Именно Mapper выполняет преобразование транспортной модели источника данных в единую каноническую модель предметной области.
Build 060.13 реализует данный уровень для WebSocket Trade.
Основная задача Build — преобразовать проверенную транспортную модель
DzengiWebSocketTradeEvent
в каноническую immutable-модель
Trade
с выполнением всех необходимых преобразований типов данных.
При этом Build не затрагивает:
- Schema Validation;
- Parser;
- Value Validation;
- WebSocket Runtime;
- Routing;
- Trades Feed.
Предпосылки
К началу Build архитектура Market Data Acquisition уже содержала полностью реализованные Mapper для остальных типов рыночных данных.
Аналогичный уровень преобразования уже существовал для:
- REST Quote;
- WebSocket Quote;
- WebSocket OHLC;
- REST Aggregate Trade.
Во всех случаях использовалась одинаковая архитектурная схема обработки.
Transport Model
│
▼
Mapper
│
▼
Canonical Model
После завершения Build 060.12 аналогичный транспортный конвейер появился и для WebSocket Trade.
ValidatedWebSocketTradeDocument
│
▼
Trade Parser
│
▼
DzengiWebSocketTradeEvent
│
▼
Value Validation
│
▼
DzengiWebSocketTradeEvent
Однако следующий обязательный уровень — преобразование транспортной модели в каноническую модель системы — ещё отсутствовал.
Таким образом WebSocket-конвейер обработки сделок оставался архитектурно незавершённым.
Архитектурное основание
Одним из базовых принципов архитектуры Dzentra является полная изоляция внутренних компонентов системы от формата данных конкретной биржи.
Любые особенности транспортного протокола должны оставаться исключительно внутри слоя адаптера.
Общий конвейер обработки рыночных данных имеет следующий вид.
Raw Source
│
▼
Schema Validation
│
▼
Transport Model
│
▼
Value Validation
│
▼
Mapper
│
▼
Domain Model
Каждый уровень Pipeline отвечает только за одну категорию задач.
Для WebSocket Trade это разделение выглядит следующим образом.
Schema Validation
гарантирует корректность структуры транспортного документа.
Parser
создаёт транспортную модель
DzengiWebSocketTradeEvent
без изменения типов данных.
Value Validation
подтверждает корректность значений транспортной модели без выполнения каких-либо преобразований.
Следующий уровень —
Mapper
выполняет преобразование транспортной модели в каноническую модель предметной области.
Именно Mapper отвечает за:
- преобразование транспортных числовых представлений в
Decimal; - преобразование транспортного timestamp в
datetime; - преобразование транспортных признаков сделки в канонические перечисления;
- переименование транспортных полей;
- создание immutable-модели
Trade.
При этом Mapper принципиально не выполняет:
- повторную Validation;
- анализ структуры JSON;
- Runtime-логику;
- бизнес-логику;
- маршрутизацию событий.
Такое разделение ответственности позволяет каждому уровню Pipeline оставаться независимым, повторно используемым и легко тестируемым.
Результаты архитектурного аудита
Перед реализацией Build был выполнен аудит существующего слоя Mapper.
В ходе анализа подтверждено наличие полностью сформированной архитектуры преобразования транспортных моделей.
Для различных типов рыночных данных уже реализованы функции:
map_dzengi_quote_to_quote(...)
map_dzengi_websocket_quote_to_quote(...)
map_dzengi_websocket_ohlc_to_candle_close_event(...)
map_dzengi_rest_agg_trades_to_trades(...)
Все существующие Mapper используют одинаковые архитектурные принципы.
Преобразование транспортных типов выполняется исключительно внутри Mapper.
Для этого повторно используются существующие helper-функции проекта.
Одновременно аудит подтвердил наличие общего исключения
TradeMappingError
используемого всеми существующими Mapper при невозможности построения канонической модели.
При этом отдельный Mapper для транспортной модели
DzengiWebSocketTradeEvent
в системе отсутствовал.
Таким образом единственным отсутствующим элементом архитектурной цепочки являлся собственный уровень Mapping для WebSocket Trade.
Build 060.13 полностью закрывает данный пробел и завершает следующий обязательный уровень Pipeline серии Build 060.
Архитектурное решение
По результатам проведённого аудита было принято решение полностью повторить архитектурный шаблон, уже используемый Mapper остальных типов рыночных данных.
В систему добавлена функция
map_dzengi_websocket_trade_to_trade(...)
которая принимает транспортную модель
DzengiWebSocketTradeEvent
и создаёт каноническую immutable-модель
Trade
Во время преобразования выполняются все необходимые преобразования транспортных типов данных.
После успешного завершения Mapping транспортная модель больше не используется последующими уровнями системы.
Конвейер WebSocket Trade принимает следующий вид.
Raw WebSocket Trade
│
▼
WebSocket Trade Schema Validation
│
▼
ValidatedWebSocketTradeDocument
│
▼
WebSocket Trade Parser
│
▼
DzengiWebSocketTradeEvent
│
▼
WebSocket Trade Value Validation
│
▼
DzengiWebSocketTradeEvent
│
▼
WebSocket Trade Mapper
│
▼
Trade
Build 060.13 не изменяет архитектуру ранее реализованных компонентов и завершает следующий обязательный уровень транспортного Pipeline.
Реализованный уровень Mapper
В файл
src/market_data/acquisition/adapters/dzengi/mapper.py
добавлена новая функция
map_dzengi_websocket_trade_to_trade(...)
Функция получает транспортную модель
DzengiWebSocketTradeEvent
и выполняет построение канонической модели
Trade
Во время выполнения Mapping создаётся новый immutable-объект предметной области.
Транспортная модель при этом остаётся неизменной.
Таким образом следующий уровень Pipeline получает уже не транспортную модель биржи, а полностью независимую внутреннюю модель системы.
Почему Mapper создаёт новую модель
Во время архитектурного проектирования отдельно рассматривался вопрос о возможности повторного использования объекта
DzengiWebSocketTradeEvent
на последующих уровнях системы.
По результатам анализа было принято решение полностью отказаться от подобного подхода.
Основные причины:
- транспортная модель отражает структуру конкретной биржи;
- транспортная модель содержит поля, отсутствующие в предметной области;
- транспортная модель использует транспортные представления числовых данных;
- транспортная модель использует транспортные соглашения о наименовании полей;
- внутренние компоненты системы не должны зависеть от API биржи.
Поэтому Mapper всегда создаёт новый экземпляр
Trade
который становится единственной моделью, используемой за пределами слоя адаптера.
Подобное решение полностью соответствует базовому архитектурному принципу Dzentra — полной изоляции внутренних компонентов от транспортных контрактов внешних источников данных.
Преобразуемая транспортная модель
Преобразование выполняется над объектом
@dataclass(frozen=True, slots=True)
class DzengiWebSocketTradeEvent:
trade_id: int
price: DzengiRawNumeric
size: DzengiRawNumeric
timestamp: int
symbol: str
buyer: bool
order_id: str
После выполнения Mapping создаётся объект
@dataclass(frozen=True, slots=True)
class Trade:
symbol: str
trade_id: int
price: Decimal
quantity: Decimal
executed_at: datetime
aggressor_side: TradeAggressorSide
source: str
Таким образом Mapper полностью устраняет зависимость системы от транспортной модели биржи.
Преобразование идентификатора сделки
Поле
trade_id
имеет одинаковую семантику в транспортной и канонической модели.
Поэтому Mapper переносит его без изменения значения.
Дополнительных преобразований не выполняется.
Преобразование цены сделки
Поле
price
в транспортной модели может быть представлено в нескольких форматах.
Например:
"63992.50"
63992
63992.50
Во время Mapping используется существующий helper проекта, преобразующий транспортное представление в
Decimal
После завершения Mapping каноническая модель всегда содержит внутренний числовой тип системы независимо от исходного представления данных.
Преобразование количества сделки
Транспортная модель использует поле
size
которое отражает терминологию API биржи.
В канонической модели используется единое наименование
quantity
Во время Mapping выполняются одновременно два действия:
- преобразование транспортного значения в
Decimal; - переименование транспортного поля в соответствии с внутренней моделью предметной области.
После завершения Mapping дальнейшая работа системы полностью абстрагируется от терминологии конкретной биржи.
Преобразование временной метки
Поле
timestamp
в транспортной модели представляет собой количество миллисекунд, прошедших с начала эпохи Unix.
Подобное представление удобно для передачи данных по сети, однако не является внутренним представлением времени в Dzentra.
Во время Mapping используется существующая helper-функция преобразования времени.
В результате каноническая модель получает объект
datetime
в часовом поясе UTC.
Таким образом все внутренние компоненты системы используют единый формат представления времени независимо от способа передачи данных биржей.
Преобразование стороны агрессора
Во время архитектурного аудита отдельно анализировалась семантика транспортного поля
buyer
В WebSocket API Dzengi данное поле определяет сторону покупателя сделки.
Однако внутренняя модель Dzentra использует перечисление
TradeAggressorSide
Поэтому Mapper выполняет явное преобразование транспортного признака в каноническое перечисление.
Используется следующее соответствие.
buyer = True
│
▼
TradeAggressorSide.BUY
buyer = False
│
▼
TradeAggressorSide.SELL
Подобное преобразование делает внутреннюю модель полностью независимой от конкретного транспортного соглашения биржи.
Преобразование источника данных
Каноническая модель
Trade
содержит поле
source
которое используется для идентификации происхождения события.
Во время Mapping данное поле получает фиксированное значение
dzengi_websocket_trade
Использование отдельного идентификатора источника позволяет последующим уровням системы различать происхождение канонических моделей без анализа транспортного Pipeline.
Почему order_id отсутствует в канонической модели
Транспортная модель WebSocket содержит дополнительное поле
order_id
которое используется исключительно транспортным контрактом биржи.
Во время архитектурного проектирования отдельно анализировался вопрос необходимости переноса данного значения в каноническую модель.
По результатам анализа было принято решение отказаться от подобного преобразования.
Основные причины:
- идентификатор ордера отсутствует в модели предметной области;
- последующие уровни системы не используют данное значение;
- сохранение транспортного идентификатора нарушило бы независимость канонической модели.
В результате поле
order_id
полностью завершается на уровне адаптера и не попадает в модель
Trade
Повторное использование существующих helper-функций
Build 060.13 не вводит новых механизмов преобразования числовых значений и времени.
Вместо этого повторно используются уже существующие helper-функции проекта.
Для преобразования числовых представлений применяется существующий механизм преобразования в
Decimal
Для преобразования временной метки используется существующая функция построения объекта
datetime
Подобный подход обеспечивает единообразное поведение всех Mapper проекта независимо от типа источника данных.
Использование TradeMappingError
Все ошибки преобразования используют существующее исключение
TradeMappingError
Build не вводит новых типов исключений.
Это сохраняет единый механизм обработки ошибок Mapping во всей подсистеме Market Data Acquisition.
Value Validation продолжает использовать
TradeValueError
а Mapper использует исключительно
TradeMappingError
Тем самым достигается чёткое разделение ошибок проверки данных и ошибок преобразования транспортной модели.
Изменённые файлы
В рамках Build были изменены только два файла.
Mapper
src/market_data/acquisition/adapters/dzengi/mapper.py
Добавлена функция
map_dzengi_websocket_trade_to_trade(...)
а также вспомогательная функция преобразования стороны агрессора WebSocket Trade.
Существующая логика Mapping Quote, OHLC и REST Trade не изменялась.
Unit-тесты
tests/unit/market_data/acquisition/adapters/dzengi/test_websocket_trade_mapper.py
Добавлен полный набор unit-тестов нового уровня Mapping.
Добавленные тесты
В рамках Build реализовано двадцать два unit-теста, полностью покрывающих новую функциональность.
Проверка корректного Mapping
Подтверждается успешное создание канонической модели
Trade
из полностью корректной транспортной модели.
Проверка преобразования стороны агрессора
Отдельные тесты подтверждают корректное преобразование обоих допустимых значений:
buyer=True;buyer=False.
Проверка преобразования числовых представлений
Параметризованные тесты подтверждают корректную обработку:
- строк;
- целых чисел;
- чисел с плавающей точкой.
для полей:
price;size.
Проверка преобразования времени
Подтверждается корректное построение объекта
datetime
в часовом поясе UTC.
Проверка канонических имён полей
Отдельные тесты подтверждают:
- преобразование
size → quantity; - удаление пробельных символов из
symbol; - заполнение поля
source; - отсутствие поля
order_idв канонической модели.
Проверка неизменяемости моделей
Отдельные тесты подтверждают:
- неизменяемость транспортной модели после Mapping;
- неизменяемость созданной модели
Trade.
Проверка ошибок преобразования
Реализованы проверки генерации
TradeMappingError
при невозможности преобразования:
- числовых значений;
- специальных числовых представлений;
- недопустимого значения timestamp.
Результаты тестирования
После завершения реализации выполнен целевой запуск нового набора unit-тестов.
python -m pytest \
tests/unit/market_data/acquisition/adapters/dzengi/test_websocket_trade_mapper.py \
-q
Результат:
22 passed in 0.04s
Все проверки новой функциональности успешно завершены.
Новый набор тестов полностью покрывает:
- успешное построение канонической модели
Trade; - преобразование транспортных числовых представлений;
- преобразование временной метки в
datetime; - преобразование стороны агрессора;
- переименование транспортных полей;
- заполнение источника данных;
- генерацию исключений для всех типов ошибок Mapping;
- неизменяемость транспортной и канонической моделей.
Параметризованные тесты позволили существенно сократить объём тестового кода без уменьшения покрытия и обеспечили единообразную проверку различных вариантов входных данных.
Регрессионное тестирование
После завершения реализации выполнен полный запуск набора unit-тестов проекта.
python -m pytest -q
Результат:
1230 passed in 2.00s
Регрессий существующей функциональности не обнаружено.
Все ранее реализованные Build продолжают работать без каких-либо изменений.
Это подтверждает, что добавленная функциональность полностью изолирована и не влияет на существующие конвейеры обработки Quote, OHLC, REST Trade и остальные подсистемы проекта.
Проверка компиляции
После завершения реализации выполнена полная проверка компиляции проекта.
python -m compileall src tests
Компиляция завершилась успешно.
Ошибок синтаксиса не обнаружено.
Все изменённые файлы успешно компилируются и не нарушают целостность проекта.
Проверка Git diff
После завершения реализации выполнена финальная проверка изменений.
git diff --check
Результат:
без замечаний
Проверка подтвердила отсутствие:
- trailing whitespace;
- ошибок окончания строк;
- конфликтов diff;
- нарушений форматирования.
Scope Build 060.13
В рамках данного Build реализован исключительно уровень
WebSocket Trade Mapper
Build не включает:
- WebSocket Trade Schema Validation;
- WebSocket Trade Parser;
- WebSocket Trade Value Validation;
- WebSocket Trade Adapter;
- Runtime Integration;
- Unified Routing;
- Trades Feed.
Подобное ограничение полностью соответствует принятому принципу атомарной реализации Build.
Каждый этап дорожной карты реализует только один архитектурный уровень Pipeline.
Архитектурный результат
После завершения Build система содержит полностью реализованный транспортный Pipeline WebSocket Trade вплоть до создания канонической модели предметной области.
Конвейер обработки принимает следующий вид.
Raw WebSocket Trade
│
▼
WebSocket Trade Schema Validation
│
▼
ValidatedWebSocketTradeDocument
│
▼
WebSocket Trade Parser
│
▼
DzengiWebSocketTradeEvent
│
▼
WebSocket Trade Value Validation
│
▼
DzengiWebSocketTradeEvent
│
▼
WebSocket Trade Mapper
│
▼
Trade
Таким образом архитектура WebSocket Trade теперь полностью повторяет ранее реализованные конвейеры обработки Quote, OHLC и REST Trade.
После завершения Mapping дальнейшие уровни системы больше не используют транспортную модель биржи.
Все последующие компоненты работают исключительно с канонической моделью
Trade
что полностью соответствует базовым архитектурным принципам Dzentra.
Состояние WebSocket Trade Pipeline
После завершения Build 060.13 конвейер имеет следующий вид.
Raw WebSocket Trade
│
▼
Schema Validation
│
▼
ValidatedWebSocketTradeDocument
│
▼
Parser
│
▼
DzengiWebSocketTradeEvent
│
▼
Value Validation
│
▼
DzengiWebSocketTradeEvent
│
▼
Mapper
│
▼
Trade
Статус реализации компонентов:
| Компонент | Build | Статус |
|---|---|---|
| Canonical Trade Model | 060.1 | ✔ Completed |
| WebSocket Trade Transport Model | 060.9 | ✔ Completed |
| WebSocket Trade Schema Validation | 060.10 | ✔ Completed |
| WebSocket Trade Parser | 060.11 | ✔ Completed |
| WebSocket Trade Value Validation | 060.12 | ✔ Completed |
| WebSocket Trade Mapper | 060.13 | ✔ Completed |
| WebSocket Trade Adapter | 060.14 | Pending |
| Unified WebSocket Routing | 060.15 | Pending |
Соблюдение архитектурных принципов
В рамках Build полностью сохранены архитектурные инварианты Dzentra.
Локальность изменений
Изменены только:
adapters/dzengi/mapper.py;- unit-тесты нового уровня Mapping.
Существующая логика Quote, OHLC и REST Trade не изменялась.
Повторное использование архитектуры
Новая реализация полностью повторяет существующий архитектурный шаблон Mapper.
Новая архитектура не проектировалась.
Использован уже существующий подход, применяемый для остальных типов рыночных данных.
Повторное использование инфраструктуры
Для преобразования числовых значений, временных меток и канонических перечислений повторно использованы существующие helper-функции проекта.
Build не вводит новых механизмов преобразования и не дублирует уже реализованную функциональность.
Это обеспечивает единообразное поведение всех Mapper проекта.
Разделение ответственности
Mapper отвечает исключительно за преобразование транспортной модели в каноническую модель предметной области.
Build не выполняет:
- Schema Validation;
- Value Validation;
- Runtime Integration;
- бизнес-логику;
- маршрутизацию событий.
Все перечисленные задачи остаются ответственностью предыдущих либо последующих уровней Pipeline.
Обратная совместимость
Существующая обработка:
- Quote;
- OHLC;
- REST Trade;
не изменилась.
Добавленная функциональность полностью изолирована и не оказывает влияния на ранее реализованные компоненты системы.
Архитектурные решения Build (ADR)
ADR-060.13-001
Mapper всегда создаёт новую каноническую модель.
После завершения преобразования транспортная модель больше не используется последующими уровнями системы.
Это обеспечивает полную изоляцию внутренней архитектуры от API конкретной биржи.
ADR-060.13-002
Все преобразования транспортных типов выполняются исключительно внутри Mapper.
Преобразование транспортных числовых представлений в Decimal, временной метки в datetime и транспортных признаков сделки в канонические перечисления не допускается ни на одном другом уровне Pipeline.
ADR-060.13-003
Транспортные поля, отсутствующие в предметной области, не переносятся в каноническую модель.
Поле
order_id
является частью транспортного контракта биржи и не включается в модель
Trade
ADR-060.13-004
Mapper использует существующее исключение TradeMappingError.
Build не вводит новых типов исключений.
Все ошибки преобразования продолжают использовать единый механизм обработки ошибок Mapping.
Критерии завершения Build
Build 060.13 считается завершённым, поскольку выполнены все поставленные задачи.
- ✔ реализована функция
map_dzengi_websocket_trade_to_trade(); - ✔ реализовано преобразование транспортной модели в каноническую модель
Trade; - ✔ реализовано преобразование транспортных числовых представлений в
Decimal; - ✔ реализовано преобразование временной метки в
datetime; - ✔ реализовано преобразование стороны агрессора;
- ✔ реализовано заполнение поля
source; - ✔ исключено транспортное поле
order_id; - ✔ повторно использованы существующие helper-функции;
- ✔ используется существующее исключение
TradeMappingError; - ✔ транспортная модель остаётся неизменяемой;
- ✔ реализовано 22 unit-теста;
- ✔ целевой набор тестов успешно проходит;
- ✔ полное регрессионное тестирование успешно завершено;
- ✔ проект успешно компилируется;
- ✔
git diff --checkне выявил замечаний; - ✔ изменения полностью укладываются в согласованный scope Build.
Следующий этап
Следующим этапом дорожной карты является
Build 060.14 — WebSocket Trade Adapter
Цель следующего Build:
- объединить Schema Validation, Parser, Value Validation и Mapper в единый адаптер;
- реализовать единую точку обработки WebSocket Trade;
- завершить адаптер получения канонической модели
Tradeиз сырого WebSocket-сообщения; - подготовить основу для последующей интеграции в Unified WebSocket Routing.
Итог
Build 060.13 завершил реализацию уровня WebSocket Trade Mapper и сделал транспортный Pipeline WebSocket Trade полностью независимым от формата данных биржи Dzengi.
Новая реализация основана на уже существующих архитектурных принципах Dzentra, повторно использует существующую инфраструктуру преобразования данных, создаёт каноническую модель предметной области и сохраняет строгое разделение ответственности между уровнями Pipeline.
Build ограничен согласованным scope, успешно прошёл целевое и полное регрессионное тестирование, подтвердил отсутствие регрессий и завершил построение транспортного конвейера WebSocket Trade до уровня канонической модели.
Следующим этапом развития серии является Build 060.14 — WebSocket Trade Adapter, который объединит все реализованные уровни Pipeline в единый компонент обработки входящих WebSocket-сообщений.