# Build 059.11 — Runtime Commands **Migration Build** --- # Цель Build После завершения Build 059.10 Runtime уже содержит два фундаментальных уровня: - Runtime Protocols - Transport Message Models Следующим шагом является появление объектов, описывающих **намерения Runtime**. Данный Build вводит отдельный слой **Runtime Commands**. Команды описывают **что необходимо сделать**, но не содержат логики выполнения. --- # Почему понадобился отдельный слой Commands До настоящего момента Runtime умел описывать: - транспортные интерфейсы; - транспортные сообщения. Однако отсутствовало понятие действия. Например: - открыть соединение; - закрыть соединение; - зарегистрировать подписку; - отправить сообщение. Все эти действия являются командами Runtime. Они не являются: - транспортными сообщениями; - событиями Runtime; - сетевыми операциями. Поэтому они выделяются в самостоятельный уровень архитектуры. --- # Архитектурная идея Runtime начинает строиться по принципу Command-driven Architecture. Получается следующая цепочка. ```text Runtime Service │ ▼ Runtime Command │ ▼ Transport │ ▼ Transport Message │ ▼ Network ``` Таким образом команды описывают исключительно намерение. Исполнение намерения остаётся обязанностью Session и Transport. --- # Новый модуль Создан файл ``` src/market_data/acquisition/runtime/runtime_commands.py ``` В нём определены immutable-модели Runtime-команд. --- # Использование Transport Messages Все команды, связанные с передачей данных, используют модели Build 059.10. То есть Runtime продолжает работать только с двумя видами транспортных сообщений: ``` TransportTextMessage TransportBinaryMessage ``` Никакие команды не работают непосредственно со строками или массивами байтов. Это обеспечивает единый транспортный контракт Runtime. --- # TransportMessage В модуле введён локальный type alias. ```python TransportMessage = TransportTextMessage | TransportBinaryMessage ``` Он используется исключительно для типизации. Никакой новой Runtime-модели не создаётся. Данный alias позволяет избежать дублирования кода в командах подписки. --- # Новые команды В Build добавлены шесть Runtime-команд. --- ## ConnectCommand Представляет намерение открыть транспортное соединение. Команда: - не содержит URL; - не содержит параметры подключения; - не выполняет подключение самостоятельно. Она лишь сообщает Runtime: > необходимо открыть соединение. --- ## DisconnectCommand Представляет намерение завершить соединение. Команда не управляет транспортом. Она только описывает действие Runtime. --- ## Почему ConnectCommand не содержит URL Во время проектирования рассматривались варианты добавить: ``` url headers timeout ssl authentication ``` От данной идеи было принято решение отказаться. Причины следующие. --- ### Причина №1 URL является частью конфигурации транспорта. Он не должен передаваться вместе с каждой командой. --- ### Причина №2 Команда должна описывать действие. Не настройки транспорта. --- ### Причина №3 Подобная архитектура позволяет легко заменить транспорт без изменения Runtime-команд. --- # SubscribeCommand Представляет команду регистрации новой подписки Runtime. Содержит два поля. ``` subscription_key message ``` --- ## subscription_key Строковый идентификатор подписки. На текущем этапе Runtime он нигде не интерпретируется. Однако именно он станет основным ключом будущего Subscription Manager. Например: ``` quotes:BTCUSDT ``` или ``` candles:ETHUSDT:1m ``` Формат идентификатора пока намеренно не стандартизируется. Это будет сделано позднее после проектирования Subscription Manager. --- ## message Содержит транспортное сообщение. Тип поля: ``` TransportMessage ``` То есть подписка может использовать: - текстовое сообщение; - бинарное сообщение. Runtime не анализирует содержимое payload. --- # Почему SubscribeCommand хранит готовое сообщение Рассматривались два варианта. --- ## Вариант 1 Хранить параметры подписки. Например ``` symbol interval channel ``` --- ## Вариант 2 Хранить уже подготовленное транспортное сообщение. Был выбран второй вариант. Причины. --- ### Runtime не знает протокол биржи Построением JSON занимается слой выше. Runtime не должен знать: - Binance; - Bybit; - Dzengi; - Kraken; - OKX. --- ### Runtime отвечает только за доставку Transport получает уже готовое сообщение. Как оно было сформировано — его не касается. --- # UnsubscribeCommand Представляет команду отмены существующей подписки. Содержит: ``` subscription_key message ``` По архитектуре полностью симметрична SubscribeCommand. Это позволит Subscription Manager использовать единый механизм регистрации и удаления подписок. --- # SendTextCommand Представляет команду отправки текстового транспортного сообщения. Содержит ``` TransportTextMessage ``` Никаких дополнительных параметров команда не имеет. --- # SendBinaryCommand Представляет команду отправки бинарного транспортного сообщения. Содержит ``` TransportBinaryMessage ``` Runtime рассматривает его исключительно как транспортный payload. --- # Почему отсутствует базовый класс Command Во время проектирования обсуждались варианты: ``` BaseCommand ``` или ``` RuntimeCommand ``` Было принято решение отказаться. Причины. --- ## Причина №1 Все команды являются immutable dataclass. Общего поведения между ними нет. --- ## Причина №2 Преждевременная иерархия усложняет архитектуру. Добавлять её без появления реальной общей логики нецелесообразно. --- ## Причина №3 При необходимости общий базовый тип может быть введён отдельным Build без нарушения обратной совместимости. --- # Почему отсутствует CommandType Также обсуждалось использование enum. Например. ``` CONNECT DISCONNECT SUBSCRIBE ``` От идеи отказались. Тип команды уже определяется её классом. Дополнительный enum создавал бы дублирование информации. --- # Почему команды ничего не делают Очень важный архитектурный принцип Runtime. Команды являются моделями данных. Они: - ничего не выполняют; - не взаимодействуют с сетью; - не содержат asyncio; - не вызывают Transport; - не знают Session. Это соответствует принципу Command Pattern. --- # Что появится позже Исполнением команд будут заниматься Runtime Services. Например. ```text Session получает ConnectCommand ↓ вызывает Transport.connect() ``` Таким образом команда остаётся полностью независимой от реализации транспорта. --- # Обновлённая архитектура Runtime После завершения Build Runtime выглядит следующим образом. ```text Runtime Protocols │ Transport Messages │ Runtime Commands │ Transport │ Network ``` Следующим Build между Commands и Transport появятся Runtime Events. --- # Что НЕ входит в Build Сознательно не реализованы: - Session; - Command Dispatcher; - очередь команд; - asyncio; - обработчики команд; - сериализация; - JSON; - Subscription Manager; - Reconnect; - Heartbeat; - Ping/Pong. Все эти элементы будут появляться постепенно отдельными Build. --- # Проверка Build Выполнена полная проверка. --- ## Компиляция ``` python -m compileall ``` Успешно. --- ## Unit Tests Созданы тесты: ``` test_runtime_commands.py ``` Проверяется: - создание каждой команды; - корректность хранения TransportMessage; - поддержка текстовых сообщений; - поддержка бинарных сообщений; - immutable-поведение dataclass. Все тесты успешно пройдены. --- ## Runtime Regression Проверены совместно: - Runtime Protocols; - Transport Messages; - Runtime Commands. Все Runtime-тесты успешно завершены. --- ## Проверка репозитория Выполнен ``` git diff --check ``` Ошибок форматирования не обнаружено. --- # Итог Build 059.11 завершает формирование слоя Runtime Commands. Теперь Runtime имеет три независимых уровня: ```text Protocols │ Transport Messages │ Runtime Commands ``` Каждый уровень отвечает только за собственную область ответственности. Это создаёт устойчивый фундамент для следующих Build, в которых будут реализованы Runtime Events, Subscription Manager, Session и механизмы обеспечения надёжности соединения. Главный принцип миграции Dzentra остаётся неизменным: - архитектура развивается небольшими независимыми шагами; - каждый Build вводит только один новый уровень ответственности; - существующий торговый бот продолжает работать без необходимости полной одномоментной миграции на новую Runtime-подсистему.