# 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//` | | 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*