refactor: establish standalone application boundary
This commit is contained in:
@@ -0,0 +1,277 @@
|
||||
# Application Architecture Standard
|
||||
|
||||
Status: Accepted
|
||||
|
||||
Date: 2026-07-29
|
||||
|
||||
## 1. Objective
|
||||
|
||||
The application remains a deployable modular monolith while its internal boundaries are made
|
||||
explicit. Governance must preserve current functionality, account isolation, visual behavior,
|
||||
and Docker simplicity. A structural refactor is not permission to change product behavior.
|
||||
|
||||
## 2. Target Topology
|
||||
|
||||
```text
|
||||
browser
|
||||
-> frontend shared API client
|
||||
-> backend HTTP controllers
|
||||
-> feature application services
|
||||
-> repositories / DataGateway / LLMGateway
|
||||
-> SQLite / market providers / model providers
|
||||
|
||||
background jobs
|
||||
-> the same feature application services
|
||||
-> the same repositories and gateways
|
||||
```
|
||||
|
||||
The browser and jobs are two delivery mechanisms. Neither owns business rules.
|
||||
|
||||
## 3. Target Source Layout
|
||||
|
||||
```text
|
||||
frontend/
|
||||
shared/
|
||||
tokens.css
|
||||
shell.js
|
||||
api.js
|
||||
state.js
|
||||
components/
|
||||
pages/<feature>/
|
||||
page.js
|
||||
desktop.css
|
||||
mobile.css
|
||||
pages.config.js
|
||||
|
||||
backend/
|
||||
bootstrap/
|
||||
http/
|
||||
router.py
|
||||
auth.py
|
||||
errors.py
|
||||
schemas/
|
||||
features/<feature>/
|
||||
routes.py
|
||||
service.py
|
||||
repository.py
|
||||
schemas.py
|
||||
data/
|
||||
gateway.py
|
||||
policy.py
|
||||
contracts.py
|
||||
providers/
|
||||
database/
|
||||
connection.py
|
||||
migrations/
|
||||
repositories/
|
||||
jobs/
|
||||
llm/
|
||||
gateway.py
|
||||
usage.py
|
||||
models.py
|
||||
prompts/
|
||||
features_config.py
|
||||
|
||||
tests/
|
||||
data/
|
||||
server.py
|
||||
```
|
||||
|
||||
The migration may use compatibility facades. Old modules are removed only after all callers
|
||||
move and regression gates pass.
|
||||
|
||||
## 4. Dependency Direction
|
||||
|
||||
Allowed direction:
|
||||
|
||||
```text
|
||||
HTTP / jobs -> feature services -> ports -> infrastructure adapters
|
||||
frontend pages -> shared components/state/API -> backend API
|
||||
```
|
||||
|
||||
Forbidden dependencies:
|
||||
|
||||
1. Frontend code must not call external market or LLM providers directly.
|
||||
2. HTTP controllers must not contain scoring, screening, divination, or persistence logic.
|
||||
3. Feature services must not instantiate Tushare, iFinD, Eastmoney, Tencent, SQLite, or LLM
|
||||
clients directly.
|
||||
4. Provider adapters must not import feature services.
|
||||
5. Repositories must not call external providers.
|
||||
6. Features must not read another feature's tables directly; cross-feature work goes through
|
||||
an application service or declared read model.
|
||||
7. Background jobs must call the same services used by HTTP flows rather than duplicate
|
||||
calculations.
|
||||
|
||||
## 5. Feature Ownership
|
||||
|
||||
Each feature owns its routes, application service, schemas, repository interface, page module,
|
||||
tests, and documentation. Initial feature IDs are defined in `features.config.json` during
|
||||
Stage 04.
|
||||
|
||||
Shared code is allowed only when at least two features use the same stable behavior. A shared
|
||||
module must not branch on page names or feature IDs to emulate unrelated components.
|
||||
|
||||
## 6. HTTP Contract
|
||||
|
||||
- Public application APIs use `/api/<resource>` and UTF-8 JSON.
|
||||
- Controllers validate transport input, authorize the request, call one application service,
|
||||
and serialize the result.
|
||||
- Dates exposed to users use `YYYY-MM-DD`; provider-specific compact dates stay inside
|
||||
adapters.
|
||||
- Datetimes include an explicit timezone. Market time is interpreted as Asia/Shanghai.
|
||||
- Successful collections use `items`; pagination uses `page`, `page_size`, and `total` when
|
||||
required.
|
||||
- Errors use a stable `code`, a user-safe `message`, and a request correlation ID. Provider
|
||||
credentials and raw response bodies never appear in browser errors.
|
||||
- Existing response shapes remain compatible until a versioned migration is approved.
|
||||
- Backend authorization is authoritative. Hidden frontend controls are not a security rule.
|
||||
|
||||
## 7. Data Contract
|
||||
|
||||
Every calculation field declares:
|
||||
|
||||
- canonical field ID and Chinese display label;
|
||||
- entity and frequency;
|
||||
- type, unit, precision, timezone, and adjustment mode;
|
||||
- unique primary source and permitted fallback sources;
|
||||
- display-only or calculation-eligible status;
|
||||
- freshness and completeness thresholds;
|
||||
- missing-value behavior;
|
||||
- point-in-time availability rules;
|
||||
- owning dataset and persistence location.
|
||||
|
||||
Missing calculation data fails closed. A strategy may not silently replace a required field
|
||||
with a proxy and continue under the original strategy name. Display fallbacks cannot enter
|
||||
screening, scoring, backtesting, sentiment, or Wentian calculations unless explicitly approved
|
||||
for that canonical field.
|
||||
|
||||
One OHLC bar or factor history must not mix providers. Source changes are recorded with the
|
||||
stored observation and invalidate incompatible cached calculations.
|
||||
|
||||
## 8. Market Provider Policy
|
||||
|
||||
- Tushare is the default deterministic source for master data, calendar, daily bars,
|
||||
fundamentals, valuations, industry data, lists, and post-close datasets.
|
||||
- iFinD is the default source for licensed realtime snapshots, intraday data, dynamic auction
|
||||
observations, charts, and approved event enrichment.
|
||||
- Eastmoney and Tencent public endpoints are display or observation fallbacks only unless a
|
||||
field contract explicitly promotes them.
|
||||
- All provider traffic passes through `DataGateway`; provider classes only translate their own
|
||||
protocol into canonical contracts.
|
||||
- Retries, timeouts, quotas, cache TTL, circuit state, and provenance are centralized.
|
||||
|
||||
## 9. Persistence Standard
|
||||
|
||||
- SQLite WAL remains the current deployment database.
|
||||
- Schema changes use ordered migration files with an immutable version ID.
|
||||
- A migration is transactional where SQLite permits it and must be safe to run once.
|
||||
- Destructive migrations require a verified backup and explicit acceptance.
|
||||
- User-owned tables include `user_id`, a foreign key, an ownership index, and cross-account
|
||||
tests.
|
||||
- Repositories return domain-shaped records; controllers never execute SQL.
|
||||
- Financial and research data preserve announcement timestamps to prevent look-ahead use.
|
||||
- A future PostgreSQL adapter must satisfy the same repository contracts.
|
||||
|
||||
## 10. Background Jobs
|
||||
|
||||
- Jobs declare an ID, schedule, input date policy, dependencies, lock key, timeout, retry
|
||||
policy, and idempotency key.
|
||||
- Job runs persist start, completion, failure code, retry count, source coverage, and output
|
||||
version.
|
||||
- A process restart must not duplicate a completed post-close calculation.
|
||||
- Exceptions are logged and surfaced in system management; they are never silently discarded.
|
||||
- The current in-process runner may remain, but jobs cannot depend on thread-local request
|
||||
context. This permits later extraction to a worker without changing business services.
|
||||
|
||||
## 11. LLM Standard
|
||||
|
||||
All model calls pass through `LLMGateway`, which owns:
|
||||
|
||||
- membership authorization and feature availability;
|
||||
- quota reservation, settlement, and daily limits;
|
||||
- model-pool selection and fallback;
|
||||
- timeout, cancellation, retry, and streaming protocol;
|
||||
- prompt version, model, latency, token usage, and failure audit;
|
||||
- removal of secrets and engineering details from user-visible errors.
|
||||
|
||||
Skill evidence, source material, and data profiles remain separate from reusable prompt
|
||||
templates. Deterministic calculations happen before the LLM call and are not delegated to the
|
||||
model.
|
||||
|
||||
## 12. Frontend Standard
|
||||
|
||||
- `shared/api.js` is the only browser request exit.
|
||||
- `shared/shell.js` owns sidebar, topbar, market summary, status bar, global dialogs, and page
|
||||
mounting.
|
||||
- `pages.config.js` owns navigation metadata and page loading, not authorization truth.
|
||||
- A page module fetches data, owns page-local state, and composes shared components.
|
||||
- Global mutable page state and cross-page DOM queries are prohibited after migration.
|
||||
- Tables use shared shells with page-owned column schemas and formatters.
|
||||
- Opening, closing, focus management, and feedback for dialogs use one dialog service.
|
||||
|
||||
## 13. CSS and Design Tokens
|
||||
|
||||
The cascade order is fixed:
|
||||
|
||||
```text
|
||||
tokens -> reset/base -> shell/layout -> shared components -> page styles -> theme overrides
|
||||
```
|
||||
|
||||
- Literal colors, font sizes, and spacing are introduced through tokens first.
|
||||
- Page styles are scoped to the page root and cannot redefine the shell or another page.
|
||||
- A later file cannot be used indefinitely as a patch layer for an earlier file.
|
||||
- Shared components have one authoritative definition.
|
||||
- CSS is removed only after selector/reference scanning, light/dark screenshots, and full
|
||||
browser regression.
|
||||
|
||||
## 14. Mobile Standard
|
||||
|
||||
Mobile shares tokens, data, permissions, components, and state with desktop, but it may use a
|
||||
different composition and interaction model.
|
||||
|
||||
- Shared mobile shell rules live in `shared/mobile-shell.css`.
|
||||
- Each complex page may own `mobile.css` and a mobile view renderer.
|
||||
- One global all-page `mobile.css` override pile is prohibited.
|
||||
- Desktop tables may become summary lists and detail views on mobile.
|
||||
- Mobile acceptance is performed independently at 390x844 and 430x932.
|
||||
- Mobile changes must not alter approved desktop geometry.
|
||||
|
||||
## 15. Configuration and Secrets
|
||||
|
||||
- Environment variables seed deployment configuration; encrypted system settings hold managed
|
||||
runtime credentials.
|
||||
- Tokens, API keys, refresh tokens, model secrets, and encryption keys never enter Git, logs,
|
||||
API responses, or browser storage.
|
||||
- Feature flags, page metadata, field contracts, and permissions are versioned configuration,
|
||||
not ad hoc conditionals.
|
||||
|
||||
## 16. Observability
|
||||
|
||||
External calls record provider, operation, elapsed time, cache result, freshness, normalized
|
||||
error code, and correlation ID. Logs must not contain secrets or personal birth data.
|
||||
|
||||
Health checks distinguish process health, database health, provider degradation, background
|
||||
job health, and model availability. Provider degradation does not make the process health
|
||||
endpoint fail unless the application itself cannot serve stored data.
|
||||
|
||||
## 17. Change and Release Contract
|
||||
|
||||
Every governance phase must:
|
||||
|
||||
1. begin from a clean Git worktree;
|
||||
2. preserve the Stage 01 baseline contract;
|
||||
3. include migration or compatibility tests for changed boundaries;
|
||||
4. pass `python tools/verify_baseline.py`;
|
||||
5. run Playwright at phase boundaries affecting runtime or frontend behavior;
|
||||
6. document residual risk;
|
||||
7. create and push a dedicated rollback commit.
|
||||
|
||||
No phase combines framework replacement, visual redesign, and business-rule changes.
|
||||
|
||||
## 18. Public-Internet Evolution
|
||||
|
||||
The modular monolith remains the default. Reverse proxy/TLS, PostgreSQL, Redis, durable jobs,
|
||||
central secrets, monitoring, and rate limiting can replace infrastructure adapters later.
|
||||
Feature services and frontend contracts must not depend on whether those adapters are local or
|
||||
remote. A feature is extracted into a separate service only when measured load, independent
|
||||
release needs, or fault isolation justifies the operational cost.
|
||||
Reference in New Issue
Block a user