build 059.10: add runtime transport message models
This commit is contained in:
@@ -0,0 +1,29 @@
|
|||||||
|
# app/src/market_data/acquisition/runtime/transport_messages.py
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True, slots=True)
|
||||||
|
class TransportTextMessage:
|
||||||
|
"""
|
||||||
|
Транспортное текстовое сообщение.
|
||||||
|
|
||||||
|
Представляет уже полученный или подготовленный для отправки
|
||||||
|
текстовый payload без какой-либо интерпретации содержимого.
|
||||||
|
"""
|
||||||
|
|
||||||
|
payload: str
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True, slots=True)
|
||||||
|
class TransportBinaryMessage:
|
||||||
|
"""
|
||||||
|
Транспортное бинарное сообщение.
|
||||||
|
|
||||||
|
Представляет уже полученный или подготовленный для отправки
|
||||||
|
бинарный payload без какой-либо интерпретации содержимого.
|
||||||
|
"""
|
||||||
|
|
||||||
|
payload: bytes
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
# app/tests/unit/market_data/acquisition/runtime/test_transport_messages.py
|
||||||
|
|
||||||
|
from dataclasses import FrozenInstanceError
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from src.market_data.acquisition.runtime.transport_messages import (
|
||||||
|
TransportBinaryMessage,
|
||||||
|
TransportTextMessage,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_transport_text_message() -> None:
|
||||||
|
message = TransportTextMessage(payload='{"ping":1}')
|
||||||
|
|
||||||
|
assert message.payload == '{"ping":1}'
|
||||||
|
|
||||||
|
|
||||||
|
def test_transport_binary_message() -> None:
|
||||||
|
message = TransportBinaryMessage(payload=b"\x01\x02")
|
||||||
|
|
||||||
|
assert message.payload == b"\x01\x02"
|
||||||
|
|
||||||
|
|
||||||
|
def test_text_message_is_immutable() -> None:
|
||||||
|
message = TransportTextMessage(payload="abc")
|
||||||
|
|
||||||
|
with pytest.raises(FrozenInstanceError):
|
||||||
|
setattr(message, "payload", "def")
|
||||||
|
|
||||||
|
|
||||||
|
def test_binary_message_is_immutable() -> None:
|
||||||
|
message = TransportBinaryMessage(payload=b"\x00")
|
||||||
|
|
||||||
|
with pytest.raises(FrozenInstanceError):
|
||||||
|
setattr(message, "payload", b"\x01")
|
||||||
423
docs/migrations/build_059_10.md
Normal file
423
docs/migrations/build_059_10.md
Normal file
@@ -0,0 +1,423 @@
|
|||||||
|
# Build 059.10 — Runtime Transport Message Models
|
||||||
|
|
||||||
|
**Migration Build**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Цель Build
|
||||||
|
|
||||||
|
После завершения Build 059.9 в проекте появился самостоятельный Runtime слой, содержащий транспортные контракты:
|
||||||
|
|
||||||
|
- WebSocketTransportProtocol
|
||||||
|
- WebSocketSessionProtocol
|
||||||
|
- WebSocketSubscriptionManagerProtocol
|
||||||
|
|
||||||
|
Следующим логическим шагом является создание моделей данных, которыми будут обмениваться компоненты Runtime.
|
||||||
|
|
||||||
|
Данный Build вводит первый уровень моделей Runtime — **Transport Message Models**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Причина появления Transport Message Models
|
||||||
|
|
||||||
|
До настоящего момента Runtime содержал только интерфейсы взаимодействия.
|
||||||
|
|
||||||
|
Например:
|
||||||
|
|
||||||
|
```
|
||||||
|
Transport
|
||||||
|
│
|
||||||
|
connect()
|
||||||
|
disconnect()
|
||||||
|
send()
|
||||||
|
receive()
|
||||||
|
```
|
||||||
|
|
||||||
|
Однако отсутствовало описание того, **какие именно объекты транспорт должен принимать и возвращать**.
|
||||||
|
|
||||||
|
Использование непосредственно типов
|
||||||
|
|
||||||
|
```python
|
||||||
|
str
|
||||||
|
bytes
|
||||||
|
```
|
||||||
|
|
||||||
|
делает код менее выразительным.
|
||||||
|
|
||||||
|
Невозможно определить:
|
||||||
|
|
||||||
|
- транспортное сообщение это;
|
||||||
|
- сериализованный JSON;
|
||||||
|
- бинарный protobuf;
|
||||||
|
- внутренний объект Runtime.
|
||||||
|
|
||||||
|
Поэтому вводится отдельный уровень моделей транспортных сообщений.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Архитектурная идея
|
||||||
|
|
||||||
|
Главная задача данного Build — отделить транспортные данные от всех остальных сущностей системы.
|
||||||
|
|
||||||
|
Получается следующая иерархия.
|
||||||
|
|
||||||
|
```text
|
||||||
|
Transport
|
||||||
|
│
|
||||||
|
Transport Message
|
||||||
|
│
|
||||||
|
Parser
|
||||||
|
│
|
||||||
|
Validation
|
||||||
|
│
|
||||||
|
Mapper
|
||||||
|
│
|
||||||
|
Canonical Models
|
||||||
|
```
|
||||||
|
|
||||||
|
Transport знает только одно:
|
||||||
|
|
||||||
|
> существует некоторый payload.
|
||||||
|
|
||||||
|
Он совершенно не знает:
|
||||||
|
|
||||||
|
- что находится внутри payload;
|
||||||
|
- как этот payload будет интерпретирован;
|
||||||
|
- является ли он JSON;
|
||||||
|
- относится ли он к конкретной бирже;
|
||||||
|
- какие модели будут построены дальше.
|
||||||
|
|
||||||
|
Это полностью соответствует принципу Single Responsibility.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Почему Runtime не знает про WebSocket
|
||||||
|
|
||||||
|
Несмотря на то, что текущим транспортом является WebSocket, Runtime проектируется максимально универсальным.
|
||||||
|
|
||||||
|
Поэтому модели транспортных сообщений намеренно не содержат слов:
|
||||||
|
|
||||||
|
- WebSocket
|
||||||
|
- Dzengi
|
||||||
|
- Exchange
|
||||||
|
- Candle
|
||||||
|
- Quote
|
||||||
|
|
||||||
|
Runtime оперирует исключительно транспортными сообщениями.
|
||||||
|
|
||||||
|
Это позволяет в будущем использовать один и тот же Runtime для различных транспортов:
|
||||||
|
|
||||||
|
- WebSocket;
|
||||||
|
- HTTP Streaming;
|
||||||
|
- FIX;
|
||||||
|
- gRPC Streaming;
|
||||||
|
- внутренние очереди сообщений.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Новые модели
|
||||||
|
|
||||||
|
Создан новый файл
|
||||||
|
|
||||||
|
```
|
||||||
|
src/market_data/acquisition/runtime/transport_messages.py
|
||||||
|
```
|
||||||
|
|
||||||
|
В Build добавлены две immutable модели.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## TransportTextMessage
|
||||||
|
|
||||||
|
Представляет текстовое транспортное сообщение.
|
||||||
|
|
||||||
|
Содержит единственное поле:
|
||||||
|
|
||||||
|
```
|
||||||
|
payload: str
|
||||||
|
```
|
||||||
|
|
||||||
|
Никакой дополнительной информации модель не содержит.
|
||||||
|
|
||||||
|
Она не знает:
|
||||||
|
|
||||||
|
- что внутри находится JSON;
|
||||||
|
- является ли сообщение командой;
|
||||||
|
- является ли сообщение событием;
|
||||||
|
- содержит ли сообщение свечу.
|
||||||
|
|
||||||
|
Она является исключительно контейнером транспортного текста.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## TransportBinaryMessage
|
||||||
|
|
||||||
|
Представляет бинарное транспортное сообщение.
|
||||||
|
|
||||||
|
Содержит единственное поле
|
||||||
|
|
||||||
|
```
|
||||||
|
payload: bytes
|
||||||
|
```
|
||||||
|
|
||||||
|
Как и текстовая модель, не содержит никакой информации о содержимом.
|
||||||
|
|
||||||
|
Runtime рассматривает бинарные данные как непрозрачный набор байтов.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Почему модели содержат только payload
|
||||||
|
|
||||||
|
Во время проектирования рассматривались варианты добавить дополнительные поля.
|
||||||
|
|
||||||
|
Например:
|
||||||
|
|
||||||
|
```
|
||||||
|
message_type
|
||||||
|
encoding
|
||||||
|
channel
|
||||||
|
metadata
|
||||||
|
timestamp
|
||||||
|
```
|
||||||
|
|
||||||
|
От данной идеи было принято решение отказаться.
|
||||||
|
|
||||||
|
Причины следующие.
|
||||||
|
|
||||||
|
## Причина №1
|
||||||
|
|
||||||
|
Транспорт не должен ничего знать о содержимом сообщения.
|
||||||
|
|
||||||
|
Если транспорт начинает анализировать содержимое сообщения, происходит смешение ответственности между Runtime и Parser.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Причина №2
|
||||||
|
|
||||||
|
Любые дополнительные поля являются предположениями относительно будущего транспорта.
|
||||||
|
|
||||||
|
На данном этапе архитектуры неизвестно:
|
||||||
|
|
||||||
|
- понадобится ли message_type;
|
||||||
|
- понадобится ли encoding;
|
||||||
|
- понадобится ли channel;
|
||||||
|
- понадобится ли metadata.
|
||||||
|
|
||||||
|
Следовательно, преждевременно добавлять подобные поля.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Причина №3
|
||||||
|
|
||||||
|
Минимальные immutable модели проще поддерживать.
|
||||||
|
|
||||||
|
При необходимости они могут быть расширены отдельным Build без нарушения обратной совместимости.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Почему здесь отсутствуют ConnectRequest
|
||||||
|
|
||||||
|
Во время проектирования рассматривалась идея добавить модели:
|
||||||
|
|
||||||
|
```
|
||||||
|
ConnectRequest
|
||||||
|
DisconnectRequest
|
||||||
|
SubscribeRequest
|
||||||
|
UnsubscribeRequest
|
||||||
|
```
|
||||||
|
|
||||||
|
Однако было принято решение отказаться от неё.
|
||||||
|
|
||||||
|
Причина заключается в разделении уровней ответственности.
|
||||||
|
|
||||||
|
ConnectRequest не является транспортным сообщением.
|
||||||
|
|
||||||
|
Это команда Runtime.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Почему отсутствуют Ping/Pong
|
||||||
|
|
||||||
|
По аналогичной причине.
|
||||||
|
|
||||||
|
Ping и Pong являются частью логики Runtime.
|
||||||
|
|
||||||
|
Транспорт получает некоторый payload.
|
||||||
|
|
||||||
|
Что именно находится внутри этого payload, транспорт не анализирует.
|
||||||
|
|
||||||
|
Следовательно:
|
||||||
|
|
||||||
|
Ping/Pong должны появиться позднее вместе с Runtime Commands.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Новая архитектура Runtime
|
||||||
|
|
||||||
|
После завершения Build Runtime начинает разделяться на несколько независимых уровней.
|
||||||
|
|
||||||
|
```text
|
||||||
|
Runtime
|
||||||
|
|
||||||
|
Transport Protocol
|
||||||
|
│
|
||||||
|
Transport Messages
|
||||||
|
│
|
||||||
|
Runtime Commands
|
||||||
|
│
|
||||||
|
Runtime Events
|
||||||
|
│
|
||||||
|
Runtime Services
|
||||||
|
```
|
||||||
|
|
||||||
|
Подобное разделение является значительно более устойчивым, чем смешение всех сущностей в одном модуле.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Обновлённый план Runtime
|
||||||
|
|
||||||
|
После выполнения Build 059.10 дальнейшее развитие Runtime выглядит следующим образом.
|
||||||
|
|
||||||
|
## Build 059.11
|
||||||
|
|
||||||
|
Runtime Commands
|
||||||
|
|
||||||
|
Будут введены команды:
|
||||||
|
|
||||||
|
- ConnectCommand
|
||||||
|
- DisconnectCommand
|
||||||
|
- SubscribeCommand
|
||||||
|
- UnsubscribeCommand
|
||||||
|
- SendTextCommand
|
||||||
|
- SendBinaryCommand
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Build 059.12
|
||||||
|
|
||||||
|
Runtime Events
|
||||||
|
|
||||||
|
Появятся события Runtime:
|
||||||
|
|
||||||
|
- ConnectedEvent
|
||||||
|
- DisconnectedEvent
|
||||||
|
- SubscriptionRestoredEvent
|
||||||
|
- HeartbeatTimeoutEvent
|
||||||
|
- ReconnectStartedEvent
|
||||||
|
- ReconnectCompletedEvent
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Build 059.13
|
||||||
|
|
||||||
|
Subscription Manager
|
||||||
|
|
||||||
|
Полноценное управление активными подписками Runtime.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Build 059.14
|
||||||
|
|
||||||
|
WebSocket Session
|
||||||
|
|
||||||
|
Жизненный цикл Runtime Session.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Build 059.15
|
||||||
|
|
||||||
|
Transport Reliability
|
||||||
|
|
||||||
|
Инфраструктура Runtime:
|
||||||
|
|
||||||
|
- Heartbeat
|
||||||
|
- Reconnect
|
||||||
|
- Ping/Pong
|
||||||
|
- Scheduler
|
||||||
|
- Recovery
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Build 059.16
|
||||||
|
|
||||||
|
Runtime Integration
|
||||||
|
|
||||||
|
Интеграция нового Runtime с существующей цепочкой Acquisition.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Build 059.17
|
||||||
|
|
||||||
|
Runtime Validation & Documentation
|
||||||
|
|
||||||
|
Полная регрессия новой архитектуры и финальная документация.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Проверка Build
|
||||||
|
|
||||||
|
Выполнены проверки.
|
||||||
|
|
||||||
|
## Компиляция
|
||||||
|
|
||||||
|
```
|
||||||
|
python -m compileall
|
||||||
|
```
|
||||||
|
|
||||||
|
Успешно.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Unit Tests
|
||||||
|
|
||||||
|
```
|
||||||
|
test_transport_messages.py
|
||||||
|
```
|
||||||
|
|
||||||
|
Все тесты успешно пройдены.
|
||||||
|
|
||||||
|
Проверяется:
|
||||||
|
|
||||||
|
- создание моделей;
|
||||||
|
- корректность хранения payload;
|
||||||
|
- immutable поведение dataclass.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Runtime Regression
|
||||||
|
|
||||||
|
Проверена совместимость новой модели сообщений с ранее созданными Runtime Protocol.
|
||||||
|
|
||||||
|
Все проверки успешно завершены.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Проверка репозитория
|
||||||
|
|
||||||
|
Выполнен
|
||||||
|
|
||||||
|
```
|
||||||
|
git diff --check
|
||||||
|
```
|
||||||
|
|
||||||
|
Ошибок форматирования не обнаружено.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Итог
|
||||||
|
|
||||||
|
Build 059.10 завершает создание базового транспортного уровня Runtime.
|
||||||
|
|
||||||
|
После него Runtime уже содержит:
|
||||||
|
|
||||||
|
- транспортные контракты;
|
||||||
|
- транспортные модели сообщений.
|
||||||
|
|
||||||
|
Следующие Build будут постепенно добавлять поведение системы (Commands, Events, Session, Subscription Manager, Reconnect), не изменяя уже построенный фундамент.
|
||||||
|
|
||||||
|
Таким образом продолжается основной принцип миграции Dzentra:
|
||||||
|
|
||||||
|
- каждый Build решает одну архитектурную задачу;
|
||||||
|
- изменения являются минимальными и независимыми;
|
||||||
|
- существующая цепочка Acquisition остаётся неизменной;
|
||||||
|
- работающий торговый бот продолжает функционировать без необходимости одновременной полной миграции.
|
||||||
Reference in New Issue
Block a user