38 KiB
Build 060.20.1 — Trade Stream State Ownership Refactoring
Engineering Migration Report
Контроль документа
| Свойство | Значение |
|---|---|
| Build | 060.20.1 |
| Название | Trade Stream State Ownership Refactoring |
| Статус | Completed |
| Проект | Dzentra |
| Подсистема | Market Data Acquisition |
| Компонент | Trades Feed (Time & Sales) |
| Версия | 1.0 |
Связанные документы
- Build 060.20.1 Architecture — архитектурная спецификация Build.
- Build 060.20 Engineering Migration Report — предыдущий Build.
Цель Build
Build 060.20 завершил формирование первой версии инфраструктурного слоя сопровождения состояния подсистемы Trades Feed (Time & Sales).
В рамках предыдущего этапа было принято решение вынести долгоживущее состояние из TradeStreamConsistencyController в отдельную инфраструктурную подсистему Trade Runtime, представленную универсальным TradeRuntimeRegistry.
Данное решение стало важным промежуточным этапом развития архитектуры, поскольку впервые отделило бизнес-логику проверки непрерывности потока сделок от хранения сопровождаемого состояния.
Однако сразу после завершения Build 060.20 был проведён дополнительный архитектурный аудит, целью которого являлась проверка соответствия реализованной инфраструктуры общей архитектуре Dzentra.
В ходе аудита система анализировалась уже не изолированно в рамках подсистемы Trades Feed, а как часть полной архитектуры автоматической торговой системы, включающей:
- Market Data Acquisition;
- Data Validation & Normalization;
- Market Data Storage;
- Market Data Processing;
- Feature Engineering;
- Market State Estimation;
- Forecasting Models;
- Trading Decision;
- Risk Management;
- Execution Management System.
Такой аудит позволил определить фактическое место инфраструктурного состояния внутри всей архитектуры проекта.
Результаты анализа показали, что выбранная в Build 060.20 модель Runtime является избыточной относительно реально существующей ответственности компонентов.
Предпосылки
К моменту начала настоящего Build подсистема Trades Feed уже обладала полностью завершёнными базовыми компонентами.
В систему входили:
- каноническая модель
Trade; - REST Pipeline;
- Trade Recovery;
- Trade Stream Consistency;
- механизм восстановления пропусков;
- единая каноническая обработка сделок независимо от источника данных.
Кроме того, Build 060.20 внедрил отдельную инфраструктурную подсистему Runtime, предназначенную для сопровождения долгоживущего состояния.
Архитектура приобрела следующий вид.
Trade Recovery
│
▼
Trade Stream Consistency
│
▼
Trade Runtime
│
▼
TradeStreamState
Практическая эксплуатация данной модели показала, что бизнес-логика работает корректно.
Все тесты успешно проходили.
Однако возник более фундаментальный вопрос.
Кто действительно является владельцем инфраструктурного состояния Trade Stream?
Именно ответ на этот вопрос и стал основной причиной проведения дополнительного архитектурного аудита.
Причины проведения архитектурного аудита
После завершения Build 060.20 дальнейшее развитие проекта переходило к следующим этапам:
- Runtime Protocol Integration;
- Runtime Service Integration;
- Acquisition Integration;
- Reconnect & Runtime Recovery.
До начала этих работ было принято решение провести дополнительную архитектурную проверку.
Главной задачей аудита являлось не изменение поведения системы.
Напротив.
Требовалось определить, соответствует ли уже реализованная инфраструктура долгосрочной архитектуре Dzentra.
Особое внимание уделялось одному вопросу.
Какой компонент действительно должен владеть долгоживущим состоянием Trade Stream?
Именно от ответа на этот вопрос зависело дальнейшее развитие всей подсистемы получения рыночных данных.
В случае неверного выбора владельца состояния ошибки постепенно распространялись бы на последующие Build, затрагивая Runtime Integration, WebSocket Integration и Composition Root.
Поэтому было принято решение выполнить архитектурную коррекцию до начала следующих этапов разработки.
Основная задача Build
Настоящий Build не изменяет бизнес-логику подсистемы Trades Feed.
Не изменяются:
- алгоритмы проверки последовательности сделок;
- алгоритмы дедупликации;
- механизм восстановления истории;
- каноническая модель
Trade; - публичное поведение
TradeStreamConsistencyController; - публичное поведение
TradeRecoveryController.
Build полностью сосредоточен исключительно на одной архитектурной задаче.
Определить единственного владельца инфраструктурного состояния подсистемы Trade Stream.
После завершения Build архитектура должна удовлетворять следующим принципам:
- владелец состояния определяется предметной областью, а не уровнем инфраструктуры;
- каждый stateful-компонент самостоятельно владеет своим состоянием;
- инфраструктурное хранилище располагается внутри собственной подсистемы;
- Runtime приложения не становится владельцем бизнес-состояния;
- Composition Root продолжает отвечать исключительно за создание графа зависимостей.
Именно достижение этих целей составляет полный Scope настоящего Build.
Результаты архитектурного аудита
Архитектурный аудит проводился уже после полного завершения Build 060.20.
Его задачей являлась не проверка корректности работы алгоритмов.
Все алгоритмы уже были подтверждены модульными тестами.
Основной целью аудита являлось определение истинственного владельца инфраструктурного состояния подсистемы Trades Feed.
Для этого реализованная архитектура была рассмотрена не изолированно, а как часть полной архитектуры Dzentra.
Анализ выполнялся относительно общей цепочки обработки данных.
Exchange
│
▼
Market Data Acquisition
│
▼
Data Validation & Normalization
│
▼
Market Data Processing
│
▼
Feature Engineering
│
▼
Market State Estimation
│
▼
Forecasting Models
│
▼
Trading Decision
При рассмотрении данной архитектуры выяснилось, что состояние Trade Stream существует исключительно внутри первого модуля — Market Data Acquisition.
Никакая последующая подсистема не использует внутреннее состояние проверки последовательности сообщений.
Оно необходимо исключительно для обеспечения корректности поступающего потока сделок.
Следовательно, это состояние не является инфраструктурным состоянием приложения в целом.
Оно является внутренним состоянием одной конкретной подсистемы.
Именно это наблюдение стало ключевым результатом проведённого аудита.
Анализ ответственности Runtime
После завершения Build 060.20 архитектура выглядела следующим образом.
Trade Recovery
│
▼
Trade Stream Consistency
│
▼
Trade Runtime
│
▼
TradeStreamState
На первый взгляд подобная декомпозиция выглядела логичной.
Runtime действительно сопровождал долгоживущее состояние.
Однако дальнейший анализ показал наличие фундаментальной архитектурной проблемы.
Runtime ничего не знает о состоянии Trade Stream.
Он:
- не проверяет последовательность сообщений;
- не знает алгоритмов дедупликации;
- не знает правил восстановления;
- не знает структуры
TradeStreamState; - не принимает никаких решений относительно состояния.
Фактически Runtime выступал исключительно в роли словаря объектов.
Подобная ответственность сама по себе не образует самостоятельную архитектурную подсистему.
Она является лишь механизмом хранения объектов.
Следовательно, Runtime не обладает собственной предметной ответственностью.
Владение состоянием
Во время аудита был сформулирован основной архитектурный вопрос.
Кто принимает решения относительно жизненного цикла TradeStreamState?
Ответ оказался однозначным.
Только один компонент системы.
TradeStreamConsistencyController
Именно он:
- создаёт состояние;
- использует состояние;
- изменяет состояние;
- читает состояние;
- определяет момент возникновения нового состояния;
- определяет необходимость обращения к состоянию.
Никакой другой компонент системы этого не делает.
Следовательно, именно подсистема Trade Stream Consistency является единственным владельцем собственного состояния.
Runtime при этом владельцем состояния не является.
Он лишь временно хранил объект, полностью принадлежащий другой подсистеме.
С точки зрения Domain-Driven Design подобная ситуация означает нарушение принципа единственного владельца агрегата.
Определение владельца инфраструктурного состояния
После завершения анализа было сформулировано главное архитектурное решение настоящего Build.
Владельцем инфраструктурного состояния является не Runtime приложения, а сама подсистема Trade Stream Consistency.
Это означает следующее.
Состояние должно располагаться рядом с компонентом, который:
- полностью определяет его жизненный цикл;
- изменяет его;
- гарантирует его корректность;
- несёт ответственность за его согласованность.
Таким компонентом является исключительно TradeStreamConsistencyController.
Поэтому инфраструктурное хранилище должно быть частью той же самой подсистемы.
Именно это решение полностью соответствует принципу локального владения состоянием, используемому в профессиональных распределённых системах обработки рыночных данных.
Почему Trade Runtime был удалён
Важно понимать, что удаление Trade Runtime не означает отказ от идеи инфраструктурного слоя.
Наоборот.
Build 060.20 оказался необходимым промежуточным этапом развития архитектуры.
Именно благодаря выделению Runtime стало очевидно, что:
- бизнес-логика уже полностью отделена от хранения состояния;
- само состояние является самостоятельной инфраструктурной сущностью;
- оставалось лишь определить её истинственного владельца.
После проведения аудита стало понятно, что универсальный Runtime Registry является избыточной абстракцией.
Он не предоставляет собственной предметной функциональности.
Он лишь дублирует ответственность будущего специализированного хранилища.
Поэтому Build 060.20.1 удаляет универсальный Runtime Registry и заменяет его специализированным компонентом, принадлежащим подсистеме Consistency.
Это не изменение поведения системы.
Это уточнение архитектурных границ ответственности.
Новая модель владения состоянием
После завершения архитектурного аудита была утверждена новая схема сопровождения состояния Trade Stream.
Архитектура приобрела следующий вид.
Trade Recovery
│
▼
Trade Stream Consistency
│
▼
TradeStreamStateStore
│
▼
TradeStreamState
В данной модели отсутствуют лишние инфраструктурные уровни.
Каждый компонент обладает собственной предметной ответственностью.
Разделение ответственности компонентов
TradeStreamConsistencyController
Контроллер остаётся центральной точкой проверки непрерывности потока сделок.
Он отвечает за:
- получение состояния торгового инструмента;
- проверку поступающих сделок;
- обнаружение дубликатов;
- обнаружение нарушения последовательности;
- обнаружение конфликтующих сообщений.
При этом контроллер больше не владеет состоянием непосредственно.
Он работает исключительно через специализированное хранилище.
TradeStreamStateStore
Build 060.20.1 вводит новый инфраструктурный компонент.
TradeStreamStateStore
Данный компонент становится единственным владельцем объектов TradeStreamState.
Его ответственность строго ограничена хранением состояния.
Store:
- создаёт состояние при первом обращении;
- возвращает существующее состояние;
- удаляет состояние;
- очищает внутреннее хранилище;
- не содержит бизнес-логики проверки последовательности.
Store не знает:
- что такое пропуск сделок;
- что такое дедупликация;
- как работает Recovery;
- какие проверки выполняет Consistency Controller.
Подобное разделение полностью соответствует принципу Single Responsibility Principle.
TradeStreamState
Класс TradeStreamState остаётся неизменным.
Он продолжает отвечать исключительно за состояние одного торгового инструмента.
В частности, он хранит:
- последний обработанный Trade ID;
- окно обнаружения дубликатов;
- историю последних сделок;
- внутреннее состояние проверки последовательности.
Build 060.20.1 не изменяет алгоритмы данного класса.
Изменяется исключительно способ владения экземплярами.
Почему Store располагается внутри Consistency
Во время проектирования рассматривались два варианта размещения нового компонента.
Вариант 1
runtime/
trade_stream_state_store.py
Вариант 2
consistency/
trade_stream_state_store.py
После анализа был выбран второй вариант.
Причины данного решения следующие.
Во-первых.
Store хранит исключительно состояние подсистемы Consistency.
Во-вторых.
Никакая другая подсистема Acquisition данным состоянием не пользуется.
В-третьих.
При дальнейшем развитии проекта аналогичные специализированные Store могут появиться и в других подсистемах.
Например:
Quotes Feed
↓
QuoteStateStore
Order Book Feed
↓
OrderBookStateStore
Candles Feed
↓
CandleStateStore
Каждая подсистема будет самостоятельно владеть собственным состоянием.
Это значительно лучше соответствует принципу высокой связности (High Cohesion).
Отказ от универсального Registry
Build 060.20 использовал универсальный Registry.
Подобная архитектура выглядела следующим образом.
TradeStreamConsistencyController
│
▼
TradeRuntimeRegistry
│
▼
TradeStreamState
После проведения аудита данная схема была признана избыточной.
Причины отказа от Registry:
- отсутствовала собственная предметная ответственность;
- Registry не использовался другими подсистемами;
- Registry являлся универсальным контейнером без собственной бизнес-функции;
- существование отдельного Runtime создавало ложное впечатление владения состоянием приложения.
Поэтому Registry был полностью удалён.
Его функции были заменены специализированным TradeStreamStateStore.
Изменения публичного API
Архитектурная коррекция практически не изменила внешний контракт подсистемы.
Основным изменением стала замена зависимости.
До Build 060.20.1:
TradeStreamConsistencyController
↓
TradeRuntimeProtocol
После Build 060.20.1:
TradeStreamConsistencyController
↓
TradeStreamStateStoreProtocol
Для вызывающего кода логика проверки сделок полностью сохранилась.
Изменился исключительно способ получения состояния.
Это позволило провести миграцию без изменения поведения системы.
Изменённые файлы
В рамках Build 060.20.1 были изменены исключительно компоненты, относящиеся к сопровождению состояния подсистемы Trade Stream.
Бизнес-логика проверки последовательности сообщений при этом не изменялась.
Новые файлы
Добавлены новые специализированные компоненты хранения состояния.
src/market_data/acquisition/consistency/
trade_stream_state_store.py
trade_stream_state_store_protocol.py
trade_stream_state_store_exceptions.py
Данные файлы полностью заменяют прежнюю инфраструктурную реализацию Runtime Registry.
Изменённые файлы
Trade Stream Consistency Controller
src/market_data/acquisition/consistency/
trade_stream_consistency_controller.py
Изменения:
- внедрена зависимость
TradeStreamStateStoreProtocol; - удалена зависимость
TradeRuntimeProtocol; - получение состояния выполняется через Store;
- публичное поведение контроллера полностью сохранено.
Тесты Consistency Controller
tests/unit/market_data/acquisition/consistency/
test_trade_stream_consistency_controller.py
Изменения:
- заменены фикстуры Runtime Registry;
- внедрён
TradeStreamStateStore; - обновлены проверки хранения состояния;
- подтверждено отсутствие изменения бизнес-поведения.
Удалённые файлы
После завершения миграции универсальная Runtime-подсистема больше не использовалась.
Поэтому она была полностью удалена.
Удалены следующие файлы.
src/market_data/acquisition/runtime/trade/
trade_runtime_registry.py
trade_runtime_protocol.py
trade_runtime_exceptions.py
Одновременно были удалены соответствующие модульные тесты.
tests/unit/market_data/acquisition/runtime/trade/
test_trade_runtime_registry.py
После удаления выполнена полная проверка проекта.
Ни одного использования Runtime Registry в кодовой базе больше не осталось.
Дополнительная архитектурная корректировка
Во время проведения миграции была обнаружена ещё одна преждевременная архитектурная абстракция.
Первоначальная версия TradeStreamStateStore содержала метод
register()
а также исключение
TradeStreamStateAlreadyExistsError
Подобный интерфейс был унаследован от удалённого Runtime Registry.
После дополнительного анализа было установлено, что специализированное хранилище состояния не является Registry.
Следовательно, операция регистрации объектов ему не требуется.
В результате были выполнены следующие изменения.
Удалены:
- метод
register(); - исключение
TradeStreamStateAlreadyExistsError; - вся связанная документация.
После этого публичный контракт Store был существенно упрощён.
Итоговый контракт TradeStreamStateStore
После завершения Build публичный интерфейс хранилища состоит исключительно из операций сопровождения состояния.
get_or_create()
↓
get()
↓
contains()
↓
remove()
↓
clear()
Подобный контракт соответствует типичной архитектуре специализированных State Store, используемых в высоконагруженных системах обработки потоковых данных.
Изменения модульных тестов
Build 060.20.1 существенно усилил тестовое покрытие новой архитектуры.
Помимо обновления существующих тестов был создан отдельный набор тестов для самого хранилища состояния.
Добавлен новый файл.
tests/unit/market_data/acquisition/consistency/
test_trade_stream_state_store.py
Покрыты следующие сценарии.
- создание пустого Store;
- создание нового состояния;
- повторное получение существующего состояния;
- независимость состояний разных символов;
- получение существующего состояния;
- отсутствие состояния;
- удаление состояния;
- удаление отсутствующего состояния;
- очистка Store;
- идемпотентность очистки;
- совместимость с Protocol;
- отсутствие побочных эффектов между символами.
В результате новый инфраструктурный компонент получил собственное независимое тестовое покрытие.
Регрессионное тестирование
После завершения всех изменений была выполнена полная проверка подсистем, затронутых миграцией.
Успешно пройдены:
Trade Stream State
13 passed
Trade Stream Consistency
19 passed
Trade Stream State Store
12 passed
Trade Recovery
70 passed
Общий подтверждённый результат.
102 passed
Ни одного изменения поведения бизнес-логики обнаружено не было.
Все изменения ограничились исключительно архитектурой владения состоянием.
Подтверждённые архитектурные инварианты
По итогам Build 060.20.1 были окончательно закреплены архитектурные принципы сопровождения состояния подсистемы Trades Feed.
Данные инварианты считаются обязательными для всех последующих Build.
1. Владелец состояния определяется предметной областью
Инфраструктурное состояние принадлежит не уровню приложения и не Runtime.
Его владельцем является исключительно та подсистема, которая:
- создаёт состояние;
- изменяет его;
- принимает решения относительно его жизненного цикла.
В случае Trades Feed таким владельцем является подсистема Trade Stream Consistency.
2. Каждая подсистема владеет только собственным состоянием
Trade Stream Consistency не имеет доступа к состояниям других компонентов.
Аналогично другие подсистемы не должны использовать внутреннее состояние Trade Stream.
Таким образом обеспечивается независимость компонентов и отсутствие скрытых связей между ними.
3. Runtime приложения не является владельцем бизнес-состояния
Runtime отвечает исключительно за жизненный цикл компонентов приложения.
В его область ответственности могут входить:
- запуск сервисов;
- остановка сервисов;
- переподключение;
- планирование задач;
- контроль соединений;
- мониторинг.
Runtime не должен хранить внутреннее состояние отдельных предметных подсистем.
4. Специализированные State Store являются инфраструктурными компонентами своих подсистем
Каждая подсистема может иметь собственное специализированное хранилище состояния.
Например.
Trade Stream Consistency
↓
TradeStreamStateStore
В дальнейшем аналогичный подход может использоваться и для других потоков рыночных данных.
Например.
Quotes Feed
↓
QuoteStateStore
Order Book Feed
↓
OrderBookStateStore
Candles Feed
↓
CandleStateStore
При этом каждое хранилище остаётся частью своей предметной подсистемы.
5. Store не содержит бизнес-логики
TradeStreamStateStore отвечает исключительно за сопровождение жизненного цикла объектов состояния.
В нём отсутствуют:
- проверка последовательности;
- дедупликация;
- восстановление истории;
- обработка сообщений биржи;
- принятие каких-либо предметных решений.
Все подобные алгоритмы продолжают находиться в TradeStreamConsistencyController и TradeStreamState.
6. Composition Root остаётся единственной точкой композиции
Создание экземпляров компонентов производится только в Composition Root.
После Build 060.20.1 граф зависимостей становится проще.
TradeStreamConsistencyController
│
▼
TradeStreamStateStore
│
▼
TradeStreamState
При этом ни Store, ни State не знают о способе своего создания.
Что сознательно не входит в Scope Build
Настоящий Build не изменяет архитектуру остальных подсистем Acquisition.
В частности, вне Scope остаются:
- WebSocket Runtime;
- Supervisor;
- Scheduler;
- Reconnect;
- Heartbeat;
- Runtime Commands;
- Runtime Events;
- Transport Messages.
Также Build не изменяет:
- Trade Recovery;
- REST Pipeline;
- Canonical Trade Model;
- REST Adapter;
- WebSocket Adapter;
- обработчики сообщений;
- механизм восстановления пропусков.
Все перечисленные компоненты продолжают работать без изменений.
Заключение
Build 060.20.1 завершает архитектурную корректировку, начатую после Build 060.20.
Первоначальная реализация Runtime Registry успешно выполнила свою роль промежуточного этапа развития архитектуры.
Она позволила:
- отделить хранение состояния от бизнес-логики;
- подтвердить необходимость выделенного инфраструктурного слоя;
- провести полноценный архитектурный аудит;
- определить фактического владельца состояния.
По результатам аудита была сформирована окончательная модель сопровождения состояния Trade Stream.
Её ключевой принцип заключается в следующем.
Инфраструктурное состояние принадлежит не уровню Runtime, а предметной подсистеме, которая им управляет.
Для Trades Feed такой подсистемой является Trade Stream Consistency.
В результате архитектура стала проще, более локализованной и лучше соответствует общей декомпозиции Dzentra.
Итоги Build
В рамках Build 060.20.1 были выполнены следующие работы.
- проведён полный архитектурный аудит Build 060.20;
- определён единственный владелец инфраструктурного состояния;
- удалена преждевременная подсистема
TradeRuntimeRegistry; - внедрён специализированный
TradeStreamStateStore; - упрощён публичный контракт хранения состояния;
- удалены неиспользуемые абстракции (
register()иTradeStreamStateAlreadyExistsError); - обновлены существующие модульные тесты;
- создан полный набор тестов для
TradeStreamStateStore; - подтверждено отсутствие регрессий;
- подтверждена совместимость архитектуры с последующими Build 060.21–060.24.
Build 060.20.1 считается полностью завершённым.
Он фиксирует окончательную архитектуру владения инфраструктурным состоянием подсистемы Trades Feed (Time & Sales) и служит базой для перехода к следующему этапу — Build 060.21 — Runtime Protocol Integration.