Files
archived-MoviePilot/docs/rules/05-architecture.md

170 lines
8.3 KiB
Markdown

# 05 — Architecture and Modules
## Layer Overview
The application is structured as four distinct layers. Each layer has a defined responsibility, and dependency may only flow in permitted directions.
```
┌──────────────────────────────────────────────────┐
│ Entrypoints │
│ (API Endpoints / CLI / Agent / Scheduler / │
│ Webhook / Message Interaction) │
└────────────────────┬─────────────────────────────┘
┌──────────────────────────────────────────────────┐
│ Chain Layer (app/chain/) │
│ Business orchestration: search, download, │
│ subscribe, transfer, message, recommend, etc. │
└──────┬──────────────┬───────────────┬────────────┘
│ │ │
▼ ▼ ▼
┌────────────┐ ┌──────────┐ ┌────────────────┐
│ Module │ │ Helper │ │ DB / Oper │
│ Layer │ │ Layer │ │ Layer │
│ (app/ │ │ (app/ │ │ (app/db/) │
│ modules/) │ │ helper/)│ │ │
└────────────┘ └──────────┘ └────────────────┘
```
---
## Layer Responsibilities and Boundaries
### Entrypoint Layer
**Directories:** `app/api/endpoints/`, `moviepilot` (CLI), `app/agent/`, scheduler callbacks, webhook handlers, message interactions.
**Responsibilities:**
- HTTP concerns: authentication, parameter parsing, response model serialization, streaming adaptation, simple input validation.
- Simple list, detail, toggle, settings read/write, and pure CRUD endpoints may call `app/db/` or a helper directly.
- Any logic that coordinates multiple modules, triggers events, touches caches, or combines workflows must be moved into `chain`.
**Rules:**
- Prefer adding new endpoints to an existing domain file. Create a new endpoint file only when introducing a new top-level resource domain.
- After adding a new endpoint, register it in `app/api/apiv1.py`.
- Endpoints must not contain business logic that belongs in `chain`.
---
### Chain Layer
**Directory:** `app/chain/`
**Responsibilities:**
- Business orchestration shared by API, CLI, agent, scheduler, and other entrypoints.
- Composes module capabilities, helpers, database access, events, and caches.
- Focuses on use cases and workflows.
**Rules:**
- Call module capabilities via `run_module()` or `async_run_module()`. Use `ModuleManager` directly only when enumerating, inspecting, or running health checks.
- Do not hold low-level protocol details, HTTP request objects, or page-specific parameter assembly.
- Before creating a new chain file, verify the workflow is genuinely reused across multiple entrypoints, or coordinates multiple modules. If it is short logic for a single endpoint, keep it in the endpoint.
- Chain-to-chain calls are allowed when reusing stable domain logic. Avoid introducing new circular dependencies.
---
### Module Layer
**Directory:** `app/modules/`
**Responsibilities:**
- Pluggable capability implementations: downloaders, media servers, message channels, metadata sources, storage backends, subtitle backends, filter backends, etc.
- Manages lifecycle (init, stop), configuration switches, priority ordering, and independent testability.
**Module categories (defined in `app/schemas/types.py`):**
| Enum | Examples |
|---|---|
| `ModuleType.Downloader` | qBittorrent, Transmission, rTorrent |
| `ModuleType.MediaServer` | Emby, Jellyfin, Plex, TrimMedia, Zspace, Ugreen |
| `ModuleType.MessageChannel` | Telegram, WeChat, Feishu, Slack, Discord |
| `ModuleType.MetaData` | TMDB, TheTVDB, Douban, Bangumi, Fanart |
| `ModuleType.Indexer` | Site-specific torrent indexers |
| `ModuleType.Storage` | Alist, rclone, u115, local storage |
**Rules:**
- A module must focus on one backend or one capability. It returns domain result objects, not HTTP responses, and must not depend on FastAPI request objects or endpoint auth.
- Do not add direct `module → module` coupling for new code. Cross-module orchestration must go through `chain`.
- Do not expand the historical `module → chain` usage pattern. If a module needs shared business logic, move that logic into `chain` or down into `helper`.
---
### Helper Layer
**Directory:** `app/helper/`
**Responsibilities:**
- Reusable low-level support: path handling, config aggregation, site index loading, protocol wrappers, rate limiting, cache utilities, page parsing, notification helpers.
**Rules:**
- Add a new helper only when the logic is reused in multiple places, or it is clearly a standalone low-level concern.
- If logic is used only by a single chain or module, keep it in the original file. Do not turn `helper` into a dumping ground.
- If the code needs configuration switches, runtime loading, priorities, or multi-implementation dispatch, it is a `module`, not a `helper`.
- `helper` must not contain full business workflows.
---
### DB / Oper Layer
**Directory:** `app/db/`
**Responsibilities:**
- SQLAlchemy models under `app/db/models/`.
- Data access wrappers (`*_oper.py`) that encapsulate all database queries.
**Rules:**
- Never issue SQLAlchemy queries directly from chain, module, or endpoint code. Always use the corresponding `*_oper.py` class.
- Any schema change requires a new Alembic migration under `database/versions/`.
---
## Permitted Call Directions
| Direction | Status |
|---|---|
| `endpoint / CLI / agent / scheduler → chain` | ✅ Preferred |
| `endpoint / CLI / agent / scheduler → db / helper` | ✅ Allowed for simple CRUD and input normalization only |
| `chain → chain` | ✅ Allowed when reusing stable, non-circular domain logic |
| `chain → module` | ✅ Via `run_module()` / `async_run_module()` |
| `chain → helper` | ✅ Allowed |
| `chain → db` | ✅ Via `*_oper.py` classes |
| `module → chain` | ⚠️ Exists in legacy code; do not expand in new code |
| `module → module` | ❌ Forbidden in new code |
| `helper → chain` | ❌ Forbidden |
| `helper → endpoint` | ❌ Forbidden |
---
## Key File Locations
| Path | Purpose |
|---|---|
| `app/api/apiv1.py` | API router registration — register new endpoints here |
| `app/core/config.py` | `ConfigModel` and `Settings` — all deployment/env-level config |
| `app/schemas/types.py` | `SystemConfigKey`, `EventType`, `ModuleType`, and all shared enums |
| `app/core/module.py` | `ModuleManager` — discovers and manages module instances |
| `app/core/plugin.py` | `PluginManager` — discovers and manages plugin instances |
| `app/core/event.py` | `EventManager` + `Event` — the application event bus |
| `app/core/context.py` | `Context`, `MediaInfo`, `TorrentInfo` — shared domain context objects |
| `app/main.py` | Application startup and FastAPI instance |
| `database/versions/` | Alembic migration scripts |
---
## Where New Capabilities Go
| Scenario | Action |
|---|---|
| New business workflow shared by multiple entrypoints | `app/chain/` |
| New downloader, media server, message channel, or storage backend | `app/modules/<backend>/` |
| New public HTTP API endpoint | `app/api/endpoints/`, register in `app/api/apiv1.py` |
| New low-level utility reused in multiple places | `app/helper/` |
| New deployment/env/startup config (ports, paths, API keys) | `ConfigModel` in `app/core/config.py` |
| New runtime business config, user-editable rule, or persistent system option | `SystemConfigKey` + `SystemConfigOper` |
| Config change should reload a long-lived object | Add `CONFIG_WATCH` + `on_config_changed()` to the relevant class |
| Few dozen lines of private logic in one chain or module | Private function in the same file; do not create a new helper |
| New module category or subtype | Also update `app/schemas/types.py` |
*Last Updated: 2026-05-25*