Files
dzentra_bot/docs/migrations/build_060_19_architecture.md

6392 lines
156 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Build 060.19 — Trade Recovery Architecture
**Статус:** Architecture Specification
**Build:** 060.19
**Ветка:** Trades Feed (Time & Sales)
**Документ:** `build_060_19_architecture.md`
**Связанные документы:** `build_060_19.md` — план реализации данного Build.
**Предыдущий Build:** Build 060.18 — Trade Stream Consistency Controller.
---
# Назначение документа
Настоящий документ является официальной архитектурной спецификацией Build 060.19 и определяет построение подсистемы восстановления потока сделок (**Trade Recovery**).
Документ фиксирует все архитектурные решения, принятые до начала реализации, и является единственным источником истины (Single Source of Truth) при разработке данного Build.
Все решения, описанные ниже, считаются утверждёнными до начала реализации и не должны изменяться в процессе написания кода без подготовки нового ADR.
Настоящий документ подготовлен на основании:
- архитектуры предыдущих Build серии 060;
- экспериментального исследования REST API биржи;
- полного аудита существующей реализации Acquisition Pipeline;
- анализа существующих транспортных адаптеров, Feed, Handler, Runtime и Consistency Layer.
Никакие положения настоящего документа не основаны на предположениях о поведении биржи. Все архитектурные решения принимаются исключительно на основании подтверждённого поведения существующей системы и экспериментально проверенного контракта REST API.
---
# Статус Build
Build 060.19 является непосредственным продолжением Build 060.18.
К моменту начала данного Build в системе уже существуют:
- Canonical Trade Model;
- Parser;
- Mapper;
- Value Validation;
- REST Adapter;
- WebSocket Adapter;
- Trades Feed;
- Trades Handler;
- Trade Stream Consistency Controller;
- Canonical Trade Stream.
Таким образом, система уже умеет:
- получать сделки через REST;
- получать сделки через WebSocket;
- преобразовывать транспортные структуры в Canonical Trade;
- формировать согласованный поток сделок.
Однако система ещё не умеет восстанавливать пропущенный участок истории после временной потери WebSocket-потока.
Именно эту задачу решает Build 060.19.
---
# Контекст
После завершения Build 060.18 система гарантирует согласованность каждой опубликованной сделки.
Однако согласованность ещё не означает полноту потока.
Рассмотрим пример.
```text
Trade #100
Trade #101
(WebSocket отключился)
Trade #110
```
С точки зрения Build 060.18:
- Trade #110 является корректной;
- порядок не нарушен;
- конфликтующих повторов нет.
Следовательно, сделка будет успешно опубликована.
Однако фактически поток потерял сделки:
```text
102
103
104
105
106
107
108
109
```
Следовательно, после появления Canonical Trade Stream возникает новая архитектурная задача.
Необходимо восстановить отсутствующий участок истории до продолжения нормальной обработки WebSocket.
Именно этот механизм вводится Build 060.19.
---
# Предпосылки
Настоящий Build опирается на архитектурные решения предыдущих Build серии 060.
## Build 057
Определены фундаментальные принципы Acquisition Layer.
Разделены транспортный и доменный уровни.
---
## Build 060.1
Построена Canonical Trade Model.
Все транспортные источники приводятся к единому объекту `Trade`.
---
## Build 060.4
Завершено разделение Parser и Mapper.
Parser отвечает исключительно за транспортный уровень.
Mapper отвечает исключительно за построение Domain Model.
---
## Build 060.9
Построена система Value Validation.
Корректность отдельных значений гарантируется до создания объекта `Trade`.
---
## Build 060.11
Определены транспортные адаптеры REST и WebSocket.
---
## Build 060.17
Построен Trades Feed.
Получение сделок полностью функционирует.
---
## Build 060.18
Построен Canonical Trade Stream.
Введён `TradeStreamConsistencyController`, обеспечивающий:
- дедупликацию;
- контроль порядка;
- обнаружение конфликтующих дублей;
- формирование единственного согласованного потока сделок.
Build 060.19 обязан использовать данный механизм без изменения его архитектуры.
Recovery не имеет права реализовывать:
- собственную дедупликацию;
- собственную проверку порядка;
- собственную модель Trade;
- собственный поток сделок.
---
# Проблема
После потери WebSocket-соединения поток сделок становится неполным.
При этом существующий Consistency Controller не способен самостоятельно восстановить отсутствующий участок истории.
Это не является недостатком Build 060.18.
Напротив, Build 060.18 сознательно ограничивает собственную ответственность исключительно проверкой согласованности уже поступающих сделок.
Следовательно, необходим отдельный компонент, который сможет:
- получить исторические сделки через REST;
- преобразовать их в Canonical Trade;
- безопасно объединить их с текущим потоком;
- использовать уже существующий механизм проверки согласованности.
Такой компонент должен быть полностью независимым от Runtime, WebSocket Transport и транспортного уровня биржи.
Именно таким компонентом становится Trade Recovery.
---
# Основная идея Build
Главная идея Build заключается в строгом разделении двух независимых задач.
Первая задача:
```text
Получение исторических сделок.
```
Вторая задача:
```text
Проверка согласованности потока.
```
Получение истории не должно знать, каким образом выполняется дедупликация.
Контроллер согласованности, в свою очередь, не должен знать, каким способом были получены сделки.
Следовательно, Recovery становится исключительно связующим звеном между существующим REST Pipeline и существующим Stream Consistency Layer.
Он не изменяет их архитектуру и не дублирует их функциональность.
---
# Цель Build
Build обязан обеспечить безопасное восстановление отсутствующего участка истории торгов без нарушения уже существующих архитектурных инвариантов.
После завершения Build система должна обладать следующими свойствами.
- Recovery использует существующий REST Pipeline.
- Recovery использует существующий TradeStreamConsistencyController.
- Recovery не создаёт собственную модель Trade.
- Recovery не создаёт собственную модель потока.
- Recovery не выполняет собственную дедупликацию.
- Recovery не реализует собственные правила Ordering.
- Recovery не зависит от Runtime.
- Recovery не зависит от WebSocket Transport.
- Recovery не зависит от механизма Reconnect.
Таким образом Recovery становится полностью автономным компонентом Acquisition Layer.
---
# Что НЕ входит в Scope Build
Настоящий Build сознательно НЕ реализует:
- автоматическое обнаружение потери WebSocket;
- обнаружение Gap;
- принятие решения о запуске Recovery;
- управление переподключением;
- Runtime Integration;
- Composition Root;
- сохранение состояния между перезапусками;
- журналирование Recovery;
- Retry Policy;
- стратегию многократного Backfill;
- телеметрию;
- метрики.
Все перечисленные задачи относятся к следующим Build серии 060.
Build 060.19 реализует исключительно механизм безопасного восстановления исторических сделок по уже подготовленному диапазону времени.
---
# Архитектурные принципы
При реализации Build 060.19 используются следующие фундаментальные принципы.
---
## 1. Canonical First
Recovery никогда не работает с транспортными структурами данных.
Любая информация, полученная через REST API, должна пройти существующий Pipeline:
```text
REST Document
Parser
Value Validation
Mapper
Canonical Trade
```
Только после появления объекта `Trade` начинается работа Recovery.
Recovery никогда не анализирует JSON-документ REST напрямую.
---
## 2. Existing Pipeline Reuse
Build 060.19 не создаёт собственный REST Pipeline.
Recovery полностью переиспользует существующие компоненты:
- REST Transport;
- REST Parser;
- REST Value Validation;
- REST Mapper;
- REST Adapter.
Во время проектирования была рассмотрена возможность реализации отдельного Recovery Adapter.
Данный вариант отклонён.
Причина:
это привело бы к появлению двух различных способов преобразования одного и того же REST-документа в Canonical Trade, что нарушило бы принцип единственной модели преобразования данных.
---
## 3. Single Domain Model
Во всей системе продолжает существовать единственная доменная модель сделки:
```python
Trade
```
Recovery не имеет собственной модели сделки.
Recovery не создаёт дополнительных DTO доменного уровня.
Все операции выполняются исключительно над существующим объектом `Trade`.
---
## 4. Existing Consistency First
Recovery никогда самостоятельно не принимает решения о корректности сделки.
После нормализации последовательности каждая сделка обязательно передаётся в существующий:
```text
TradeStreamConsistencyController
```
Именно данный компонент остаётся единственным владельцем правил:
- Ordering;
- Deduplication;
- Conflict Detection.
Recovery не имеет права дублировать эти проверки.
---
## 5. Stateless Recovery
Recovery не хранит собственного состояния потока.
Он не знает:
- последний обработанный trade_id;
- последний опубликованный Trade;
- состояние дедупликации;
- историю уже обработанных сделок.
Recovery является stateless-компонентом.
Всё состояние системы по-прежнему принадлежит только Build 060.18.
---
## 6. Runtime Independence
Recovery не зависит от Runtime.
Он не знает:
- произошло ли переподключение;
- был ли разрыв WebSocket;
- почему потребовалось восстановление;
- сколько времени отсутствовало соединение.
Recovery получает уже сформированный запрос восстановления и выполняет только его.
---
## 7. Time-Based Recovery
Recovery использует исключительно временные диапазоны.
Во время проектирования были рассмотрены различные варианты построения курсора восстановления.
Использование `trade_id` было отклонено после экспериментального исследования REST API.
Официальным механизмом навигации Build 060.19 являются:
```text
start_time
end_time
```
Других типов курсоров Build не предусматривает.
---
## 8. One Recovery Direction
REST API возвращает сделки в порядке:
```text
DESCENDING
```
Однако Canonical Trade Stream существует исключительно в порядке:
```text
ASCENDING
```
Следовательно Build 060.19 вводит обязательный этап нормализации порядка.
Никакая сделка не может быть передана в Consistency Layer до завершения данной нормализации.
---
## 9. Separation Of Responsibilities
Recovery отвечает исключительно за:
- получение истории;
- нормализацию порядка;
- передачу сделок в Consistency Layer.
Recovery сознательно не отвечает за:
- принятие решения о запуске;
- вычисление диапазона восстановления;
- повторные попытки;
- работу WebSocket;
- Runtime Integration.
Все перечисленные обязанности относятся к следующим Build серии 060.
---
## 10. Unique File Naming
Во всём проекте Dzentra продолжает действовать правило уникальности имён файлов.
Новые файлы Build 060.19 обязаны иметь уникальные имена во всём репозитории.
Допускается единственное исключение:
```text
__init__.py
```
Использование общих имён файлов запрещается.
Например, не допускаются:
```text
controller.py
request.py
normalizer.py
exceptions.py
```
Файлы должны отражать своё назначение.
Например:
```text
trade_recovery_controller.py
trade_recovery_request.py
trade_recovery_normalizer.py
trade_recovery_protocol.py
trade_recovery_exceptions.py
```
---
# Архитектурный фундамент Recovery
После завершения Build 060.18 система получила понятие:
```text
Canonical Trade Stream
```
Build 060.19 не изменяет данную модель.
Вместо этого появляется новая независимая архитектурная сущность.
```text
Trade Recovery
```
Recovery не становится частью Stream Consistency.
Recovery располагается перед ним.
Архитектурно система принимает следующий вид.
```text
REST
Trade Recovery
Trade Stream Consistency
Canonical Trade Stream
```
Таким образом Build 060.19 не расширяет обязанности Consistency Controller.
Он вводит новый независимый уровень Acquisition Pipeline.
---
# Место Recovery в Acquisition Pipeline
До Build 060.19 существовала следующая архитектурная схема.
```text
REST / WebSocket
Parser
Value Validation
Mapper
Trade
TradeStreamConsistencyController
Canonical Trade Stream
```
После завершения Build 060.19 появляется дополнительный путь обработки исторических сделок.
```text
REST
Parser
Value Validation
Mapper
Trade Recovery
TradeStreamConsistencyController
Canonical Trade Stream
```
При этом путь обработки WebSocket-сделок остаётся неизменным.
Recovery является дополнительной веткой Acquisition Pipeline и не изменяет существующую архитектуру получения потоковых сделок.
---
# Экспериментальное исследование REST API
Перед проектированием Recovery было выполнено отдельное инженерное исследование поведения REST API биржи.
Целью исследования являлось подтверждение фактического поведения endpoint получения исторических сделок и исключение архитектурных решений, основанных исключительно на документации биржи.
Исследование выполнялось специализированным диагностическим инструментом:
```text
check_trade_backfill_api_final_test.py
```
Все архитектурные решения настоящего Build принимаются исключительно на основании подтверждённого поведения API.
---
# Подтверждённый контракт REST API
Исследование подтвердило следующие свойства endpoint:
```text
GET /api/v2/aggTrades
```
Все перечисленные ниже свойства считаются частью архитектурного контракта Build 060.19.
---
# Endpoint
Экспериментально подтверждено:
- endpoint доступен;
- endpoint стабилен;
- endpoint детерминирован;
- повторные запросы возвращают предсказуемый результат.
Recovery полностью опирается на данный контракт.
---
# Порядок выдачи
REST API возвращает сделки в порядке:
```text
DESCENDING
```
то есть
```text
newest
oldest
```
Это фундаментальное свойство Build.
Recovery никогда не имеет права передавать данный поток непосредственно в Stream Consistency.
Перед публикацией последовательность обязательно нормализуется.
```text
REST
DESCENDING
Recovery Normalizer
ASCENDING
TradeStreamConsistencyController
```
---
# Timestamp
Экспериментально подтверждено:
при уменьшении `trade_id`
уменьшается
```text
executed_at
```
Следовательно:
- timestamp согласован с порядком выдачи;
- дополнительная сортировка по времени не требуется.
---
# Limit
Подтверждены следующие ограничения.
Корректно работают значения:
```text
1
...
1000
```
Запрос
```text
limit = 1001
```
возвращает
```text
HTTP 400
```
Следовательно Build фиксирует официальный диапазон.
```text
1 <= limit <= 1000
```
Recovery не имеет права нарушать данный инвариант.
---
# Time Filters
Экспериментально подтверждена корректная работа параметров:
```text
startTime
```
```text
endTime
```
а также их совместного использования.
Recovery использует исключительно временные диапазоны.
Поддержка навигации по времени считается официальной частью архитектурного контракта.
---
# Ограничение временного диапазона
При одновременном использовании:
```text
startTime
+
endTime
```
биржа требует, чтобы длина диапазона была меньше одного часа.
Следовательно Recovery принимает следующий инвариант.
```text
end_time > start_time
```
и
```text
end_time - start_time < 1 hour
```
Нарушение данного условия считается ошибкой формирования Recovery Request.
---
# fromId
Во время исследования была проведена серия диагностических тестов параметра:
```text
fromId
```
Исследовались:
- исторические значения;
- глубокие исторические значения;
- отрицательные значения;
- будущие значения.
Во всех случаях подтверждена одинаковая картина.
REST продолжает возвращать последнюю страницу сделок.
Следовательно использование `fromId` как курсора восстановления экспериментально не подтверждено.
Build 060.19 полностью исключает любую зависимость от данного параметра.
---
# Trade IDs
Экспериментально подтверждено:
`trade_id`
не образует арифметически непрерывную последовательность.
Например:
```text
100
101
118
141
205
```
является полностью корректной последовательностью.
Следовательно Recovery никогда не делает предположений вида:
```text
next_trade_id = previous_trade_id + 1
```
Пропуски идентификаторов сами по себе не являются ошибкой.
---
# Repeatability
Повторный запрос одного и того же диапазона времени возвращает одинаковую последовательность `trade_id`.
Это означает:
- результаты детерминированы;
- повторное получение диапазона безопасно;
- Recovery может повторно использовать один и тот же диапазон без риска появления новых сделок внутри уже завершённого исторического окна.
---
# Выводы исследования
На основании проведённого исследования Build принимает следующие архитектурные решения.
Recovery:
- не использует `fromId`;
- использует только временные диапазоны;
- никогда не предполагает непрерывность `trade_id`;
- всегда нормализует порядок выдачи;
- полностью доверяет существующему REST Pipeline;
- не выполняет дополнительную сортировку по времени.
Все перечисленные положения считаются официальными архитектурными инвариантами Build 060.19.
---
# Аудит существующей реализации
После завершения исследования REST API был выполнен полный аудит существующей реализации Acquisition Layer.
Цель аудита состояла в определении минимального объёма изменений, необходимых для реализации Recovery без нарушения уже существующей архитектуры.
В ходе аудита были исследованы:
- REST Source;
- REST Adapter;
- Parser;
- Mapper;
- Value Validation;
- Trades Feed;
- Trades Handler;
- Trade Stream Consistency;
- Runtime;
- Subscription Layer;
- WebSocket Protocol.
Результаты аудита определяют архитектурные границы настоящего Build.
---
# Вывод №1
REST Pipeline уже полностью реализован.
Существующий транспортный источник поддерживает получение исторических сделок по временным диапазонам.
Build 060.19 не создаёт нового REST клиента.
---
# Вывод №2
REST Adapter уже полностью реализован.
Pipeline:
```text
REST Document
Parser
Value Validation
Mapper
Trade
```
уже существует.
Recovery полностью переиспользует данный Pipeline.
Создание второго Adapter запрещается.
---
# Вывод №3
Canonical Trade полностью соответствует требованиям Recovery.
Build не изменяет:
- модель Trade;
- Value Validation;
- Parser;
- Mapper.
Recovery использует существующий доменный объект без каких-либо изменений.
---
# Вывод №4
TradeStreamConsistencyController полностью готов к интеграции.
Recovery обязан использовать существующий публичный контракт:
```python
accept(trade: Trade) -> Trade | None
```
Никаких дополнительных методов Controller Build 060.19 не требует.
---
# Вывод №5
Trades Feed не требует изменений архитектуры.
Recovery не интегрируется непосредственно в Feed.
Интеграция с Runtime будет реализована отдельным Build.
Следовательно существующая архитектура Feed остаётся неизменной.
---
# Вывод №6
Trades Handler не требует изменения ответственности.
Во время аудита подтверждено, что Handler уже выполняет исключительно транспортные обязанности:
- проверку структуры входящего документа;
- вызов существующего Adapter;
- получение канонических объектов `Trade`.
Recovery не переносит в Handler никакой дополнительной логики.
Handler остаётся полностью stateless.
---
# Вывод №7
Runtime уже содержит архитектурные сущности, необходимые для последующей интеграции Recovery.
Во время аудита обнаружены отдельные Runtime-компоненты:
- Runtime Events;
- Runtime Commands;
- Subscription Layer;
- Runtime Protocol.
Однако Build 060.19 сознательно не использует их напрямую.
Recovery остаётся полностью независимым компонентом.
Интеграция с Runtime переносится на последующие Build серии 060.
---
# Вывод №8
Recovery не отвечает за управление подписками.
Во время аудита подтверждено, что существующий Subscription Layer уже определяет архитектуру подписок WebSocket.
Следовательно Recovery не:
- создаёт подписки;
- восстанавливает подписки;
- отслеживает подписки;
- управляет жизненным циклом подписок.
Данные обязанности принадлежат Runtime Layer.
---
# Вывод №9
Recovery не зависит от WebSocket Transport.
Во время аудита подтверждено, что существующий WebSocket Protocol определяет исключительно транспортные контракты.
Recovery никогда не работает с:
- WebSocket Message;
- Subscription Event;
- Transport DTO.
Recovery получает уже готовый запрос восстановления.
Таким образом между Recovery и WebSocket отсутствует прямая зависимость.
---
# Вывод №10
Существующий Runtime ещё не содержит реализации Recovery.
Во время аудита подтверждено, что Build 060.19 может быть реализован как полностью автономная подсистема.
Это позволяет:
- реализовать Recovery;
- полностью протестировать его;
- завершить Build;
до появления Runtime Integration.
Данное решение существенно уменьшает связанность системы.
---
# Архитектура Recovery
После завершения аудита необходимо определить архитектурную модель Recovery.
Build вводит новую внутреннюю подсистему Acquisition Layer.
Она состоит из нескольких независимых компонентов.
```text
TradeRecoveryController
TradeRecoveryNormalizer
TradeStreamConsistencyController
```
Все перечисленные компоненты относятся исключительно к Build 060.19.
Никакие существующие подсистемы не изменяют собственную ответственность.
---
# Общая архитектурная схема
Полный путь восстановления исторических сделок выглядит следующим образом.
```text
TradeRecoveryRequest
DzengiTradesDocumentSource
REST Document
REST Parser
Value Validation
REST Mapper
tuple[Trade]
TradeRecoveryNormalizer
ASCENDING Trade Stream
TradeStreamConsistencyController
TradeRecoveryResult
```
Данная схема становится официальной архитектурой Recovery Pipeline.
---
# Граница Recovery
Recovery располагается между существующим REST Adapter и существующим Stream Consistency.
До Recovery существует только набор независимых объектов Trade.
```text
REST
Parser
Validation
Mapper
Trade
```
После Recovery появляется уже согласованный поток.
```text
Trade
Recovery
TradeStreamConsistencyController
Canonical Trade Stream
```
Recovery никогда не публикует сделки самостоятельно.
Публикация возможна только после успешного прохождения Stream Consistency.
---
# TradeRecoveryController
## Назначение
TradeRecoveryController является центральным компонентом Build 060.19.
Именно он управляет полным процессом восстановления исторического диапазона.
Контроллер отвечает исключительно за оркестрацию существующих компонентов.
Он не реализует:
- транспорт;
- парсинг;
- дедупликацию;
- Ordering;
- Runtime.
---
# Ответственность Controller
TradeRecoveryController отвечает за:
- получение Recovery Request;
- вызов существующего REST Source;
- вызов существующего REST Adapter;
- передачу полученного потока в Recovery Normalizer;
- последовательную передачу сделок в Stream Consistency;
- формирование итогового результата восстановления.
---
# Controller НЕ отвечает
TradeRecoveryController сознательно не отвечает за:
- вычисление диапазона времени;
- выбор момента запуска;
- Retry Policy;
- обнаружение Gap;
- работу WebSocket;
- управление подписками;
- Runtime Integration;
- хранение состояния.
Все перечисленные обязанности относятся к следующим Build.
---
# Главный принцип Controller
TradeRecoveryController не принимает доменных решений.
Все решения уже принадлежат существующим компонентам системы.
Контроллер лишь организует их совместную работу.
Именно поэтому RecoveryController рассматривается как orchestration component, а не как Domain Service.
---
# TradeRecoveryRequest
## Назначение
Recovery выполняется исключительно по заранее подготовленному запросу.
Build сознательно разделяет:
- вычисление диапазона;
- выполнение восстановления.
Следовательно Recovery получает готовый объект запроса.
---
# Содержимое Recovery Request
Минимальная модель запроса включает:
```text
symbol
start_time
end_time
limit
```
Этого достаточно для выполнения одного Recovery Pipeline.
Никакие дополнительные поля Build 060.19 не требует.
---
# Почему отсутствует last_processed_timestamp
Во время проектирования рассматривался вариант хранения внутри Recovery собственного курсора:
```text
last_processed_timestamp
```
Данный вариант отклонён.
Причины:
- появление внутреннего состояния;
- зависимость от Runtime;
- нарушение принципа Stateless Recovery;
- смешение ответственности Runtime и Recovery.
Recovery никогда самостоятельно не вычисляет диапазон восстановления.
Он лишь выполняет уже подготовленный запрос.
---
# TradeRecoveryRequest
## Инварианты
Каждый экземпляр `TradeRecoveryRequest` обязан удовлетворять следующим требованиям.
---
### Инвариант №1
Запрос относится ровно к одному торговому инструменту.
Например:
```text
BTCUSDT
```
Recovery никогда не выполняет восстановление нескольких символов одновременно.
---
### Инвариант №2
Временной диапазон обязан быть корректным.
Всегда должно выполняться условие:
```text
start_time < end_time
```
Нарушение данного условия считается ошибкой формирования Recovery Request.
---
### Инвариант №3
Продолжительность диапазона не должна превышать ограничение REST API.
```text
end_time - start_time < 1 hour
```
Recovery не выполняет автоматическое разбиение диапазона.
Данная задача относится к следующим Build.
---
### Инвариант №4
Поле `limit`, если оно указано, обязано удовлетворять диапазону:
```text
1 <= limit <= 1000
```
Recovery не корректирует ошибочные значения автоматически.
---
### Инвариант №5
Recovery Request является неизменяемым объектом.
После создания его содержимое никогда не изменяется.
---
# Почему используется готовый Request
Во время проектирования рассматривались несколько вариантов.
---
## Вариант 1
Передавать только:
```text
symbol
```
Отклонён.
Recovery пришлось бы самостоятельно вычислять диапазон восстановления.
Это нарушает разделение ответственности.
---
## Вариант 2
Передавать:
```text
last_trade_id
```
Отклонён.
Экспериментально подтверждено, что Recovery не должен строиться вокруг `trade_id`.
Кроме того, существующий REST API не предоставляет надёжного механизма навигации через `fromId`.
---
## Вариант 3
Передавать:
```text
start_time
end_time
limit
```
Принят.
Recovery полностью независим от Runtime и выполняет исключительно уже сформированный запрос.
---
# TradeRecoveryNormalizer
## Назначение
TradeRecoveryNormalizer является вторым компонентом Recovery Pipeline.
Его единственная задача —
привести последовательность сделок, полученную от REST API, к каноническому порядку публикации.
RecoveryNormalizer не анализирует содержимое сделок.
Он работает исключительно с их последовательностью.
---
# Почему необходим отдельный компонент
Во время проектирования рассматривалась возможность выполнения нормализации непосредственно внутри Controller.
Данный вариант был отклонён.
Причины:
- смешение обязанностей;
- ухудшение тестируемости;
- усложнение Controller;
- невозможность повторного использования алгоритма.
Разделение Controller и Normalizer соответствует общим архитектурным принципам серии Build 060.
---
# Ответственность Normalizer
TradeRecoveryNormalizer отвечает исключительно за:
- анализ направления последовательности;
- нормализацию порядка сделок;
- проверку корректности направления потока.
Никаких других обязанностей компонент не имеет.
---
# Normalizer НЕ отвечает
TradeRecoveryNormalizer сознательно не отвечает за:
- REST;
- Parser;
- Mapper;
- Value Validation;
- Deduplication;
- Ordering;
- Runtime;
- Recovery Request.
---
# Контракт Normalizer
Normalizer получает:
```text
tuple[Trade]
```
и возвращает:
```text
tuple[Trade]
```
Количество сделок никогда не изменяется.
Normalizer никогда:
- не удаляет сделки;
- не добавляет сделки;
- не модифицирует сделки.
Изменяется исключительно порядок их следования.
---
# Модель нормализации
Во время исследования REST API было подтверждено следующее поведение.
REST API возвращает сделки в порядке:
```text
DESCENDING
```
Следовательно Recovery обязан преобразовать поток в:
```text
ASCENDING
```
Только после этого сделки могут быть переданы в Stream Consistency.
---
# Допустимые варианты последовательности
Build 060.19 формально определяет допустимые варианты входной последовательности.
---
## Вариант №1
Последовательность уже является возрастающей.
Например:
```text
100
105
121
140
```
В этом случае RecoveryNormalizer возвращает её без изменений.
---
## Вариант №2
Последовательность является убывающей.
Например:
```text
140
121
105
100
```
В этом случае выполняется нормализация.
Результат:
```text
100
105
121
140
```
---
## Вариант №3
Последовательность имеет смешанный порядок.
Например:
```text
100
150
120
180
```
Такой поток считается архитектурно недопустимым.
Normalizer обязан завершить обработку ошибкой.
Никакие сделки не передаются в Stream Consistency.
---
# Почему запрещается смешанный порядок
Смешанная последовательность означает нарушение фундаментального контракта транспортного уровня.
Recovery не должен самостоятельно исправлять подобные ошибки.
Подобная ситуация рассматривается как нарушение архитектурных инвариантов Acquisition Layer.
Следовательно единственно допустимым поведением является генерация доменного исключения.
---
# Пустая последовательность
Если REST Pipeline возвращает:
```text
()
```
Normalizer возвращает ту же пустую последовательность.
Ошибки не возникает.
Это означает отсутствие сделок внутри указанного диапазона времени.
---
# Последовательность из одной сделки
Если получена единственная сделка:
```text
Trade
```
никакая нормализация не требуется.
Trade передаётся в Stream Consistency без изменений.
---
# Неизменяемость Trade
Во время нормализации запрещается изменять объект Trade.
Допускается изменение исключительно порядка следования элементов внутри коллекции.
Canonical Trade остаётся полностью immutable.
---
# Передача сделок в Stream Consistency
После завершения нормализации Recovery начинает публикацию сделок в существующий `TradeStreamConsistencyController`.
Передача выполняется строго последовательно.
Для каждой сделки вызывается существующий публичный контракт:
```python
accept(trade: Trade) -> Trade | None
```
Recovery никогда не обходит данный интерфейс.
---
# Последовательность обработки
После получения нормализованной последовательности обработка всегда выполняется по одной и той же схеме.
```text
Trade #1
accept()
Trade #2
accept()
Trade #3
accept()
...
```
Recovery никогда не передаёт контроллеру сразу всю коллекцию.
Контроллер продолжает работать исключительно со входящим потоком отдельных сделок.
Это сохраняет единый механизм обработки как для REST, так и для WebSocket.
---
# Почему используется последовательная обработка
Во время проектирования рассматривалась возможность добавить новый метод вида:
```python
accept_many(...)
```
Данный вариант был отклонён.
Причины:
- появление второго публичного API;
- дублирование логики;
- нарушение единой модели обработки потока;
- увеличение сложности тестирования.
Build 060.19 сохраняет существующий контракт без изменений.
---
# Роль TradeStreamConsistencyController
Recovery не принимает решений относительно результата обработки сделки.
Все решения принадлежат исключительно существующему Controller.
Для каждой сделки возможны только три сценария.
---
## Сценарий №1
Controller возвращает:
```python
Trade
```
Это означает успешное прохождение проверки согласованности.
Recovery включает сделку в итоговый результат восстановления.
---
## Сценарий №2
Controller возвращает:
```python
None
```
Это означает обнаружение корректного дубликата.
Recovery не считает подобную ситуацию ошибкой.
Такая сделка просто не включается в итоговый поток восстановления.
---
## Сценарий №3
Controller генерирует исключение.
Например:
```text
TradeConsistencyError
```
или
```text
TradeOrderingError
```
Recovery немедленно прекращает выполнение.
Итог восстановления считается неуспешным.
---
# Почему Recovery не перехватывает ошибки Consistency
Во время проектирования рассматривался вариант автоматического продолжения восстановления после ошибок согласованности.
Данный вариант отклонён.
Причины:
- нарушение архитектурных инвариантов;
- сокрытие ошибок потока;
- появление недостоверного результата восстановления.
Recovery никогда не скрывает ошибки Controller.
Исключения распространяются вверх без изменения их смысла.
---
# Формирование результата Recovery
После обработки всех сделок Recovery формирует единый результат выполнения.
Build 060.19 вводит отдельную доменную модель результата.
```text
TradeRecoveryResult
```
Данный объект не относится к транспортному уровню.
Он описывает исключительно итог работы Recovery.
---
# Почему вводится отдельный Result
Во время проектирования рассматривались несколько вариантов.
---
## Вариант №1
Возвращать:
```python
tuple[Trade]
```
Отклонён.
По коллекции невозможно определить:
- была ли выполнена обработка полностью;
- сколько сделок было отброшено как дубликаты;
- завершилось ли восстановление успешно.
---
## Вариант №2
Возвращать:
```python
list[Trade]
```
Отклонён по тем же причинам.
Кроме того, Canonical Pipeline использует неизменяемые коллекции.
---
## Вариант №3
Использовать специализированный объект результата.
Принят.
Именно он становится официальным контрактом Recovery.
---
# Назначение TradeRecoveryResult
TradeRecoveryResult описывает завершённую операцию восстановления.
Он не является журналом выполнения.
Он не содержит внутреннего состояния Recovery.
Он лишь фиксирует итог уже завершённой операции.
---
# Минимальный состав Result
Build 060.19 определяет следующий минимальный набор информации.
```text
Recovered Trades
Skipped Duplicates
```
Этого достаточно для оценки результата работы Recovery.
Расширенные диагностические поля будут добавлены в следующих Build.
---
# Почему Result не содержит ошибки
Во время проектирования рассматривался вариант хранения исключения внутри объекта результата.
Например:
```text
error
```
или
```text
exception
```
Данный вариант отклонён.
Recovery использует стандартную модель обработки ошибок Python.
При возникновении исключения объект результата не создаётся.
---
# Обработка пустого восстановления
Если REST не возвращает ни одной сделки,
Recovery успешно завершается.
Результат содержит:
```text
Recovered Trades = 0
Skipped Duplicates = 0
```
Подобная ситуация считается полностью корректной.
---
# Обработка полного дублирования
Если все полученные сделки уже присутствуют в Canonical Stream,
Controller вернёт:
```python
None
```
для каждой сделки.
Recovery завершится успешно.
Результат будет иметь вид:
```text
Recovered Trades = 0
Skipped Duplicates = N
```
Ошибки не возникает.
---
# Обработка частичного восстановления
Наиболее типичный сценарий.
Например:
REST вернул:
```text
100
105
121
140
```
Controller определил:
```text
100 -> duplicate
105 -> accepted
121 -> accepted
140 -> accepted
```
Итог Recovery:
```text
Recovered Trades = 3
Skipped Duplicates = 1
```
Именно подобный сценарий считается основной моделью работы Recovery.
---
# Завершение Recovery
Recovery считается успешно завершённым только после выполнения всех условий:
- REST Pipeline завершился без ошибок;
- Normalizer успешно обработал последовательность;
- все сделки переданы в Stream Consistency;
- Controller не сгенерировал исключений;
- сформирован TradeRecoveryResult.
Только после этого операция восстановления считается завершённой.
---
# Исключения Recovery
Build 060.19 вводит собственный набор доменных исключений Recovery.
Они описывают ошибки исключительно уровня восстановления истории и не заменяют существующие исключения других компонентов системы.
Recovery никогда не создаёт новые исключения для задач:
- Parser;
- Mapper;
- Value Validation;
- Trade Stream Consistency.
Каждый уровень системы продолжает использовать собственную модель ошибок.
---
# Принцип распространения исключений
Recovery придерживается принципа Fail Fast.
Если любой нижележащий компонент завершает работу исключением,
Recovery немедленно прекращает выполнение.
Никакие ошибки не:
- скрываются;
- преобразуются;
- игнорируются;
- журналируются внутри Recovery.
Ответственность Recovery ограничивается только распространением ошибки вызывающему компоненту.
---
# Собственные ошибки Recovery
Build 060.19 предусматривает появление специализированных исключений Recovery.
Например:
```text
TradeRecoveryError
```
базовое исключение Recovery.
От него могут наследоваться специализированные ошибки.
Например:
```text
TradeRecoveryNormalizationError
```
Ошибка нормализации последовательности.
---
```text
TradeRecoveryRequestError
```
Некорректный Recovery Request.
---
```text
TradeRecoveryConfigurationError
```
Некорректная конфигурация Recovery.
---
Данный перечень может быть расширен в последующих Build без изменения публичной архитектуры Recovery.
---
# Ошибки, которые Recovery не создаёт
Recovery никогда не создаёт:
```text
TradeConsistencyError
```
или
```text
TradeOrderingError
```
Данные исключения принадлежат исключительно Build 060.18.
Recovery лишь распространяет их без изменения.
---
# Обработка транспортных ошибок
Если REST Source завершает работу исключением,
например:
```text
HTTP Error
Network Error
Timeout
```
Recovery не пытается выполнить повторный запрос.
Retry Policy не входит в Scope Build 060.19.
Исключение распространяется вызывающему компоненту.
---
# Обработка ошибок Parser
Если Parser обнаруживает нарушение транспортного контракта,
Recovery не вмешивается.
Исключение считается критическим.
Восстановление прекращается.
---
# Обработка ошибок Value Validation
Если хотя бы одна сделка не проходит существующую систему Validation,
Recovery завершается ошибкой.
Продолжение восстановления после подобных ошибок запрещается.
---
# Обработка ошибок Mapper
Если Mapper не способен сформировать Canonical Trade,
Recovery немедленно прекращает выполнение.
Никакие частично обработанные результаты не публикуются.
---
# Обработка ошибок Normalizer
Если Normalizer обнаруживает смешанный порядок последовательности,
генерируется:
```text
TradeRecoveryNormalizationError
```
После возникновения данной ошибки:
- ни одна сделка не передаётся в Stream Consistency;
- результат Recovery не создаётся;
- выполнение прекращается.
---
# Обработка ошибок Stream Consistency
Если TradeStreamConsistencyController обнаруживает нарушение инвариантов потока,
Recovery не выполняет никаких дополнительных действий.
Исключение распространяется вверх без изменения.
Таким образом единственным владельцем логики проверки согласованности остаётся Build 060.18.
---
# Тестируемость Recovery
Recovery проектируется как полностью детерминированная подсистема.
При одинаковых входных данных Recovery всегда обязан выдавать одинаковый результат.
Никакие внешние факторы не должны влиять на результат выполнения.
---
# Принцип детерминированности
При фиксированных:
- Recovery Request;
- ответе REST;
- состоянии Stream Consistency;
результат Recovery всегда обязан быть идентичным.
Это значительно упрощает автоматическое тестирование.
---
# Изоляция компонентов
Каждый компонент Recovery должен тестироваться независимо.
Например:
TradeRecoveryNormalizer может быть протестирован без:
- REST;
- Runtime;
- Stream Consistency.
TradeRecoveryController может быть протестирован с использованием Mock Source и Mock ConsistencyController.
Подобное разделение является обязательным архитектурным требованием.
---
# Unit-тестирование
Минимальный набор Unit-тестов должен покрывать:
## Recovery Request
- корректное создание;
- нарушение временного диапазона;
- нарушение ограничения limit;
- неизменяемость объекта.
---
## Recovery Normalizer
- пустая последовательность;
- одна сделка;
- ASCENDING;
- DESCENDING;
- смешанный порядок;
- отсутствие изменения объектов Trade.
---
## Recovery Controller
- успешное восстановление;
- пустое восстановление;
- полное дублирование;
- частичное восстановление;
- ошибка REST;
- ошибка Parser;
- ошибка Mapper;
- ошибка Validation;
- ошибка Consistency Controller.
---
# Интеграционные тесты
После завершения Unit-тестирования Build предусматривает отдельный набор интеграционных сценариев.
Минимально должны быть проверены:
- получение истории через существующий REST Pipeline;
- передача результата в Recovery;
- нормализация последовательности;
- взаимодействие с существующим TradeStreamConsistencyController;
- формирование итогового TradeRecoveryResult.
---
# Архитектурные инварианты Build
После завершения Build 060.19 система обязана удовлетворять следующим инвариантам.
---
## Инвариант №1
Recovery никогда не работает с транспортными структурами.
---
## Инвариант №2
Recovery использует исключительно существующий REST Pipeline.
---
## Инвариант №3
Recovery использует исключительно существующий TradeStreamConsistencyController.
---
## Инвариант №4
Recovery никогда не реализует собственную дедупликацию.
---
## Инвариант №5
Recovery никогда не реализует собственную проверку порядка сделок.
---
## Инвариант №6
Recovery никогда не изменяет объект Trade.
---
## Инвариант №7
Recovery никогда не вычисляет диапазон восстановления самостоятельно.
---
## Инвариант №8
Recovery полностью независим от Runtime.
---
## Инвариант №9
Recovery полностью независим от WebSocket Transport.
---
## Инвариант №10
Recovery не изменяет архитектуру существующих Build серии 060.
Он исключительно дополняет Acquisition Layer новой автономной подсистемой восстановления истории.
---
# Итоги Build 060.19
После реализации Build 060.19 система впервые получает полноценный механизм безопасного восстановления исторических сделок.
При этом сохраняются все архитектурные принципы серии Build 060:
- единая Canonical Trade Model;
- единый REST Pipeline;
- единая система Validation;
- единый Mapper;
- единый Trade Stream Consistency Controller;
- единый Canonical Trade Stream.
Recovery не создаёт альтернативную архитектуру обработки сделок.
Напротив, он органично встраивается в уже существующий Acquisition Pipeline и использует ранее построенные компоненты без дублирования их ответственности.
Именно этот подход обеспечивает минимальную связанность подсистем, высокую тестируемость, предсказуемость поведения и возможность дальнейшего развития Runtime, Reconnect и Backfill-механизмов в последующих Build без изменения фундаментальной архитектуры Recovery.
---
# Приложение A. Architecture Decision Records (ADR)
---
# Назначение приложения
Настоящее приложение фиксирует все ключевые архитектурные решения, принятые при проектировании Build 060.19.
Основной документ описывает итоговую архитектуру системы.
ADR, в свою очередь, отвечает на другой вопрос:
> **Почему архитектура выглядит именно так?**
Каждый ADR фиксирует:
- исходную проблему;
- рассмотренные варианты;
- принятое решение;
- причины выбора;
- последствия данного решения.
После утверждения ADR считается частью архитектурного контракта системы.
Изменение любого ADR требует подготовки нового архитектурного решения и не допускается в процессе реализации Build.
---
# ADR-060.19-001
# Recovery использует существующий TradeStreamConsistencyController
## Статус
```text
Accepted
```
---
## Контекст
После появления механизма восстановления истории необходимо определить, каким образом проверяется корректность восстановленного потока.
К моменту начала Build уже существует полноценный компонент:
```text
TradeStreamConsistencyController
```
который гарантирует:
- дедупликацию;
- контроль порядка;
- обнаружение конфликтующих дублей;
- защиту Canonical Trade Stream.
Возникает вопрос:
должен ли Recovery реализовывать аналогичную функциональность самостоятельно?
---
## Рассмотренные варианты
### Вариант 1
Recovery реализует собственную дедупликацию.
Преимущества:
- независимость.
Недостатки:
- дублирование логики;
- появление второго источника истины;
- риск расхождения алгоритмов;
- двойное сопровождение.
---
### Вариант 2
Recovery реализует собственную проверку порядка.
Преимущества:
локальная автономность.
Недостатки:
- две различные реализации Ordering;
- вероятность различного поведения REST и WebSocket;
- нарушение принципа единственного владельца бизнес-правил.
---
### Вариант 3
Recovery полностью использует существующий TradeStreamConsistencyController.
Преимущества:
- единая логика проверки;
- единая дедупликация;
- единая модель Ordering;
- отсутствие дублирования;
- минимальная связанность.
Недостатков не обнаружено.
---
## Принятое решение
Build 060.19 использует исключительно существующий публичный контракт:
```python
accept(trade: Trade) -> Trade | None
```
Recovery никогда не реализует собственную проверку согласованности.
---
## Последствия
Во всей системе существует только один компонент, отвечающий за:
- Ordering;
- Deduplication;
- Conflict Detection.
Таким компонентом является:
```text
TradeStreamConsistencyController
```
Recovery остаётся исключительно оркестратором.
---
# ADR-060.19-002
# Recovery использует временные диапазоны вместо trade_id
## Статус
```text
Accepted
```
---
## Контекст
Необходимо определить способ получения исторических сделок.
Первоначально рассматривались два варианта:
- восстановление по trade_id;
- восстановление по времени.
---
## Исследование
Перед принятием решения было выполнено экспериментальное исследование REST API.
Подтверждено:
- `startTime` работает корректно;
- `endTime` работает корректно;
- совместное использование поддерживается;
- результаты детерминированы.
Одновременно было установлено:
использование:
```text
fromId
```
не обеспечивает надёжного позиционирования внутри истории.
Во многих случаях REST возвращает последнюю страницу сделок независимо от указанного значения.
Следовательно построение архитектуры Recovery вокруг `trade_id` признано небезопасным.
---
## Рассмотренные варианты
### Вариант 1
Использовать:
```text
fromId
```
Отклонён.
Причина:
контракт REST API не подтверждён экспериментально.
---
### Вариант 2
Использовать:
```text
trade_id
```
как внутренний курсор Runtime.
Отклонён.
Причины:
- привязка Recovery к внутреннему состоянию;
- невозможность гарантировать корректное восстановление;
- зависимость от неподтверждённого поведения биржи.
---
### Вариант 3
Использовать исключительно временной диапазон.
Принят.
---
## Принятое решение
Recovery использует только:
```text
start_time
end_time
```
Все остальные механизмы навигации исключены из архитектуры Build.
---
## Последствия
Recovery становится полностью независимым от внутренней структуры идентификаторов сделок.
Даже если биржа изменит механизм формирования `trade_id`, архитектура Recovery останется корректной.
---
# ADR-060.19-003
# Recovery не хранит собственного состояния
## Статус
```text
Accepted
```
---
## Контекст
Во время проектирования возник вопрос:
должен ли Recovery хранить информацию о последнем успешно восстановленном диапазоне?
Например:
```text
last_trade_id
или
last_timestamp
```
---
## Рассмотренные варианты
### Stateful Recovery
Recovery самостоятельно сохраняет:
- последний trade_id;
- последний timestamp;
- информацию о последнем запуске.
Преимущества:
локальная автономность.
Недостатки:
- необходимость хранения состояния;
- усложнение тестирования;
- зависимость от Runtime;
- необходимость восстановления собственного состояния после перезапуска.
---
### Stateless Recovery
Recovery ничего не хранит.
Все необходимые параметры приходят внутри Recovery Request.
Преимущества:
- простая архитектура;
- отсутствие собственного состояния;
- высокая тестируемость;
- независимость от Runtime;
- отсутствие необходимости синхронизации.
Недостатков не выявлено.
---
## Принятое решение
Recovery является полностью Stateless-компонентом.
Любая информация, необходимая для восстановления, передаётся исключительно через:
```text
TradeRecoveryRequest
```
---
## Последствия
Recovery становится полностью детерминированным.
При одинаковом запросе он всегда выполняет одинаковые действия независимо от предыдущих запусков.
---
# ADR-060.19-004
# Recovery полностью независим от Runtime
## Статус
```text
Accepted
```
---
## Контекст
После появления Recovery возник вопрос:
должен ли Recovery самостоятельно взаимодействовать с Runtime?
Например:
- получать Runtime Events;
- определять момент восстановления;
- инициировать переподключение;
- принимать решение о завершении Recovery.
---
## Рассмотренные варианты
### Вариант 1
Recovery становится частью Runtime.
Преимущества:
- тесная интеграция;
- меньше промежуточных компонентов.
Недостатки:
- высокая связанность;
- невозможность автономного тестирования;
- сложность повторного использования;
- нарушение принципа разделения ответственности.
---
### Вариант 2
Recovery представляет собой независимый сервис.
Runtime лишь вызывает его.
Преимущества:
- слабая связанность;
- простое тестирование;
- возможность автономного использования;
- отсутствие циклических зависимостей.
Недостатков не обнаружено.
---
## Принятое решение
Recovery ничего не знает о Runtime.
Recovery не знает:
- почему произошло восстановление;
- почему был потерян WebSocket;
- сколько времени отсутствовало соединение;
- требуется ли последующее переподключение.
Recovery выполняет только одну операцию:
```text
TradeRecoveryRequest
TradeRecoveryResult
```
---
## Последствия
Runtime становится владельцем жизненного цикла Recovery.
Recovery остаётся полностью независимым компонентом Acquisition Layer.
Интеграция между ними выполняется исключительно через публичный API.
---
# ADR-060.19-005
# Controller и Normalizer разделены
## Статус
```text
Accepted
```
---
## Контекст
Во время проектирования Recovery возник вопрос:
следует ли выполнять нормализацию последовательности непосредственно внутри Controller?
---
## Рассмотренные варианты
### Вариант 1
Controller самостоятельно выполняет:
- получение истории;
- нормализацию;
- передачу в Consistency.
Преимущество:
меньшее количество компонентов.
Недостатки:
- смешение обязанностей;
- увеличение размера Controller;
- снижение тестируемости;
- невозможность повторного использования алгоритма нормализации.
---
### Вариант 2
Выделить отдельный компонент:
```text
TradeRecoveryNormalizer
```
Преимущества:
- Single Responsibility;
- независимое тестирование;
- простая модификация алгоритма;
- повторное использование.
Недостатков не выявлено.
---
## Принятое решение
Recovery состоит минимум из двух независимых компонентов.
```text
TradeRecoveryController
TradeRecoveryNormalizer
```
Controller отвечает исключительно за оркестрацию.
Normalizer отвечает исключительно за порядок сделок.
---
## Последствия
Каждый компонент имеет одну ответственность.
Изменение алгоритма нормализации не требует изменения Controller.
---
# ADR-060.19-006
# RecoveryResult является отдельной Domain Model
## Статус
```text
Accepted
```
---
## Контекст
После завершения Recovery необходимо определить способ возврата результата.
Рассматривались различные модели.
---
## Рассмотренные варианты
### Вариант 1
Вернуть:
```python
tuple[Trade]
```
Недостатки:
- отсутствует информация о количестве пропущенных дублей;
- невозможно отличить пустой диапазон от полного дублирования;
- отсутствует описание завершённой операции.
---
### Вариант 2
Вернуть:
```python
list[Trade]
```
Недостатки аналогичны.
Кроме того, нарушается использование immutable-коллекций.
---
### Вариант 3
Создать отдельную доменную модель результата.
Преимущества:
- расширяемость;
- единый контракт;
- возможность добавления диагностической информации;
- отсутствие изменения публичного API в будущем.
---
## Принятое решение
Recovery возвращает исключительно:
```text
TradeRecoveryResult
```
Этот объект описывает уже завершённую операцию восстановления.
---
## Последствия
Публичный API Recovery остаётся стабильным.
В следующих Build возможно расширение модели результата без изменения сигнатур Controller.
---
# ADR-060.19-007
# Recovery не реализует Retry Policy
## Статус
```text
Accepted
```
---
## Контекст
Во время проектирования возник вопрос:
следует ли Recovery автоматически повторять REST-запросы при временных ошибках сети?
---
## Рассмотренные варианты
### Вариант 1
Автоматический Retry внутри Recovery.
Преимущества:
- уменьшение количества временных ошибок.
Недостатки:
- появление внутреннего состояния;
- усложнение Recovery;
- необходимость настройки стратегий ожидания;
- смешение обязанностей;
- невозможность централизованного управления повторными попытками.
---
### Вариант 2
Retry полностью принадлежит Runtime.
Recovery выполняет единственную попытку.
При ошибке возвращает управление вызывающему компоненту.
Преимущества:
- простая архитектура;
- единая стратегия повторных попыток;
- отсутствие дублирования Retry между компонентами;
- соответствие принципу Single Responsibility.
Недостатков не выявлено.
---
## Принятое решение
Build 060.19 не реализует Retry Policy.
Recovery выполняет одну попытку получения исторических данных.
Любая транспортная ошибка немедленно распространяется вызывающему компоненту.
---
## Последствия
Recovery остаётся простым и детерминированным.
Все стратегии:
- Retry;
- Exponential Backoff;
- Circuit Breaker;
- ограничение количества попыток;
- таймеры ожидания;
будут реализованы на уровне Runtime в последующих Build.
---
# Итоги приложения A
Настоящие ADR фиксируют фундаментальные архитектурные решения Build 060.19.
Они определяют не только текущее устройство Recovery, но и границы его дальнейшего развития.
Любая будущая модификация Recovery должна проверяться на соответствие данным решениям.
Если новое архитектурное решение противоречит одному из настоящих ADR, оно не может быть реализовано без подготовки нового Architecture Decision Record и пересмотра архитектурной спецификации Build.
---
# Приложение B. Диаграммы последовательностей
---
# Назначение приложения
Настоящее приложение фиксирует последовательности взаимодействия компонентов Recovery.
В отличие от основной части документа, описывающей архитектурные сущности и их ответственность, настоящее приложение показывает:
- порядок вызова компонентов;
- направление передачи данных;
- точки возникновения исключений;
- завершение успешных и неуспешных сценариев.
Все диаграммы являются частью архитектурного контракта Build 060.19.
---
# Обозначения
Во всех диаграммах используются одинаковые обозначения.
```text
Синхронный вызов
```
---
```text
Возврат результата
```
---
```text
X
Возникновение исключения
```
---
```text
Успешное завершение этапа
```
---
# Диаграмма 1
# Полный успешный сценарий Recovery
```text
TradeRecoveryController
DzengiTradesDocumentSource
REST Request (start/end)
REST Response (Document)
REST Parser
Value Validation
REST Mapper
tuple[Trade]
TradeRecoveryNormalizer
ASCENDING tuple
TradeStreamConsistencyController
accept(trade #1)
accepted
accept(trade #2)
accepted
...
TradeRecoveryResult
Recovery Done
```
---
# Основные свойства сценария
В данном сценарии:
- REST завершился успешно;
- Parser завершился успешно;
- Validation завершилась успешно;
- Mapper завершился успешно;
- Normalizer завершился успешно;
- все сделки прошли Stream Consistency.
Recovery завершается формированием результата.
---
# Диаграмма 2
# Восстановление пустого диапазона
```text
TradeRecoveryController
REST Source
REST Response
()
TradeRecoveryNormalizer
()
TradeRecoveryResult
Recovered Trades = 0
Skipped Duplicates = 0
```
---
# Основные свойства сценария
Пустой диапазон не считается ошибкой.
Recovery завершается успешно.
TradeStreamConsistencyController не вызывается.
---
# Диаграмма 3
# Полное дублирование
```text
REST
tuple[Trade]
Normalizer
Trade #1
Consistency
None
Trade #2
Consistency
None
Trade #3
Consistency
None
TradeRecoveryResult
Recovered = 0
Duplicates = 3
```
---
# Основные свойства сценария
Все сделки уже присутствуют в Canonical Trade Stream.
Recovery:
- не генерирует ошибку;
- не публикует сделки;
- успешно завершается.
---
# Диаграмма 4
# Частичное восстановление
```text
REST
Trade #100
Consistency
None
────────────
Trade #105
Consistency
Trade
────────────
Trade #121
Consistency
Trade
────────────
Trade #140
Consistency
Trade
────────────
TradeRecoveryResult
Recovered = 3
Duplicates = 1
```
---
# Основные свойства сценария
Это основной рабочий сценарий Recovery.
Часть сделок уже существует.
Остальные успешно публикуются.
Recovery завершается успешно.
---
# Диаграмма 5
# REST возвращает DESCENDING последовательность
```text
REST
140
121
105
100
TradeRecoveryNormalizer
100
105
121
140
TradeStreamConsistencyController
```
---
# Основные свойства сценария
Нормализация выполняется полностью внутри RecoveryNormalizer.
TradeStreamConsistencyController получает уже канонический поток.
Recovery никогда не передаёт Controller последовательность в обратном порядке.
---
# Диаграмма 6
# REST возвращает ASCENDING последовательность
```text
REST
100
105
121
140
TradeRecoveryNormalizer
100
105
121
140
TradeStreamConsistencyController
```
---
# Основные свойства сценария
Несмотря на то, что текущий контракт REST API предусматривает выдачу сделок в порядке DESCENDING, RecoveryNormalizer проектируется универсальным.
Если в будущем REST API начнёт возвращать последовательность уже в каноническом порядке, Recovery не потребует изменений архитектуры.
Normalizer обнаружит, что последовательность уже соответствует Canonical Trade Stream, и вернёт её без изменений.
---
# Диаграмма 7
# REST возвращает смешанный порядок
```text
REST
100
140
121
180
TradeRecoveryNormalizer
X
TradeRecoveryNormalizationError
```
---
# Основные свойства сценария
Смешанный порядок рассматривается как нарушение транспортного контракта.
Recovery не предпринимает попыток:
- отсортировать последовательность;
- определить правильный порядок;
- восстановить повреждённые данные.
Работа немедленно прекращается.
Ни одна сделка не передаётся в TradeStreamConsistencyController.
---
# Диаграмма 8
# Ошибка Parser
```text
TradeRecoveryController
REST Source
REST Parser
X
ParserError
```
---
# Основные свойства сценария
Recovery не вмешивается в работу Parser.
Parser остаётся единственным владельцем транспортной валидации структуры документа.
Recovery лишь распространяет возникшее исключение вызывающему компоненту.
---
# Диаграмма 9
# Ошибка Value Validation
```text
REST
Parser
Value Validation
X
ValidationError
```
---
# Основные свойства сценария
Если хотя бы одна сделка не проходит существующую систему проверки значений,
Recovery немедленно прекращает выполнение.
TradeRecoveryResult не создаётся.
---
# Диаграмма 10
# Ошибка Mapper
```text
REST
Parser
Validation
Mapper
X
MappingError
```
---
# Основные свойства сценария
Recovery никогда не работает с частично сформированными объектами.
Если Mapper не смог построить корректный объект Trade,
дальнейшая обработка невозможна.
---
# Диаграмма 11
# Ошибка Stream Consistency
```text
REST
Parser
Validation
Mapper
TradeRecoveryNormalizer
TradeStreamConsistencyController
Trade #100
accepted
Trade #105
X
TradeOrderingError
```
---
# Основные свойства сценария
Recovery не перехватывает исключения Consistency Layer.
После возникновения ошибки:
- дальнейшая обработка прекращается;
- TradeRecoveryResult не создаётся;
- исключение распространяется вверх.
Таким образом владельцем логики проверки потока остаётся исключительно Build 060.18.
---
# Диаграмма 12
# Полный жизненный цикл Recovery
```text
TradeRecoveryRequest
TradeRecoveryController
DzengiTradesDocumentSource
REST Document
Parser
Value Validation
Mapper
tuple[Trade]
TradeRecoveryNormalizer
Normalized tuple[Trade]
TradeStreamConsistencyController
TradeRecoveryResult
Caller
```
---
# Архитектурные выводы
Все приведённые диаграммы подтверждают несколько фундаментальных принципов Build 060.19.
---
## Принцип №1
Recovery никогда не работает с транспортными структурами после завершения этапа Mapping.
Начиная с момента формирования объекта `Trade`, Recovery использует исключительно каноническую доменную модель.
---
## Принцип №2
Recovery не изменяет существующий Acquisition Pipeline.
Он расширяет его, добавляя дополнительный этап между REST Adapter и TradeStreamConsistencyController.
---
## Принцип №3
Recovery не принимает доменных решений.
Все решения относительно:
- корректности сделки;
- порядка сделок;
- дубликатов;
- конфликтов;
по-прежнему принадлежат TradeStreamConsistencyController.
---
## Принцип №4
Recovery является полностью линейным Pipeline.
Каждый компонент вызывается строго один раз.
Обратные переходы отсутствуют.
Циклические зависимости отсутствуют.
---
## Принцип №5
Любое исключение немедленно завершает выполнение Recovery.
Частично завершённое восстановление не считается успешным.
TradeRecoveryResult формируется только после успешного прохождения всех этапов Pipeline.
---
# Итоги приложения B
Последовательности взаимодействия, приведённые в настоящем приложении, являются нормативным описанием поведения Recovery.
Любая реализация Build 060.19 должна соответствовать данным диаграммам.
Изменение порядка взаимодействия компонентов, появление дополнительных зависимостей или перенос ответственности между компонентами допускаются только после подготовки нового архитектурного решения (ADR) и внесения соответствующих изменений в архитектурную спецификацию.
---
# Приложение C. Контракты публичного API
---
# Назначение приложения
Настоящее приложение фиксирует официальный публичный API подсистемы Trade Recovery.
Основной документ описывает архитектуру Recovery.
Настоящее приложение определяет:
- публичные классы;
- публичные методы;
- входные параметры;
- возвращаемые значения;
- предусловия;
- постусловия;
- архитектурные инварианты.
Любой внешний компонент системы имеет право взаимодействовать с Recovery исключительно через описанные здесь контракты.
Все остальные классы Recovery считаются внутренней реализацией Build 060.19.
---
# Общие принципы публичного API
Recovery строится на следующих принципах.
---
## Минимальность
Публичный API должен содержать только действительно необходимые точки входа.
Recovery не предоставляет вспомогательных методов.
---
## Детерминированность
При одинаковых входных данных любой публичный метод обязан возвращать одинаковый результат.
---
## Отсутствие побочных эффектов
Ни один публичный метод Recovery не изменяет:
- состояние Runtime;
- состояние WebSocket;
- состояние подписок.
Recovery воздействует исключительно на Canonical Trade Stream через существующий TradeStreamConsistencyController.
---
## Иммутабельность
Все входные модели Recovery являются неизменяемыми.
Recovery никогда не модифицирует переданные объекты.
---
# Публичный класс
# TradeRecoveryController
---
## Назначение
TradeRecoveryController является единственной точкой входа в Recovery Pipeline.
Никакой другой компонент Recovery не должен вызываться напрямую внешними подсистемами.
---
## Ответственность
Контроллер отвечает исключительно за выполнение полного цикла восстановления.
Он:
- получает Recovery Request;
- инициирует получение исторических сделок;
- выполняет нормализацию;
- передаёт сделки в Stream Consistency;
- формирует итоговый результат.
---
## Публичный контракт
```python
recover(
request: TradeRecoveryRequest,
) -> TradeRecoveryResult
```
---
## Входные параметры
```text
TradeRecoveryRequest
```
Запрос восстановления.
---
## Возвращаемое значение
```text
TradeRecoveryResult
```
Описание завершённой операции восстановления.
---
## Возможные исключения
Контроллер может распространять:
```text
TradeRecoveryRequestError
```
---
```text
TradeRecoveryNormalizationError
```
---
```text
TradeConsistencyError
```
---
```text
TradeOrderingError
```
---
а также исключения существующих компонентов:
- REST;
- Parser;
- Mapper;
- Value Validation.
---
## Предусловия
Перед вызовом метода должны выполняться следующие условия.
- Recovery Request успешно создан.
- Все обязательные поля заполнены.
- Диапазон времени корректен.
- RecoveryController полностью сконфигурирован.
---
## Постусловия
При успешном завершении гарантируется:
- все сделки обработаны;
- все сделки прошли через TradeStreamConsistencyController;
- сформирован TradeRecoveryResult.
---
## Побочные эффекты
Единственным допустимым побочным эффектом является публикация новых сделок в существующий Canonical Trade Stream.
Других изменений состояния системы Controller не выполняет.
---
# Публичный класс
# TradeRecoveryRequest
---
## Назначение
TradeRecoveryRequest описывает один запрос восстановления.
После создания объект никогда не изменяется.
---
## Обязательные поля
```text
symbol
```
---
```text
start_time
```
---
```text
end_time
```
---
## Необязательные поля
```text
limit
```
---
## Инварианты
Всегда выполняются условия.
```text
start_time < end_time
```
---
```text
limit ∈ [1;1000]
```
если limit указан.
---
Запрос относится только к одному символу.
---
Объект является immutable.
---
## Предусловия создания
Все поля проходят базовую проверку корректности.
---
## Постусловия создания
После создания Recovery Request считается валидным и может использоваться RecoveryController.
---
# Публичный класс
# TradeRecoveryResult
---
## Назначение
TradeRecoveryResult описывает итог выполнения Recovery.
Он создаётся исключительно после успешного завершения Recovery Pipeline.
---
## Основные поля
```text
Recovered Trades
```
Количество новых опубликованных сделок.
---
```text
Skipped Duplicates
```
Количество корректных дублей.
---
## Инварианты
Количество восстановленных сделок всегда больше либо равно нулю.
Количество пропущенных дублей всегда больше либо равно нулю.
Все значения являются согласованными относительно завершённого Recovery Pipeline.
---
## Предусловия создания
Recovery успешно завершён.
---
## Постусловия
Result полностью описывает завершённую операцию.
После создания объект не изменяется.
---
# Внутренний публичный компонент
# TradeRecoveryNormalizer
---
## Назначение
Несмотря на то, что Normalizer используется только Controller, его контракт фиксируется отдельно.
Это позволяет независимо тестировать данный компонент.
---
## Публичный контракт
```python
normalize(
trades: tuple[Trade, ...],
) -> tuple[Trade, ...]
```
---
## Входные параметры
Последовательность Canonical Trade.
---
## Возвращаемое значение
Та же последовательность,
но гарантированно приведённая к каноническому порядку.
---
## Возможные исключения
```text
TradeRecoveryNormalizationError
```
---
## Предусловия
Все элементы коллекции являются корректными объектами Trade.
---
## Постусловия
Количество элементов сохраняется.
Ни один объект Trade не изменяется.
Допускается изменение исключительно порядка следования элементов.
---
# Контракт взаимодействия компонентов
Настоящий раздел определяет допустимые направления вызовов между компонентами Recovery.
---
# Допустимые зависимости
TradeRecoveryController имеет право зависеть от:
```text
TradeRecoveryRequest
```
---
```text
DzengiTradesDocumentSource
```
---
```text
REST Adapter
```
---
```text
TradeRecoveryNormalizer
```
---
```text
TradeStreamConsistencyController
```
---
```text
TradeRecoveryResult
```
Других зависимостей Controller иметь не должен.
---
# Недопустимые зависимости
TradeRecoveryController не должен зависеть от:
- Runtime;
- Reconnect;
- Subscription Manager;
- WebSocket Transport;
- Event Bus;
- Scheduler;
- Timer;
- Retry Engine.
Появление подобных зависимостей рассматривается как нарушение архитектуры Build 060.19.
---
# Контракт TradeRecoveryNormalizer
Normalizer представляет собой полностью детерминированную функцию.
Он зависит исключительно от:
```text
tuple[Trade]
```
Никакие другие объекты системы ему не требуются.
---
## Запрещённые зависимости Normalizer
Normalizer никогда не взаимодействует с:
- REST;
- Runtime;
- WebSocket;
- Controller;
- Repository;
- Cache;
- Configuration;
- Logger.
Это обеспечивает возможность полностью изолированного тестирования.
---
# Контракт взаимодействия с REST Pipeline
Recovery не обращается к REST API напрямую.
Единственная допустимая точка взаимодействия —
существующий транспортный источник.
Архитектурная схема выглядит следующим образом.
```text
TradeRecoveryController
DzengiTradesDocumentSource
REST API
```
Recovery не формирует HTTP-запросы самостоятельно.
Recovery не знает формат REST-документа.
Recovery не знает структуру JSON.
---
# Контракт взаимодействия с Parser
Recovery никогда не вызывает Parser напрямую.
Вызов Parser осуществляется исключительно существующим REST Adapter.
Таким образом сохраняется единый Pipeline преобразования транспортных данных.
---
# Контракт взаимодействия с Mapper
Recovery никогда не создаёт объект Trade самостоятельно.
Появление Canonical Trade возможно исключительно после успешного завершения Mapper.
Recovery использует только уже готовые доменные объекты.
---
# Контракт взаимодействия со Stream Consistency
TradeRecoveryController взаимодействует с TradeStreamConsistencyController исключительно через его публичный API.
Recovery запрещается:
- изменять внутреннее состояние Controller;
- обращаться к внутренним коллекциям;
- использовать приватные методы;
- обходить механизм `accept()`.
Таким образом сохраняется строгая инкапсуляция Consistency Layer.
---
# Контракт взаимодействия с Runtime
В Build 060.19 Runtime не является участником Recovery Pipeline.
Единственная допустимая форма взаимодействия:
```text
Runtime
TradeRecoveryController
TradeRecoveryResult
```
Runtime рассматривается исключительно как вызывающая сторона.
Recovery не знает о его внутреннем устройстве.
---
# Контракт расширяемости
Recovery проектируется с учётом последующего развития системы.
Следующие компоненты могут быть добавлены без изменения существующего публичного API.
Например:
```text
Retry Policy
```
---
```text
Metrics
```
---
```text
Tracing
```
---
```text
Telemetry
```
---
```text
Performance Monitor
```
---
```text
Recovery Statistics
```
Все перечисленные расширения должны использовать композицию.
Изменение публичного API Recovery не допускается.
---
# Контракт потокобезопасности
Build 060.19 не накладывает требований на многопоточную обработку.
Recovery выполняет один запрос восстановления за один вызов.
Параллельное выполнение нескольких Recovery Pipeline не входит в Scope настоящего Build.
Вопрос конкурентного восстановления нескольких символов рассматривается в последующих Build.
---
# Контракт детерминированности
Recovery обязан удовлетворять следующему свойству.
При одинаковых:
- Recovery Request;
- ответе REST;
- состоянии TradeStreamConsistencyController;
результат выполнения обязан быть идентичным.
Данное свойство считается обязательным архитектурным инвариантом публичного API.
---
# Контракт обратной совместимости
После утверждения настоящего документа публичный API Recovery считается стабильным.
Изменение следующих сущностей требует подготовки нового архитектурного решения (ADR):
- сигнатуры `recover()`;
- структуры `TradeRecoveryRequest`;
- структуры `TradeRecoveryResult`;
- публичного контракта `TradeRecoveryNormalizer`.
Допускается только расширение функциональности без нарушения существующих контрактов.
---
# Матрица ответственности публичных компонентов
| Компонент | Основная ответственность | Не отвечает за |
|-----------|--------------------------|----------------|
| TradeRecoveryController | Оркестрация полного процесса Recovery | Runtime, Retry, Deduplication, Ordering |
| TradeRecoveryRequest | Описание параметров восстановления | Вычисление диапазона восстановления |
| TradeRecoveryNormalizer | Нормализация порядка сделок | REST, Validation, Deduplication |
| TradeRecoveryResult | Представление результата Recovery | Хранение внутреннего состояния Recovery |
---
# Матрица владения бизнес-правилами
| Бизнес-правило | Владелец |
|----------------|----------|
| Получение исторических данных | DzengiTradesDocumentSource |
| Разбор REST-документа | REST Parser |
| Проверка корректности значений | Value Validation |
| Построение Canonical Trade | REST Mapper |
| Нормализация порядка | TradeRecoveryNormalizer |
| Проверка порядка сделок | TradeStreamConsistencyController |
| Дедупликация | TradeStreamConsistencyController |
| Формирование результата Recovery | TradeRecoveryController |
---
# Итоги приложения C
Настоящее приложение фиксирует публичный API Build 060.19 и определяет архитектурные границы взаимодействия Recovery с остальными подсистемами Dzentra.
После утверждения настоящего приложения любые новые компоненты должны интегрироваться с Recovery исключительно через описанные контракты.
Это обеспечивает:
- минимальную связанность подсистем;
- стабильность публичного API;
- возможность безопасного расширения функциональности в последующих Build без нарушения существующей архитектуры.
---
# Приложение D. Сценарии выполнения Recovery
---
# Назначение приложения
Настоящее приложение содержит нормативные сценарии выполнения Build 060.19.
Если приложение B описывает последовательности взаимодействия компонентов, то настоящее приложение описывает ожидаемое поведение Recovery при различных входных данных.
Каждый сценарий фиксирует:
- исходное состояние системы;
- последовательность действий;
- ожидаемый результат;
- архитектурные инварианты.
Данные сценарии являются эталоном для разработки интеграционных тестов Build 060.19.
---
# Scenario 1
# Полное успешное восстановление
## Исходное состояние
В системе существует активный Canonical Trade Stream.
Recovery получает корректный диапазон времени.
REST API возвращает четыре сделки.
```text
140
121
105
100
```
---
## Последовательность выполнения
1. RecoveryController получает TradeRecoveryRequest.
2. Выполняется REST-запрос.
3. REST Adapter строит четыре объекта Trade.
4. TradeRecoveryNormalizer определяет обратный порядок.
5. Последовательность нормализуется.
```text
100
105
121
140
```
6. Каждая сделка последовательно передаётся в TradeStreamConsistencyController.
7. Все сделки успешно принимаются.
8. Формируется TradeRecoveryResult.
---
## Ожидаемый результат
```text
Recovered Trades = 4
Skipped Duplicates = 0
```
Recovery завершается успешно.
---
## Проверяемые инварианты
- нормализация выполнена;
- все сделки прошли через Stream Consistency;
- ни одна сделка не была изменена;
- результат сформирован.
---
# Scenario 2
# В указанном диапазоне отсутствуют сделки
## Исходное состояние
REST возвращает пустую последовательность.
```text
()
```
---
## Последовательность выполнения
1. RecoveryController выполняет REST-запрос.
2. Parser успешно обрабатывает ответ.
3. Mapper формирует пустую коллекцию.
4. Normalizer получает пустую последовательность.
5. Stream Consistency не вызывается.
6. Формируется TradeRecoveryResult.
---
## Ожидаемый результат
```text
Recovered Trades = 0
Skipped Duplicates = 0
```
Recovery завершается успешно.
---
## Проверяемые инварианты
Пустой диапазон не считается ошибкой.
---
# Scenario 3
# Все сделки являются корректными дубликатами
## Исходное состояние
REST возвращает:
```text
100
105
121
```
Все три сделки уже присутствуют в Canonical Trade Stream.
---
## Последовательность выполнения
Каждая сделка последовательно передаётся Controller.
Controller возвращает:
```python
None
```
для каждой сделки.
После завершения обработки создаётся TradeRecoveryResult.
---
## Ожидаемый результат
```text
Recovered Trades = 0
Skipped Duplicates = 3
```
---
## Проверяемые инварианты
Recovery не считает корректные дубликаты ошибкой.
---
# Scenario 4
# Частичное восстановление диапазона
## Исходное состояние
REST возвращает:
```text
140
121
105
100
```
Controller определяет:
```text
100 -> duplicate
105 -> accepted
121 -> accepted
140 -> accepted
```
---
## Последовательность выполнения
Recovery нормализует поток.
Каждая сделка проходит через Controller.
Дубликат исключается.
Три новые сделки публикуются.
---
## Ожидаемый результат
```text
Recovered Trades = 3
Skipped Duplicates = 1
```
---
## Проверяемые инварианты
Recovery не прекращает выполнение при обнаружении корректного дубликата.
---
# Scenario 5
# REST возвращает ASCENDING последовательность
## Исходное состояние
REST возвращает:
```text
100
105
121
140
```
---
## Последовательность выполнения
Normalizer анализирует направление.
Последовательность уже соответствует Canonical Trade Stream.
Изменения не выполняются.
---
## Ожидаемый результат
Все сделки передаются Controller в первоначальном порядке.
---
## Проверяемые инварианты
Normalizer не выполняет лишних преобразований.
Последовательность сохраняется без изменений.
---
# Scenario 6
# REST возвращает DESCENDING последовательность
## Исходное состояние
REST возвращает:
```text
140
121
105
100
```
---
## Последовательность выполнения
Normalizer определяет обратное направление.
Выполняется нормализация.
Полученный поток передаётся Controller.
---
## Ожидаемый результат
Controller получает:
```text
100
105
121
140
```
---
## Проверяемые инварианты
TradeStreamConsistencyController никогда не получает последовательность в обратном порядке.
---
# Scenario 7
# REST возвращает смешанный порядок
## Исходное состояние
REST возвращает последовательность:
```text
100
150
121
180
```
Последовательность не является ни возрастающей, ни убывающей.
---
## Последовательность выполнения
1. RecoveryController получает результат REST Pipeline.
2. Последовательность передаётся в TradeRecoveryNormalizer.
3. Normalizer анализирует направление последовательности.
4. Обнаруживается нарушение архитектурного инварианта.
5. Генерируется:
```text
TradeRecoveryNormalizationError
```
6. Выполнение Recovery прекращается.
---
## Ожидаемый результат
Recovery завершается ошибкой.
TradeStreamConsistencyController не вызывается.
TradeRecoveryResult не создаётся.
---
## Проверяемые инварианты
- смешанная последовательность не допускается;
- Recovery не пытается исправить поток;
- никакие сделки не публикуются.
---
# Scenario 8
# Ошибка Parser
## Исходное состояние
REST возвращает документ с нарушением транспортного контракта.
Parser обнаруживает ошибку структуры.
---
## Последовательность выполнения
1. RecoveryController инициирует получение истории.
2. REST Adapter вызывает Parser.
3. Parser генерирует исключение.
4. Recovery прекращает выполнение.
---
## Ожидаемый результат
TradeRecoveryResult отсутствует.
Исключение распространяется вызывающему компоненту.
---
## Проверяемые инварианты
Recovery не вмешивается в транспортную обработку данных.
Parser остаётся единственным владельцем логики разбора REST-документа.
---
# Scenario 9
# Ошибка Value Validation
## Исходное состояние
Parser успешно завершил обработку.
Во время проверки значений обнаружено нарушение одного из инвариантов Canonical Trade.
---
## Последовательность выполнения
1. Parser завершает работу.
2. Запускается Value Validation.
3. Validation обнаруживает некорректное значение.
4. Генерируется исключение Validation.
5. Recovery прекращает выполнение.
---
## Ожидаемый результат
TradeRecoveryResult отсутствует.
Никакие сделки не публикуются.
---
## Проверяемые инварианты
Recovery никогда не работает с объектами, не прошедшими Validation.
---
# Scenario 10
# Ошибка Mapper
## Исходное состояние
Parser и Validation успешно завершены.
Mapper не способен сформировать объект Canonical Trade.
---
## Последовательность выполнения
1. Recovery получает транспортные данные.
2. Mapper генерирует исключение.
3. Recovery немедленно завершает выполнение.
---
## Ожидаемый результат
TradeRecoveryResult отсутствует.
TradeRecoveryNormalizer не вызывается.
---
## Проверяемые инварианты
Recovery никогда не получает частично сформированные объекты Trade.
---
# Scenario 11
# Ошибка TradeStreamConsistencyController
## Исходное состояние
REST Pipeline завершился успешно.
Normalizer успешно выполнил нормализацию.
Во время обработки одной из сделок Controller обнаруживает нарушение инвариантов потока.
---
## Последовательность выполнения
1. Recovery начинает последовательную передачу сделок.
2. Первые сделки успешно принимаются.
3. Для очередной сделки Controller генерирует:
```text
TradeOrderingError
```
или
```text
TradeConsistencyError
```
4. Recovery прекращает выполнение.
---
## Ожидаемый результат
TradeRecoveryResult отсутствует.
Исключение распространяется вызывающему компоненту.
---
## Проверяемые инварианты
Recovery не скрывает ошибки Stream Consistency.
Recovery не пытается продолжить обработку.
---
# Scenario 12
# Ошибка получения исторических данных
## Исходное состояние
Во время обращения к REST API возникает транспортная ошибка.
Например:
- Timeout;
- Network Error;
- HTTP Error.
---
## Последовательность выполнения
1. RecoveryController вызывает DzengiTradesDocumentSource.
2. Источник генерирует исключение.
3. Recovery немедленно завершает выполнение.
---
## Ожидаемый результат
TradeRecoveryResult отсутствует.
Повторный запрос не выполняется.
---
## Проверяемые инварианты
Retry Policy отсутствует.
Recovery выполняет только одну попытку получения истории.
---
# Матрица покрытия сценариев
| Сценарий | REST | Normalizer | Stream Consistency | Result |
|-----------|------|------------|--------------------|--------|
| Полное восстановление | ✓ | ✓ | ✓ | ✓ |
| Пустой диапазон | ✓ | ✓ | — | ✓ |
| Полные дубликаты | ✓ | ✓ | ✓ | ✓ |
| Частичное восстановление | ✓ | ✓ | ✓ | ✓ |
| ASCENDING | ✓ | ✓ | ✓ | ✓ |
| DESCENDING | ✓ | ✓ | ✓ | ✓ |
| Смешанный порядок | ✓ | ✗ | — | ✗ |
| Ошибка Parser | ✗ | — | — | ✗ |
| Ошибка Validation | ✗ | — | — | ✗ |
| Ошибка Mapper | ✗ | — | — | ✗ |
| Ошибка Consistency | ✓ | ✓ | ✗ | ✗ |
| Ошибка REST | ✗ | — | — | ✗ |
---
# Использование сценариев
Настоящие сценарии являются нормативной моделью поведения Recovery.
Они используются как основа для:
- Unit-тестирования;
- Integration-тестирования;
- проверки архитектурных инвариантов;
- регрессионного тестирования последующих Build серии 060.
Каждый новый сценарий, добавляемый в Recovery, должен сопровождаться соответствующим дополнением настоящего приложения.
---
# Итоги приложения D
Настоящее приложение описывает эталонное поведение подсистемы Recovery в наиболее важных эксплуатационных ситуациях.
Любая реализация Build 060.19 должна демонстрировать поведение, полностью соответствующее данным сценариям.
Отклонение от описанных сценариев допускается только после внесения изменений в архитектурную спецификацию и утверждения нового Architecture Decision Record (ADR).
---
# Приложение E. План реализации Build 060.19
---
# Назначение приложения
Настоящее приложение определяет официальный план реализации Build 060.19.
Основной документ фиксирует архитектуру Recovery.
Настоящее приложение определяет последовательность разработки, которая обеспечивает:
- минимальные изменения существующего кода;
- отсутствие нарушения архитектуры предыдущих Build;
- возможность тестирования каждого этапа отдельно;
- безопасную интеграцию Recovery в существующую систему.
Все этапы должны выполняться строго последовательно.
Переход к следующему этапу допускается только после полного завершения предыдущего.
---
# Общая последовательность реализации
```text
Этап 1
Recovery Domain Models
Этап 2
Recovery Normalizer
Этап 3
Recovery Controller
Этап 4
Unit Tests
Этап 5
Integration Tests
Build Complete
```
---
# Этап 1
# Recovery Domain Models
---
## Цель
Создать все доменные модели Build 060.19.
На данном этапе не реализуется никакая бизнес-логика.
Создаются исключительно структуры данных.
---
## Новые сущности
Минимальный состав моделей:
```text
TradeRecoveryRequest
```
---
```text
TradeRecoveryResult
```
---
```text
TradeRecoveryError
```
---
```text
TradeRecoveryRequestError
```
---
```text
TradeRecoveryNormalizationError
```
---
## Требования
Все модели должны быть:
- immutable;
- полностью типизированными;
- независимыми от Runtime;
- независимыми от REST.
---
## Не допускается
На данном этапе запрещается:
- выполнять Recovery;
- обращаться к REST;
- создавать Controller;
- изменять существующий Pipeline.
---
## Критерии завершения
Этап считается завершённым, если:
- все модели реализованы;
- модели проходят статическую проверку типов;
- модели покрыты Unit-тестами.
---
# Этап 2
# TradeRecoveryNormalizer
---
## Цель
Реализовать алгоритм нормализации порядка сделок.
---
## Функциональность
Normalizer обязан:
- принимать tuple[Trade];
- определять направление последовательности;
- возвращать ASCENDING поток;
- обнаруживать смешанный порядок;
- генерировать TradeRecoveryNormalizationError.
---
## Не допускается
Normalizer не должен:
- обращаться к REST;
- использовать Runtime;
- использовать Controller;
- выполнять дедупликацию;
- изменять Trade.
---
## Минимальный набор тестов
Проверяются сценарии:
- пустой поток;
- одна сделка;
- ASCENDING;
- DESCENDING;
- смешанный порядок.
---
## Критерии завершения
Этап считается завершённым после успешного прохождения всех Unit-тестов Normalizer.
---
# Этап 3
# TradeRecoveryController
---
## Цель
Реализовать центральный компонент Recovery.
---
## Функциональность
Controller обязан:
- принять Recovery Request;
- вызвать существующий REST Pipeline;
- вызвать Normalizer;
- передать сделки в TradeStreamConsistencyController;
- сформировать TradeRecoveryResult.
---
## Используемые компоненты
Controller использует только существующие компоненты системы.
Никаких новых транспортных механизмов не создаётся.
---
## Не допускается
Controller не должен:
- вычислять диапазон времени;
- выполнять Retry;
- работать с Runtime;
- работать с WebSocket;
- выполнять дедупликацию.
---
## Критерии завершения
Controller успешно проходит все Unit-тесты.
---
# Этап 4
# Unit Testing
---
## Цель
Проверить корректность каждого компонента Recovery изолированно.
---
## Проверяемые компоненты
```text
TradeRecoveryRequest
```
---
```text
TradeRecoveryResult
```
---
```text
TradeRecoveryNormalizer
```
---
```text
TradeRecoveryController
```
---
## Требования
Unit-тесты не должны обращаться:
- к REST API;
- к Runtime;
- к WebSocket.
Все внешние зависимости заменяются Mock-объектами.
---
## Критерии завершения
Все Unit-тесты успешно проходят.
Покрываются все архитектурные сценарии, определённые приложением D.
---
# Этап 5
# Integration Testing
---
## Цель
Проверить корректность работы Recovery как единой подсистемы Acquisition Layer.
На данном этапе тестируются взаимодействия между уже существующими компонентами системы.
Проверяется не отдельная логика компонентов, а корректность всей цепочки обработки исторических сделок.
---
## Интегрируемые компоненты
В интеграционных тестах участвуют:
```text
DzengiTradesDocumentSource
```
```text
REST Adapter
```
```text
TradeRecoveryNormalizer
```
```text
TradeStreamConsistencyController
```
```text
TradeRecoveryResult
```
---
## Проверяемые сценарии
Минимальный набор интеграционных тестов включает:
### Сценарий 1
Полное успешное восстановление.
---
### Сценарий 2
Пустой диапазон.
---
### Сценарий 3
Полное дублирование.
---
### Сценарий 4
Частичное восстановление.
---
### Сценарий 5
REST возвращает DESCENDING поток.
---
### Сценарий 6
REST возвращает ASCENDING поток.
---
### Сценарий 7
REST возвращает смешанную последовательность.
---
### Сценарий 8
TradeStreamConsistencyController обнаруживает ошибку порядка.
---
### Сценарий 9
REST Pipeline завершается исключением.
---
## Критерии завершения
Этап считается завершённым при выполнении следующих условий.
- Все интеграционные тесты успешно проходят.
- Все архитектурные инварианты подтверждены.
- Ни один существующий Build не требует изменения своей архитектуры.
- Recovery полностью совместим с существующим Canonical Trade Stream.
---
# Проверка соответствия архитектуре
После завершения реализации выполняется архитектурная проверка Build.
Проверяются следующие требования.
---
## Проверка №1
Recovery использует существующий REST Pipeline.
---
## Проверка №2
Recovery использует существующий TradeStreamConsistencyController.
---
## Проверка №3
Recovery не содержит собственной реализации Deduplication.
---
## Проверка №4
Recovery не содержит собственной реализации Ordering.
---
## Проверка №5
Recovery не зависит от Runtime.
---
## Проверка №6
Recovery не зависит от WebSocket Transport.
---
## Проверка №7
Recovery не изменяет Canonical Trade Model.
---
## Проверка №8
Recovery не изменяет Parser.
---
## Проверка №9
Recovery не изменяет Mapper.
---
## Проверка №10
Recovery не изменяет Value Validation.
---
## Проверка №11
Recovery не нарушает архитектурные решения Build 060.18.
---
# Definition of Done (DoD)
Build 060.19 считается завершённым только при выполнении всех перечисленных условий.
---
## Архитектура
- Архитектура полностью соответствует настоящему документу.
- Все ADR соблюдены.
- Все архитектурные инварианты сохранены.
---
## Код
- Реализованы все модели Recovery.
- Реализован TradeRecoveryNormalizer.
- Реализован TradeRecoveryController.
- Не изменены существующие публичные контракты предыдущих Build без отдельного ADR.
---
## Качество
- Код проходит статическую проверку типов.
- Код соответствует принятому стилю проекта.
- Новые сущности имеют уникальные имена файлов.
- Не допущено дублирование существующей функциональности.
---
## Тестирование
- Все Unit-тесты проходят успешно.
- Все Integration-тесты проходят успешно.
- Покрыты все сценарии, описанные в Приложении D.
---
## Документация
Подготовлены и согласованы:
- `build_060_19_architecture.md`;
- `build_060_19.md` (Engineering Report);
- комментарии в коде (при необходимости);
- обновлены внутренние ссылки на связанные Build.
---
# Зависимости от последующих Build
> **Ретроспективное уточнение 060.30.5 (2026-08-03).**
>
> Все последующие roadmap-таблицы этого документа сохраняют прогноз на
> момент проектирования 060.19. Фактически после
> [060.20](build_060_20.md) и [060.20.1](build_060_20_1.md) этапы
> Protocol, Runtime Service, Acquisition Integration и Recovery выполнены
> в [060.21](build_060_21.md)[060.24](build_060_24.md), Production
> Runtime — в [060.25](build_060_25.md), Integration &
> Regression — в [060.26](build_060_26.md), Storage/Checkpoint/Access —
> в [060.27](build_060_27.md)[060.29](build_060_29.md), а Final
> Documentation — в [060.30](build_060_30_architecture.md). Каноническая
> последовательность находится в
> [Master Roadmap](../roadmap/master-roadmap.md).
> После 060.30 утверждён только 061.00; номера следующих Feed-веток ещё
> не назначены.
Build 060.19 сознательно ограничивает собственную область ответственности.
Следующие задачи не входят в его Scope и будут реализованы позже.
---
## Build 060.20
Trade Recovery Registry.
Хранение и управление состоянием восстановления для нескольких символов.
---
## Build 060.21
Recovery Acquisition Protocol.
Определение протокола взаимодействия Runtime и Recovery.
---
## Build 060.22
Recovery Acquisition Service.
Высокоуровневый сервис восстановления, объединяющий Registry и Controller.
---
## Build 060.23
Runtime Integration.
Интеграция Recovery с жизненным циклом Runtime.
---
## Build 060.24
Reconnect Integration.
Автоматический запуск Recovery после восстановления WebSocket-соединения.
---
## Build 060.25
Полная интеграция Trades Feed.
Объединение потоковых и исторических сделок в единый производственный Pipeline.
---
## Build 060.26
Финальная документация, регрессионный аудит и подтверждение соответствия всей серии Build 060.
---
# Заключение
Build 060.19 завершает следующий важный этап развития подсистемы Trades Feed.
После его реализации система впервые получает архитектурно корректный механизм восстановления исторических сделок, построенный на уже существующих компонентах без нарушения их ответственности.
Recovery не создаёт альтернативную модель обработки данных и не дублирует ранее реализованную функциональность. Вместо этого он объединяет:
- существующий REST Pipeline;
- существующую Canonical Trade Model;
- существующий TradeStreamConsistencyController;
в единый механизм восстановления истории.
Это решение обеспечивает:
- единый источник истины для проверки согласованности потока;
- минимальную связанность подсистем;
- высокую тестируемость;
- возможность безопасного масштабирования архитектуры в следующих Build.
Настоящий документ завершает архитектурное проектирование Build 060.19 и является нормативной спецификацией, на основании которой должна выполняться реализация.
---
# Приложение F. Архитектурная совместимость и дальнейшее развитие Build 060.19
---
# Назначение приложения
Настоящее приложение определяет место Build 060.19 в архитектуре Dzentra серии Build 060.
Оно фиксирует:
- архитектурные зависимости;
- совместимость с предыдущими Build;
- влияние на существующие подсистемы;
- направления дальнейшего развития Recovery;
- гарантии обратной совместимости.
Данное приложение служит контрольной точкой эволюции архитектуры Trades Feed.
---
# Место Build 060.19 в серии Build 060
Build 060.19 является первым Build серии, реализующим механизм восстановления исторических сделок (Trade Recovery).
Recovery не создаёт новую модель обработки данных.
Recovery использует существующие архитектурные компоненты и объединяет их в единый процесс восстановления истории.
Таким образом Build 060.19 является интеграционным Build, а не Build, изменяющим фундаментальную архитектуру Trades Feed.
---
# Архитектурные зависимости
Build 060.19 непосредственно зависит только от следующих компонентов.
```text
Canonical Trade Model
```
```text
REST Trade Pipeline
```
```text
TradeStreamConsistencyController
```
Других обязательных архитектурных зависимостей Recovery не имеет.
---
# Совместимость с предыдущими Build
Build 060.19 полностью совместим со всеми ранее утверждёнными Build серии 060.
| Build | Назначение | Совместимость |
|--------|------------|---------------|
| Build 060.1 | Canonical Trade Model | Полная |
| Build 060.2060.8 | Базовые доменные модели и инфраструктура | Полная |
| Build 060.9 | REST Trade Pipeline | Полная |
| Build 060.10060.16 | Парсинг, Validation, Mapping | Полная |
| Build 060.17 | Stream Consistency | Полная |
| Build 060.18 | REST Pipeline Integration | Полная |
Ни один из перечисленных Build не требует модификации для внедрения Recovery.
---
# Build, которые Recovery не изменяет
Build 060.19 сознательно не изменяет архитектуру следующих компонентов.
- Canonical Trade;
- REST Parser;
- Value Validation;
- REST Mapper;
- REST Adapter;
- TradeStreamConsistencyController;
- Transport Layer;
- Exchange Integration;
- Runtime.
Все перечисленные подсистемы используются без изменения их ответственности.
---
# Build, использующие Recovery
После завершения Build 060.19 механизм Recovery становится частью архитектурного фундамента следующих Build.
| Build | Использование Recovery |
|--------|------------------------|
| Build 060.20 | Registry хранения состояния Recovery |
| Build 060.21 | Acquisition Protocol |
| Build 060.22 | Acquisition Service |
| Build 060.23 | Runtime Integration |
| Build 060.24 | Reconnect Integration |
| Build 060.25 | Полная интеграция Trades Feed |
| Build 060.26 | Финальная документация и аудит |
Таким образом Recovery становится базовым компонентом всей последующей серии Build.
---
# Архитектурные гарантии
Настоящий Build гарантирует следующие свойства.
## Единственный источник проверки согласованности
Проверка порядка сделок выполняется исключительно через:
```text
TradeStreamConsistencyController
```
Recovery не реализует собственную проверку согласованности.
---
## Единственная Canonical Model
Во всей системе существует только одна модель сделки:
```text
Trade
```
Recovery не создаёт альтернативных представлений Trade.
---
## Единственный REST Pipeline
Recovery использует исключительно существующий REST Pipeline.
Создание второго Pipeline запрещается.
---
## Единственный механизм нормализации
Нормализация порядка выполняется только в пределах Recovery и исключительно перед передачей сделок в Stream Consistency.
После прохождения данного этапа все последующие компоненты работают только с Canonical ASCENDING-потоком.
---
# Архитектурные ограничения
Build 060.19 сознательно не решает следующие задачи.
- автоматический запуск Recovery;
- вычисление диапазона восстановления;
- управление несколькими символами;
- повторные попытки получения истории;
- планирование Recovery;
- мониторинг Recovery;
- сбор метрик;
- журналирование Recovery;
- хранение состояния Recovery.
Все перечисленные задачи относятся к следующим Build серии 060.
---
# Гарантии обратной совместимости
После реализации Build 060.19 гарантируется следующее.
- Все существующие Build продолжают работать без изменений.
- Существующие публичные API остаются совместимыми.
- Existing REST Pipeline продолжает использоваться без модификации.
- Canonical Trade Model остаётся неизменной.
- TradeStreamConsistencyController остаётся единственным владельцем правил согласованности Trade Stream.
Таким образом внедрение Recovery не нарушает существующую архитектуру проекта.
---
# Правила дальнейшего развития
Любое развитие Recovery должно соответствовать следующим принципам.
## Не допускается
- создание альтернативного Trade Pipeline;
- создание второго Controller согласованности;
- создание альтернативной модели Trade;
- изменение ответственности RecoveryController;
- дублирование логики TradeStreamConsistencyController.
---
## Допускается
- добавление Retry Policy;
- добавление Metrics;
- добавление Telemetry;
- добавление Tracing;
- добавление Performance Monitoring;
- расширение RecoveryResult;
- добавление новых диагностических возможностей.
Расширение допускается только при сохранении существующего публичного API.
---
# Матрица архитектурной эволюции
| Build | Статус | Роль |
|--------|--------|------|
| 060.1 | ✅ Завершён | Canonical Trade Model |
| 060.2060.8 | ✅ Завершены | Базовая инфраструктура |
| 060.9 | ✅ Завершён | REST Trade Pipeline |
| 060.10060.16 | ✅ Завершены | Parser / Validation / Mapper |
| 060.17 | ✅ Завершён | Stream Consistency |
| 060.18 | ✅ Завершён | REST Pipeline Integration |
| **060.19** | **🟢 Текущий Build** | **Trade Recovery** |
| 060.20 | ⏳ Следующий | Recovery Registry |
| 060.21 | ⏳ Планируется | Acquisition Protocol |
| 060.22 | ⏳ Планируется | Acquisition Service |
| 060.23 | ⏳ Планируется | Runtime Integration |
| 060.24 | ⏳ Планируется | Reconnect Integration |
| 060.25 | ⏳ Планируется | Trades Feed Integration |
| 060.26 | ⏳ Планируется | Финальный архитектурный аудит |
---
# Архитектурная зрелость Build
После завершения Build 060.19 подсистема Trades Feed получает завершённый фундамент для работы с историческими сделками.
В результате серия Build 060 впервые включает полный жизненный цикл обработки Trade.
```text
REST
Parser
Validation
Mapper
Canonical Trade
Recovery Normalizer
TradeStreamConsistencyController
Canonical Trade Stream
```
Следующие Build будут расширять данный Pipeline, не изменяя его фундаментальную архитектуру.
---
# Итоги приложения F
Настоящее приложение фиксирует архитектурное положение Build 060.19 в общей системе Dzentra и определяет правила его дальнейшей эволюции.
После утверждения настоящего приложения Build 060.19 рассматривается как стабильный архитектурный фундамент для всех последующих работ по подсистеме Trades Feed.
Любые изменения, затрагивающие описанные в данном приложении архитектурные гарантии, должны сопровождаться новым Architecture Decision Record (ADR) и обновлением архитектурной спецификации.