13 KiB
Приложения, хранилище и взаимодействия
Представление C4 Container раскрывает внутреннее устройство WealthLedger. Контейнер здесь означает приложение или хранилище, а не Docker-контейнер. Внешние системы совпадают с System Context.
Представление Container
flowchart LR
U["Частный инвестор<br/>Человек"]
subgraph W["WealthLedger · программная система"]
F["React SPA<br/>TypeScript, Vite, Mantine, TanStack Query"]
B["Backend API<br/>NestJS, Node.js"]
D[("Локальная база<br/>Embedded SQLite")]
F -->|"HTTP JSON /api/v1; Bearer и auth cookie"| B
B -->|"Prisma + better-sqlite3; доступ к локальному файлу"| D
end
T["T-Bank Invest API<br/>Внешняя система"]
M["MOEX ISS<br/>Внешняя система"]
N["Реестр НКР<br/>Внешняя система"]
C["T-Bank Brands CDN<br/>Внешняя система"]
U -->|"Просматривает данные и управляет подключением"| F
B -->|"gRPC over TLS; token пользователя"| T
B -->|"HTTPS; публичные данные"| M
B -->|"HTTPS; публичный HTML"| N
F -->|"HTTPS; изображения без авторизации"| C
SPA выполняется в браузере пользователя. Backend — отдельное локальное приложение; SQLite используется внутри его процесса через библиотеку и файл базы. Между API и SQLite нет сетевого database server. Граница системы на схеме показывает принадлежность частей, а не их размещение на одном узле.
Ответственность частей
| Часть | Ответственность |
|---|---|
| React SPA | Экраны, навигация, формы, состояние входа и клиентский кэш ответов; форматирование денежных значений |
| Backend API | Локальная идентичность, сессии, проверка доступа, подключение брокера, нормализация внешних данных, точные расчёты и серверные кэши |
| SQLite | Долговременное хранение локальных пользователей, сессий, зашифрованных подключений и ручной банковской истории |
| Внешние системы | Данные брокера, публичные сведения об инструментах и изображения в пределах своей ответственности |
Frontend организован слоями app → pages → features → entities → shared:
каркас связывает маршруты, страницы собирают сценарии, а общие средства
обслуживают транспорт и представление. Backend остаётся модульным NestJS API,
где локальная аутентификация отделена от подключения брокера и инвестиционных
сценариев. Это внутренняя организация приложений, не набор микросервисов.
Финансовые HTTP-запросы браузера идут через backend. Пользователь вводит token T-Bank в форме и отправляет его для сохранения, но API не возвращает token в ответах; SPA не использует его для прямых брокерских запросов. Прямая загрузка публичного изображения из CDN не проходит через эту финансовую границу.
Типизированный frontend-клиент производится из предоставленного OpenAPI snapshot во время разработки. В runtime SPA вызывает API, а не генератор или OpenAPI endpoint. Форма запросов и ошибок описана в HTTP API; направление генерации принято в ADR 002, а доверие frontend к предоставленному snapshot — в ADR 014.
Хранимые данные и границы доступа
SQLite содержит пользователя с нормализованным email и hash пароля, его сессии с hash текущего refresh token, сроком действия и признаком отзыва, а также одно зашифрованное подключение T-Bank с временем подключения. Пароли хешируются bcrypt; token брокера шифруется AES-256-GCM. Plaintext credentials не являются сохраняемым представлением этих данных.
Ручной банковский учёт хранит карты, пользовательские категории и операции в отдельных таблицах, связанных с User. Начальный остаток и суммы записаны целыми bigint-копейками, календарные даты — днями без времени. Перевод хранится одной операцией с двумя картами; возврат связан с исходной покупкой. Баланс вычисляется из неудалённой истории и отдельно не сохраняется.
Банковские запросы проверяют локального владельца каждой связи и не используют подключение брокера. Транзакции, ограничения уникальности и версии согласуют переводы, возвраты и конкурирующие исправления. Fingerprint исходного ввода и отметка удаления защищают от повторного создания после потерянного ответа или перезапуска. Архив обратим и сохраняет историю. Подробности — банковские карты.
Сессии и подключение связаны с владельцем. Access token подтверждает
локального пользователя, но accountId из URL сам по себе не даёт права
читать брокерский счёт. Backend получает текущее подключение пользователя
и его generation перед использованием финансового кэша. Проверка доступности
счёта у брокера зависит от сценария: журнал выполняет её до чтения страницы
кэша; свежий портфель одного счёта может быть возвращён без нового запроса
списка счетов. При обновлении портфеля доступ проверяется у брокера.
Ротация сессии атомарно заменяет hash текущего refresh token: два конкурентных запроса не могут оба успешно выполнить одну ротацию. Вызов брокера и запись в локальную базу не образуют общую транзакцию: при сохранении подключения сначала проверяется token, затем записывается шифротекст. Подробности находятся в аутентификации и интеграции T-Bank.
Брокерские счета, сводки, портфель, страницы журнала и будущие события не являются долговременной историей в SQLite. Они получаются для текущего подключения и могут временно кэшироваться. Перезапуск backend не восстанавливает прежние финансовые снимки и cursor continuations из базы; модель свежести отделяет это от хранения учётных данных.
Стратегия решения и её основания
Локальная база и Prisma дают типизированный доступ к локальному состоянию без отдельного сервера базы; исходные причины и пределы выбора сохранены в ADR 001. Централизованная серверная граница T-Bank сосредоточивает credentials, лимиты и повторы вне браузера, а шифрование подключения задаёт способ хранения брокерского секрета.
Политика сессий разделяет короткоживущий access token и вращаемый refresh token. Политика query-кэша сохраняет доступные данные при обновлении и не запускает refetch при возврате фокуса. Ограниченные повторы T-Bank и структурированные журналы обслуживают внешнюю границу. ADR сохраняют контекст принятия решения; текущие детали уточняются в тематических описаниях.
Качество и ограничения
Точность представления денег требует сохранять валюту и decimal-значение; неизвестная сумма не заменяется нулём. Свежесть наблюдения не равна актуальности рынка в реальном времени. Временные ошибки внешнего поставщика обрабатываются ограниченными повторами и локальными состояниями интерфейса, без обещания успешного ответа при недоступном upstream.
Авторизация доступа, недопущение token в публичный ответ и атомарность ротации сессии — отдельные свойства; наличие типизированного клиента не доказывает их. Известные расхождения регистрации и деталей валидации отмечены в аутентификации и HTTP API.
Подтверждённая топология — локальная SPA и локальный API с файлом SQLite. Origin браузера, адрес API и cookie policy должны быть согласованы; условия и диагностика описаны в локальном окружении. Наличие локального запуска не задаёт production-топологию или гарантии SLA.
Кэши и лимитер T-Bank действуют на экземпляр backend. Ротация ключа шифрования подключений и согласование изменений подключения между распределёнными экземплярами не входят в реализованный механизм. Эти ограничения не позволяют переносить локальную схему на несколько экземпляров без отдельного решения о состоянии, credentials и лимитах.
Runtime-сценарии раскрываются у соответствующих владельцев: восстановление сессии, сохранение подключения и загрузка портфеля.