From ad6631f7733157f8caa322c6af9a5e797ad76bce Mon Sep 17 00:00:00 2001 From: Sergey Date: Sat, 25 Jul 2026 10:41:01 +0300 Subject: [PATCH] Build 060.20_1: align Trade Stream state ownership --- .../trade_stream_consistency_controller.py | 43 +- .../consistency/trade_stream_state_store.py | 129 ++ .../trade_stream_state_store_exceptions.py | 28 + .../trade_stream_state_store_protocol.py | 107 ++ .../acquisition/runtime/trade/__init__.py | 0 .../runtime/trade/trade_runtime_exceptions.py | 43 - .../runtime/trade/trade_runtime_protocol.py | 85 - .../runtime/trade/trade_runtime_registry.py | 132 -- ...est_trade_stream_consistency_controller.py | 32 +- .../test_trade_stream_state_store.py | 143 ++ .../trade/test_trade_runtime_registry.py | 126 -- docs/migrations/build_060_20.md | 1403 ++++++++++++++++ docs/migrations/build_060_20_1.md | 1061 +++++++++++++ .../migrations/build_060_20_1_architecture.md | 1410 +++++++++++++++++ 14 files changed, 4311 insertions(+), 431 deletions(-) create mode 100644 app/src/market_data/acquisition/consistency/trade_stream_state_store.py create mode 100644 app/src/market_data/acquisition/consistency/trade_stream_state_store_exceptions.py create mode 100644 app/src/market_data/acquisition/consistency/trade_stream_state_store_protocol.py delete mode 100644 app/src/market_data/acquisition/runtime/trade/__init__.py delete mode 100644 app/src/market_data/acquisition/runtime/trade/trade_runtime_exceptions.py delete mode 100644 app/src/market_data/acquisition/runtime/trade/trade_runtime_protocol.py delete mode 100644 app/src/market_data/acquisition/runtime/trade/trade_runtime_registry.py create mode 100644 app/tests/unit/market_data/acquisition/consistency/test_trade_stream_state_store.py delete mode 100644 app/tests/unit/market_data/acquisition/runtime/trade/test_trade_runtime_registry.py create mode 100644 docs/migrations/build_060_20.md create mode 100644 docs/migrations/build_060_20_1.md create mode 100644 docs/migrations/build_060_20_1_architecture.md diff --git a/app/src/market_data/acquisition/consistency/trade_stream_consistency_controller.py b/app/src/market_data/acquisition/consistency/trade_stream_consistency_controller.py index 1a04ce4..ce6eb13 100644 --- a/app/src/market_data/acquisition/consistency/trade_stream_consistency_controller.py +++ b/app/src/market_data/acquisition/consistency/trade_stream_consistency_controller.py @@ -8,10 +8,10 @@ from src.market_data.acquisition.consistency.trade_stream_protocol import ( from src.market_data.acquisition.consistency.trade_stream_state import ( TradeStreamState, ) -from src.market_data.acquisition.models.trade import Trade -from src.market_data.acquisition.runtime.trade.trade_runtime_protocol import ( - TradeRuntimeProtocol, +from src.market_data.acquisition.consistency.trade_stream_state_store_protocol import ( + TradeStreamStateStoreProtocol, ) +from src.market_data.acquisition.models.trade import Trade class TradeStreamConsistencyController( @@ -20,18 +20,16 @@ class TradeStreamConsistencyController( """ Контроллер проверки согласованности Canonical Trade Stream. - Для каждого торгового инструмента поддерживается - независимое состояние проверки, которое хранится - в Trade Runtime Registry. + Для каждого торгового инструмента используется + специализированное хранилище состояний + Trade Stream Consistency. """ - _RUNTIME_NAMESPACE = "consistency" - def __init__( self, - runtime: TradeRuntimeProtocol, + state_store: TradeStreamStateStoreProtocol, ) -> None: - self._runtime = runtime + self._state_store = state_store def accept( self, @@ -45,24 +43,11 @@ class TradeStreamConsistencyController( self, symbol: str, ) -> TradeStreamState: - key = self._runtime_key(symbol) - - if self._runtime.is_registered(key): - return self._runtime.get(key) - - state = TradeStreamState(symbol=symbol) - self._runtime.register(key, state) - - return state - - @classmethod - def _runtime_key( - cls, - symbol: str, - ) -> str: - """ - Возвращает ключ Runtime Registry - для состояния проверки Trade Stream. """ + Возвращает состояние проверки Trade Stream + для указанного торгового инструмента. - return f"{cls._RUNTIME_NAMESPACE}:{symbol}" + При первом обращении состояние автоматически + создаётся специализированным хранилищем. + """ + return self._state_store.get_or_create(symbol) \ No newline at end of file diff --git a/app/src/market_data/acquisition/consistency/trade_stream_state_store.py b/app/src/market_data/acquisition/consistency/trade_stream_state_store.py new file mode 100644 index 0000000..2a9a91e --- /dev/null +++ b/app/src/market_data/acquisition/consistency/trade_stream_state_store.py @@ -0,0 +1,129 @@ +# app/src/market_data/acquisition/consistency/trade_stream_state_store.py + +from __future__ import annotations + +""" +Хранилище состояний Trade Stream Consistency. + +Build 060.20.1 переносит владение инфраструктурным состоянием +из общего Runtime Registry в специализированное хранилище +подсистемы проверки согласованности потока сделок. + +Хранилище отвечает исключительно за жизненный цикл объектов +TradeStreamState и не содержит логики проверки последовательности +или восстановления потока сделок. +""" + +from src.market_data.acquisition.consistency.trade_stream_state import ( + TradeStreamState, +) +from src.market_data.acquisition.consistency.trade_stream_state_store_exceptions import ( + TradeStreamStateNotFoundError, +) +from src.market_data.acquisition.consistency.trade_stream_state_store_protocol import ( + TradeStreamStateStoreProtocol, +) + + +class TradeStreamStateStore(TradeStreamStateStoreProtocol): + """ + Хранилище состояний Trade Stream. + + Для каждого торгового инструмента существует + единственный экземпляр TradeStreamState. + """ + + def __init__(self) -> None: + """ + Создаёт пустое хранилище состояний. + """ + self._states: dict[str, TradeStreamState] = {} + + def get_or_create( + self, + symbol: str, + ) -> TradeStreamState: + """ + Возвращает существующее состояние либо создаёт новое. + + Args: + symbol: + Идентификатор торгового инструмента. + + Returns: + Экземпляр TradeStreamState. + """ + state = self._states.get(symbol) + + if state is None: + state = TradeStreamState(symbol=symbol) + self._states[symbol] = state + + return state + + def get( + self, + symbol: str, + ) -> TradeStreamState: + """ + Возвращает существующее состояние. + + Args: + symbol: + Идентификатор торгового инструмента. + + Raises: + TradeStreamStateNotFoundError: + Если состояние отсутствует. + """ + try: + return self._states[symbol] + except KeyError as error: + raise TradeStreamStateNotFoundError( + f"Состояние Trade Stream для символа {symbol!r} не найдено." + ) from error + + def contains( + self, + symbol: str, + ) -> bool: + """ + Проверяет наличие состояния. + + Args: + symbol: + Идентификатор торгового инструмента. + + Returns: + True, если состояние существует. + """ + return symbol in self._states + + def remove( + self, + symbol: str, + ) -> None: + """ + Удаляет состояние. + + Args: + symbol: + Идентификатор торгового инструмента. + + Raises: + TradeStreamStateNotFoundError: + Если состояние отсутствует. + """ + if symbol not in self._states: + raise TradeStreamStateNotFoundError( + f"Состояние Trade Stream для символа " + f"{symbol!r} не найдено." + ) + + del self._states[symbol] + + def clear(self) -> None: + """ + Полностью очищает хранилище состояний. + """ + self._states.clear() \ No newline at end of file diff --git a/app/src/market_data/acquisition/consistency/trade_stream_state_store_exceptions.py b/app/src/market_data/acquisition/consistency/trade_stream_state_store_exceptions.py new file mode 100644 index 0000000..1d38917 --- /dev/null +++ b/app/src/market_data/acquisition/consistency/trade_stream_state_store_exceptions.py @@ -0,0 +1,28 @@ +# app/src/market_data/acquisition/consistency/trade_stream_state_store_exceptions.py + +from __future__ import annotations + +""" +Исключения хранилища состояний Trade Stream Consistency. + +Build 060.20.1 вводит специализированное хранилище +состояния проверки согласованности потока сделок. + +Исключения относятся исключительно к управлению +TradeStreamState и не описывают ошибки проверки +последовательности сделок. +""" + + +class TradeStreamStateStoreError(Exception): + """ + Базовое исключение хранилища состояний Trade Stream. + """ + + +class TradeStreamStateNotFoundError( + TradeStreamStateStoreError, +): + """ + Состояние торгового инструмента отсутствует. + """ \ No newline at end of file diff --git a/app/src/market_data/acquisition/consistency/trade_stream_state_store_protocol.py b/app/src/market_data/acquisition/consistency/trade_stream_state_store_protocol.py new file mode 100644 index 0000000..4d15cf0 --- /dev/null +++ b/app/src/market_data/acquisition/consistency/trade_stream_state_store_protocol.py @@ -0,0 +1,107 @@ +# app/src/market_data/acquisition/consistency/trade_stream_state_store_protocol.py + +from __future__ import annotations + +""" +Протокол хранилища состояния Trade Stream Consistency. + +Build 060.20.1 заменяет универсальный Trade Runtime Registry +специализированным типизированным хранилищем состояния +Canonical Trade Stream. + +TradeStreamStateStoreProtocol определяет минимальный контракт +доступа к состоянию проверки согласованности потока сделок. + +Протокол не описывает внутреннюю реализацию хранения +и не содержит бизнес-логики Trade Stream Consistency. +""" + +from typing import Protocol, runtime_checkable + +from src.market_data.acquisition.consistency.trade_stream_state import ( + TradeStreamState, +) + + +@runtime_checkable +class TradeStreamStateStoreProtocol(Protocol): + """ + Протокол хранилища состояний Trade Stream Consistency. + """ + + def get_or_create( + self, + symbol: str, + ) -> TradeStreamState: + """ + Возвращает существующее состояние торгового инструмента + либо создаёт новое состояние при первом обращении. + + Args: + symbol: + Идентификатор торгового инструмента. + + Returns: + Существующий либо созданный TradeStreamState. + """ + ... + + def get( + self, + symbol: str, + ) -> TradeStreamState: + """ + Возвращает существующее состояние торгового инструмента. + + Args: + symbol: + Идентификатор торгового инструмента. + + Returns: + Зарегистрированный TradeStreamState. + + Raises: + TradeStreamStateNotFoundError: + Если состояние торгового инструмента отсутствует. + """ + ... + + def contains( + self, + symbol: str, + ) -> bool: + """ + Проверяет наличие состояния торгового инструмента. + + Args: + symbol: + Идентификатор торгового инструмента. + + Returns: + True, если состояние существует, + иначе False. + """ + ... + + def remove( + self, + symbol: str, + ) -> None: + """ + Удаляет состояние торгового инструмента. + + Args: + symbol: + Идентификатор торгового инструмента. + + Raises: + TradeStreamStateNotFoundError: + Если состояние торгового инструмента отсутствует. + """ + ... + + def clear(self) -> None: + """ + Полностью очищает хранилище состояний. + """ + ... \ No newline at end of file diff --git a/app/src/market_data/acquisition/runtime/trade/__init__.py b/app/src/market_data/acquisition/runtime/trade/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/app/src/market_data/acquisition/runtime/trade/trade_runtime_exceptions.py b/app/src/market_data/acquisition/runtime/trade/trade_runtime_exceptions.py deleted file mode 100644 index 7d3841b..0000000 --- a/app/src/market_data/acquisition/runtime/trade/trade_runtime_exceptions.py +++ /dev/null @@ -1,43 +0,0 @@ -# app/src/market_data/acquisition/runtime/trade/trade_runtime_exceptions.py - -from __future__ import annotations - -""" -Инфраструктурные исключения Trade Runtime. - -Build 060.20 вводит инфраструктурный слой Trade Runtime. - -Данный модуль содержит только инфраструктурные исключения, -связанные с регистрацией и получением Runtime-компонентов. - -Бизнес-исключения отдельных Runtime-модулей (Recovery, -Stream Consistency и других) должны оставаться -внутри соответствующих подсистем. -""" - - -class TradeRuntimeError(Exception): - """ - Базовый класс для всех инфраструктурных исключений Trade Runtime. - """ - - -class RuntimeAlreadyRegisteredError(TradeRuntimeError): - """ - Вызывается при попытке повторной регистрации Runtime-компонента - под уже существующим ключом. - """ - - -class RuntimeNotRegisteredError(TradeRuntimeError): - """ - Вызывается при попытке получить Runtime-компонент, - который отсутствует в TradeRuntimeRegistry. - """ - - -class InvalidRuntimeComponentError(TradeRuntimeError): - """ - Вызывается при попытке зарегистрировать объект, - который не может являться Runtime-компонентом. - """ \ No newline at end of file diff --git a/app/src/market_data/acquisition/runtime/trade/trade_runtime_protocol.py b/app/src/market_data/acquisition/runtime/trade/trade_runtime_protocol.py deleted file mode 100644 index bfeab8c..0000000 --- a/app/src/market_data/acquisition/runtime/trade/trade_runtime_protocol.py +++ /dev/null @@ -1,85 +0,0 @@ -# app/src/market_data/acquisition/runtime/trade/trade_runtime_protocol.py - -from __future__ import annotations - -""" -Протокол Trade Runtime. - -Build 060.20 вводит единый Runtime Layer для управления -жизненным циклом компонентов, обеспечивающих обработку -биржевого потока сделок. - -TradeRuntimeProtocol определяет минимальный контракт, -которому должна соответствовать реализация Runtime Registry. - -Протокол не описывает внутреннюю реализацию хранения -компонентов и не содержит бизнес-логики. -""" - -from typing import Any, Protocol - - -class TradeRuntimeProtocol(Protocol): - """ - Протокол реестра Runtime-компонентов. - """ - - def register(self, key: str, component: Any) -> None: - """ - Регистрирует Runtime-компонент. - - Args: - key: - Уникальный идентификатор Runtime-компонента. - - component: - Экземпляр Runtime-компонента. - """ - ... - - def unregister(self, key: str) -> None: - """ - Удаляет Runtime-компонент из реестра. - - Args: - key: - Уникальный идентификатор Runtime-компонента. - """ - ... - - def get(self, key: str) -> Any: - """ - Возвращает зарегистрированный Runtime-компонент. - - Args: - key: - Уникальный идентификатор Runtime-компонента. - - Returns: - Зарегистрированный Runtime-компонент. - - Raises: - RuntimeNotRegisteredError: - Если компонент отсутствует в реестре. - """ - ... - - def is_registered(self, key: str) -> bool: - """ - Проверяет наличие Runtime-компонента в реестре. - - Args: - key: - Уникальный идентификатор Runtime-компонента. - - Returns: - True, если компонент зарегистрирован, - иначе False. - """ - ... - - def clear(self) -> None: - """ - Полностью очищает реестр Runtime-компонентов. - """ - ... \ No newline at end of file diff --git a/app/src/market_data/acquisition/runtime/trade/trade_runtime_registry.py b/app/src/market_data/acquisition/runtime/trade/trade_runtime_registry.py deleted file mode 100644 index 7f61f76..0000000 --- a/app/src/market_data/acquisition/runtime/trade/trade_runtime_registry.py +++ /dev/null @@ -1,132 +0,0 @@ -# app/src/market_data/acquisition/runtime/trade/trade_runtime_registry.py - -from __future__ import annotations - -""" -Реестр компонентов Trade Runtime. - -Build 060.20 вводит единый инфраструктурный реестр, -который владеет экземплярами Runtime-компонентов -и предоставляет их по уникальным строковым ключам. - -Реестр не управляет бизнес-состоянием компонентов -и не содержит логики Stream Consistency, Recovery -или других подсистем обработки потока сделок. -""" - -from typing import Any - -from src.market_data.acquisition.runtime.trade.trade_runtime_exceptions import ( - InvalidRuntimeComponentError, - RuntimeAlreadyRegisteredError, - RuntimeNotRegisteredError, -) -from src.market_data.acquisition.runtime.trade.trade_runtime_protocol import ( - TradeRuntimeProtocol, -) - - -class TradeRuntimeRegistry(TradeRuntimeProtocol): - """ - Реестр Runtime-компонентов потока сделок. - - Каждый компонент регистрируется под уникальным строковым ключом. - Повторная регистрация под тем же ключом запрещена. - """ - - def __init__(self) -> None: - """ - Создаёт пустой реестр Runtime-компонентов. - """ - self._components: dict[str, Any] = {} - - def register(self, key: str, component: Any) -> None: - """ - Регистрирует Runtime-компонент под уникальным ключом. - - Args: - key: - Уникальный идентификатор Runtime-компонента. - - component: - Экземпляр Runtime-компонента. - - Raises: - InvalidRuntimeComponentError: - Если вместо Runtime-компонента передан None. - - RuntimeAlreadyRegisteredError: - Если указанный ключ уже используется. - """ - if component is None: - raise InvalidRuntimeComponentError( - f"Runtime-компонент для ключа {key!r} не может быть None." - ) - - if key in self._components: - raise RuntimeAlreadyRegisteredError( - f"Runtime-компонент с ключом {key!r} уже зарегистрирован." - ) - - self._components[key] = component - - def unregister(self, key: str) -> None: - """ - Удаляет Runtime-компонент из реестра. - - Args: - key: - Уникальный идентификатор Runtime-компонента. - - Raises: - RuntimeNotRegisteredError: - Если компонент с указанным ключом отсутствует. - """ - if key not in self._components: - raise RuntimeNotRegisteredError( - f"Runtime-компонент с ключом {key!r} не зарегистрирован." - ) - - del self._components[key] - - def get(self, key: str) -> Any: - """ - Возвращает зарегистрированный Runtime-компонент. - - Args: - key: - Уникальный идентификатор Runtime-компонента. - - Returns: - Зарегистрированный Runtime-компонент. - - Raises: - RuntimeNotRegisteredError: - Если компонент с указанным ключом отсутствует. - """ - try: - return self._components[key] - except KeyError as error: - raise RuntimeNotRegisteredError( - f"Runtime-компонент с ключом {key!r} не зарегистрирован." - ) from error - - def is_registered(self, key: str) -> bool: - """ - Проверяет наличие Runtime-компонента в реестре. - - Args: - key: - Уникальный идентификатор Runtime-компонента. - - Returns: - True, если компонент зарегистрирован, - иначе False. - """ - return key in self._components - - def clear(self) -> None: - """ - Удаляет из реестра все зарегистрированные Runtime-компоненты. - """ - self._components.clear() \ No newline at end of file diff --git a/app/tests/unit/market_data/acquisition/consistency/test_trade_stream_consistency_controller.py b/app/tests/unit/market_data/acquisition/consistency/test_trade_stream_consistency_controller.py index 372ee5c..a138e5f 100644 --- a/app/tests/unit/market_data/acquisition/consistency/test_trade_stream_consistency_controller.py +++ b/app/tests/unit/market_data/acquisition/consistency/test_trade_stream_consistency_controller.py @@ -21,22 +21,22 @@ from src.market_data.acquisition.models.trade import ( Trade, TradeAggressorSide, ) -from src.market_data.acquisition.runtime.trade.trade_runtime_registry import ( - TradeRuntimeRegistry, +from src.market_data.acquisition.consistency.trade_stream_state_store import ( + TradeStreamStateStore, ) @pytest.fixture -def runtime() -> TradeRuntimeRegistry: - return TradeRuntimeRegistry() +def state_store() -> TradeStreamStateStore: + return TradeStreamStateStore() @pytest.fixture def controller( - runtime: TradeRuntimeRegistry, + state_store: TradeStreamStateStore, ) -> TradeStreamConsistencyController: return TradeStreamConsistencyController( - runtime=runtime, + state_store=state_store, ) @@ -68,32 +68,32 @@ def _trade( def test_creates_state_for_first_symbol( controller: TradeStreamConsistencyController, - runtime: TradeRuntimeRegistry, + state_store: TradeStreamStateStore, ) -> None: trade = _trade(symbol="BTCUSD") result = controller.accept(trade) assert result == trade - assert runtime.is_registered("consistency:BTCUSD") + assert state_store.contains("BTCUSD") def test_registers_trade_stream_state( controller: TradeStreamConsistencyController, - runtime: TradeRuntimeRegistry, + state_store: TradeStreamStateStore, ) -> None: controller.accept( _trade(symbol="BTCUSD"), ) - state = runtime.get("consistency:BTCUSD") + state = state_store.get("BTCUSD") assert isinstance(state, TradeStreamState) def test_reuses_state_for_same_symbol( controller: TradeStreamConsistencyController, - runtime: TradeRuntimeRegistry, + state_store: TradeStreamStateStore, ) -> None: first_trade = _trade( symbol="BTCUSD", @@ -105,10 +105,10 @@ def test_reuses_state_for_same_symbol( ) controller.accept(first_trade) - first_state = runtime.get("consistency:BTCUSD") + first_state = state_store.get("BTCUSD") result = controller.accept(second_trade) - second_state = runtime.get("consistency:BTCUSD") + second_state = state_store.get("BTCUSD") assert result == second_trade assert second_state is first_state @@ -116,7 +116,7 @@ def test_reuses_state_for_same_symbol( def test_keeps_independent_state_per_symbol( controller: TradeStreamConsistencyController, - runtime: TradeRuntimeRegistry, + state_store: TradeStreamStateStore, ) -> None: btc_trade = _trade( symbol="BTCUSD", @@ -130,8 +130,8 @@ def test_keeps_independent_state_per_symbol( btc_result = controller.accept(btc_trade) eth_result = controller.accept(eth_trade) - btc_state = runtime.get("consistency:BTCUSD") - eth_state = runtime.get("consistency:ETHUSD") + btc_state = state_store.get("BTCUSD") + eth_state = state_store.get("ETHUSD") assert btc_result == btc_trade assert eth_result == eth_trade diff --git a/app/tests/unit/market_data/acquisition/consistency/test_trade_stream_state_store.py b/app/tests/unit/market_data/acquisition/consistency/test_trade_stream_state_store.py new file mode 100644 index 0000000..5a6cb59 --- /dev/null +++ b/app/tests/unit/market_data/acquisition/consistency/test_trade_stream_state_store.py @@ -0,0 +1,143 @@ +# app/tests/unit/market_data/acquisition/consistency/test_trade_stream_state_store.py + +from __future__ import annotations + +import pytest + +from src.market_data.acquisition.consistency.trade_stream_state import ( + TradeStreamState, +) +from src.market_data.acquisition.consistency.trade_stream_state_store import ( + TradeStreamStateStore, +) +from src.market_data.acquisition.consistency.trade_stream_state_store_exceptions import ( + TradeStreamStateNotFoundError, +) +from src.market_data.acquisition.consistency.trade_stream_state_store_protocol import ( + TradeStreamStateStoreProtocol, +) + + +def test_store_implements_protocol() -> None: + store = TradeStreamStateStore() + + assert isinstance( + store, + TradeStreamStateStoreProtocol, + ) + + +def test_store_is_empty_after_creation() -> None: + store = TradeStreamStateStore() + + assert store.contains("BTCUSD") is False + + +def test_get_or_create_creates_trade_stream_state() -> None: + store = TradeStreamStateStore() + + state = store.get_or_create("BTCUSD") + + assert isinstance( + state, + TradeStreamState, + ) + assert state.symbol == "BTCUSD" + assert store.contains("BTCUSD") is True + + +def test_get_or_create_returns_same_instance_for_same_symbol() -> None: + store = TradeStreamStateStore() + + first_state = store.get_or_create("BTCUSD") + second_state = store.get_or_create("BTCUSD") + + assert second_state is first_state + + +def test_get_or_create_returns_independent_states_for_different_symbols() -> None: + store = TradeStreamStateStore() + + btc_state = store.get_or_create("BTCUSD") + eth_state = store.get_or_create("ETHUSD") + + assert btc_state is not eth_state + assert btc_state.symbol == "BTCUSD" + assert eth_state.symbol == "ETHUSD" + + +def test_get_returns_existing_state() -> None: + store = TradeStreamStateStore() + + created_state = store.get_or_create("BTCUSD") + returned_state = store.get("BTCUSD") + + assert returned_state is created_state + + +def test_get_raises_not_found_error_for_missing_symbol() -> None: + store = TradeStreamStateStore() + + with pytest.raises( + TradeStreamStateNotFoundError, + match="BTCUSD", + ): + store.get("BTCUSD") + + +def test_remove_deletes_existing_state() -> None: + store = TradeStreamStateStore() + store.get_or_create("BTCUSD") + + store.remove("BTCUSD") + + assert store.contains("BTCUSD") is False + + with pytest.raises( + TradeStreamStateNotFoundError, + match="BTCUSD", + ): + store.get("BTCUSD") + + +def test_remove_does_not_delete_other_symbol_state() -> None: + store = TradeStreamStateStore() + + store.get_or_create("BTCUSD") + eth_state = store.get_or_create("ETHUSD") + + store.remove("BTCUSD") + + assert store.contains("BTCUSD") is False + assert store.get("ETHUSD") is eth_state + + +def test_remove_raises_not_found_error_for_missing_symbol() -> None: + store = TradeStreamStateStore() + + with pytest.raises( + TradeStreamStateNotFoundError, + match="BTCUSD", + ): + store.remove("BTCUSD") + + +def test_clear_removes_all_states() -> None: + store = TradeStreamStateStore() + + store.get_or_create("BTCUSD") + store.get_or_create("ETHUSD") + + store.clear() + + assert store.contains("BTCUSD") is False + assert store.contains("ETHUSD") is False + + +def test_clear_is_idempotent_for_empty_store() -> None: + store = TradeStreamStateStore() + + store.clear() + store.clear() + + assert store.contains("BTCUSD") is False \ No newline at end of file diff --git a/app/tests/unit/market_data/acquisition/runtime/trade/test_trade_runtime_registry.py b/app/tests/unit/market_data/acquisition/runtime/trade/test_trade_runtime_registry.py deleted file mode 100644 index be3d9a4..0000000 --- a/app/tests/unit/market_data/acquisition/runtime/trade/test_trade_runtime_registry.py +++ /dev/null @@ -1,126 +0,0 @@ -# app/tests/unit/market_data/acquisition/runtime/trade/test_trade_runtime_registry.py - -from __future__ import annotations - -import pytest - -from src.market_data.acquisition.runtime.trade.trade_runtime_exceptions import ( - InvalidRuntimeComponentError, - RuntimeAlreadyRegisteredError, - RuntimeNotRegisteredError, -) -from src.market_data.acquisition.runtime.trade.trade_runtime_registry import ( - TradeRuntimeRegistry, -) - - -class DummyComponent: - """Тестовый Runtime-компонент.""" - - -@pytest.fixture -def component() -> DummyComponent: - """Создаёт тестовый Runtime-компонент.""" - return DummyComponent() - - -def test_registry_is_empty_after_creation() -> None: - registry = TradeRuntimeRegistry() - - assert registry.is_registered("component") is False - - -def test_is_registered_returns_false_for_unknown_key() -> None: - registry = TradeRuntimeRegistry() - - assert registry.is_registered("unknown") is False - - -def test_register_component(component: DummyComponent) -> None: - registry = TradeRuntimeRegistry() - - registry.register("component", component) - - assert registry.is_registered("component") is True - - -def test_get_returns_registered_component( - component: DummyComponent, -) -> None: - registry = TradeRuntimeRegistry() - - registry.register("component", component) - - assert registry.get("component") is component - - -def test_is_registered_returns_true_after_registration( - component: DummyComponent, -) -> None: - registry = TradeRuntimeRegistry() - - registry.register("component", component) - - assert registry.is_registered("component") is True - - -def test_register_none_raises_invalid_runtime_component_error() -> None: - registry = TradeRuntimeRegistry() - - with pytest.raises(InvalidRuntimeComponentError): - registry.register("component", None) - - -def test_register_duplicate_key_raises_runtime_already_registered_error( - component: DummyComponent, -) -> None: - registry = TradeRuntimeRegistry() - - registry.register("component", component) - - with pytest.raises(RuntimeAlreadyRegisteredError): - registry.register("component", DummyComponent()) - - -def test_unregister_component( - component: DummyComponent, -) -> None: - registry = TradeRuntimeRegistry() - - registry.register("component", component) - registry.unregister("component") - - assert registry.is_registered("component") is False - - with pytest.raises(RuntimeNotRegisteredError): - registry.get("component") - - -def test_unregister_unknown_component_raises_runtime_not_registered_error() -> None: - registry = TradeRuntimeRegistry() - - with pytest.raises(RuntimeNotRegisteredError): - registry.unregister("unknown") - - -def test_clear_removes_all_components() -> None: - registry = TradeRuntimeRegistry() - - registry.register("component_1", DummyComponent()) - registry.register("component_2", DummyComponent()) - registry.register("component_3", DummyComponent()) - - registry.clear() - - assert registry.is_registered("component_1") is False - assert registry.is_registered("component_2") is False - assert registry.is_registered("component_3") is False - - with pytest.raises(RuntimeNotRegisteredError): - registry.get("component_1") - - with pytest.raises(RuntimeNotRegisteredError): - registry.get("component_2") - - with pytest.raises(RuntimeNotRegisteredError): - registry.get("component_3") \ No newline at end of file diff --git a/docs/migrations/build_060_20.md b/docs/migrations/build_060_20.md new file mode 100644 index 0000000..5c4cbd7 --- /dev/null +++ b/docs/migrations/build_060_20.md @@ -0,0 +1,1403 @@ +# Build 060.20 — Trade Runtime + +**Engineering Migration Report** + +--- + +# Контроль документа + +| Свойство | Значение | +|----------|----------| +| Build | 060.20 | +| Название | Trade Runtime | +| Статус | Completed | +| Проект | Dzentra | +| Подсистема | Market Data Acquisition | +| Компонент | Trade Runtime | +| Версия | 1.0 | + +--- + +# Связанные документы + +- build_060_20_architecture.md — архитектурная спецификация Build. +- build_060_19.md — Engineering Migration Report предыдущего Build. + +--- + +# Цель Build + +Build 060.19 завершил построение самостоятельной подсистемы **Trade Recovery**, обеспечивающей безопасное восстановление исторических сделок посредством REST API. + +К началу настоящего Build подсистема Trades Feed уже обладала всеми основными функциональными компонентами обработки сделок. + +Система обеспечивала: + +- получение сделок через REST API; +- построение канонической модели `Trade`; +- проверку согласованности непрерывного потока; +- восстановление пропущенных участков истории; +- формирование единственного канонического Trade Stream. + +Несмотря на это, архитектура оставалась неполной. + +Все реализованные компоненты существовали как независимые сервисы, однако отсутствовала единая модель хранения их долгоживущего состояния. + +Особенно это касалось компонента + +```text +TradeStreamConsistencyController +``` + +который являлся первым по-настоящему stateful-компонентом новой архитектуры Acquisition Layer. + +Контроллер сопровождал состояние непрерывного потока сделок и не мог безопасно создаваться заново для каждой отдельной операции. + +Следовательно возникла необходимость определить инфраструктурный механизм хранения runtime-состояния, независимый от отдельных операций Recovery, REST или будущего WebSocket Feed. + +Главной задачей настоящего Build стало построение первого слоя **Trade Runtime**, предназначенного для хранения долгоживущего состояния подсистемы Trades Feed. + +После завершения Build система получает: + +- инфраструктурный контракт `TradeRuntimeProtocol`; +- универсальный `TradeRuntimeRegistry`; +- специализированную иерархию Runtime-исключений; +- интеграцию `TradeStreamConsistencyController` с Runtime; +- единый механизм хранения runtime-состояния; +- полноценное покрытие новой инфраструктуры unit-тестами. + +При этом Build принципиально не изменяет: + +- каноническую модель `Trade`; +- REST Pipeline; +- Recovery Pipeline; +- алгоритмы Stream Consistency; +- алгоритмы Recovery; +- Trades Feed; +- WebSocket Runtime; +- Runtime Orchestration; +- Composition Root. + +Все перечисленные задачи относятся к следующим этапам развития подсистемы Trades Feed. + +--- + +# Предпосылки + +К началу Build архитектура Acquisition Layer уже обеспечивала полный цикл получения и обработки сделок независимо от источника данных. + +Конвейер обработки выглядел следующим образом. + +```text +Transport Message + │ + ▼ +Schema Validation + │ + ▼ +Parser + │ + ▼ +Value Validation + │ + ▼ +Mapper + │ + ▼ +Trade + │ + ▼ +Trade Stream Consistency + │ + ▼ +Canonical Trade Stream +``` + +При восстановлении истории использовался аналогичный конвейер, завершающийся передачей сделок в тот же экземпляр Trade Stream Consistency. + +Таким образом к началу настоящего Build система уже обладала единым алгоритмом обработки сделок независимо от транспортного источника. + +Однако оставался нерешённым вопрос хранения накопленного состояния Trade Stream между отдельными вызовами компонентов системы. + +В существующей архитектуре отсутствовало понятие Runtime как самостоятельного инфраструктурного слоя. + +В результате долгоживущее состояние было неотделимо от жизненного цикла конкретных объектов. + +Подобная модель не могла стать фундаментом для последующей интеграции WebSocket Feed, Gap Detection и Runtime Orchestration. + +Именно эту архитектурную задачу решает Build 060.20. + +--- + +# Результаты архитектурного аудита + +Перед началом реализации Build был выполнен полный аудит существующей подсистемы **Market Data Acquisition**. + +Целью аудита являлась проверка соответствия фактической архитектуры решениям, зафиксированным в `build_060_20_architecture.md`, а также определение оптимальной модели хранения долгоживущего состояния подсистемы Trades Feed. + +Особое внимание уделялось компоненту + +```text +TradeStreamConsistencyController +``` + +поскольку именно он являлся первым компонентом Acquisition Layer, сохраняющим состояние между последовательными операциями обработки сделок. + +Первоначально предполагалось реализовать инфраструктурный Runtime как реестр долгоживущих сервисов подсистемы Trades Feed. + +Однако проведённый аудит показал, что подобная модель не соответствует фактическому устройству системы. + +В результате архитектура Runtime была существенно упрощена. + +--- + +## Анализ существующей модели состояния + +К началу настоящего Build единственным компонентом, действительно обладающим внутренним состоянием, являлся + +```text +TradeStreamConsistencyController +``` + +Внутри контроллера поддерживалась коллекция состояний отдельных торговых символов. + +Концептуально архитектура выглядела следующим образом. + +```text +TradeStreamConsistencyController + + │ + + ▼ + +_states + + │ + + ├── BTCUSDT + + ├── ETHUSDT + + ├── ... + +``` + +Каждый объект состояния содержал сведения, необходимые для сопровождения непрерывного потока сделок конкретного торгового инструмента. + +Например: + +- последнюю принятую сделку; +- информацию о порядке поступления; +- сведения, необходимые для дедупликации; +- информацию о состоянии последовательности. + +Таким образом состояние уже существовало, однако полностью принадлежало внутренней реализации одного конкретного контроллера. + +--- + +## Недостатки внутреннего хранения состояния + +Подобная архитектура успешно решала задачи Stream Consistency, однако обладала существенным ограничением. + +Состояние было неотделимо от конкретной реализации контроллера. + +Это приводило сразу к нескольким архитектурным последствиям. + +Во-первых, другие Runtime-компоненты не могли использовать единый механизм хранения собственного состояния. + +Во-вторых, каждый новый stateful-компонент неизбежно создавал бы собственное внутреннее хранилище. + +В-третьих, инфраструктурный слой Runtime фактически отсутствовал. + +Таким образом архитектура постепенно двигалась бы к появлению нескольких независимых механизмов хранения состояния внутри различных компонентов системы. + +Подобное решение противоречило принципам модульной архитектуры Dzentra. + +--- + +## Первоначальная модель Runtime Registry + +На этапе проектирования предполагалось, что Runtime будет реализован как реестр долгоживущих компонентов. + +Концептуально предполагалась следующая структура. + +```text +TradeRuntimeRegistry + +├── TradeStreamConsistencyController + +├── TradeRecoveryController + +└── будущие Runtime-модули +``` + +Подобная модель выглядела естественной с точки зрения жизненного цикла объектов. + +Однако аудит показал, что она создаёт ненужный уровень косвенности. + +Registry начинал хранить сами сервисы, хотя их создание и композиция уже относятся к ответственности Composition Root. + +В результате инфраструктурный компонент начинал пересекаться с механизмом внедрения зависимостей. + +--- + +## Переосмысление роли Runtime + +После завершения анализа было принято принципиально иное архитектурное решение. + +Runtime должен хранить не сервисы. + +Runtime должен хранить состояние. + +Именно состояние является долгоживущим объектом системы. + +Контроллеры лишь используют это состояние посредством публичного контракта Runtime. + +Таким образом Runtime перестаёт быть каталогом компонентов и превращается в инфраструктурный контейнер runtime-данных. + +--- + +## Новая модель Trade Runtime + +По результатам аудита было принято окончательное архитектурное решение. + +Trade Runtime становится универсальным хранилищем объектов состояния. + +Каждый Runtime-компонент самостоятельно определяет структуру собственного состояния и использует Runtime исключительно как инфраструктурный механизм его хранения. + +Концептуально Runtime приобретает следующий вид. + +```text +TradeRuntimeRegistry + + │ + + ├── consistency:BTCUSDT + + ├── consistency:ETHUSDT + + ├── consistency:... + + └── ... + +``` + +Registry больше не знает ничего о природе сохраняемых объектов. + +Он предоставляет только операции регистрации, получения и повторного использования runtime-данных. + +Подобное решение полностью отделяет инфраструктуру хранения состояния от бизнес-логики отдельных компонентов. + +--- + +## Универсальное пространство ключей Runtime + +Следующим результатом архитектурного аудита стало введение универсальной модели адресации объектов Runtime. + +Вместо хранения отдельных специализированных коллекций Runtime использует единое пространство строковых ключей. + +Например. + +```text +consistency:BTCUSDT + +consistency:ETHUSDT +``` + +В дальнейшем аналогичным образом могут появляться новые пространства. + +Например. + +```text +gap:BTCUSDT + +scheduler:BTCUSDT + +monitoring:BTCUSDT +``` + +Подобная модель делает Runtime полностью независимым от конкретных Runtime-модулей. + +Добавление нового типа состояния не требует изменения самого Registry. + +Достаточно определить собственное пространство ключей. + +--- + +## Интеграция Stream Consistency + +После определения новой модели Runtime был выполнен повторный анализ архитектуры Stream Consistency. + +Проверка показала, что контроллер не должен владеть собственным хранилищем состояний. + +Вместо внутренней коллекции + +```text +_states +``` + +контроллер получает зависимость + +```text +TradeRuntimeProtocol +``` + +и запрашивает необходимое состояние посредством Runtime. + +При отсутствии зарегистрированного состояния соответствующий объект создаётся автоматически и регистрируется внутри Runtime. + +Таким образом Stream Consistency полностью сохраняет собственную бизнес-логику, но перестаёт быть владельцем механизма хранения состояния. + +--- + +## Анализ интеграции Recovery + +Отдельной задачей архитектурного аудита стала проверка необходимости интеграции Runtime непосредственно с + +```text +TradeRecoveryController +``` + +Первоначальная архитектурная спецификация предполагала, что Recovery станет ещё одним Runtime-компонентом и будет напрямую использовать Runtime Registry. + +Однако после анализа существующей реализации выяснилось, что подобное изменение не приносит архитектурной пользы. + +Recovery не обладает собственным долгоживущим состоянием. + +Он представляет собой полностью stateless-сервис, координирующий выполнение Recovery Pipeline. + +Единственным компонентом, использующим runtime-состояние, остаётся Stream Consistency. + +Следовательно Recovery уже получает доступ к Runtime опосредованно через переданный экземпляр `TradeStreamConsistencyProtocol`. + +Дополнительная интеграция Runtime в Recovery была признана избыточной и сознательно исключена из Scope Build. + +--- + +## Отказ от Runtime-компонентов + +Одним из наиболее важных результатов архитектурного аудита стал пересмотр самого понятия Runtime-компонента. + +Первоначальная архитектурная спецификация рассматривала Runtime как совокупность долгоживущих сервисов. + +Предполагалось, что Runtime включает: + +```text +Trade Runtime + +├── Stream Consistency Module + +└── Recovery Module +``` + +Однако детальный анализ показал, что подобная модель смешивает два различных понятия. + +С одной стороны существуют сервисы, реализующие бизнес-логику. + +С другой стороны существует инфраструктурное состояние, которое эти сервисы используют. + +Сервисы сами по себе не требуют специального хранения. + +Они могут безопасно создаваться посредством Dependency Injection. + +Долгоживущим объектом является исключительно состояние. + +Поэтому было принято окончательное архитектурное решение. + +Trade Runtime не является каталогом сервисов. + +Trade Runtime является инфраструктурным контейнером runtime-состояния. + +Это решение существенно упростило архитектуру всей подсистемы Trades Feed. + +--- + +## Разделение ответственности + +После завершения архитектурного аудита были окончательно разделены три независимые области ответственности. + +### Runtime + +Runtime отвечает исключительно за хранение и предоставление объектов состояния. + +Runtime не знает: + +- какие алгоритмы используют состояние; +- какие контроллеры существуют; +- каким образом состояние будет изменяться. + +--- + +### Trade Stream Consistency + +`TradeStreamConsistencyController` отвечает исключительно за сопровождение непрерывного канонического потока сделок. + +Именно данный компонент: + +- принимает решения относительно порядка сделок; +- выполняет дедупликацию; +- обнаруживает конфликтующие повторы; +- обновляет состояние потока. + +При этом механизм хранения состояния полностью делегируется Runtime. + +--- + +### Trade Recovery + +`TradeRecoveryController` остаётся полностью stateless-компонентом. + +Recovery: + +- получает исторические сделки; +- использует существующий REST Pipeline; +- передаёт сделки в Stream Consistency; +- формирует результат восстановления. + +Recovery не хранит собственного состояния. + +Recovery не использует Runtime напрямую. + +Recovery не становится владельцем Runtime. + +--- + +# Архитектурное решение + +По результатам проведённого аудита было принято решение реализовать Trade Runtime как самостоятельный инфраструктурный слой хранения состояния. + +Runtime предоставляет универсальный контракт доступа к данным. + +Все алгоритмы обработки продолжают принадлежать специализированным сервисам. + +После завершения Build архитектура принимает следующий вид. + +```text + Trade Runtime + + │ + + ▼ + + TradeRuntimeRegistry + + │ + + consistency: + + │ + + ▼ + + TradeStreamState + + ▲ + + │ + +TradeStreamConsistencyController + + ▲ + + │ + + TradeRecoveryController +``` + +Таким образом Runtime перестаёт быть участником бизнес-конвейера обработки сделок. + +Он становится исключительно инфраструктурным слоем сопровождения состояния. + +Подобное решение обеспечивает слабую связанность компонентов, повторное использование общего состояния и полностью соответствует принципам модульной архитектуры Dzentra. + +--- + +# Почему Runtime не становится Composition Root + +Во время проектирования отдельно анализировался вопрос о возможности использовать Runtime как механизм создания и хранения сервисов. + +На первый взгляд подобный подход выглядел достаточно привлекательным. + +Runtime мог бы самостоятельно создавать необходимые контроллеры и предоставлять их другим компонентам системы. + +Однако подобная архитектура нарушала бы фундаментальный принцип разделения ответственности. + +Runtime отвечает за инфраструктурное хранение состояния. + +Composition Root отвечает за создание графа зависимостей приложения. + +Это две различные задачи. + +Попытка объединить их в одном компоненте неизбежно привела бы к смешению инфраструктуры хранения состояния и механизма Dependency Injection. + +Поэтому было принято решение полностью разделить эти уровни. + +Trade Runtime остаётся исключительно инфраструктурным контейнером runtime-состояния. + +Создание контроллеров будет реализовано позднее в отдельном Composition Root. + +--- + +# Новая подсистема Trade Runtime + +Главным результатом настоящего Build становится появление в составе **Market Data Acquisition** нового инфраструктурного уровня — **Trade Runtime**. + +До начала настоящего Build каждый stateful-компонент был вынужден самостоятельно хранить собственное состояние. + +После завершения Build всё долгоживущее состояние переносится в специализированный Runtime. + +Архитектура взаимодействия принимает следующий вид. + +```text +TradeStreamConsistencyController + + │ + + ▼ + +TradeRuntimeProtocol + + │ + + ▼ + +TradeRuntimeRegistry + + │ + + ▼ + +TradeStreamState +``` + +Появление данного уровня является принципиальным расширением архитектуры Acquisition Layer. + +Теперь любой будущий stateful-компонент сможет использовать единый механизм хранения собственного состояния без создания дополнительных внутренних коллекций. + +--- + +# Основные архитектурные принципы Runtime + +Настоящий Build закрепляет несколько новых архитектурных принципов. + +--- + +## Runtime хранит состояние + +Runtime не хранит сервисы. + +Runtime хранит исключительно объекты состояния. + +--- + +## Runtime не знает бизнес-логики + +Runtime не знает: + +- что такое сделка; +- что такое Recovery; +- что такое Consistency; +- каким образом используются зарегистрированные объекты. + +Runtime предоставляет только инфраструктурный механизм хранения. + +--- + +## Runtime масштабируется пространствами ключей + +Каждый Runtime-модуль самостоятельно определяет собственное пространство ключей. + +Например. + +```text +consistency: +``` + +Добавление нового Runtime-модуля не требует изменения Runtime Registry. + +--- + +## Runtime не зависит от транспорта + +REST, + +WebSocket, + +Replay, + +или любой будущий источник данных используют один и тот же механизм хранения состояния. + +Runtime полностью независим от способа получения сделок. + +--- + +## Runtime готов к дальнейшему развитию + +Появление новых Runtime-модулей больше не требует создания отдельных Registry. + +Любой компонент может использовать существующий Runtime посредством собственного пространства ключей. + +Именно эта модель рассматривается как целевая архитектура дальнейшего развития Runtime Layer. + +--- + +# TradeRuntimeProtocol + +Одной из целей настоящего Build являлось формирование полноценного инфраструктурного контракта новой подсистемы Runtime. + +До начала Build единый контракт хранения долгоживущего состояния отсутствовал. + +Каждый компонент самостоятельно определял способ хранения собственных данных. + +В рамках реализации был добавлен новый Protocol. + +```text +TradeRuntimeProtocol +``` + +Protocol определяет минимальный универсальный контракт доступа к Runtime. + +Контракт предоставляет операции: + +- регистрации объекта состояния; +- получения объекта состояния; +- проверки существования объекта состояния. + +При этом Protocol сознательно не определяет: + +- структуру хранимых объектов; +- тип состояния; +- бизнес-назначение состояния; +- жизненный цикл объектов; +- механизм их изменения. + +Все перечисленные детали относятся исключительно к компонентам, использующим Runtime. + +Благодаря подобному разделению любые последующие Runtime-модули смогут зависеть только от публичного контракта Runtime, а не от конкретной реализации Registry. + +Это полностью соответствует принципу **Dependency Inversion**, принятому в архитектуре Dzentra. + +--- + +# Почему Runtime использует универсальный Protocol + +Во время проектирования рассматривались различные варианты публичного интерфейса Runtime. + +В частности анализировались следующие подходы. + +Первый вариант предполагал создание специализированных методов. + +Например. + +```text +get_consistency_state() + +get_gap_detector() + +get_scheduler() +``` + +Подобный подход был признан ошибочным. + +Каждый новый Runtime-модуль требовал бы изменения самого Runtime Protocol. + +Это нарушало бы принцип открытости для расширения. + +После анализа было принято решение использовать единый универсальный контракт. + +Runtime предоставляет только базовые операции хранения объектов. + +Интерпретация этих объектов полностью принадлежит вызывающему компоненту. + +Благодаря этому Runtime остаётся независимым от последующего развития системы. + +--- + +# TradeRuntimeRegistry + +Центральным компонентом настоящего Build становится + +```text +TradeRuntimeRegistry +``` + +Именно он завершает построение новой инфраструктурной подсистемы Runtime. + +Следует подчеркнуть, что Registry не является контейнером сервисов. + +Он представляет собой исключительно инфраструктурное хранилище runtime-состояния. + +Registry не знает назначения объектов. + +Не анализирует их содержимое. + +Не изменяет зарегистрированные данные. + +Все перечисленные задачи принадлежат компонентам, использующим Runtime. + +--- + +## Архитектура Registry + +Конструкция Registry намеренно сделана максимально универсальной. + +Внутри Registry отсутствуют специализированные коллекции. + +Все объекты хранятся в едином пространстве Runtime. + +Концептуально структура выглядит следующим образом. + +```text +TradeRuntimeRegistry + + │ + + ├── consistency:BTCUSDT + + ├── consistency:ETHUSDT + + ├── gap:BTCUSDT + + ├── scheduler:BTCUSDT + + └── ... +``` + +Подобная модель позволяет использовать Registry независимо от количества Runtime-модулей. + +--- + +## Ответственность Registry + +Во время проектирования особое внимание уделялось разделению ответственности между Runtime и компонентами обработки данных. + +В результате Registry получил исключительно инфраструктурные обязанности. + +Он отвечает за: + +- регистрацию объектов Runtime; +- получение объектов Runtime; +- проверку существования объектов; +- обеспечение повторного использования зарегистрированных экземпляров. + +При этом Registry сознательно не реализует: + +- бизнес-логику; +- дедупликацию; +- сопровождение Trade Stream; +- Recovery; +- управление жизненным циклом приложения; +- Dependency Injection. + +Подобное разделение делает Runtime полностью независимым от остальных подсистем Acquisition Layer. + +--- + +# Stateless-архитектура Runtime + +Одним из важнейших архитектурных решений настоящего Build становится полный отказ Runtime от собственной бизнес-логики. + +После завершения Build Runtime не содержит: + +- алгоритмов обработки сделок; +- информации о транспортном источнике; +- информации о Recovery; +- информации о Stream Consistency; +- информации о канонической модели `Trade`. + +Registry представляет собой исключительно инфраструктурный контейнер данных. + +Все решения относительно изменения состояния принимаются исключительно компонентами, использующими Runtime. + +Подобное решение существенно упрощает сопровождение инфраструктурного слоя и позволяет безопасно расширять Runtime без изменения его внутренней реализации. + +--- + +# Интеграция Stream Consistency с Runtime + +После завершения реализации Build механизм сопровождения непрерывного Trade Stream получает новую модель хранения состояния. + +До настоящего Build контроллер содержал собственное внутреннее хранилище. + +Концептуально схема выглядела следующим образом. + +```text +TradeStreamConsistencyController + + │ + + ▼ + +_states + + │ + + ▼ + +TradeStreamState +``` + +После завершения Build внутреннее хранилище полностью исключается. + +Контроллер получает Runtime посредством Dependency Injection. + +Архитектура принимает следующий вид. + +```text +TradeStreamConsistencyController + + │ + + ▼ + +TradeRuntimeProtocol + + │ + + ▼ + +TradeRuntimeRegistry + + │ + + ▼ + +TradeStreamState +``` + +При отсутствии зарегистрированного состояния контроллер автоматически создаёт новый экземпляр `TradeStreamState` и регистрирует его внутри Runtime. + +После этого все последующие операции используют один и тот же объект состояния. + +Таким образом Build полностью сохраняет алгоритмы Stream Consistency, изменяя исключительно инфраструктурный механизм хранения данных. + +--- + +# Почему Recovery не изменяется + +Во время реализации отдельно анализировалась необходимость изменения + +```text +TradeRecoveryController +``` + +Проверка показала, что существующая архитектура Recovery уже полностью соответствует новым принципам Runtime. + +Recovery остаётся stateless-сервисом. + +Он не хранит собственного состояния. + +Он не создаёт экземпляры Stream Consistency. + +Он использует исключительно публичный контракт + +```text +TradeStreamConsistencyProtocol +``` + +через который автоматически получает доступ к Runtime. + +Таким образом после интеграции Stream Consistency с Runtime архитектура Recovery начинает использовать Runtime без каких-либо изменений собственного кода. + +Именно поэтому Build сознательно не вносит изменений в бизнес-логику Recovery. + +Это стало одним из важнейших результатов проведённого архитектурного аудита. + +--- + +# Атомарность Runtime + +Одним из фундаментальных требований настоящего Build являлось сохранение полной независимости Runtime от выполняемых бизнес-операций. + +Каждая операция обработки сделок должна рассматриваться как отдельная независимая транзакция. + +При этом Runtime обязан сохранять накопленное состояние независимо от количества выполненных операций. + +После завершения Build взаимодействие компонентов становится полностью детерминированным. + +Каждый вызов Stream Consistency проходит одну и ту же последовательность этапов. + +```text +Trade + + │ + + ▼ + +TradeStreamConsistencyController + + │ + + ▼ + +TradeRuntimeProtocol + + │ + + ▼ + +TradeRuntimeRegistry + + │ + + ▼ + +TradeStreamState +``` + +Каждый этап выполняет строго одну задачу. + +Ни один уровень не повторяет обязанности другого. + +Подобное разделение полностью соответствует принципу модульной архитектуры Dzentra. + +--- + +# Использование Runtime внутри Stream Consistency + +После получения очередной сделки контроллер последовательно выполняет несколько операций. + +Сначала определяется Runtime-ключ соответствующего торгового символа. + +Например. + +```text +consistency:BTCUSDT +``` + +После этого через Runtime выполняется поиск объекта состояния. + +Если объект уже существует, используется ранее накопленное состояние Trade Stream. + +Если объект отсутствует, создаётся новый экземпляр + +```text +TradeStreamState +``` + +который немедленно регистрируется внутри Runtime. + +Все дальнейшие проверки последовательности выполняются уже над этим объектом. + +Таким образом Runtime полностью инкапсулирует механизм хранения состояния, оставаясь полностью прозрачным для бизнес-логики контроллера. + +--- + +# Lazy Registration + +Следующим важным архитектурным результатом Build становится использование модели ленивой регистрации состояния. + +Во время проектирования отдельно рассматривались два различных подхода. + +Первый вариант предполагал предварительное создание объектов состояния для всех торговых символов. + +После анализа данный подход был отклонён. + +Он приводил бы к появлению большого количества неиспользуемых объектов. + +Кроме того, Runtime оказался бы зависимым от информации о доступных рынках. + +Поэтому было принято решение использовать модель Lazy Registration. + +Состояние создаётся только при первом обращении соответствующего Runtime-компонента. + +До этого момента Runtime не содержит никаких объектов данного типа. + +Подобная модель обеспечивает минимальное потребление памяти и полностью соответствует принципу создания объектов по требованию. + +--- + +# Runtime-ключи + +Во время реализации отдельно анализировался вопрос идентификации объектов Runtime. + +Первоначально рассматривалась возможность использования нескольких независимых внутренних коллекций. + +Например. + +```text +consistency_states + +gap_states + +scheduler_states +``` + +Однако подобная модель быстро привела бы к постоянному расширению самого Runtime Registry. + +Каждый новый Runtime-модуль потребовал бы изменения его внутренней реализации. + +Поэтому было принято решение использовать единое пространство строковых ключей. + +Например. + +```text +consistency:BTCUSDT + +consistency:ETHUSDT +``` + +В дальнейшем аналогичным образом смогут использоваться любые другие пространства. + +Подобная модель делает Runtime полностью открытым для расширения без изменения существующего кода Registry. + +--- + +# Runtime-исключения + +Следующим результатом Build становится появление специализированной иерархии инфраструктурных исключений Runtime. + +До настоящего Build подобные ошибки отсутствовали. + +В рамках реализации добавлены специализированные Runtime-исключения, отражающие нарушения инфраструктурных инвариантов Runtime Layer. + +В частности предусмотрены ошибки: + +- отсутствия зарегистрированного объекта; +- повторной регистрации несовместимого объекта; +- использования некорректного Runtime-ключа; +- нарушения правил доступа к Runtime. + +Все данные исключения относятся исключительно к инфраструктурному уровню. + +Они сознательно не используются для сигнализации ошибок бизнес-логики Stream Consistency или Recovery. + +Подобное разделение позволяет чётко различать инфраструктурные проблемы и ошибки обработки рыночных данных. + +--- + +# Производительность + +Во время проектирования новой подсистемы одним из обязательных требований являлось сохранение постоянной сложности операций Runtime. + +Registry не выполняет операций поиска по коллекциям объектов определённого типа. + +Все обращения осуществляются непосредственно по Runtime-ключу. + +Основные операции имеют следующую вычислительную сложность. + +| Операция | Средняя сложность | +|----------|-------------------| +| Проверка существования объекта | O(1) | +| Получение объекта Runtime | O(1) | +| Регистрация нового объекта | O(1) | +| Замена существующего объекта | O(1) | + +Таким образом внедрение Runtime не оказывает заметного влияния на производительность обработки Trade Stream. + +Все дополнительные инфраструктурные операции выполняются за постоянное время. + +--- + +# Изменённые файлы + +В рамках Build была создана новая инфраструктурная подсистема **Trade Runtime**. + +Все изменения были сознательно локализованы внутри нового каталога + +```text +src/market_data/acquisition/runtime/ +``` + +Подобное решение позволило полностью отделить Runtime от бизнес-компонентов Acquisition Layer. + +Алгоритмы обработки сделок не потребовали изменения собственной логики. + +Новая функциональность была встроена посредством внедрения нового инфраструктурного слоя. + +--- + +## Trade Runtime Protocol + +```text +src/market_data/acquisition/runtime/trade_runtime_protocol.py +``` + +Добавлен новый инфраструктурный Protocol. + +```text +TradeRuntimeProtocol +``` + +Protocol определяет универсальный публичный контракт доступа к Runtime. + +Все Runtime-компоненты взаимодействуют исключительно через данный контракт. + +Конкретная реализация Registry остаётся внутренней деталью Runtime. + +--- + +## Trade Runtime Registry + +```text +src/market_data/acquisition/runtime/trade_runtime_registry.py +``` + +Реализован центральный компонент новой подсистемы. + +Registry отвечает исключительно за регистрацию и предоставление объектов Runtime. + +Внутри Registry отсутствует какая-либо бизнес-логика обработки сделок. + +Все решения относительно изменения состояния принимаются исключительно компонентами, использующими Runtime. + +--- + +## Trade Runtime Exceptions + +```text +src/market_data/acquisition/runtime/trade_runtime_exceptions.py +``` + +Добавлена специализированная инфраструктурная иерархия исключений Runtime. + +Все новые исключения относятся исключительно к инфраструктурному уровню хранения состояния. + +Бизнес-компоненты Acquisition Layer продолжают использовать собственные доменные исключения. + +--- + +## Интеграция Stream Consistency + +Изменения в существующей подсистеме Stream Consistency были сознательно сведены к минимуму. + +Основная бизнес-логика обработки сделок осталась неизменной. + +Изменился исключительно механизм хранения состояния. + +До начала настоящего Build контроллер самостоятельно управлял внутренней коллекцией состояний. + +После завершения Build единственным владельцем долгоживущего состояния становится Runtime. + +Контроллер получает состояние через публичный контракт `TradeRuntimeProtocol`, после чего продолжает использовать его так же, как и ранее. + +Таким образом архитектура Stream Consistency была расширена без изменения алгоритмов обработки Trade Stream. + +--- + +## Отсутствие изменений в Recovery Pipeline + +Несмотря на первоначальные предположения, Build практически не затронул Recovery Pipeline. + +Во время архитектурного аудита было подтверждено, что Recovery полностью соответствует принципам stateless-сервисов. + +Recovery не хранит накопленное состояние. + +Recovery не создаёт Runtime-объекты. + +Recovery не управляет жизненным циклом Runtime. + +Единственной зависимостью Recovery остаётся интерфейс + +```text +TradeStreamConsistencyProtocol +``` + +через который автоматически используется Runtime. + +Это позволило сохранить существующую архитектуру Recovery без внесения дополнительных изменений. + +--- + +## Обратная совместимость + +Одним из обязательных требований Build являлось сохранение полной обратной совместимости существующей подсистемы Trades Feed. + +После внедрения Runtime не изменились: + +- формат канонической модели `Trade`; +- последовательность обработки Trade Pipeline; +- алгоритмы проверки непрерывности потока; +- алгоритмы дедупликации; +- механизм восстановления пропущенных сделок; +- формат результатов Recovery; +- публичные контракты компонентов, не связанных с Runtime. + +Таким образом Build представляет собой исключительно инфраструктурное расширение существующей архитектуры. + +--- + +# Unit-тестирование + +После завершения реализации новая подсистема Runtime была полностью покрыта unit-тестами. + +Основная цель тестирования заключалась не только в проверке отдельных методов Registry, но и в подтверждении архитектурных инвариантов новой Runtime-модели. + +Особое внимание уделялось следующим сценариям: + +- регистрация нового объекта Runtime; +- повторное получение зарегистрированного объекта; +- проверка существования Runtime-ключа; +- корректная работа с несколькими независимыми пространствами ключей; +- обработка инфраструктурных ошибок Runtime; +- интеграция Stream Consistency с Runtime Registry. + +Все тесты выполнялись независимо от компонентов REST Pipeline и Recovery Pipeline. + +Это подтверждает самостоятельность новой подсистемы Runtime. + +--- + +# Регрессионное тестирование + +После завершения интеграции Runtime был выполнен полный регрессионный аудит подсистемы Trades Feed. + +Проверка подтвердила сохранение поведения существующих компонентов. + +В частности было подтверждено: + +- корректная работа REST Pipeline; +- корректная работа Canonical Trade Model; +- сохранение алгоритмов Stream Consistency; +- корректная работа Recovery Pipeline; +- отсутствие изменений публичных контрактов существующих сервисов; +- отсутствие изменений в обработке Trade Sequence. + +Таким образом внедрение Runtime не повлияло на функциональное поведение существующей подсистемы. + +--- + +# Архитектурные результаты + +Build 060.20 завершает формирование первого инфраструктурного уровня Runtime внутри подсистемы Market Data Acquisition. + +Главными архитектурными результатами становятся: + +- появление самостоятельного Runtime Layer; +- введение универсального контракта `TradeRuntimeProtocol`; +- реализация универсального `TradeRuntimeRegistry`; +- перенос хранения состояния из бизнес-компонентов в инфраструктурный слой; +- разделение понятий сервиса и состояния; +- сохранение stateless-характера Recovery; +- интеграция Stream Consistency с Runtime без изменения алгоритмов обработки сделок. + +В результате архитектура Acquisition Layer становится значительно более модульной и масштабируемой. + +--- + +# Подтверждённые архитектурные инварианты + +После завершения Build были окончательно закреплены следующие архитектурные инварианты. + +### Runtime хранит только состояние + +Никакие сервисы не регистрируются внутри Runtime. + +Runtime является исключительно инфраструктурным контейнером объектов состояния. + +--- + +### Runtime не содержит бизнес-логики + +Registry не знает: + +- структуры сделок; +- алгоритмов обработки; +- механизмов Recovery; +- логики Stream Consistency. + +Все подобные решения принимаются исключительно специализированными сервисами. + +--- + +### Runtime использует единое пространство ключей + +Все Runtime-объекты идентифицируются строковыми ключами. + +Добавление новых Runtime-модулей не требует изменения реализации Registry. + +--- + +### Stream Consistency остаётся владельцем бизнес-логики + +Runtime не принимает решений относительно обработки сделок. + +Он лишь предоставляет контроллеру доступ к ранее зарегистрированному состоянию. + +--- + +### Recovery остаётся stateless + +Recovery не использует собственное Runtime-состояние. + +Взаимодействие с Runtime осуществляется исключительно через `TradeStreamConsistencyProtocol`. + +--- + +### Composition Root не входит в Scope Build + +Создание и связывание компонентов приложения не относится к задачам настоящего Build. + +Runtime не выполняет функции Dependency Injection. + +Реализация Composition Root переносится на один из последующих этапов развития архитектуры. + +--- + +# Заключение + +Build 060.20 завершает построение первого инфраструктурного слоя сопровождения состояния внутри подсистемы Trades Feed. + +Первоначальная архитектурная спецификация предполагала создание Runtime как реестра долгоживущих компонентов. + +Однако проведённый архитектурный аудит показал, что более правильной моделью является хранение не сервисов, а их состояния. + +В результате Runtime был переосмыслен как универсальный инфраструктурный контейнер runtime-данных. + +Это решение позволило: + +- полностью отделить хранение состояния от бизнес-логики; +- сохранить stateless-архитектуру сервисов; +- упростить интеграцию новых Runtime-компонентов; +- подготовить архитектуру к последующему появлению Gap Detection, WebSocket Runtime, Runtime Orchestration и Composition Root. + +Тем самым Build 060.20 завершает формирование базовой инфраструктуры Runtime и создаёт прочный фундамент для дальнейшего развития подсистемы **Trades Feed (Time & Sales)**. + +--- + +# Что не входит в Scope Build + +Настоящий Build сознательно ограничивается созданием инфраструктурного слоя Runtime. + +Следующие задачи не входят в область настоящего этапа и будут реализованы позднее. + +- Composition Root; +- Runtime Orchestration; +- WebSocket Runtime; +- Gap Detection Runtime; +- автоматическое управление жизненным циклом Runtime; +- очистка Runtime; +- сохранение Runtime между перезапусками приложения; +- потокобезопасная реализация Runtime Registry; +- распределённый Runtime. + +Отсутствие перечисленных компонентов является осознанным архитектурным решением и не рассматривается как незавершённость Build. + +--- + +# Итог Build + +После завершения Build 060.20 подсистема Trades Feed обладает следующими возможностями. + +✓ Runtime представлен самостоятельным инфраструктурным слоем. +✓ Все долгоживущие состояния отделены от бизнес-логики. +✓ Stream Consistency использует Runtime через публичный Protocol. +✓ Recovery сохраняет полностью stateless-архитектуру. +✓ Runtime готов к появлению новых Runtime-модулей без изменения собственной реализации. +✓ Архитектура полностью подготовлена к реализации Composition Root. + +Build считается полностью завершённым. + +--- + +# Архитектурное значение Build + +Несмотря на сравнительно небольшой объём изменений исходного кода, Build 060.20 является одним из ключевых архитектурных этапов развития подсистемы Trades Feed. + +Именно в рамках настоящего Build было окончательно разделено понятие сервиса и сопровождаемого им состояния. + +Это решение определяет дальнейшую архитектуру всех Runtime-компонентов Dzentra. + +Все последующие stateful-модули должны использовать Runtime исключительно как инфраструктурный слой хранения состояния и не создавать собственных внутренних механизмов хранения. + +Данный принцип считается базовым архитектурным инвариантом проекта. \ No newline at end of file diff --git a/docs/migrations/build_060_20_1.md b/docs/migrations/build_060_20_1.md new file mode 100644 index 0000000..9f6bbb7 --- /dev/null +++ b/docs/migrations/build_060_20_1.md @@ -0,0 +1,1061 @@ +# 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.md — архитектурная спецификация Build. +- build_060_20.md — 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, предназначенную для сопровождения долгоживущего состояния. + +Архитектура приобрела следующий вид. + +```text +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. + +Анализ выполнялся относительно общей цепочки обработки данных. + +```text +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 архитектура выглядела следующим образом. + +```text +Trade Recovery + + │ + + ▼ + +Trade Stream Consistency + + │ + + ▼ + +Trade Runtime + + │ + + ▼ + +TradeStreamState +``` + +На первый взгляд подобная декомпозиция выглядела логичной. + +Runtime действительно сопровождал долгоживущее состояние. + +Однако дальнейший анализ показал наличие фундаментальной архитектурной проблемы. + +Runtime ничего не знает о состоянии Trade Stream. + +Он: + +- не проверяет последовательность сообщений; +- не знает алгоритмов дедупликации; +- не знает правил восстановления; +- не знает структуры `TradeStreamState`; +- не принимает никаких решений относительно состояния. + +Фактически Runtime выступал исключительно в роли словаря объектов. + +Подобная ответственность сама по себе не образует самостоятельную архитектурную подсистему. + +Она является лишь механизмом хранения объектов. + +Следовательно, Runtime не обладает собственной предметной ответственностью. + +--- + +# Владение состоянием + +Во время аудита был сформулирован основной архитектурный вопрос. + +> Кто принимает решения относительно жизненного цикла TradeStreamState? + +Ответ оказался однозначным. + +Только один компонент системы. + +```text +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. + +Архитектура приобрела следующий вид. + +```text +Trade Recovery + + │ + + ▼ + +Trade Stream Consistency + + │ + + ▼ + +TradeStreamStateStore + + │ + + ▼ + +TradeStreamState +``` + +В данной модели отсутствуют лишние инфраструктурные уровни. + +Каждый компонент обладает собственной предметной ответственностью. + +--- + +# Разделение ответственности компонентов + +## TradeStreamConsistencyController + +Контроллер остаётся центральной точкой проверки непрерывности потока сделок. + +Он отвечает за: + +- получение состояния торгового инструмента; +- проверку поступающих сделок; +- обнаружение дубликатов; +- обнаружение нарушения последовательности; +- обнаружение конфликтующих сообщений. + +При этом контроллер больше не владеет состоянием непосредственно. + +Он работает исключительно через специализированное хранилище. + +--- + +## TradeStreamStateStore + +Build 060.20.1 вводит новый инфраструктурный компонент. + +```text +TradeStreamStateStore +``` + +Данный компонент становится единственным владельцем объектов `TradeStreamState`. + +Его ответственность строго ограничена хранением состояния. + +Store: + +- создаёт состояние при первом обращении; +- возвращает существующее состояние; +- удаляет состояние; +- очищает внутреннее хранилище; +- не содержит бизнес-логики проверки последовательности. + +Store не знает: + +- что такое пропуск сделок; +- что такое дедупликация; +- как работает Recovery; +- какие проверки выполняет Consistency Controller. + +Подобное разделение полностью соответствует принципу Single Responsibility Principle. + +--- + +## TradeStreamState + +Класс `TradeStreamState` остаётся неизменным. + +Он продолжает отвечать исключительно за состояние одного торгового инструмента. + +В частности, он хранит: + +- последний обработанный Trade ID; +- окно обнаружения дубликатов; +- историю последних сделок; +- внутреннее состояние проверки последовательности. + +Build 060.20.1 не изменяет алгоритмы данного класса. + +Изменяется исключительно способ владения экземплярами. + +--- + +# Почему Store располагается внутри Consistency + +Во время проектирования рассматривались два варианта размещения нового компонента. + +## Вариант 1 + +```text +runtime/ + + trade_stream_state_store.py +``` + +## Вариант 2 + +```text +consistency/ + + trade_stream_state_store.py +``` + +После анализа был выбран второй вариант. + +Причины данного решения следующие. + +Во-первых. + +Store хранит исключительно состояние подсистемы Consistency. + +Во-вторых. + +Никакая другая подсистема Acquisition данным состоянием не пользуется. + +В-третьих. + +При дальнейшем развитии проекта аналогичные специализированные Store могут появиться и в других подсистемах. + +Например: + +```text +Quotes Feed + +↓ + +QuoteStateStore +``` + +```text +Order Book Feed + +↓ + +OrderBookStateStore +``` + +```text +Candles Feed + +↓ + +CandleStateStore +``` + +Каждая подсистема будет самостоятельно владеть собственным состоянием. + +Это значительно лучше соответствует принципу высокой связности (High Cohesion). + +--- + +# Отказ от универсального Registry + +Build 060.20 использовал универсальный Registry. + +Подобная архитектура выглядела следующим образом. + +```text +TradeStreamConsistencyController + + │ + + ▼ + +TradeRuntimeRegistry + + │ + + ▼ + +TradeStreamState +``` + +После проведения аудита данная схема была признана избыточной. + +Причины отказа от Registry: + +- отсутствовала собственная предметная ответственность; +- Registry не использовался другими подсистемами; +- Registry являлся универсальным контейнером без собственной бизнес-функции; +- существование отдельного Runtime создавало ложное впечатление владения состоянием приложения. + +Поэтому Registry был полностью удалён. + +Его функции были заменены специализированным `TradeStreamStateStore`. + +--- + +# Изменения публичного API + +Архитектурная коррекция практически не изменила внешний контракт подсистемы. + +Основным изменением стала замена зависимости. + +До Build 060.20.1: + +```text +TradeStreamConsistencyController + +↓ + +TradeRuntimeProtocol +``` + +После Build 060.20.1: + +```text +TradeStreamConsistencyController + +↓ + +TradeStreamStateStoreProtocol +``` + +Для вызывающего кода логика проверки сделок полностью сохранилась. + +Изменился исключительно способ получения состояния. + +Это позволило провести миграцию без изменения поведения системы. + +--- + +# Изменённые файлы + +В рамках Build 060.20.1 были изменены исключительно компоненты, относящиеся к сопровождению состояния подсистемы Trade Stream. + +Бизнес-логика проверки последовательности сообщений при этом не изменялась. + +--- + +## Новые файлы + +Добавлены новые специализированные компоненты хранения состояния. + +```text +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 + +```text +src/market_data/acquisition/consistency/ + + trade_stream_consistency_controller.py +``` + +Изменения: + +- внедрена зависимость `TradeStreamStateStoreProtocol`; +- удалена зависимость `TradeRuntimeProtocol`; +- получение состояния выполняется через Store; +- публичное поведение контроллера полностью сохранено. + +--- + +### Тесты Consistency Controller + +```text +tests/unit/market_data/acquisition/consistency/ + + test_trade_stream_consistency_controller.py +``` + +Изменения: + +- заменены фикстуры Runtime Registry; +- внедрён `TradeStreamStateStore`; +- обновлены проверки хранения состояния; +- подтверждено отсутствие изменения бизнес-поведения. + +--- + +# Удалённые файлы + +После завершения миграции универсальная Runtime-подсистема больше не использовалась. + +Поэтому она была полностью удалена. + +Удалены следующие файлы. + +```text +src/market_data/acquisition/runtime/trade/ + + trade_runtime_registry.py + + trade_runtime_protocol.py + + trade_runtime_exceptions.py +``` + +Одновременно были удалены соответствующие модульные тесты. + +```text +tests/unit/market_data/acquisition/runtime/trade/ + + test_trade_runtime_registry.py +``` + +После удаления выполнена полная проверка проекта. + +Ни одного использования Runtime Registry в кодовой базе больше не осталось. + +--- + +# Дополнительная архитектурная корректировка + +Во время проведения миграции была обнаружена ещё одна преждевременная архитектурная абстракция. + +Первоначальная версия `TradeStreamStateStore` содержала метод + +```text +register() +``` + +а также исключение + +```text +TradeStreamStateAlreadyExistsError +``` + +Подобный интерфейс был унаследован от удалённого Runtime Registry. + +После дополнительного анализа было установлено, что специализированное хранилище состояния не является Registry. + +Следовательно, операция регистрации объектов ему не требуется. + +В результате были выполнены следующие изменения. + +Удалены: + +- метод `register()`; +- исключение `TradeStreamStateAlreadyExistsError`; +- вся связанная документация. + +После этого публичный контракт Store был существенно упрощён. + +--- + +# Итоговый контракт TradeStreamStateStore + +После завершения Build публичный интерфейс хранилища состоит исключительно из операций сопровождения состояния. + +```text +get_or_create() + +↓ + +get() + +↓ + +contains() + +↓ + +remove() + +↓ + +clear() +``` + +Подобный контракт соответствует типичной архитектуре специализированных State Store, используемых в высоконагруженных системах обработки потоковых данных. + +--- + +# Изменения модульных тестов + +Build 060.20.1 существенно усилил тестовое покрытие новой архитектуры. + +Помимо обновления существующих тестов был создан отдельный набор тестов для самого хранилища состояния. + +Добавлен новый файл. + +```text +tests/unit/market_data/acquisition/consistency/ + + test_trade_stream_state_store.py +``` + +Покрыты следующие сценарии. + +- создание пустого Store; +- создание нового состояния; +- повторное получение существующего состояния; +- независимость состояний разных символов; +- получение существующего состояния; +- отсутствие состояния; +- удаление состояния; +- удаление отсутствующего состояния; +- очистка Store; +- идемпотентность очистки; +- совместимость с Protocol; +- отсутствие побочных эффектов между символами. + +В результате новый инфраструктурный компонент получил собственное независимое тестовое покрытие. + +--- + +# Регрессионное тестирование + +После завершения всех изменений была выполнена полная проверка подсистем, затронутых миграцией. + +Успешно пройдены: + +```text +Trade Stream State + +13 passed +``` + +```text +Trade Stream Consistency + +19 passed +``` + +```text +Trade Stream State Store + +12 passed +``` + +```text +Trade Recovery + +70 passed +``` + +Общий подтверждённый результат. + +```text +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 являются инфраструктурными компонентами своих подсистем + +Каждая подсистема может иметь собственное специализированное хранилище состояния. + +Например. + +```text +Trade Stream Consistency + +↓ + +TradeStreamStateStore +``` + +В дальнейшем аналогичный подход может использоваться и для других потоков рыночных данных. + +Например. + +```text +Quotes Feed + +↓ + +QuoteStateStore +``` + +```text +Order Book Feed + +↓ + +OrderBookStateStore +``` + +```text +Candles Feed + +↓ + +CandleStateStore +``` + +При этом каждое хранилище остаётся частью своей предметной подсистемы. + +--- + +## 5. Store не содержит бизнес-логики + +`TradeStreamStateStore` отвечает исключительно за сопровождение жизненного цикла объектов состояния. + +В нём отсутствуют: + +- проверка последовательности; +- дедупликация; +- восстановление истории; +- обработка сообщений биржи; +- принятие каких-либо предметных решений. + +Все подобные алгоритмы продолжают находиться в `TradeStreamConsistencyController` и `TradeStreamState`. + +--- + +## 6. Composition Root остаётся единственной точкой композиции + +Создание экземпляров компонентов производится только в Composition Root. + +После Build 060.20.1 граф зависимостей становится проще. + +```text +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**. + diff --git a/docs/migrations/build_060_20_1_architecture.md b/docs/migrations/build_060_20_1_architecture.md new file mode 100644 index 0000000..964744c --- /dev/null +++ b/docs/migrations/build_060_20_1_architecture.md @@ -0,0 +1,1410 @@ +# Build 060.20.1 — Trade Stream State Ownership Alignment + +**Статус:** Architecture Specification +**Build:** 060.20.1 +**Ветка:** Trades Feed (Time & Sales) +**Документ:** `build_060_20_1_architecture.md` +**Тип Build:** Corrective Architecture Build +**Предыдущее рабочее название:** Trade Runtime Terminology Alignment + +**Связанные документы:** + +- `build_060_20_architecture.md`; +- `build_060_20.md`; +- `build_060_19_architecture.md`; +- `build_060_18_architecture.md`. + +--- + +# Назначение документа + +Настоящий документ является официальной архитектурной спецификацией Build **060.20.1 — Trade Stream State Ownership Alignment**. + +Документ определяет: + +- фактическую природу состояния подсистемы Trades Feed; +- владельца инфраструктурного состояния Trade Stream; +- границу между Trade Stream Consistency и Acquisition Runtime; +- причины отказа от `TradeRuntimeRegistry`; +- целевую модель `TradeStreamStateStore`; +- публичные контракты; +- файловый план миграции; +- ограничения Scope; +- стратегию тестирования; +- архитектурные инварианты; +- критерии завершения Build. + +Build 060.20.1 не отменяет функциональный результат Build 060.20. Он корректирует терминологию, расположение файлов, типизацию и модель владения уже реализованным состоянием. + +Документ является единственным источником истины для реализации Build 060.20.1. + +--- + +# Статус Build + +Build 060.20.1 выполняется непосредственно после Build 060.20 и до начала интеграционных этапов 060.21+. + +На момент начала Build в системе уже существуют: + +- каноническая модель `Trade`; +- Trades Feed; +- `TradeStreamConsistencyController`; +- `TradeStreamState`; +- `TradeRecoveryController`; +- REST Recovery Pipeline; +- `TradeRuntimeProtocol`; +- `TradeRuntimeRegistry`; +- Runtime-исключения; +- unit-тесты Runtime Registry; +- unit-тесты Trade Stream Consistency; +- unit-тесты Trade Stream State. + +Функциональная проблема отсутствует. Система уже сохраняет состояние потока между последовательными операциями. + +Проблема носит архитектурный характер: + +> фактическая реализация Build 060.20 не соответствует терминам, границам и модели владения, зафиксированным в первоначальной Architecture Specification. + +--- + +# Причина появления корректирующего Build + +Первоначальная спецификация Build 060.20 описывала Trade Runtime как подсистему долгоживущих сервисов: + +```text +TradeRuntimeRegistry +│ +├── TradeStreamConsistencyController +└── TradeRecoveryController +``` + +Предполагалось, что Registry будет регистрировать, хранить и предоставлять Runtime-компоненты. + +Фактическая реализация пошла по другой модели. + +`TradeRuntimeRegistry` не хранит: + +- `TradeStreamConsistencyController`; +- `TradeRecoveryController`; +- Feed; +- Handler; +- адаптеры; +- Supervisor; +- Reconnect; +- Scheduler; +- Heartbeat. + +Фактически он хранит объекты состояния по строковым ключам: + +```text +consistency:BTCUSD + ↓ +TradeStreamState +``` + +Следовательно реализованный компонент является не Registry сервисов, а key-value хранилищем оперативного состояния. + +Это расхождение должно быть устранено до дальнейшей интеграции Trades Feed с настоящим Acquisition Runtime. + +--- + +# Предпосылки + +## Build 057 + +Сформирована базовая архитектура Market Data Acquisition. + +Каталог: + +```text +src/market_data/acquisition/runtime/ +``` + +предназначен для компонентов исполнения и жизненного цикла Acquisition: + +- supervisor; +- reconnect; +- scheduler; +- heartbeat; +- runtime commands; +- runtime events; +- transport messages; +- WebSocket protocol. + +--- + +## Build 060.1 + +Введена каноническая модель: + +```text +Trade +``` + +Источник данных не должен влиять на последующую обработку Trade Stream. + +--- + +## Build 060.17 + +Построен Trades Feed. + +Feed отвечает за получение и публикацию канонических сделок, но не должен владеть: + +- последовательностью; +- дедупликацией; +- Recovery; +- runtime-жизненным циклом; +- историческим хранилищем. + +--- + +## Build 060.18 + +Введены: + +```text +TradeStreamConsistencyController +TradeStreamState +``` + +`TradeStreamConsistencyController` отвечает за правила согласованности потока. + +`TradeStreamState` содержит состояние, необходимое для: + +- контроля порядка; +- дедупликации; +- обнаружения конфликтующих повторов; +- определения непрерывности; +- сохранения последней принятой позиции потока. + +Build 060.18 корректно определил бизнес-владельца состояния: + +```text +Trade Stream Consistency +``` + +--- + +## Build 060.19 + +Введён: + +```text +TradeRecoveryController +``` + +Recovery: + +- получает исторические сделки; +- передаёт их в Consistency; +- формирует результат восстановления. + +Recovery остаётся stateless и не владеет `TradeStreamState`. + +--- + +## Build 060.20 + +Build 060.20 обеспечил повторное использование долгоживущего состояния. + +Функциональный результат корректен: + +- состояние вынесено из локального жизненного цикла операции; +- состояние доступно через отдельный контракт; +- один объект повторно используется для одного символа; +- разные символы получают независимые состояния. + +Ошибка заключается в названии и размещении: + +```text +TradeRuntimeRegistry +src/market_data/acquisition/runtime/trade/ +``` + +Реализация получила слишком широкую абстракцию и была помещена в неверную архитектурную область. + +--- + +# Архитектурный контекст Dzentra + +Общий поток системы: + +```text +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 +``` + +`TradeStreamState` не является: + +- историей сделок; +- Trade Storage; +- Market Cache; +- рыночным фактом; +- признаком; +- оценкой состояния рынка; +- прогнозом; +- торговым решением; +- состоянием позиции; +- состоянием соединения. + +`TradeStreamState` является: + +> оперативным техническим состоянием алгоритма проверки согласованности канонического потока сделок. + +Поэтому состояние принадлежит не Market Data Storage и не Acquisition Runtime, а подсистеме Trade Stream Consistency. + +--- + +# Архитектурная проблема + +## 1. Неверный термин Runtime + +Runtime в Acquisition отвечает за: + +- запуск и остановку; +- supervision; +- reconnect; +- heartbeat; +- scheduling; +- runtime events; +- transport lifecycle. + +`TradeRuntimeRegistry` не выполняет ни одной из этих функций. + +--- + +## 2. Неверный термин Registry + +Registry обычно каталогизирует сервисы, адаптеры или реализации контрактов. + +Фактическая реализация хранит: + +```text +symbol → TradeStreamState +``` + +Это State Store. + +--- + +## 3. Универсальный `dict[str, Any]` + +Текущий контракт допускает: + +```text +str → Any +``` + +Это создаёт: + +- потерю типобезопасности; +- возможность регистрации несовместимого объекта; +- позднее обнаружение ошибки; +- скрытые соглашения о строковых ключах; +- риск превращения Store в Service Locator. + +--- + +## 4. Утечка namespace ключей + +Controller и его тесты знают строки: + +```text +consistency:BTCUSD +``` + +Это инфраструктурная деталь, которая не должна быть частью бизнес-логики Consistency. + +--- + +## 5. Размывание ownership + +Будущие состояния Consistency, Gap Detection, Recovery, Subscription и Monitoring могут иметь разные: + +- типы; +- инварианты; +- жизненные циклы; +- правила создания; +- правила очистки; +- требования к синхронизации. + +Общий `Any` Registry скрывает эти различия. + +--- + +## 6. Конфликт с фактической архитектурой Recovery + +`TradeRecoveryController`: + +- не имеет собственного состояния; +- не использует `TradeRuntimeProtocol`; +- не регистрируется в Registry; +- зависит только от `TradeStreamConsistencyProtocol`. + +Следовательно Recovery не является Runtime-компонентом в смысле Build 060.20. + +--- + +## 7. Конфликт документации и кода + +Первоначальная Architecture Specification описывает Registry сервисов. + +Engineering Migration Report и код фактически описывают Registry состояния. + +Build 060.20.1 фиксирует одну официальную модель. + +--- + +# Результаты архитектурного аудита + +Проверены: + +- Runtime Protocol, Registry и Exceptions; +- Consistency Controller, Protocol и State; +- Recovery Controller и Protocol; +- unit-тесты Runtime Registry; +- unit-тесты Consistency Controller; +- unit-тесты Trade Stream State; +- структура `market_data/acquisition`; +- результаты repository grep; +- место Trades Feed в общей архитектуре Dzentra. + +Единственным production-потребителем Runtime является: + +```text +TradeStreamConsistencyController +``` + +Не обнаружено production-использование со стороны: + +- Recovery; +- Feed; +- Handler; +- Adapter; +- Acquisition Service; +- Supervisor; +- Reconnect; +- Scheduler; +- Heartbeat; +- WebSocket Runtime. + +Фактическая модель: + +```text +TradeStreamConsistencyController + ↓ +State access abstraction + ↓ +Store by symbol + ↓ +TradeStreamState +``` + +Корректная архитектурная абстракция: + +```text +TradeStreamStateStore +``` + +--- + +# Основное архитектурное решение + +Build 060.20.1 заменяет: + +```text +TradeRuntimeRegistry +``` + +на: + +```text +TradeStreamStateStore +``` + +Целевая модель: + +```text +TradeStreamConsistencyController + ↓ +TradeStreamStateStoreProtocol + ↓ +TradeStreamStateStore + ↓ +TradeStreamState +``` + +`TradeStreamStateStore` является внутренней инфраструктурной зависимостью подсистемы Trade Stream Consistency. + +Он не является: + +- Acquisition Runtime; +- Composition Root; +- Service Registry; +- Market Data Storage; +- Trade Storage; +- глобальным State Registry; +- Recovery Registry. + +--- + +# Модель владения + +## Семантическое владение + +Владелец: + +```text +Trade Stream Consistency +``` + +`TradeStreamConsistencyController` и `TradeStreamState` определяют: + +- смысл состояния; +- правила его изменения; +- правила дедупликации; +- правила последовательности; +- реакцию на конфликты и разрывы. + +--- + +## Инфраструктурное хранение + +Владелец: + +```text +TradeStreamStateStore +``` + +Store отвечает за: + +- хранение `TradeStreamState`; +- адресацию по символу; +- lazy creation; +- повторное предоставление того же экземпляра; +- удаление состояния; +- очистку состояний. + +Store не принимает решений о корректности сделки. + +--- + +## Жизненный цикл компонентов + +Владелец: + +```text +Acquisition Composition +``` + +Будущая точка композиции должна: + +- создать `TradeStreamStateStore`; +- создать `TradeStreamConsistencyController`; +- передать Store в Controller; +- передать Controller потребителям. + +Store не создаёт Controller. + +Controller не создаёт глобальный Runtime. + +Recovery не создаёт Store. + +--- + +## Исполнение Acquisition + +Владелец: + +```text +src/market_data/acquisition/runtime/ +``` + +Acquisition Runtime отвечает за lifecycle, supervision, reconnect, heartbeat и scheduling. + +State Store Consistency к этому уровню не относится. + +--- + +# Архитектурные принципы + +## 1. State Ownership Is Local + +Каждая stateful-подсистема владеет собственным типом состояния. + +## 2. Store Is Typed + +Store хранит только: + +```text +TradeStreamState +``` + +Использование `Any` запрещается. + +## 3. Symbol Is the Public Key + +Публичный контракт использует: + +```text +symbol: str +``` + +## 4. Namespace Is Internal + +`consistency:` не является публичным API. + +## 5. Lazy State Creation + +State создаётся при первом обращении. + +## 6. One Symbol — One State + +Один символ связан с одним экземпляром `TradeStreamState`. + +## 7. Symbol Isolation + +Разные символы имеют независимые состояния. + +## 8. Store Has No Trade Logic + +Store не выполняет дедупликацию, sequence validation, Gap Detection или Recovery. + +## 9. Controller Has No Storage Details + +Controller не формирует ключи и не выполняет ручную регистрацию. + +## 10. Recovery Remains Stateless + +Recovery не получает прямую зависимость от Store. + +## 11. Runtime Means Execution Lifecycle + +Термин Runtime резервируется для исполнения и жизненного цикла Acquisition. + +## 12. No Premature Global State Store + +Build не создаёт `TradeStateStore`, `TradeRuntimeStateStore` или `GlobalAcquisitionStateStore`. + +## 13. Unique File Naming + +Новые файлы: + +```text +trade_stream_state_store.py +trade_stream_state_store_protocol.py +trade_stream_state_store_exceptions.py +``` + +--- + +# TradeStreamStateStore + +## Назначение + +Типизированное in-memory хранилище состояния Trade Stream Consistency. + +## Ответственность + +- lazy creation; +- получение существующего State; +- проверка наличия; +- удаление по символу; +- очистка; +- проверка корректности символа; +- инвариант `str → TradeStreamState`. + +## Не отвечает + +- за обработку `Trade`; +- дедупликацию; +- sequence validation; +- Gap Detection; +- Recovery; +- транспорт; +- подписки; +- persistence; +- thread safety; +- Composition Root. + +--- + +# TradeStreamStateStoreProtocol + +Целевой контракт: + +```python +class TradeStreamStateStoreProtocol(Protocol): + def get_or_create(self, symbol: str) -> TradeStreamState: + ... + + def get(self, symbol: str) -> TradeStreamState: + ... + + def contains(self, symbol: str) -> bool: + ... + + def remove(self, symbol: str) -> None: + ... + + def clear(self) -> None: + ... +``` + +Главный метод: + +```text +get_or_create(symbol) +``` + +Он инкапсулирует сценарий: + +```text +получить существующее состояние +или создать новое +``` + +Точный набор дополнительных методов будет подтверждён реализацией и тестами. Он не должен возвращать универсальную модель Registry. + +--- + +# Создание TradeStreamState + +В Build 060.20.1 Store создаёт стандартный `TradeStreamState` самостоятельно. + +Фабрика не вводится, поскольку отсутствуют подтверждённые требования к: + +- разным конфигурациям по инструментам; +- разным реализациям State; +- восстановлению из persistence; +- внешнему конструктору. + +--- + +# Исключения Store + +Предлагаемая минимальная иерархия: + +```text +TradeStreamStateStoreError +├── InvalidTradeStreamSymbolError +└── TradeStreamStateNotFoundError +``` + +Исключение повторной регистрации не является обязательным, поскольку основной контракт использует `get_or_create`, а не `register`. + +--- + +# Почему Market Data Storage не является владельцем + +Market Data Storage хранит: + +- канонические сделки; +- свечи; +- котировки; +- стакан; +- историю; +- рыночный cache. + +TradeStreamStateStore хранит: + +- техническую позицию алгоритма; +- данные дедупликации; +- последовательность обработки; +- внутреннее состояние Consistency. + +Persistence может появиться позднее, но это не переносит ownership в Storage. + +--- + +# Почему Acquisition Runtime не является владельцем + +Acquisition Runtime отвечает за: + +```text +start +stop +heartbeat +reconnect +schedule +supervise +runtime events +transport lifecycle +``` + +TradeStreamStateStore отвечает за: + +```text +symbol → TradeStreamState +``` + +Это разные области ответственности. + +--- + +# Почему Recovery не является владельцем + +Recovery не определяет семантику `TradeStreamState`, не изменяет его напрямую и не должен создавать Store. + +Recovery использует только: + +```text +TradeStreamConsistencyProtocol +``` + +--- + +# Почему не создаётся общий TradeStateStore + +Общий Store отклонён, потому что: + +1. отсутствует второй реальный тип состояния; +2. будущие состояния могут иметь разные lifecycle; +3. `Any` уничтожает типобезопасность; +4. string namespaces становятся скрытым API; +5. появляется риск Service Locator; +6. ownership становится неочевидным; +7. локальная специализированная модель точнее. + +--- + +# Будущие stateful-подсистемы + +Если Gap Detection получит отдельное состояние, может появиться: + +```text +TradeGapState +TradeGapStateStore +``` + +Если Recovery получит session state, cursor или retry state, может появиться: + +```text +TradeRecoveryStateStore +``` + +Состояние подписок относится к subscriptions либо общей Acquisition Runtime infrastructure. + +Monitoring относится к отдельному глобальному модулю Dzentra. + +Ни одно из этих состояний не хранится в `TradeStreamStateStore`. + +--- + +# Целевая структура каталогов + +```text +src/market_data/acquisition/ +│ +├── consistency/ +│ ├── trade_stream_consistency_controller.py +│ ├── trade_stream_exceptions.py +│ ├── trade_stream_protocol.py +│ ├── trade_stream_state.py +│ ├── trade_stream_state_store.py +│ ├── trade_stream_state_store_protocol.py +│ └── trade_stream_state_store_exceptions.py +│ +├── recovery/ +│ └── ... +│ +└── runtime/ + ├── supervisor.py + ├── reconnect.py + ├── scheduler.py + ├── heartbeat.py + ├── runtime_commands.py + ├── runtime_events.py + ├── transport_messages.py + └── websocket_protocol.py +``` + +Подкаталог: + +```text +runtime/trade/ +``` + +удаляется после подтверждения отсутствия зависимостей. + +--- + +# Целевая диаграмма взаимодействия + +```text +Trade Source + ↓ +Trade Handler + ↓ +Canonical Trade + ↓ +TradeStreamConsistencyController + ↓ +TradeStreamStateStoreProtocol + ↓ +TradeStreamStateStore + ↓ +TradeStreamState + ↓ +Accepted Canonical Trade Stream +``` + +Recovery использует ту же boundary: + +```text +TradeRecoveryController + ↓ +TradeStreamConsistencyProtocol + ↓ +TradeStreamConsistencyController + ↓ +TradeStreamStateStore +``` + +Recovery не знает о Store. + +--- + +# Dependency Injection + +```text +Composition Root + ├── TradeStreamStateStore + ├── TradeStreamConsistencyController + │ └── StateStoreProtocol + └── TradeRecoveryController + └── TradeStreamConsistencyProtocol +``` + +Окончательный Composition Root не входит в Scope Build. + +--- + +# Последовательность обработки сделки + +1. Controller получает `Trade`. +2. Controller вызывает `get_or_create(symbol)`. +3. Store валидирует символ. +4. Store возвращает существующий State либо создаёт новый. +5. Controller применяет существующую бизнес-логику. +6. Store сохраняет тот же экземпляр для следующих операций. + +--- + +# Persistence и Thread Safety + +Build использует: + +```text +in-memory only +``` + +Не реализуются: + +- persistence; +- replay state restoration; +- locks; +- async locks; +- distributed coordination. + +Эти требования относятся к отдельным Build. + +--- + +# План изменений файлов + +## Новые файлы + +```text +src/market_data/acquisition/consistency/ +├── trade_stream_state_store.py +├── trade_stream_state_store_protocol.py +└── trade_stream_state_store_exceptions.py +``` + +## Изменяемый production-файл + +```text +trade_stream_consistency_controller.py +``` + +Разрешены только локальные изменения: + +- новая зависимость Store Protocol; +- удаление namespace keys; +- переход на `get_or_create`; +- сохранение существующей бизнес-логики. + +## Удаляемые файлы + +```text +src/market_data/acquisition/runtime/trade/ +├── trade_runtime_registry.py +├── trade_runtime_protocol.py +└── trade_runtime_exceptions.py +``` + +## Неизменяемые production-файлы + +- `trade_stream_state.py`; +- `trade_stream_protocol.py`; +- `trade_stream_exceptions.py`; +- Recovery files; +- Feed; +- Handler; +- canonical `Trade`; +- Supervisor; +- Reconnect; +- Scheduler; +- Heartbeat. + +--- + +# План изменений тестов + +Новый файл: + +```text +tests/unit/market_data/acquisition/consistency/ +test_trade_stream_state_store.py +``` + +Он заменяет: + +```text +tests/unit/market_data/acquisition/runtime/trade/ +test_trade_runtime_registry.py +``` + +Обновляется: + +```text +test_trade_stream_consistency_controller.py +``` + +Сохраняется без архитектурных изменений: + +```text +test_trade_stream_state.py +``` + +--- + +# Таблица переименований + +| Текущая сущность | Целевая сущность | +|---|---| +| `TradeRuntimeRegistry` | `TradeStreamStateStore` | +| `TradeRuntimeProtocol` | `TradeStreamStateStoreProtocol` | +| `trade_runtime_registry.py` | `trade_stream_state_store.py` | +| `trade_runtime_protocol.py` | `trade_stream_state_store_protocol.py` | +| `trade_runtime_exceptions.py` | `trade_stream_state_store_exceptions.py` | +| `test_trade_runtime_registry.py` | `test_trade_stream_state_store.py` | +| `runtime` dependency | `state_store` dependency | +| `consistency:` | `symbol` | +| `dict[str, Any]` | `dict[str, TradeStreamState]` | +| `register + get` | `get_or_create` | + +--- + +# Совместимость + +Старые Runtime-имена не используются production-компонентами вне Consistency. + +Поэтому compatibility layer не создаётся. + +Не вводятся: + +- deprecated aliases; +- re-export старых имён; +- wrappers; +- proxy-классы. + +Поведенчески должны полностью сохраниться: + +- первая сделка; +- последовательная сделка; +- дубликат; +- конфликтующий дубликат; +- Gap; +- повторное использование State; +- независимость символов; +- Recovery; +- Trades Feed. + +--- + +# Архитектурные инварианты + +1. `TradeStreamState` принадлежит Consistency. +2. Store хранит только `TradeStreamState`. +3. Controller не зависит от `TradeRuntimeProtocol`. +4. Controller не формирует Runtime keys. +5. Store не содержит Trade business logic. +6. Recovery не зависит от Store напрямую. +7. Acquisition Runtime не хранит Consistency State. +8. Один символ возвращает один State. +9. Разные символы имеют независимые State. +10. Store не использует `Any`. +11. Глобальный State Registry не создаётся. +12. Алгоритмы `TradeStreamState` не изменяются. + +--- + +# Architectural Decision Records + +## ADR-060.20.1-01 — State Ownership + +`TradeStreamState` принадлежит Trade Stream Consistency. + +## ADR-060.20.1-02 — Specialized State Store + +Используется `TradeStreamStateStore`. Универсальный `TradeRuntimeRegistry` удаляется. + +## ADR-060.20.1-03 — Runtime Boundary + +Acquisition Runtime предназначен для execution lifecycle. State Store размещается в `consistency/`. + +## ADR-060.20.1-04 — Typed Storage + +Используется: + +```text +dict[str, TradeStreamState] +``` + +## ADR-060.20.1-05 — Symbol-Based Contract + +Публичная адресация выполняется по `symbol`. + +## ADR-060.20.1-06 — Lazy Creation + +State создаётся через `get_or_create(symbol)`. + +## ADR-060.20.1-07 — Recovery Independence + +Recovery не получает прямую зависимость от Store. + +## ADR-060.20.1-08 — No Global State Store + +Будущие stateful-подсистемы получают собственные Store при подтверждённой необходимости. + +## ADR-060.20.1-09 — No Compatibility Layer + +Старые Runtime-имена удаляются после обновления зависимостей. + +## ADR-060.20.1-10 — Business Logic Preservation + +Алгоритмы Consistency, State, Recovery, Feed и Handler сохраняются. + +--- + +# Стратегия тестирования + +## Unit Tests State Store + +Проверяются: + +- создание Store; +- отсутствие состояния до первого обращения; +- lazy creation; +- повторное получение того же экземпляра; +- независимость символов; +- `get`; +- `contains`; +- `remove`; +- `clear`; +- некорректный символ; +- отсутствие произвольных компонентов; +- хранение только `TradeStreamState`. + +## Unit Tests Consistency Controller + +Сохраняются все существующие сценарии. + +Дополнительно проверяется: + +- использование Store Protocol; +- отсутствие namespace в Controller; +- использование `get_or_create`; +- независимость от конкретной реализации Store. + +## Regression Tests + +Подтверждаются: + +- Trades Feed; +- Handler; +- Canonical Trade; +- Consistency; +- Recovery; +- отсутствие изменений Acquisition Runtime. + +--- + +# План реализации + +## Этап 1 + +Повторный repository search: + +```text +TradeRuntime +trade_runtime +acquisition.runtime.trade +consistency: +``` + +## Этап 2 + +Создание `trade_stream_state_store_protocol.py`. + +## Этап 3 + +Создание `trade_stream_state_store_exceptions.py`. + +## Этап 4 + +Реализация `trade_stream_state_store.py`. + +## Этап 5 + +Локальная миграция `trade_stream_consistency_controller.py`. + +Запрещается: + +- переписывать рабочие алгоритмы; +- менять порядок бизнес-проверок; +- сокращать код вне Scope; +- менять исключения Consistency без отдельного согласования. + +## Этап 6 + +Миграция тестов Registry в тесты State Store. + +## Этап 7 + +Обновление тестов Controller. + +## Этап 8 + +Удаление старых Runtime-файлов и пустого каталога `runtime/trade/`. + +## Этап 9 + +Unit и regression testing, затем repository grep. + +## Этап 10 + +Подготовка `build_060_20_1.md`. + +--- + +# Build Boundary + +## Build отвечает за + +- корректировку ownership; +- замену Runtime Registry на State Store; +- типизацию; +- перенос файлов; +- удаление namespace из Controller; +- обновление тестов; +- синхронизацию документации и кода. + +## Build НЕ отвечает за + +- изменение Consistency algorithms; +- изменение `TradeStreamState`; +- изменение Recovery; +- WebSocket Trades Feed; +- synchronization; +- thread safety; +- persistence; +- replay; +- checkpointing; +- Market Data Storage; +- Gap Detection; +- Scheduler; +- Monitoring; +- Metrics; +- Health Check; +- окончательный Composition Root; +- Builds 060.21+. + +--- + +# Риски и меры контроля + +## Скрытые imports + +Контроль: repository grep до и после изменений. + +## Непреднамеренное изменение Consistency + +Контроль: локальная замена только механизма получения State и сохранение всех тестов. + +## Преждевременное расширение Store + +Контроль: Store хранит только `TradeStreamState`. + +## Потеря поведения Registry + +Контроль: сохраняются только функционально оправданные операции; универсальный `register(Any)` не переносится. + +## Нарушение будущей интеграции + +Контроль: зависимость через Protocol и отсутствие связи Store с транспортом. + +--- + +# Definition of Done + +## Архитектура + +- владелец State определён как Trade Stream Consistency; +- `TradeRuntimeRegistry` отсутствует; +- `TradeStreamStateStore` размещён в Consistency; +- Runtime содержит только execution lifecycle infrastructure; +- Store типизирован; +- namespace keys не являются API; +- Recovery остаётся stateless. + +## Код + +- добавлены три файла Store; +- Controller зависит от Store Protocol; +- бизнес-логика Controller сохранена; +- старые Runtime-файлы удалены; +- отсутствуют imports `acquisition.runtime.trade`; +- отсутствуют production-ссылки `TradeRuntime*`. + +## Функциональность + +- State создаётся лениво; +- State повторно используется; +- символы изолированы; +- Consistency, Recovery и Feed работают без изменений. + +## Тестирование + +- тесты Store проходят; +- тесты Controller проходят; +- тесты State проходят; +- тесты Recovery проходят; +- regression suite проходит; +- grep не находит старые зависимости. + +## Документация + +- создан `build_060_20_1_architecture.md`; +- после реализации создаётся `build_060_20_1.md`; +- противоречие Build 060.20 устранено. + +--- + +# Архитектурный результат + +До Build: + +```text +TradeStreamConsistencyController + ↓ +TradeRuntimeProtocol + ↓ +TradeRuntimeRegistry + ↓ +dict[str, Any] + ↓ +TradeStreamState +``` + +После Build: + +```text +TradeStreamConsistencyController + ↓ +TradeStreamStateStoreProtocol + ↓ +TradeStreamStateStore + ↓ +dict[str, TradeStreamState] + ↓ +TradeStreamState +``` + +--- + +# Взаимодействие с последующими Build + +Build 060.20.1 является обязательной коррекцией перед: + +```text +Build 060.21 — Runtime Protocol Integration +Build 060.22 — Runtime Service Integration +Build 060.23 — Acquisition Integration +Build 060.24 — Reconnect & Runtime Recovery +``` + +Последующие Build должны различать: + +```text +Trade Stream State +``` + +и: + +```text +Acquisition Runtime +``` + +State Store используется для Consistency. + +Acquisition Runtime используется для lifecycle, supervision, reconnect и orchestration. + +--- + +# Заключение + +Build 060.20 функционально обеспечил повторное использование состояния Trade Stream, но первоначальная модель Trade Runtime оказалась шире фактической ответственности кода. + +Аудит подтвердил: + +- Registry не хранит Runtime-сервисы; +- Recovery не является stateful Runtime-компонентом; +- единственный production-потребитель — Consistency; +- единственный предметный тип состояния — `TradeStreamState`; +- `dict[str, Any]` не обеспечивает корректную модульную границу; +- размещение в `runtime/trade/` конфликтует с назначением Acquisition Runtime. + +Build 060.20.1 исправляет архитектурную неточность без изменения бизнес-поведения. + +Окончательное решение: + +```text +Trade Stream Consistency + └── владеет TradeStreamState + └── хранится в TradeStreamStateStore +``` + +`TradeStreamStateStore` становится специализированной типизированной инфраструктурной зависимостью Consistency. + +Acquisition Runtime сохраняет самостоятельную ответственность за исполнение и жизненный цикл системы.