feat: add market data architecture and complete migration through build 039
This commit is contained in:
898
docs/migrations/build_035.md
Normal file
898
docs/migrations/build_035.md
Normal file
@@ -0,0 +1,898 @@
|
||||
# Build 035 — Перевод market runtime на Quotes Feed
|
||||
|
||||
**Статус:** Завершён
|
||||
**Подсистема:** Market Data
|
||||
**Контур:** Market Data Acquisition / Quotes Feed / Market Runtime
|
||||
**Проект:** Dzentra
|
||||
**Язык документации:** Русский
|
||||
|
||||
---
|
||||
|
||||
## 1. Цель Build
|
||||
|
||||
Цель Build 035 — перевести существующий WebSocket market runtime с самостоятельного legacy parsing рыночных сообщений на канонический контур обработки котировок, созданный в предыдущих Build.
|
||||
|
||||
До Build 035 runtime самостоятельно извлекал цены из WebSocket-сообщений Dzengi и передавал примитивные значения в legacy facade:
|
||||
|
||||
```text
|
||||
Dzengi WebSocket message
|
||||
↓
|
||||
legacy runtime parsing
|
||||
↓
|
||||
float price / bid / ask
|
||||
↓
|
||||
MarketPriceCache.set_price()
|
||||
↓
|
||||
Quote Store
|
||||
```
|
||||
|
||||
После Build 035 рабочий runtime-путь использует специализированный WebSocket-адаптер и каноническую модель `Quote`:
|
||||
|
||||
```text
|
||||
Dzengi WebSocket message
|
||||
↓
|
||||
ExchangeWebSocketClient
|
||||
↓
|
||||
DzengiWebSocketQuoteAdapter
|
||||
↓
|
||||
canonical Quote
|
||||
↓
|
||||
MarketPriceCache.set_quote()
|
||||
↓
|
||||
Quote Store
|
||||
```
|
||||
|
||||
Таким образом, WebSocket runtime больше не выполняет собственное преобразование транспортного формата Dzengi в набор примитивных ценовых значений.
|
||||
|
||||
---
|
||||
|
||||
## 2. Предпосылки
|
||||
|
||||
Build 035 опирается на результаты предыдущих этапов миграции.
|
||||
|
||||
### Build 027
|
||||
|
||||
Создана каноническая модель:
|
||||
|
||||
```text
|
||||
Quote
|
||||
```
|
||||
|
||||
### Build 028
|
||||
|
||||
Созданы:
|
||||
|
||||
```text
|
||||
REST quote schema validation
|
||||
REST quote parser
|
||||
REST quote value validation
|
||||
```
|
||||
|
||||
### Build 029
|
||||
|
||||
Созданы:
|
||||
|
||||
```text
|
||||
REST quote mapper
|
||||
DzengiQuoteDocumentHandler
|
||||
```
|
||||
|
||||
### Build 030
|
||||
|
||||
Создан полный REST Quotes Feed:
|
||||
|
||||
```text
|
||||
Dzengi REST ticker/24hr
|
||||
↓
|
||||
DzengiQuoteDocumentSource
|
||||
↓
|
||||
QuotesFeed
|
||||
↓
|
||||
QuoteAcquisitionService
|
||||
↓
|
||||
canonical Quote
|
||||
```
|
||||
|
||||
### Build 031
|
||||
|
||||
Новый REST Quotes Feed подключён под legacy `ExchangeService` facade.
|
||||
|
||||
### Build 032
|
||||
|
||||
Создан канонический:
|
||||
|
||||
```text
|
||||
Quote Store
|
||||
```
|
||||
|
||||
### Build 033
|
||||
|
||||
`MarketPriceCache` переведён на использование `Quote Store` как внутреннего хранилища.
|
||||
|
||||
### Build 034
|
||||
|
||||
Создан специализированный контур обработки WebSocket-котировок:
|
||||
|
||||
```text
|
||||
Dzengi WebSocket message
|
||||
↓
|
||||
schema validation
|
||||
↓
|
||||
parser
|
||||
↓
|
||||
value validation
|
||||
↓
|
||||
mapper
|
||||
↓
|
||||
DzengiWebSocketQuoteAdapter
|
||||
↓
|
||||
canonical Quote
|
||||
```
|
||||
|
||||
Build 035 подключает этот контур к существующему market runtime.
|
||||
|
||||
---
|
||||
|
||||
## 3. Архитектурная проблема до Build 035
|
||||
|
||||
До Build 035 существовало несколько независимых путей обработки котировок.
|
||||
|
||||
REST-контур уже использовал каноническую модель:
|
||||
|
||||
```text
|
||||
REST ticker/24hr
|
||||
↓
|
||||
Quotes Feed
|
||||
↓
|
||||
Quote
|
||||
↓
|
||||
Quote Store
|
||||
```
|
||||
|
||||
Но WebSocket runtime продолжал самостоятельно разбирать транспортные сообщения.
|
||||
|
||||
### `market_stream.py`
|
||||
|
||||
Рабочий путь имел вид:
|
||||
|
||||
```text
|
||||
WebSocket message
|
||||
↓
|
||||
_payload_from_message()
|
||||
↓
|
||||
_extract_market_event()
|
||||
↓
|
||||
float price / bid / ask
|
||||
↓
|
||||
MarketPriceCache.set_price()
|
||||
```
|
||||
|
||||
### `market_data_runner.py`
|
||||
|
||||
Рабочий путь имел вид:
|
||||
|
||||
```text
|
||||
WebSocket message
|
||||
↓
|
||||
_extract_depth_payload()
|
||||
↓
|
||||
_extract_best_price()
|
||||
↓
|
||||
float midpoint
|
||||
↓
|
||||
MarketPriceCache.set_price()
|
||||
```
|
||||
|
||||
Таким образом, логика понимания формата Dzengi WebSocket существовала одновременно:
|
||||
|
||||
```text
|
||||
в новом DzengiWebSocketQuoteAdapter
|
||||
в market_stream.py
|
||||
в market_data_runner.py
|
||||
```
|
||||
|
||||
Это нарушало архитектурную границу:
|
||||
|
||||
```text
|
||||
transport-specific parsing
|
||||
↓
|
||||
только adapter layer
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Архитектурный результат
|
||||
|
||||
После Build 035 оба WebSocket runtime-пути используют единый канонический адаптер:
|
||||
|
||||
```text
|
||||
ExchangeWebSocketClient
|
||||
↓
|
||||
decoded WebSocket message
|
||||
↓
|
||||
DzengiWebSocketQuoteAdapter
|
||||
↓
|
||||
canonical Quote
|
||||
↓
|
||||
MarketPriceCache compatibility facade
|
||||
↓
|
||||
Quote Store
|
||||
```
|
||||
|
||||
Runtime больше не должен самостоятельно знать:
|
||||
|
||||
```text
|
||||
как устроены payload / Payload
|
||||
как извлекается symbolName
|
||||
как извлекаются bids / asks
|
||||
какие варианты depth item поддерживает Dzengi
|
||||
как вычисляется canonical last_price
|
||||
как преобразуется timestamp
|
||||
```
|
||||
|
||||
Эта ответственность принадлежит:
|
||||
|
||||
```text
|
||||
src/market_data/acquisition/adapters/dzengi/websocket.py
|
||||
```
|
||||
|
||||
и связанному с ним контуру:
|
||||
|
||||
```text
|
||||
schema validation
|
||||
parser
|
||||
value validation
|
||||
mapper
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Изменённые production-файлы
|
||||
|
||||
В рамках Build 035 изменены:
|
||||
|
||||
```text
|
||||
src/integrations/exchange/market_cache.py
|
||||
src/integrations/exchange/market_stream.py
|
||||
src/integrations/exchange/market_data_runner.py
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Изменённые тестовые файлы
|
||||
|
||||
Расширены существующие тесты:
|
||||
|
||||
```text
|
||||
tests/unit/integrations/exchange/test_market_cache.py
|
||||
tests/unit/integrations/exchange/test_market_stream.py
|
||||
tests/unit/integrations/exchange/test_market_data_runner.py
|
||||
```
|
||||
|
||||
Новые тестовые файлы не создавались.
|
||||
|
||||
---
|
||||
|
||||
## 7. Расширение MarketPriceCache
|
||||
|
||||
В `MarketPriceCache` добавлен новый метод:
|
||||
|
||||
```python
|
||||
@classmethod
|
||||
def set_quote(
|
||||
cls,
|
||||
quote: Quote,
|
||||
*,
|
||||
runtime_key: str = "default",
|
||||
) -> None:
|
||||
...
|
||||
```
|
||||
|
||||
Его задача — принять уже готовый канонический объект:
|
||||
|
||||
```text
|
||||
Quote
|
||||
```
|
||||
|
||||
и записать его в существующее внутреннее хранилище через compatibility facade.
|
||||
|
||||
Цепочка:
|
||||
|
||||
```text
|
||||
canonical Quote
|
||||
↓
|
||||
MarketPriceCache.set_quote()
|
||||
↓
|
||||
Quote Store
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. Сохранение канонического Quote без повторного mapping
|
||||
|
||||
До Build 035 при наличии уже готового `Quote` потенциально мог возникнуть лишний цикл:
|
||||
|
||||
```text
|
||||
Quote
|
||||
↓
|
||||
float values
|
||||
↓
|
||||
MarketPriceCache.set_price()
|
||||
↓
|
||||
создание нового Quote
|
||||
↓
|
||||
Quote Store
|
||||
```
|
||||
|
||||
После Build 035 используется прямой путь:
|
||||
|
||||
```text
|
||||
Quote
|
||||
↓
|
||||
MarketPriceCache.set_quote()
|
||||
↓
|
||||
Quote Store
|
||||
```
|
||||
|
||||
При этом не требуется:
|
||||
|
||||
```text
|
||||
преобразовывать Decimal в float
|
||||
повторно создавать Quote
|
||||
повторно вычислять received_at
|
||||
повторно преобразовывать exchange_timestamp
|
||||
изменять source
|
||||
```
|
||||
|
||||
Таким образом, сохраняется исходный канонический объект.
|
||||
|
||||
---
|
||||
|
||||
## 9. Сохранение legacy set_price()
|
||||
|
||||
Существующий публичный метод:
|
||||
|
||||
```text
|
||||
MarketPriceCache.set_price()
|
||||
```
|
||||
|
||||
не удалён.
|
||||
|
||||
Это необходимо для сохранения обратной совместимости существующего бота и legacy-потребителей.
|
||||
|
||||
После Build 035 его архитектурная роль:
|
||||
|
||||
```text
|
||||
legacy primitive values
|
||||
↓
|
||||
MarketPriceCache.set_price()
|
||||
↓
|
||||
canonical Quote
|
||||
↓
|
||||
MarketPriceCache.set_quote()
|
||||
↓
|
||||
Quote Store
|
||||
```
|
||||
|
||||
Таким образом, `set_price()` остаётся compatibility entry point, а непосредственная запись готового канонического объекта выполняется через:
|
||||
|
||||
```text
|
||||
set_quote()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10. Перевод market_stream.py
|
||||
|
||||
Рабочий WebSocket-путь `market_stream.py` переведён на:
|
||||
|
||||
```text
|
||||
ExchangeWebSocketClient.stream_depth()
|
||||
↓
|
||||
DzengiWebSocketQuoteAdapter.map_message()
|
||||
↓
|
||||
Quote
|
||||
↓
|
||||
MarketPriceCache.set_quote()
|
||||
```
|
||||
|
||||
Новый runtime-путь больше не использует legacy-функцию:
|
||||
|
||||
```text
|
||||
_extract_market_event()
|
||||
```
|
||||
|
||||
для обработки рабочих WebSocket-сообщений.
|
||||
|
||||
---
|
||||
|
||||
## 11. Проверка символа в market_stream.py
|
||||
|
||||
После получения канонического `Quote` выполняется проверка соответствия символа ожидаемому инструменту.
|
||||
|
||||
Концептуально:
|
||||
|
||||
```text
|
||||
requested symbol
|
||||
↕
|
||||
Quote.symbol
|
||||
```
|
||||
|
||||
Если сообщение относится к другому инструменту, оно не должно записываться в runtime namespace.
|
||||
|
||||
Это предотвращает сохранение чужой котировки в контексте текущего WebSocket-потока.
|
||||
|
||||
---
|
||||
|
||||
## 12. Обработка невалидных сообщений в market_stream.py
|
||||
|
||||
Ошибка обработки отдельного WebSocket-сообщения не должна немедленно завершать весь market stream.
|
||||
|
||||
Ошибки канонического Acquisition-контура отдельного сообщения обрабатываются внутри цикла:
|
||||
|
||||
```text
|
||||
invalid WebSocket message
|
||||
↓
|
||||
DzengiWebSocketQuoteAdapter
|
||||
↓
|
||||
MarketDataAcquisitionError
|
||||
↓
|
||||
сообщение пропускается
|
||||
↓
|
||||
stream продолжает работу
|
||||
```
|
||||
|
||||
При этом сетевые, transport и connection errors не маскируются этим механизмом и продолжают обрабатываться существующим reconnect-контуром.
|
||||
|
||||
---
|
||||
|
||||
## 13. Перевод MarketDataRunner
|
||||
|
||||
Основной WebSocket runtime в:
|
||||
|
||||
```text
|
||||
MarketDataRunner._run_websocket()
|
||||
```
|
||||
|
||||
переведён с самостоятельного извлечения:
|
||||
|
||||
```text
|
||||
best_bid
|
||||
best_ask
|
||||
```
|
||||
|
||||
на канонический путь:
|
||||
|
||||
```text
|
||||
raw WebSocket payload
|
||||
↓
|
||||
DzengiWebSocketQuoteAdapter.map_message()
|
||||
↓
|
||||
Quote
|
||||
```
|
||||
|
||||
После успешного mapping готовый объект записывается:
|
||||
|
||||
```text
|
||||
Quote
|
||||
↓
|
||||
MarketPriceCache.set_quote(
|
||||
runtime_key=context.runtime_key
|
||||
)
|
||||
↓
|
||||
Quote Store
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 14. Runtime isolation
|
||||
|
||||
Сохраняется существующая изоляция runtime-контекстов через:
|
||||
|
||||
```text
|
||||
runtime_key
|
||||
```
|
||||
|
||||
Примеры существующих runtime:
|
||||
|
||||
```text
|
||||
auto
|
||||
debug_auto
|
||||
default
|
||||
```
|
||||
|
||||
При записи через:
|
||||
|
||||
```text
|
||||
MarketPriceCache.set_quote()
|
||||
```
|
||||
|
||||
используется соответствующий:
|
||||
|
||||
```text
|
||||
context.runtime_key
|
||||
```
|
||||
|
||||
Таким образом, котировки разных runtime не смешиваются.
|
||||
|
||||
---
|
||||
|
||||
## 15. Bid и ask после перехода на Quote
|
||||
|
||||
До Build 035 `MarketDataRunner` самостоятельно извлекал:
|
||||
|
||||
```text
|
||||
best_bid
|
||||
best_ask
|
||||
```
|
||||
|
||||
из сырого WebSocket payload.
|
||||
|
||||
После Build 035 эти значения берутся из уже проверенного канонического объекта:
|
||||
|
||||
```text
|
||||
Quote.bid_price
|
||||
Quote.ask_price
|
||||
```
|
||||
|
||||
Для legacy logging boundary при необходимости допускается преобразование:
|
||||
|
||||
```text
|
||||
Decimal → float
|
||||
```
|
||||
|
||||
Но внутренний канонический объект остаётся основанным на:
|
||||
|
||||
```text
|
||||
Decimal
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 16. Last price для depth-сообщений
|
||||
|
||||
Согласно контракту Build 034 для WebSocket depth-сообщений:
|
||||
|
||||
```text
|
||||
last_price = midpoint(best_bid, best_ask)
|
||||
```
|
||||
|
||||
После Build 035 runtime больше не должен самостоятельно повторять этот расчёт.
|
||||
|
||||
Он получает готовое значение:
|
||||
|
||||
```text
|
||||
Quote.last_price
|
||||
```
|
||||
|
||||
из `DzengiWebSocketQuoteAdapter`.
|
||||
|
||||
Таким образом, правило определения `last_price` имеет одну каноническую реализацию.
|
||||
|
||||
---
|
||||
|
||||
## 17. Сохранение invalid payload semantics
|
||||
|
||||
До Build 035 `MarketDataRunner` поддерживал счётчик последовательных невалидных WebSocket-сообщений.
|
||||
|
||||
Существующая семантика сохранена:
|
||||
|
||||
```text
|
||||
invalid message
|
||||
↓
|
||||
invalid_payload_count += 1
|
||||
```
|
||||
|
||||
Успешный `Quote`:
|
||||
|
||||
```text
|
||||
valid Quote
|
||||
↓
|
||||
invalid_payload_count = 0
|
||||
```
|
||||
|
||||
После пяти последовательных невалидных сообщений:
|
||||
|
||||
```text
|
||||
5 consecutive invalid messages
|
||||
↓
|
||||
RuntimeError
|
||||
↓
|
||||
существующий fallback-контур
|
||||
```
|
||||
|
||||
Таким образом, Build 035 не изменяет существующую политику деградации runtime.
|
||||
|
||||
---
|
||||
|
||||
## 18. REST fallback
|
||||
|
||||
REST fallback не потребовал изменения.
|
||||
|
||||
К моменту Build 035 REST-путь уже использует новый Quotes Feed:
|
||||
|
||||
```text
|
||||
Dzengi REST ticker/24hr
|
||||
↓
|
||||
DzengiQuoteDocumentSource
|
||||
↓
|
||||
QuotesFeed
|
||||
↓
|
||||
QuoteAcquisitionService
|
||||
↓
|
||||
canonical Quote
|
||||
↓
|
||||
MarketPriceCache facade
|
||||
↓
|
||||
Quote Store
|
||||
```
|
||||
|
||||
После Build 035 WebSocket и REST-пути сходятся на одной канонической модели:
|
||||
|
||||
```text
|
||||
WebSocket
|
||||
↓
|
||||
DzengiWebSocketQuoteAdapter
|
||||
↓
|
||||
Quote
|
||||
↓
|
||||
Quote Store
|
||||
↑
|
||||
Quote
|
||||
↑
|
||||
REST Quotes Feed
|
||||
↑
|
||||
REST
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 19. Что намеренно не изменялось
|
||||
|
||||
В Build 035 не изменялись:
|
||||
|
||||
```text
|
||||
src/integrations/exchange/ws_client.py
|
||||
src/market_data/acquisition/adapters/dzengi/websocket.py
|
||||
src/market_data/acquisition/models/quote.py
|
||||
src/storage/quote_store.py
|
||||
```
|
||||
|
||||
Причина:
|
||||
|
||||
- `ws_client.py` уже имеет достаточный transport-only контракт;
|
||||
- WebSocket-адаптер завершён в Build 034;
|
||||
- каноническая модель `Quote` не требует расширения;
|
||||
- `Quote Store` уже предоставляет необходимый контракт хранения.
|
||||
|
||||
---
|
||||
|
||||
## 20. Legacy parsing helpers
|
||||
|
||||
После перевода рабочего runtime-пути некоторые legacy helper-функции больше не являются частью основного пути обработки котировок.
|
||||
|
||||
В частности:
|
||||
|
||||
```text
|
||||
market_stream.py:
|
||||
_extract_market_event()
|
||||
|
||||
market_data_runner.py:
|
||||
_extract_best_price()
|
||||
```
|
||||
|
||||
Они не удалены в Build 035.
|
||||
|
||||
Это намеренное решение.
|
||||
|
||||
Окончательная очистка legacy parsing относится к последующему этапу:
|
||||
|
||||
```text
|
||||
Build 039 — Удаление legacy quote parsing и MarketPriceCache
|
||||
```
|
||||
|
||||
Build 035 меняет рабочий runtime-путь, но не выполняет преждевременную очистку compatibility layer.
|
||||
|
||||
---
|
||||
|
||||
## 21. Обратная совместимость
|
||||
|
||||
Build 035 сохраняет работоспособность существующего бота.
|
||||
|
||||
Не удалены:
|
||||
|
||||
```text
|
||||
MarketPriceCache
|
||||
MarketPriceCache.set_price()
|
||||
legacy read APIs
|
||||
runtime_key isolation
|
||||
REST fallback
|
||||
существующие runtime lifecycle contracts
|
||||
```
|
||||
|
||||
Это соответствует принятому принципу миграции Dzentra:
|
||||
|
||||
```text
|
||||
новый контур создаётся
|
||||
↓
|
||||
существующие потребители постепенно переключаются
|
||||
↓
|
||||
legacy удаляется только после завершения миграции
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 22. Проверка компиляции
|
||||
|
||||
Выполнена команда:
|
||||
|
||||
```bash
|
||||
python -m py_compile \
|
||||
src/integrations/exchange/market_cache.py \
|
||||
src/integrations/exchange/market_stream.py \
|
||||
src/integrations/exchange/market_data_runner.py \
|
||||
tests/unit/integrations/exchange/test_market_cache.py \
|
||||
tests/unit/integrations/exchange/test_market_stream.py \
|
||||
tests/unit/integrations/exchange/test_market_data_runner.py
|
||||
```
|
||||
|
||||
Результат:
|
||||
|
||||
```text
|
||||
успешно
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 23. Специализированные тесты
|
||||
|
||||
Выполнена команда:
|
||||
|
||||
```bash
|
||||
python -m pytest \
|
||||
tests/unit/integrations/exchange/test_market_cache.py \
|
||||
tests/unit/integrations/exchange/test_market_stream.py \
|
||||
tests/unit/integrations/exchange/test_market_data_runner.py \
|
||||
tests/unit/market_data/acquisition/adapters/dzengi/test_websocket_quote_adapter.py \
|
||||
-q
|
||||
```
|
||||
|
||||
Результат:
|
||||
|
||||
```text
|
||||
33 passed in 0.13s
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 24. Полная регрессия
|
||||
|
||||
Выполнена команда:
|
||||
|
||||
```bash
|
||||
python -m pytest -q
|
||||
```
|
||||
|
||||
Результат:
|
||||
|
||||
```text
|
||||
608 passed in 0.29s
|
||||
```
|
||||
|
||||
Количество тестов до Build 035:
|
||||
|
||||
```text
|
||||
602 passed
|
||||
```
|
||||
|
||||
Количество тестов после Build 035:
|
||||
|
||||
```text
|
||||
608 passed
|
||||
```
|
||||
|
||||
Добавлено:
|
||||
|
||||
```text
|
||||
6 тестов
|
||||
```
|
||||
|
||||
Полная регрессия подтверждает отсутствие обнаруженных регрессий в существующем коде проекта.
|
||||
|
||||
---
|
||||
|
||||
## 25. Итог Build
|
||||
|
||||
Build 035 завершён полностью.
|
||||
|
||||
Реализованы:
|
||||
|
||||
```text
|
||||
подключение DzengiWebSocketQuoteAdapter к market_stream
|
||||
подключение DzengiWebSocketQuoteAdapter к MarketDataRunner
|
||||
передача canonical Quote непосредственно в compatibility facade
|
||||
добавление MarketPriceCache.set_quote()
|
||||
сохранение legacy MarketPriceCache.set_price()
|
||||
сохранение runtime_key isolation
|
||||
сохранение invalid payload semantics
|
||||
сохранение REST fallback
|
||||
сохранение обратной совместимости
|
||||
```
|
||||
|
||||
Подтверждён новый рабочий WebSocket-путь:
|
||||
|
||||
```text
|
||||
ExchangeWebSocketClient
|
||||
↓
|
||||
DzengiWebSocketQuoteAdapter
|
||||
↓
|
||||
canonical Quote
|
||||
↓
|
||||
MarketPriceCache.set_quote()
|
||||
↓
|
||||
Quote Store
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 26. Архитектурное состояние после Build 035
|
||||
|
||||
После завершения Build 035 Dzentra имеет два канонических пути получения текущей котировки.
|
||||
|
||||
### WebSocket
|
||||
|
||||
```text
|
||||
Dzengi WebSocket
|
||||
↓
|
||||
ExchangeWebSocketClient
|
||||
↓
|
||||
DzengiWebSocketQuoteAdapter
|
||||
↓
|
||||
Quote
|
||||
↓
|
||||
MarketPriceCache compatibility facade
|
||||
↓
|
||||
Quote Store
|
||||
```
|
||||
|
||||
### REST
|
||||
|
||||
```text
|
||||
Dzengi REST ticker/24hr
|
||||
↓
|
||||
DzengiQuoteDocumentSource
|
||||
↓
|
||||
QuotesFeed
|
||||
↓
|
||||
QuoteAcquisitionService
|
||||
↓
|
||||
Quote
|
||||
↓
|
||||
MarketPriceCache compatibility facade
|
||||
↓
|
||||
Quote Store
|
||||
```
|
||||
|
||||
Таким образом, оба transport-пути приводят данные к единой канонической модели:
|
||||
|
||||
```text
|
||||
Quote
|
||||
```
|
||||
|
||||
до передачи их потребителям.
|
||||
|
||||
---
|
||||
|
||||
## 27. Следующий Build
|
||||
|
||||
Следующий этап:
|
||||
|
||||
```text
|
||||
Build 036 — Перевод read-only и UI-потребителей
|
||||
```
|
||||
|
||||
Его задача — определить и перевести read-only и UI-потребителей текущей рыночной котировки с legacy-представлений на канонический `Quote` и новый контур хранения там, где это архитектурно обосновано.
|
||||
|
||||
Build 036 должен выполняться без изменения execution semantics и без преждевременного удаления legacy compatibility layer.
|
||||
|
||||
Execution-потребители остаются отдельным последующим этапом миграции.
|
||||
Reference in New Issue
Block a user