1424 lines
39 KiB
Markdown
1424 lines
39 KiB
Markdown
# Build 060.22 — Acquisition Runtime Service
|
||
|
||
**Статус:** Architecture Specification
|
||
**Build:** 060.22
|
||
**Ветка:** Trades Feed (Time & Sales)
|
||
**Документ:** `build_060_22_architecture.md`
|
||
**Связанные документы:**
|
||
|
||
- `build_060_21_architecture.md` — архитектурная спецификация Runtime Integration Contracts;
|
||
- `build_060_21.md` — Engineering Migration Report предыдущего Build;
|
||
- `build_060_20_1_architecture.md` — спецификация владения состоянием Trade Stream.
|
||
|
||
---
|
||
|
||
# Назначение документа
|
||
|
||
Настоящий документ является официальной архитектурной спецификацией **Build 060.22 — Acquisition Runtime Service**.
|
||
|
||
Документ определяет создание первого исполняемого сервисного компонента Runtime Layer подсистемы **Market Data Acquisition**.
|
||
|
||
Build 060.21 сформировал типизированную контрактную границу Runtime:
|
||
|
||
```text
|
||
AcquisitionRuntimeCommand
|
||
|
||
AcquisitionRuntimeEvent
|
||
|
||
AcquisitionRuntimeCommandDispatcherProtocol
|
||
|
||
AcquisitionRuntimeEventPublisherProtocol
|
||
```
|
||
|
||
Однако созданные контракты пока не имеют производственной реализации.
|
||
|
||
В системе отсутствует компонент, который:
|
||
|
||
- принимает типизированные Runtime-команды;
|
||
- маршрутизирует их к соответствующим инфраструктурным зависимостям;
|
||
- публикует результирующие Runtime-события;
|
||
- формирует единую точку исполнения команд Acquisition Runtime.
|
||
|
||
Build 060.22 закрывает именно эту архитектурную задачу.
|
||
|
||
Документ служит единственным источником истины при реализации данного Build.
|
||
|
||
Изменение зафиксированных решений в процессе написания кода не допускается без отдельного архитектурного пересмотра.
|
||
|
||
---
|
||
|
||
# Статус Build
|
||
|
||
Build 060.22 является следующим этапом серии Build 060, посвящённой формированию полноценной подсистемы **Trades Feed (Time & Sales)**.
|
||
|
||
К моменту начала данного Build в проекте уже существуют:
|
||
|
||
- транспортные модели WebSocket;
|
||
- Runtime Commands;
|
||
- Runtime Events;
|
||
- WebSocket transport protocol;
|
||
- WebSocket session protocol;
|
||
- WebSocket subscription manager protocol;
|
||
- контракт диспетчеризации Runtime-команд;
|
||
- контракт публикации Runtime-событий;
|
||
- Canonical Trade Model;
|
||
- Trades Feed;
|
||
- Trade Stream Consistency;
|
||
- Trade Recovery;
|
||
- специализированное хранилище `TradeStreamStateStore`.
|
||
|
||
При этом Runtime Layer пока состоит только из контрактов и моделей сообщений.
|
||
|
||
Фактический сервис исполнения Runtime-команд отсутствует.
|
||
|
||
Следовательно, Runtime ещё не является работающей подсистемой.
|
||
|
||
Build 060.22 вводит минимальную исполняемую реализацию сервисного уровня, не подключая её пока к Trades Feed и корневому Acquisition Service.
|
||
|
||
---
|
||
|
||
# Контекст
|
||
|
||
После завершения Build 060.21 архитектура Runtime Protocol Layer выглядит следующим образом.
|
||
|
||
```text
|
||
AcquisitionRuntimeCommand
|
||
│
|
||
▼
|
||
AcquisitionRuntimeCommandDispatcherProtocol
|
||
```
|
||
|
||
и:
|
||
|
||
```text
|
||
AcquisitionRuntimeEvent
|
||
│
|
||
▼
|
||
AcquisitionRuntimeEventPublisherProtocol
|
||
```
|
||
|
||
Контракты определяют:
|
||
|
||
- какие команды допустимы внутри Acquisition Runtime;
|
||
- какие события может публиковать Runtime;
|
||
- каким образом вызывающий компонент передаёт команду;
|
||
- каким образом инфраструктурный результат передаётся потребителям.
|
||
|
||
Однако контракт сам по себе не выполняет команду.
|
||
|
||
Например:
|
||
|
||
```text
|
||
ConnectCommand
|
||
```
|
||
|
||
уже существует как типизированная команда, но в системе пока отсутствует компонент, который обязан:
|
||
|
||
```text
|
||
ConnectCommand
|
||
│
|
||
▼
|
||
WebSocketSessionProtocol.start()
|
||
```
|
||
|
||
Аналогично:
|
||
|
||
```text
|
||
DisconnectCommand
|
||
│
|
||
▼
|
||
WebSocketSessionProtocol.stop()
|
||
```
|
||
|
||
Для команд управления подписками и отправки сообщений также пока отсутствует единая точка исполнения.
|
||
|
||
Таким образом между публичным Runtime Protocol Layer и низкоуровневыми WebSocket-контрактами остаётся незаполненная сервисная граница.
|
||
|
||
---
|
||
|
||
# Предпосылки
|
||
|
||
Настоящий Build опирается на архитектурные решения, принятые в предыдущих этапах.
|
||
|
||
## Build 059.10
|
||
|
||
Сформированы базовые контракты WebSocket Runtime:
|
||
|
||
```text
|
||
WebSocketTransportProtocol
|
||
|
||
WebSocketSessionProtocol
|
||
|
||
WebSocketSubscriptionManagerProtocol
|
||
```
|
||
|
||
Данные контракты описывают отдельные инфраструктурные возможности, но не объединяют их в единый сервис исполнения команд.
|
||
|
||
---
|
||
|
||
## Build 059.11–059.12
|
||
|
||
Сформированы транспортные команды и события Runtime.
|
||
|
||
Появились типизированные модели:
|
||
|
||
```text
|
||
ConnectCommand
|
||
|
||
DisconnectCommand
|
||
|
||
SubscribeCommand
|
||
|
||
UnsubscribeCommand
|
||
|
||
SendTextCommand
|
||
|
||
SendBinaryCommand
|
||
```
|
||
|
||
а также соответствующие инфраструктурные события.
|
||
|
||
---
|
||
|
||
## Build 060.18
|
||
|
||
Создана подсистема Trade Stream Consistency.
|
||
|
||
Она полностью независима от Runtime Transport и принимает только канонические объекты `Trade`.
|
||
|
||
---
|
||
|
||
## Build 060.19
|
||
|
||
Создана подсистема Trade Recovery.
|
||
|
||
Recovery остаётся stateless и зависит только от:
|
||
|
||
```text
|
||
TradeStreamConsistencyProtocol
|
||
```
|
||
|
||
---
|
||
|
||
## Build 060.20.1
|
||
|
||
Владение состоянием Trade Stream перенесено в:
|
||
|
||
```text
|
||
TradeStreamStateStore
|
||
```
|
||
|
||
Runtime Transport окончательно перестал рассматриваться как владелец состояния предметной области.
|
||
|
||
---
|
||
|
||
## Build 060.21
|
||
|
||
Сформирован интеграционный контракт Runtime Protocol Layer.
|
||
|
||
Добавлены:
|
||
|
||
```text
|
||
AcquisitionRuntimeCommand
|
||
|
||
AcquisitionRuntimeEvent
|
||
|
||
AcquisitionRuntimeCommandDispatcherProtocol
|
||
|
||
AcquisitionRuntimeEventPublisherProtocol
|
||
```
|
||
|
||
Имена были намеренно ограничены контекстом `Acquisition`, чтобы исключить конфликт с существующей общесистемной подсистемой:
|
||
|
||
```text
|
||
src/runtime_events/
|
||
```
|
||
|
||
Build 060.21 определил границы взаимодействия, но сознательно не создавал производственную реализацию.
|
||
|
||
---
|
||
|
||
# Проблема
|
||
|
||
В текущем состоянии каждая Runtime-команда является только неизменяемым объектом данных.
|
||
|
||
Например:
|
||
|
||
```python
|
||
ConnectCommand()
|
||
```
|
||
|
||
не содержит логики подключения.
|
||
|
||
Она только выражает намерение вызывающего компонента.
|
||
|
||
То же относится к:
|
||
|
||
```python
|
||
DisconnectCommand()
|
||
SubscribeCommand(...)
|
||
UnsubscribeCommand(...)
|
||
SendTextCommand(...)
|
||
SendBinaryCommand(...)
|
||
```
|
||
|
||
Для выполнения команд необходим отдельный сервис, который:
|
||
|
||
1. принимает объект команды;
|
||
2. определяет его конкретный тип;
|
||
3. выбирает соответствующую инфраструктурную зависимость;
|
||
4. выполняет строго определённую операцию;
|
||
5. не содержит знаний о Trade, Consistency или Recovery.
|
||
|
||
Без такого компонента Runtime Protocol Layer остаётся декларативным и не может использоваться следующими уровнями системы.
|
||
|
||
---
|
||
|
||
# Основная идея Build
|
||
|
||
Главная идея Build 060.22 заключается в создании тонкого сервиса маршрутизации Runtime-команд.
|
||
|
||
Новый компонент:
|
||
|
||
```text
|
||
AcquisitionRuntimeService
|
||
```
|
||
|
||
становится производственной реализацией:
|
||
|
||
```text
|
||
AcquisitionRuntimeCommandDispatcherProtocol
|
||
```
|
||
|
||
Сервис принимает типизированную команду и делегирует выполнение уже существующим инфраструктурным контрактам.
|
||
|
||
Концептуально:
|
||
|
||
```text
|
||
AcquisitionRuntimeCommand
|
||
│
|
||
▼
|
||
AcquisitionRuntimeService
|
||
│
|
||
┌─────┴─────┐
|
||
▼ ▼
|
||
WebSocket Subscription
|
||
Session Manager
|
||
```
|
||
|
||
Сервис не реализует WebSocket самостоятельно.
|
||
|
||
Он не содержит сетевого клиента.
|
||
|
||
Он не создаёт транспорт.
|
||
|
||
Он только координирует существующие зависимости.
|
||
|
||
---
|
||
|
||
# Цель Build
|
||
|
||
Build обязан создать единственную производственную точку исполнения типизированных Acquisition Runtime Commands.
|
||
|
||
После завершения этапа система должна обеспечивать:
|
||
|
||
- приём всех команд из `AcquisitionRuntimeCommand`;
|
||
- детерминированную маршрутизацию каждой команды;
|
||
- делегирование lifecycle-команд WebSocket Session;
|
||
- делегирование управления подписками Subscription Manager;
|
||
- делегирование отправки транспортных сообщений соответствующей зависимости;
|
||
- соответствие `AcquisitionRuntimeCommandDispatcherProtocol`;
|
||
- независимое unit-тестирование без реального сетевого соединения.
|
||
|
||
Build не должен изменять поведение существующих Runtime Commands и Runtime Events.
|
||
|
||
---
|
||
|
||
# Что НЕ входит в Scope Build
|
||
|
||
Настоящий Build сознательно не реализует:
|
||
|
||
- интеграцию Runtime Service в корневой Acquisition Service;
|
||
- интеграцию Runtime Service с Trades Feed;
|
||
- Composition Root;
|
||
- реальный WebSocket client;
|
||
- Runtime Supervisor;
|
||
- Reconnect Controller;
|
||
- Heartbeat;
|
||
- Scheduler;
|
||
- автоматическое восстановление подписок;
|
||
- автоматический REST Recovery после reconnect;
|
||
- обработку входящих Trade-сообщений;
|
||
- публикацию Canonical Trade Stream;
|
||
- регистрацию Runtime Service в Registry;
|
||
- общесистемную шину событий;
|
||
- конкурентную очередь команд;
|
||
- фоновый worker;
|
||
- сохранение Runtime-состояния между перезапусками.
|
||
|
||
Эти задачи относятся к Build 060.23–060.25 либо к отдельным последующим этапам.
|
||
|
||
Build 060.22 отвечает только за синхронную с точки зрения порядка и асинхронную с точки зрения Python API диспетчеризацию одной команды за один вызов.
|
||
|
||
---
|
||
|
||
# Архитектурные принципы
|
||
|
||
При реализации Build 060.22 используются следующие обязательные принципы.
|
||
|
||
## 1. Unique File Naming
|
||
|
||
Во всём репозитории Dzentra запрещено создание нескольких файлов с одинаковым именем независимо от расположения в каталогах.
|
||
|
||
Единственное разрешённое исключение:
|
||
|
||
```text
|
||
__init__.py
|
||
```
|
||
|
||
Поэтому в Build не создаются файлы:
|
||
|
||
```text
|
||
service.py
|
||
protocol.py
|
||
exceptions.py
|
||
test_service.py
|
||
```
|
||
|
||
Утверждённые уникальные имена:
|
||
|
||
```text
|
||
acquisition_runtime_service.py
|
||
|
||
acquisition_runtime_service_protocol.py
|
||
|
||
test_acquisition_runtime_service.py
|
||
```
|
||
|
||
Имя каждого файла должно однозначно определять его назначение без учёта родительского каталога.
|
||
|
||
---
|
||
|
||
## 2. Protocol Before Implementation
|
||
|
||
Публичный контракт Runtime Service фиксируется отдельно от его реализации.
|
||
|
||
Внешние потребители в последующих Build должны зависеть от:
|
||
|
||
```text
|
||
AcquisitionRuntimeServiceProtocol
|
||
```
|
||
|
||
а не от конкретного класса:
|
||
|
||
```text
|
||
AcquisitionRuntimeService
|
||
```
|
||
|
||
---
|
||
|
||
## 3. Thin Application Service
|
||
|
||
Runtime Service является тонким координатором.
|
||
|
||
Он:
|
||
|
||
- принимает команду;
|
||
- определяет её тип;
|
||
- делегирует операцию;
|
||
- завершает вызов.
|
||
|
||
Сервис не переносит в себя логику нижележащих компонентов.
|
||
|
||
---
|
||
|
||
## 4. No Domain Knowledge
|
||
|
||
Runtime Service не должен импортировать:
|
||
|
||
```text
|
||
Trade
|
||
TradeStreamConsistencyProtocol
|
||
TradeRecoveryProtocol
|
||
TradesFeed
|
||
TradeStreamStateStore
|
||
```
|
||
|
||
Он работает исключительно с транспортными командами и инфраструктурными контрактами.
|
||
|
||
---
|
||
|
||
## 5. Dependency Injection
|
||
|
||
Все зависимости передаются в конструктор.
|
||
|
||
Сервис не создаёт самостоятельно:
|
||
|
||
- WebSocket Session;
|
||
- Transport;
|
||
- Subscription Manager;
|
||
- Publisher;
|
||
- Adapter.
|
||
|
||
Создание графа зависимостей относится к Composition Root и не входит в Scope Build.
|
||
|
||
---
|
||
|
||
## 6. One Command — One Deterministic Route
|
||
|
||
Каждый тип команды должен иметь ровно один допустимый маршрут исполнения.
|
||
|
||
Команда не может быть обработана несколькими зависимостями одновременно, если это отдельно не определено спецификацией.
|
||
|
||
---
|
||
|
||
## 7. No Premature Orchestration
|
||
|
||
Runtime Service не является Supervisor.
|
||
|
||
Он не запускает фоновые задачи.
|
||
|
||
Не выполняет retries.
|
||
|
||
Не планирует reconnect.
|
||
|
||
Не контролирует heartbeat.
|
||
|
||
Перечисленные обязанности появятся в специализированных компонентах позднее.
|
||
|
||
---
|
||
|
||
## 8. Existing Contracts Remain Stable
|
||
|
||
Build не изменяет публичные модели:
|
||
|
||
```text
|
||
runtime_commands.py
|
||
runtime_events.py
|
||
transport_messages.py
|
||
websocket_protocol.py
|
||
```
|
||
|
||
Если реализация обнаружит недостаточность существующего контракта, изменение должно быть отдельно согласовано до внесения в код.
|
||
|
||
---
|
||
|
||
# Acquisition Runtime Service
|
||
|
||
## Назначение
|
||
|
||
`AcquisitionRuntimeService` является первым исполняемым компонентом Runtime Layer.
|
||
|
||
Его единственная задача — выполнение типизированных инфраструктурных Runtime-команд.
|
||
|
||
Сервис не содержит собственной бизнес-логики.
|
||
|
||
Он выполняет только маршрутизацию команд к уже существующим инфраструктурным зависимостям.
|
||
|
||
Концептуально:
|
||
|
||
```text
|
||
Runtime Command
|
||
│
|
||
▼
|
||
AcquisitionRuntimeService
|
||
│
|
||
├──────────────► WebSocketSession
|
||
│
|
||
├──────────────► WebSocketTransport
|
||
│
|
||
└──────────────► SubscriptionManager
|
||
```
|
||
|
||
Таким образом сервис становится единственной производственной точкой исполнения Runtime-команд.
|
||
|
||
---
|
||
|
||
# Почему появляется отдельный Service
|
||
|
||
Во время проектирования были рассмотрены несколько вариантов.
|
||
|
||
---
|
||
|
||
## Вариант №1
|
||
|
||
Каждый вызывающий компонент самостоятельно выполняет команды.
|
||
|
||
Например:
|
||
|
||
```text
|
||
TradesFeed
|
||
|
||
↓
|
||
|
||
if ConnectCommand
|
||
|
||
↓
|
||
|
||
session.start()
|
||
```
|
||
|
||
Отклонён.
|
||
|
||
Причины:
|
||
|
||
- дублирование логики;
|
||
- нарушение принципа единственной ответственности;
|
||
- отсутствие единой точки маршрутизации.
|
||
|
||
---
|
||
|
||
## Вариант №2
|
||
|
||
Перенести выполнение команд внутрь WebSocketSession.
|
||
|
||
Например:
|
||
|
||
```text
|
||
session.dispatch(command)
|
||
```
|
||
|
||
Отклонён.
|
||
|
||
Причины:
|
||
|
||
Session начинает знать о:
|
||
|
||
- Subscription;
|
||
- Transport;
|
||
- Runtime Commands.
|
||
|
||
Тем самым нарушается разделение обязанностей.
|
||
|
||
---
|
||
|
||
## Вариант №3
|
||
|
||
Создать специализированный Runtime Service.
|
||
|
||
Принят.
|
||
|
||
Именно Runtime Service становится единственным компонентом, который понимает соответствие между:
|
||
|
||
```text
|
||
Runtime Command
|
||
|
||
↓
|
||
|
||
Infrastructure Action
|
||
```
|
||
|
||
---
|
||
|
||
# Граница ответственности
|
||
|
||
Runtime Service отвечает исключительно за выполнение Runtime-команд.
|
||
|
||
Он НЕ отвечает за:
|
||
|
||
- сетевой протокол;
|
||
- обработку сообщений;
|
||
- Consistency;
|
||
- Recovery;
|
||
- Trades Feed;
|
||
- управление жизненным циклом приложения;
|
||
- Supervisor;
|
||
- Scheduler;
|
||
- Heartbeat;
|
||
- Reconnect.
|
||
|
||
Все перечисленные обязанности принадлежат другим Build.
|
||
|
||
---
|
||
|
||
# AcquisitionRuntimeServiceProtocol
|
||
|
||
## Назначение
|
||
|
||
Build 060.22 вводит отдельный публичный контракт сервисного уровня.
|
||
|
||
```text
|
||
AcquisitionRuntimeServiceProtocol
|
||
```
|
||
|
||
Он описывает единственную публичную возможность Runtime Service.
|
||
|
||
```python
|
||
dispatch(...)
|
||
```
|
||
|
||
Все последующие Build должны зависеть именно от данного Protocol.
|
||
|
||
Конкретная реализация может изменяться без влияния на потребителей.
|
||
|
||
---
|
||
|
||
# Почему вводится отдельный Protocol
|
||
|
||
На первый взгляд может показаться, что уже существует:
|
||
|
||
```text
|
||
AcquisitionRuntimeCommandDispatcherProtocol
|
||
```
|
||
|
||
Однако данный Protocol описывает инфраструктурную возможность диспетчеризации команд.
|
||
|
||
Он не определяет существование самостоятельного сервисного компонента.
|
||
|
||
Build 060.22 впервые вводит именно сервисный уровень Runtime.
|
||
|
||
Поэтому появляется отдельный сервисный контракт.
|
||
|
||
Это позволяет в будущем заменить реализацию Runtime Service без изменения Composition Root и вышестоящих компонентов.
|
||
|
||
---
|
||
|
||
# Публичный API
|
||
|
||
Сервис предоставляет только один публичный метод.
|
||
|
||
```python
|
||
async def dispatch(
|
||
command: AcquisitionRuntimeCommand,
|
||
) -> None
|
||
```
|
||
|
||
Других публичных методов Build 060.22 не вводит.
|
||
|
||
В частности отсутствуют:
|
||
|
||
```python
|
||
connect()
|
||
|
||
disconnect()
|
||
|
||
subscribe()
|
||
|
||
unsubscribe()
|
||
|
||
send()
|
||
```
|
||
|
||
Все операции выражаются исключительно через типизированные команды.
|
||
|
||
---
|
||
|
||
# Почему отсутствуют отдельные методы
|
||
|
||
Во время проектирования рассматривалась следующая модель.
|
||
|
||
```python
|
||
runtime.connect()
|
||
|
||
runtime.disconnect()
|
||
|
||
runtime.subscribe(...)
|
||
```
|
||
|
||
Она была отклонена.
|
||
|
||
Причина:
|
||
|
||
типизированные Runtime Commands уже являются официальным языком взаимодействия Runtime Layer.
|
||
|
||
Создание второго API привело бы к существованию двух независимых способов выполнения одной и той же операции.
|
||
|
||
Архитектура должна содержать единственный публичный механизм.
|
||
|
||
---
|
||
|
||
# Зависимости Runtime Service
|
||
|
||
Сервис получает все зависимости через Dependency Injection.
|
||
|
||
Минимальный набор зависимостей выглядит следующим образом.
|
||
|
||
```text
|
||
AcquisitionRuntimeService
|
||
|
||
│
|
||
|
||
├────────► WebSocketSessionProtocol
|
||
|
||
│
|
||
|
||
├────────► WebSocketTransportProtocol
|
||
|
||
│
|
||
|
||
├────────► WebSocketSubscriptionManagerProtocol
|
||
|
||
│
|
||
|
||
└────────► AcquisitionRuntimeEventPublisherProtocol
|
||
```
|
||
|
||
Никакие другие зависимости Build 060.22 не предусматривает.
|
||
|
||
---
|
||
|
||
# Почему внедряется Event Publisher
|
||
|
||
Хотя Build 060.22 ещё не реализует полноценную публикацию Runtime-событий, сервис уже принимает зависимость:
|
||
|
||
```text
|
||
AcquisitionRuntimeEventPublisherProtocol
|
||
```
|
||
|
||
Это принципиальное архитектурное решение.
|
||
|
||
Причины:
|
||
|
||
- исключается изменение конструктора в следующих Build;
|
||
- Runtime Service сразу проектируется как источник инфраструктурных событий;
|
||
- интеграция публикации становится локальным изменением без перестройки графа зависимостей.
|
||
|
||
На данном этапе Publisher допускается не использовать.
|
||
|
||
---
|
||
|
||
# Почему внедряется WebSocketTransportProtocol
|
||
|
||
Большинство текущих операций выполняются через:
|
||
|
||
```text
|
||
WebSocketSessionProtocol
|
||
```
|
||
|
||
Однако команды:
|
||
|
||
```text
|
||
SendTextCommand
|
||
|
||
SendBinaryCommand
|
||
```
|
||
|
||
относятся к транспортному уровню.
|
||
|
||
Поэтому Runtime Service сразу получает доступ к Transport.
|
||
|
||
Это предотвращает последующее изменение конструктора.
|
||
|
||
---
|
||
|
||
# Маршрутизация команд
|
||
|
||
Главной обязанностью Runtime Service является детерминированная маршрутизация.
|
||
|
||
Каждый тип команды имеет ровно один маршрут исполнения.
|
||
|
||
---
|
||
|
||
## ConnectCommand
|
||
|
||
```text
|
||
ConnectCommand
|
||
|
||
↓
|
||
|
||
WebSocketSession.start()
|
||
```
|
||
|
||
После успешного завершения управление возвращается вызывающему компоненту.
|
||
|
||
Сам Runtime Service не создаёт сетевое соединение.
|
||
|
||
---
|
||
|
||
## DisconnectCommand
|
||
|
||
```text
|
||
DisconnectCommand
|
||
|
||
↓
|
||
|
||
WebSocketSession.stop()
|
||
```
|
||
|
||
Сервис не закрывает транспорт напрямую.
|
||
|
||
Эта ответственность принадлежит Session.
|
||
|
||
---
|
||
|
||
## SubscribeCommand
|
||
|
||
```text
|
||
SubscribeCommand
|
||
|
||
↓
|
||
|
||
WebSocketSubscriptionManager.subscribe()
|
||
```
|
||
|
||
Runtime Service не хранит список активных подписок.
|
||
|
||
Он лишь делегирует выполнение соответствующему компоненту.
|
||
|
||
---
|
||
|
||
## UnsubscribeCommand
|
||
|
||
```text
|
||
UnsubscribeCommand
|
||
|
||
↓
|
||
|
||
WebSocketSubscriptionManager.unsubscribe()
|
||
```
|
||
|
||
После завершения операции Runtime Service не изменяет собственного состояния.
|
||
|
||
---
|
||
|
||
## SendTextCommand
|
||
|
||
```text
|
||
SendTextCommand
|
||
|
||
↓
|
||
|
||
WebSocketTransport.send_text()
|
||
```
|
||
|
||
Передаваемое сообщение считается полностью сформированным.
|
||
|
||
Runtime Service не сериализует полезную нагрузку повторно.
|
||
|
||
---
|
||
|
||
## SendBinaryCommand
|
||
|
||
```text
|
||
SendBinaryCommand
|
||
|
||
↓
|
||
|
||
WebSocketTransport.send_binary()
|
||
```
|
||
|
||
Команда делегируется транспортному уровню без каких-либо преобразований.
|
||
|
||
---
|
||
|
||
# Официальная матрица маршрутизации
|
||
|
||
| Runtime Command | Исполнитель |
|
||
|-----------------|-------------|
|
||
| ConnectCommand | WebSocketSessionProtocol |
|
||
| DisconnectCommand | WebSocketSessionProtocol |
|
||
| SubscribeCommand | WebSocketSubscriptionManagerProtocol |
|
||
| UnsubscribeCommand | WebSocketSubscriptionManagerProtocol |
|
||
| SendTextCommand | WebSocketTransportProtocol |
|
||
| SendBinaryCommand | WebSocketTransportProtocol |
|
||
|
||
Данная таблица считается официальной спецификацией Build 060.22.
|
||
|
||
Любое изменение маршрутов требует отдельного архитектурного решения (ADR).
|
||
|
||
---
|
||
|
||
# Детерминированность маршрутизации
|
||
|
||
Runtime Service рассматривается как детерминированный маршрутизатор.
|
||
|
||
Для каждого входящего объекта существует единственный допустимый маршрут.
|
||
|
||
Формально:
|
||
|
||
```text
|
||
Runtime Command
|
||
|
||
↓
|
||
|
||
Dispatch Table
|
||
|
||
↓
|
||
|
||
Infrastructure Operation
|
||
```
|
||
|
||
Никакие дополнительные проверки, эвристики или выбор стратегии Build 060.22 не предусматривает.
|
||
|
||
---
|
||
|
||
# Обработка неизвестной команды
|
||
|
||
Build 060.22 не допускает существование неизвестных Runtime-команд.
|
||
|
||
Если в сервис поступает объект, не входящий в объединение:
|
||
|
||
```text
|
||
AcquisitionRuntimeCommand
|
||
```
|
||
|
||
это считается внутренней архитектурной ошибкой.
|
||
|
||
В таком случае сервис обязан немедленно завершить выполнение исключением.
|
||
|
||
Это гарантирует, что добавление новой команды никогда не останется незамеченным и потребует явного обновления таблицы маршрутизации.
|
||
|
||
---
|
||
|
||
# Диаграмма взаимодействия
|
||
|
||
После реализации Build 060.22 выполнение Runtime-команд приобретает следующий вид.
|
||
|
||
```text
|
||
Caller
|
||
│
|
||
│ dispatch(command)
|
||
▼
|
||
AcquisitionRuntimeService
|
||
│
|
||
├──────────────► WebSocketSessionProtocol
|
||
│
|
||
├──────────────► WebSocketTransportProtocol
|
||
│
|
||
├──────────────► WebSocketSubscriptionManagerProtocol
|
||
│
|
||
└──────────────► AcquisitionRuntimeEventPublisherProtocol
|
||
```
|
||
|
||
Важно отметить, что Runtime Service остаётся полностью синхронным с точки зрения архитектуры.
|
||
|
||
Он:
|
||
|
||
- не создаёт фоновых задач;
|
||
- не ставит команды в очередь;
|
||
- не выполняет повторные попытки;
|
||
- не содержит собственного event loop.
|
||
|
||
Каждый вызов `dispatch()` завершается только после завершения соответствующей инфраструктурной операции.
|
||
|
||
---
|
||
|
||
# Жизненный цикл Runtime Service
|
||
|
||
Runtime Service является долгоживущим инфраструктурным сервисом.
|
||
|
||
Типичный жизненный цикл выглядит следующим образом.
|
||
|
||
```text
|
||
Composition Root
|
||
|
||
↓
|
||
|
||
создание зависимостей
|
||
|
||
↓
|
||
|
||
создание Runtime Service
|
||
|
||
↓
|
||
|
||
передача Runtime Service вызывающим компонентам
|
||
|
||
↓
|
||
|
||
многократные вызовы dispatch()
|
||
|
||
↓
|
||
|
||
завершение приложения
|
||
```
|
||
|
||
Сам Runtime Service не требует отдельной инициализации.
|
||
|
||
Также отсутствуют методы:
|
||
|
||
```python
|
||
start()
|
||
|
||
stop()
|
||
|
||
shutdown()
|
||
|
||
dispose()
|
||
```
|
||
|
||
Экземпляр полностью готов к работе сразу после создания.
|
||
|
||
---
|
||
|
||
# Отношение к состоянию
|
||
|
||
Runtime Service является stateless-компонентом.
|
||
|
||
Он не хранит:
|
||
|
||
- состояние подключения;
|
||
- список подписок;
|
||
- очередь сообщений;
|
||
- информацию о последней выполненной команде;
|
||
- счётчики reconnect;
|
||
- состояние Recovery;
|
||
- состояние Consistency.
|
||
|
||
Все перечисленные данные принадлежат специализированным компонентам.
|
||
|
||
Следовательно экземпляр Runtime Service можно считать чистым координатором.
|
||
|
||
---
|
||
|
||
# Обработка исключений
|
||
|
||
Build 060.22 сознательно не вводит собственую иерархию исключений.
|
||
|
||
Причина проста.
|
||
|
||
Runtime Service не принимает самостоятельных решений.
|
||
|
||
Он лишь вызывает инфраструктурные зависимости.
|
||
|
||
Следовательно любые исключения должны передаваться вызывающему компоненту без изменения.
|
||
|
||
Например:
|
||
|
||
```text
|
||
Caller
|
||
|
||
↓
|
||
|
||
Runtime Service
|
||
|
||
↓
|
||
|
||
WebSocket Session
|
||
|
||
↓
|
||
|
||
ConnectionError
|
||
```
|
||
|
||
Исключение должно пройти обратно без обёртки.
|
||
|
||
Это сохраняет прозрачность поведения системы.
|
||
|
||
---
|
||
|
||
# Почему исключения не преобразуются
|
||
|
||
Во время проектирования рассматривался вариант создания:
|
||
|
||
```text
|
||
RuntimeDispatchError
|
||
```
|
||
|
||
Он был отклонён.
|
||
|
||
Причины:
|
||
|
||
- потеря информации о первичном исключении;
|
||
- необходимость лишнего уровня обработки;
|
||
- усложнение диагностики;
|
||
- нарушение принципа прозрачной инфраструктуры.
|
||
|
||
Runtime Service не должен скрывать происхождение ошибки.
|
||
|
||
---
|
||
|
||
# Влияние на существующую архитектуру
|
||
|
||
Build 060.22 практически не изменяет существующую систему.
|
||
|
||
Новый сервис располагается между контрактами Runtime и инфраструктурными зависимостями.
|
||
|
||
До Build:
|
||
|
||
```text
|
||
Runtime Commands
|
||
|
||
↓
|
||
|
||
(нет реализации)
|
||
```
|
||
|
||
После Build:
|
||
|
||
```text
|
||
Runtime Commands
|
||
|
||
↓
|
||
|
||
AcquisitionRuntimeService
|
||
|
||
↓
|
||
|
||
Infrastructure
|
||
```
|
||
|
||
Никакие другие подсистемы не изменяются.
|
||
|
||
---
|
||
|
||
# Влияние на Trade Stream Consistency
|
||
|
||
Подсистема:
|
||
|
||
```text
|
||
Trade Stream Consistency
|
||
```
|
||
|
||
не получает никаких новых зависимостей.
|
||
|
||
Она продолжает работать исключительно с:
|
||
|
||
```text
|
||
Trade
|
||
```
|
||
|
||
и
|
||
|
||
```text
|
||
TradeStreamConsistencyProtocol
|
||
```
|
||
|
||
Build 060.22 не создаёт прямой связи между Runtime и Consistency.
|
||
|
||
---
|
||
|
||
# Влияние на Trade Recovery
|
||
|
||
Trade Recovery также не изменяется.
|
||
|
||
Контроллер восстановления по-прежнему зависит только от:
|
||
|
||
```text
|
||
TradeStreamConsistencyProtocol
|
||
```
|
||
|
||
Runtime Service не импортирует Recovery.
|
||
|
||
Recovery не импортирует Runtime Service.
|
||
|
||
Архитектурная независимость сохраняется полностью.
|
||
|
||
---
|
||
|
||
# Влияние на Trades Feed
|
||
|
||
На данном этапе Trades Feed ещё не использует Runtime Service.
|
||
|
||
Интеграция будет выполнена отдельным Build:
|
||
|
||
```text
|
||
060.23
|
||
```
|
||
|
||
Это позволяет протестировать Runtime Service изолированно.
|
||
|
||
---
|
||
|
||
# План изменения структуры проекта
|
||
|
||
Build добавляет только два новых файла.
|
||
|
||
```text
|
||
runtime/
|
||
|
||
├── acquisition_runtime_service.py
|
||
|
||
└── acquisition_runtime_service_protocol.py
|
||
```
|
||
|
||
Также добавляется один файл тестов.
|
||
|
||
```text
|
||
tests/
|
||
|
||
runtime/
|
||
|
||
└── test_acquisition_runtime_service.py
|
||
```
|
||
|
||
Другие файлы Runtime Build 060.22 не изменяет.
|
||
|
||
---
|
||
|
||
# Почему используется отдельный Protocol
|
||
|
||
Наличие собственного:
|
||
|
||
```text
|
||
AcquisitionRuntimeServiceProtocol
|
||
```
|
||
|
||
позволяет в будущем заменить реализацию.
|
||
|
||
Например:
|
||
|
||
```text
|
||
Simple Runtime Service
|
||
|
||
↓
|
||
|
||
Queued Runtime Service
|
||
|
||
↓
|
||
|
||
Distributed Runtime Service
|
||
```
|
||
|
||
Все перечисленные варианты смогут реализовывать один и тот же публичный контракт.
|
||
|
||
Поэтому вышестоящие компоненты не будут зависеть от конкретного класса.
|
||
|
||
---
|
||
|
||
# Стратегия тестирования
|
||
|
||
Build 060.22 покрывается исключительно Unit Test.
|
||
|
||
Никакие интеграционные тесты пока не требуются.
|
||
|
||
Каждая Runtime-команда проверяется отдельно.
|
||
|
||
Минимальный набор сценариев включает:
|
||
|
||
- соответствие `AcquisitionRuntimeServiceProtocol`;
|
||
- корректную маршрутизацию `ConnectCommand`;
|
||
- корректную маршрутизацию `DisconnectCommand`;
|
||
- корректную маршрутизацию `SubscribeCommand`;
|
||
- корректную маршрутизацию `UnsubscribeCommand`;
|
||
- корректную маршрутизацию `SendTextCommand`;
|
||
- корректную маршрутизацию `SendBinaryCommand`;
|
||
- отсутствие побочных эффектов;
|
||
- прозрачное распространение исключений;
|
||
- отсутствие собственного состояния между вызовами.
|
||
|
||
Все зависимости заменяются простыми Fake-реализациями.
|
||
|
||
Использование реального WebSocket Build 060.22 запрещает.
|
||
|
||
---
|
||
|
||
# ADR (Architecture Decision Record)
|
||
|
||
## ADR-060.22-01
|
||
|
||
**Решение**
|
||
|
||
Создать специализированный сервис:
|
||
|
||
```text
|
||
AcquisitionRuntimeService
|
||
```
|
||
|
||
реализующий выполнение всех типизированных Runtime-команд.
|
||
|
||
**Статус**
|
||
|
||
Accepted.
|
||
|
||
---
|
||
|
||
## ADR-060.22-02
|
||
|
||
**Решение**
|
||
|
||
Ввести отдельный публичный контракт:
|
||
|
||
```text
|
||
AcquisitionRuntimeServiceProtocol
|
||
```
|
||
|
||
независимо от уже существующего:
|
||
|
||
```text
|
||
AcquisitionRuntimeCommandDispatcherProtocol
|
||
```
|
||
|
||
**Статус**
|
||
|
||
Accepted.
|
||
|
||
---
|
||
|
||
## ADR-060.22-03
|
||
|
||
**Решение**
|
||
|
||
Runtime Service остаётся stateless.
|
||
|
||
**Статус**
|
||
|
||
Accepted.
|
||
|
||
---
|
||
|
||
## ADR-060.22-04
|
||
|
||
**Решение**
|
||
|
||
Все зависимости передаются исключительно через Dependency Injection.
|
||
|
||
**Статус**
|
||
|
||
Accepted.
|
||
|
||
---
|
||
|
||
## ADR-060.22-05
|
||
|
||
**Решение**
|
||
|
||
Не создавать новых файлов с именами:
|
||
|
||
```text
|
||
service.py
|
||
|
||
protocol.py
|
||
|
||
test_service.py
|
||
```
|
||
|
||
Использовать только уникальные имена файлов.
|
||
|
||
**Статус**
|
||
|
||
Accepted.
|
||
|
||
---
|
||
|
||
# Definition of Done
|
||
|
||
Build считается завершённым только при выполнении всех условий.
|
||
|
||
Обязательно должны существовать:
|
||
|
||
- `acquisition_runtime_service_protocol.py`;
|
||
- `acquisition_runtime_service.py`;
|
||
- `test_acquisition_runtime_service.py`.
|
||
|
||
Runtime Service обязан:
|
||
|
||
- реализовывать `AcquisitionRuntimeServiceProtocol`;
|
||
- принимать зависимости через конструктор;
|
||
- поддерживать все типы `AcquisitionRuntimeCommand`;
|
||
- выполнять детерминированную маршрутизацию;
|
||
- не хранить собственного состояния;
|
||
- не создавать зависимости самостоятельно;
|
||
- прозрачно распространять исключения.
|
||
|
||
Все новые Unit Test должны успешно проходить.
|
||
|
||
Build не должен изменять поведение существующих Runtime Commands, Runtime Events, Trade Recovery и Trade Stream Consistency.
|
||
|
||
---
|
||
|
||
# Следующий Build
|
||
|
||
После завершения Build 060.22 архитектура Runtime впервые получит полноценный исполняемый сервисный слой.
|
||
|
||
Следующим этапом станет:
|
||
|
||
```text
|
||
Build 060.23
|
||
|
||
Acquisition Integration
|
||
```
|
||
|
||
На этом этапе `AcquisitionRuntimeService` будет интегрирован в подсистему получения рыночных данных и станет использоваться как единая точка исполнения инфраструктурных Runtime-команд.
|
||
|
||
После Build 060.23 Runtime перестанет существовать только как набор контрактов и сервисов и начнёт участвовать в реальном конвейере получения данных от биржи. |