mirror of
https://github.com/Vateron-Media/XC_VM.git
synced 2026-10-27 03:03:31 +01:00
613 lines
35 KiB
Markdown
613 lines
35 KiB
Markdown
# XC_VM — Архитектура
|
||
|
||
## Содержание
|
||
|
||
0. [Стратегические цели](#0-стратегические-цели)
|
||
1. [Архитектурный стиль и принципы](#1-архитектурный-стиль-и-принципы)
|
||
2. [Структура `/src`](#2-структура-src)
|
||
3. [Описание компонентов](#3-описание-компонентов)
|
||
4. [Система модулей](#4-система-модулей)
|
||
5. [Варианты сборки: MAIN vs LoadBalancer](#5-варианты-сборки-main-vs-loadbalancer)
|
||
6. [Транзакции и производительность](#6-транзакции-и-производительность)
|
||
|
||
> **План миграции** (фазы, стратегия рисков, порядок выполнения) — см. [MIGRATION.md](MIGRATION.md).
|
||
> **Правила для контрибьюторов** — см. [CONTRIBUTING.md](CONTRIBUTING.md).
|
||
|
||
---
|
||
|
||
## 0. Стратегические цели
|
||
|
||
| # | Цель | Приоритет | Метрика успеха |
|
||
|---|------|-----------|----------------|
|
||
| 1 | **Контрибьюторская доступность** | 🔴 Критический | PHP-разработчик среднего уровня делает первый PR за 2 часа, не зная DDD |
|
||
| 2 | **Поддерживаемость** | 🔴 Критический | Типичное изменение (добавить поток, изменить лимиты) < 1 час без риска сайд-эффектов |
|
||
| 3 | **Изоляция отказов** | 🔴 Критический | Баг в admin-панели не ломает стриминг. Баг в модуле не ломает ядро |
|
||
| 4 | **Multi-node** | 🟡 Важный | LB-сервер собирается из подмножества кода. Streaming-путь не загружает admin-логику |
|
||
| 5 | **Open-core коммерция** | 🟡 Важный | Коммерческие модули подключаются без модификации ядра. Удаление модуля не ломает систему |
|
||
| 6 | **Тестируемость** | 🟢 Поддерживающий | Любой сервис можно тестировать, подставив mock зависимости |
|
||
|
||
### Чем это НЕ является
|
||
|
||
- **Не DDD.** Нет Event Sourcing, CQRS, Aggregate Root. Простой паттерн: Controller → Service → Repository.
|
||
- **Не академический проект.** Архитектура оптимизирована под понятность, а не под теоретическую чистоту.
|
||
|
||
### Принцип принятия решений
|
||
|
||
1. **Контрибьютор поймёт за 5 минут?** → если нет — упростить
|
||
2. **Не ломает streaming hot path?** → если ломает — отклонить
|
||
3. **Можно изолировать в модуль?** → если нет — обосновать
|
||
|
||
Если решение улучшает «красоту кода», но повышает барьер входа — **отклонить**.
|
||
|
||
---
|
||
|
||
## 1. Архитектурный стиль и принципы
|
||
|
||
### 1.1. Формализация
|
||
|
||
**Структурированный монолит с предсказуемой организацией по контексту.**
|
||
|
||
Не DDD. Не Hexagonal. Не Clean Architecture. Не микросервисы.
|
||
Простой PHP-монолит, разложенный по контекстам с минимумом абстракций.
|
||
|
||
#### Паттерн: Controller → Service → Repository
|
||
|
||
Каждый контекст (Streams, VOD, Lines, Users...) внутри устроен одинаково:
|
||
|
||
```
|
||
domain/Stream/
|
||
├── StreamService.php # Бизнес-логика + оркестрация
|
||
├── StreamRepository.php # SQL-запросы (SELECT/INSERT/UPDATE/DELETE)
|
||
└── StreamProcess.php # Специализированные операции (ffmpeg, kill)
|
||
|
||
public/Controllers/Admin/
|
||
└── StreamController.php # Принять запрос → вызвать Service → отдать ответ
|
||
```
|
||
|
||
#### Правила зависимостей
|
||
|
||
```
|
||
Controller → Service → Repository → Database
|
||
↓
|
||
Infrastructure (nginx, redis, ffmpeg)
|
||
```
|
||
|
||
| Слой | Можно зависеть от | НЕЛЬЗЯ зависеть от |
|
||
|------|-------------------|--------------------|
|
||
| `public/` | `domain/` (Service + Repository), `core/` | `streaming/`, `modules/` напрямую |
|
||
| `domain/` | `core/` (Database, Cache, Events) | `public/`, `streaming/`, `modules/`, `infrastructure/` |
|
||
| `core/` | Только другие `core/` подкаталоги | Всё остальное |
|
||
| `streaming/` | `core/` (subset), `domain/` (read-only queries) | `public/`, `modules/` |
|
||
| `modules/` | `domain/` (Service, Repository), `core/` | Другие модули (без явной зависимости), `public/`, `streaming/` |
|
||
|
||
### 1.2. Strict Constructor Injection
|
||
|
||
`ServiceContainer` используется **ТОЛЬКО** на этапе bootstrap (composition root).
|
||
После bootstrap все зависимости передаются **через конструктор**. Ни один сервис не вызывает `$container->get()` внутри своих методов.
|
||
|
||
```php
|
||
// ✅ ПРАВИЛЬНО — constructor injection:
|
||
class StreamService {
|
||
public function __construct(
|
||
private StreamRepository $repository,
|
||
private EventDispatcher $events,
|
||
private FileLogger $logger
|
||
) {}
|
||
}
|
||
|
||
// ❌ ЗАПРЕЩЕНО — Service Locator внутри сервиса:
|
||
class StreamService {
|
||
public function create(array $data): int {
|
||
$db = ServiceContainer::getInstance()->get('db'); // ← АНТИПАТТЕРН
|
||
}
|
||
}
|
||
```
|
||
|
||
**Исключения (временные, фаза миграции):**
|
||
Legacy-код в `infrastructure/legacy/` может обращаться к контейнеру напрямую. Каждое такое обращение помечается `// @legacy-container`.
|
||
|
||
### 1.3. Консолидация мелких классов
|
||
|
||
Не создавать отдельный файл/класс, если в нём < 150 строк **и** < 5 публичных методов.
|
||
|
||
| Ситуация | Что делать |
|
||
|----------|-----------|
|
||
| Repository < 5 методов | Объединить Service + Repository в один файл |
|
||
| Контекст = Service с 1-2 методами | Влить в ближайший родственный контекст |
|
||
| Отдельный «Validator» с 1 методом | Сделать private-методом в Service |
|
||
| Service + Repository > 300 строк суммарно | Разделить в отдельные файлы |
|
||
|
||
**Когда МОЖНО создавать отдельный файл:**
|
||
- Класс > 150 строк
|
||
- Класс имеет ≥ 5 публичных методов
|
||
- Класс используется из 3+ разных контекстов
|
||
- Интерфейс для DI (`CacheInterface`, `LoggerInterface`)
|
||
|
||
### 1.4. Нет Entity-классов
|
||
|
||
Данные передаются как `array`. Это PHP — массивы проще и понятнее, чем анемичные DTO-объекты.
|
||
|
||
### 1.5. Модуль = изолированная директория
|
||
|
||
Модуль — это директория с известным контрактом. Его можно удалить, и система продолжит работать (деградируя в функциональности, но не падая). Ядро (`core/`) не содержит проверок лицензий, шифрования или скрытых ограничений.
|
||
|
||
---
|
||
|
||
## 2. Структура `/src`
|
||
|
||
```
|
||
src/
|
||
├── autoload.php # PSR-подобный автозагрузчик (class map)
|
||
├── bootstrap.php # Единый bootstrap: DI container, config, контексты
|
||
├── console.php # CLI entry point (php console.php cron:*, cmd:*)
|
||
├── service # Bash: управление демонами
|
||
├── update # Bash: процесс обновления
|
||
│
|
||
├── core/ # ═══ ЯДРО (инфраструктурные сервисы) ═══
|
||
│ ├── Auth/ # SessionManager, Authenticator, Authorization,
|
||
│ │ # BruteforceGuard, PageAuthorization
|
||
│ ├── Backup/ # BackupService
|
||
│ ├── Cache/ # CacheInterface, FileCache, RedisCache
|
||
│ ├── Config/ # AppConfig, ConfigLoader, ConfigReader, Paths, Binaries,
|
||
│ │ # DomainResolver, SettingsManager, SettingsRepository
|
||
│ ├── Container/ # ServiceContainer (DI)
|
||
│ ├── Database/ # Database (PDO), DatabaseHandler, MigrationRunner, QueryHelper
|
||
│ ├── Device/ # MobileDetect
|
||
│ ├── Diagnostics/ # DiagnosticsService
|
||
│ ├── Error/ # ErrorHandler, ErrorCodes
|
||
│ ├── Events/ # EventDispatcher, EventInterface
|
||
│ ├── GeoIP/ # GeoIPService
|
||
│ ├── Http/ # Request, Response, Router, ApiClient, CurlClient,
|
||
│ │ # RequestGuard, RequestManager, Middleware/
|
||
│ ├── Init/ # LegacyInitializer
|
||
│ ├── Localization/ # Translator
|
||
│ ├── Logging/ # LoggerInterface, FileLogger, DatabaseLogger, Logger
|
||
│ ├── Module/ # ModuleInterface, ModuleLoader
|
||
│ ├── Parsing/ # XmlStringStreamer
|
||
│ ├── Process/ # ProcessManager, Multithread, Thread
|
||
│ ├── Storage/ # DropboxClient
|
||
│ ├── Updates/ # GithubReleases
|
||
│ ├── Util/ # Encryption, GeoIP, ImageUtils, NetworkUtils,
|
||
│ │ # StreamUtils, SystemInfo, TimeUtils, AdminHelpers
|
||
│ └── Validation/ # InputValidator
|
||
│
|
||
├── domain/ # ═══ БИЗНЕС-ЛОГИКА (сервисы и репозитории) ═══
|
||
│ ├── Auth/ # AuthService, AuthRepository
|
||
│ ├── Bouquet/ # BouquetService
|
||
│ ├── Device/ # MagService, EnigmaService
|
||
│ ├── Epg/ # EPG (XML-парсер), EpgService
|
||
│ ├── Line/ # LineService, LineRepository, PackageService
|
||
│ ├── Security/ # BlocklistService
|
||
│ ├── Server/ # ServerService, ServerRepository, SettingsService
|
||
│ ├── Stream/ # StreamService, StreamRepository, StreamProcess,
|
||
│ │ # ChannelService, CategoryService, ConnectionTracker,
|
||
│ │ # StreamSorter, PlaylistGenerator, M3UEntry, M3UParser,
|
||
│ │ # ProfileService, ProviderService, RadioService,
|
||
│ │ # StreamConfigRepository
|
||
│ ├── User/ # UserService, UserRepository, GroupService, TicketRepository
|
||
│ └── Vod/ # MovieService, SeriesService, EpisodeService, TMDbService
|
||
│
|
||
├── streaming/ # ═══ СТРИМИНГ-ДВИЖОК (hot path) ═══
|
||
│ ├── StreamingBootstrap.php # Лёгкий init для стриминг-контекста
|
||
│ ├── AsyncFileOperations.php # Асинхронные файловые операции
|
||
│ ├── TimeshiftClient.php # Клиент для timeshift-запросов
|
||
│ ├── Auth/ # StreamAuth, StreamAuthMiddleware
|
||
│ ├── Balancer/ # ProxySelector
|
||
│ ├── Codec/ # FFmpegCommand, FFprobeRunner, FfmpegPaths
|
||
│ ├── Delivery/ # HLSGenerator, OffAirHandler, SegmentReader,
|
||
│ │ # SignalSender, StreamRedirector
|
||
│ ├── Health/ # ProcessChecker
|
||
│ ├── Lifecycle/ # ShutdownHandler
|
||
│ └── Protection/ # ConnectionLimiter
|
||
│
|
||
├── public/ # ═══ HTTP ТОЧКИ ВХОДА ═══
|
||
│ ├── index.php # Единый front controller
|
||
│ ├── routes/ # admin.php, reseller.php, player.php
|
||
│ ├── Controllers/
|
||
│ │ ├── Admin/ # 96 контроллеров (BaseAdminController + по одному на страницу)
|
||
│ │ ├── Reseller/ # 31 контроллер (BaseResellerController + по одному на страницу)
|
||
│ │ ├── Api/ # 9 контроллеров (Admin, Reseller, Player, Internal, Enigma2...)
|
||
│ │ └── Player/ # 13 контроллеров
|
||
│ ├── Views/
|
||
│ │ ├── admin/ # 127 шаблонов
|
||
│ │ ├── reseller/ # 24 шаблона
|
||
│ │ ├── player/ # 7 шаблонов
|
||
│ │ └── layouts/ # admin.php, footer.php, player/, reseller/
|
||
│ └── assets/ # admin/, player/, reseller/ (CSS/JS/images/fonts)
|
||
│
|
||
├── cli/ # ═══ CLI ТОЧКИ ВХОДА ═══
|
||
│ ├── CommandInterface.php
|
||
│ ├── CommandRegistry.php
|
||
│ ├── CronTrait.php
|
||
│ ├── DaemonTrait.php
|
||
│ ├── migration_logic.php
|
||
│ ├── Commands/ # 24 команды (Monitor, Watchdog, Startup, Scanner, Queue...)
|
||
│ └── CronJobs/ # 21 крон (StreamsCronJob, ServersCronJob, CacheCronJob...)
|
||
│
|
||
├── modules/ # ═══ ОПЦИОНАЛЬНЫЕ МОДУЛИ ═══
|
||
│ ├── ministra/ # Ministra/Stalker Portal middleware
|
||
│ ├── plex/ # Plex integration
|
||
│ ├── tmdb/ # TMDB metadata fetching
|
||
│ ├── watch/ # Watch/DVR recording
|
||
│ ├── fingerprint/ # Watermarking
|
||
│ ├── theft-detection/ # Anti-theft detection
|
||
│ └── magscan/ # MAG device scanning
|
||
│
|
||
├── infrastructure/ # ═══ СИСТЕМНАЯ ИНФРАСТРУКТУРА ═══
|
||
│ ├── bootstrap/ # Функции/сессии для admin, reseller, player (facade layer)
|
||
│ ├── cache/ # CacheReader
|
||
│ ├── database/ # DatabaseFactory
|
||
│ ├── legacy/ # resize_body, reseller_api_actions и др.
|
||
│ ├── nginx/ # templates/
|
||
│ ├── redis/ # RedisManager
|
||
│ └── service/ # (bash-скрипты демонов)
|
||
│
|
||
├── resources/ # ═══ РЕСУРСЫ ═══
|
||
│ ├── data/ # admin_constants.php
|
||
│ ├── langs/ # bg, de, en, es, fr, pt, ru (.ini)
|
||
│ └── libs/ # (зарезервировано)
|
||
│
|
||
├── config/ # ═══ КОНФИГУРАЦИЯ ═══
|
||
│ ├── modules.php # Список включённых модулей
|
||
│ ├── permissions.php # Определения прав доступа
|
||
│ └── rclone.conf
|
||
│
|
||
├── www/ # ═══ WEB ENTRY POINTS ═══
|
||
│ ├── constants.php # Фасад обратной совместимости
|
||
│ ├── init.php # Legacy init
|
||
│ ├── index.html # Ministra portal HTML
|
||
│ ├── probe.php # FFprobe endpoint
|
||
│ ├── progress.php # Progress reporting
|
||
│ ├── images/ # Статические изображения
|
||
│ ├── admin/ # 7 entry-points (api.php, index.php, live.php...)
|
||
│ └── stream/ # 11 streaming endpoints (auth, live, vod, segment...)
|
||
│
|
||
├── bin/ # Внешние бинарники (ffmpeg, certbot, yt-dlp, nginx, nginx_rtmp, redis, php...)
|
||
├── content/ # Медиа-контент (archive, created, delayed, epg, playlists, streams, video, vod)
|
||
├── backups/ # Резервные копии
|
||
├── signals/ # Сигнальные файлы
|
||
├── tmp/ # Временные файлы
|
||
├── migrations/ # SQL-миграции (001_update_crontab_filenames.sql...)
|
||
└── ministra/ # Ministra JS-файлы (отдаются nginx напрямую)
|
||
```
|
||
|
||
---
|
||
|
||
## 3. Описание компонентов
|
||
|
||
### 3.1. `core/` — Ядро
|
||
|
||
Инфраструктурные сервисы для любого контекста исполнения. Не содержит бизнес-логики.
|
||
|
||
**Ключевое правило:** `core/` не знает о существовании `domain/`, `streaming/`, `modules/`, `public/`. Зависимости направлены только внутрь ядра.
|
||
|
||
| Подкаталог | Что даёт |
|
||
|------------|----------|
|
||
| `Auth/` | Единая авторизация (admin + reseller), RBAC, brute-force защита, авторизация страниц |
|
||
| `Backup/` | Бэкапы (BackupService) |
|
||
| `Cache/` | Унифицированный кэш (File + Redis) через `CacheInterface` |
|
||
| `Config/` | Загрузка конфигурации, резолв путей, управление настройками |
|
||
| `Container/` | DI-контейнер (composition root only — §1.2) |
|
||
| `Database/` | PDO-обёртка (Database + DatabaseHandler + QueryHelper), миграции |
|
||
| `Device/` | Определение устройства (MobileDetect) |
|
||
| `Diagnostics/` | Диагностика системы |
|
||
| `Error/` | Обработчик ошибок, коды ошибок |
|
||
| `Events/` | Event bus для модульных хуков |
|
||
| `GeoIP/` | Гео-определение по IP |
|
||
| `Http/` | Абстракция запросов, роутинг, ApiClient, CurlClient, middleware pipeline |
|
||
| `Init/` | Legacy-инициализация (LegacyInitializer) |
|
||
| `Localization/` | Перевод интерфейса (Translator) |
|
||
| `Logging/` | Унифицированное логирование (File + Database + Logger) |
|
||
| `Module/` | Загрузчик модулей, `ModuleInterface` |
|
||
| `Parsing/` | Парсинг XML (XmlStringStreamer) |
|
||
| `Process/` | Управление PID, потоками (Multithread, Thread) |
|
||
| `Storage/` | Облачное хранилище (DropboxClient) |
|
||
| `Updates/` | Обновления (GithubReleases) |
|
||
| `Util/` | Утилиты без состояния (Encryption, Network, Time, Image, Stream, AdminHelpers) |
|
||
| `Validation/` | Валидация входных данных |
|
||
|
||
### 3.2. `domain/` — Бизнес-логика
|
||
|
||
Сервисы и репозитории, организованные по контекстам.
|
||
|
||
**Правила:**
|
||
1. **Service** = вся бизнес-логика контекста (валидация, транзакции, side-effects)
|
||
2. **Repository** = только SQL. Принимает массивы, возвращает массивы
|
||
3. Если Repository < 5 методов и < 150 строк — живёт в одном файле с Service (§1.3)
|
||
|
||
> **Текущее состояние:** Часть domain-классов уже использует constructor injection (как показано ниже). Другая часть (StreamService, StreamRepository и др.) ещё работает через `global $db` и статические методы — миграция в процессе.
|
||
|
||
```php
|
||
// ═══ ЦЕЛЕВОЙ ПАТТЕРН (новый код) ═══
|
||
|
||
// Service делает всё:
|
||
class StreamService {
|
||
public function __construct(
|
||
private StreamRepository $repository,
|
||
private ProcessManager $processManager,
|
||
private FileLogger $logger,
|
||
private Database $db
|
||
) {}
|
||
|
||
public function create(array $data): int {
|
||
if (empty($data['stream_source'])) {
|
||
throw new \InvalidArgumentException('Source required');
|
||
}
|
||
$this->db->beginTransaction();
|
||
try {
|
||
$id = $this->repository->insert($data);
|
||
$this->db->commit();
|
||
} catch (\Throwable $e) {
|
||
$this->db->rollBack();
|
||
throw $e;
|
||
}
|
||
$this->logger->log('stream', "Created stream {$id}");
|
||
return $id;
|
||
}
|
||
}
|
||
|
||
// Repository = только SQL:
|
||
class StreamRepository {
|
||
public function __construct(private Database $db) {}
|
||
public function findById(int $id): ?array {
|
||
return $this->db->row("SELECT * FROM streams WHERE id = ?", [$id]);
|
||
}
|
||
}
|
||
```
|
||
|
||
```php
|
||
// ═══ LEGACY-ПАТТЕРН (ещё не мигрировано) ═══
|
||
// StreamService, StreamRepository — статические методы + global $db
|
||
// Миграция на constructor injection запланирована (см. MIGRATION.md)
|
||
|
||
class StreamRepository {
|
||
public static function getById($rID) {
|
||
global $db;
|
||
$db->query('SELECT * FROM `streams` WHERE `id` = ?;', $rID);
|
||
return $db->fetchRecord();
|
||
}
|
||
}
|
||
```
|
||
|
||
### 3.3. `streaming/` — Стриминг-движок
|
||
|
||
Весь hot path доставки видео. Выделен отдельно от `domain/` по причинам:
|
||
- Критичен к производительности (нельзя загружать всю бизнес-логику)
|
||
- Имеет собственный лёгкий bootstrap (`StreamingBootstrap.php`)
|
||
- Работает на уровне байтов/сегментов, а не CRUD
|
||
|
||
**Изоляция:**
|
||
|
||
```
|
||
streaming/ зависит от:
|
||
✅ core/ (Database, Redis, Logging, GeoIP, Encryption, NetworkUtils)
|
||
✅ domain/ (Repository — только SELECT-запросы)
|
||
|
||
❌ НЕ зависит от:
|
||
- public/ (контроллеры)
|
||
- modules/
|
||
- domain/*Service.php (бизнес-мутации)
|
||
```
|
||
|
||
Streaming вызывает **только read-методы** Repository. Запись — через собственные классы (`ConnectionTracker`).
|
||
|
||
### 3.4. `public/` — HTTP точки входа
|
||
|
||
Получение запроса → вызов Service → формирование ответа. Никакой бизнес-логики.
|
||
|
||
- Один front controller `public/index.php` + `Router`
|
||
- Контроллеры вызывают **Service** для мутаций, **Repository** для чтения
|
||
- Шаблоны (`Views/`) — HTML с минимумом PHP-вставок
|
||
- Admin и Reseller — разные контроллеры, общие шаблоны
|
||
|
||
### 3.5. `cli/` — CLI точки входа
|
||
|
||
- Каждый демон/крон — отдельный Command/CronJob класс
|
||
- Все вызовы через `console.php` (например `php console.php cron:streams`)
|
||
- Общая инициализация через `bootstrap.php` + `ServiceContainer`
|
||
|
||
### 3.6. `modules/` — Опциональные модули
|
||
|
||
Каждый модуль — самодостаточная директория с `module.json` + класс, реализующий `ModuleInterface`.
|
||
|
||
| Модуль | Назначение |
|
||
|--------|-----------|
|
||
| `ministra/` | Ministra/Stalker Portal middleware |
|
||
| `plex/` | Plex integration |
|
||
| `tmdb/` | TMDB metadata fetching |
|
||
| `watch/` | Watch/DVR recording |
|
||
| `fingerprint/` | Watermarking |
|
||
| `theft-detection/` | Anti-theft detection |
|
||
| `magscan/` | MAG device scanning |
|
||
|
||
### 3.7. `infrastructure/legacy/` — Остаточный legacy-код
|
||
|
||
Остаточный legacy-код, вынесенный из удалённого `includes/`:
|
||
- `resize_body.php` — логика resize для admin
|
||
- `reseller_api.php` — legacy reseller API-обработчик
|
||
- `reseller_api_actions.php` — действия reseller API
|
||
- `reseller_table_body.php` — формирование таблиц для reseller
|
||
|
||
**Статус:** Директория `includes/` полностью удалена (Phase 15 завершена). Оставшийся код в `infrastructure/legacy/` подлежит дальнейшей миграции в `domain/` сервисы.
|
||
|
||
### 3.8. Bootstrap — контексты инициализации
|
||
|
||
`bootstrap.php` предоставляет `XC_Bootstrap` с четырьмя контекстами:
|
||
|
||
| Контекст | Что загружает | Для чего |
|
||
|----------|--------------|----------|
|
||
| `CONTEXT_MINIMAL` | autoload + constants + config + Logger | Скрипты, которым нужны только пути и конфиг |
|
||
| `CONTEXT_CLI` | + Database + LegacyInitializer (+ Redis опционально) | Cron-задачи, CLI-скрипты |
|
||
| `CONTEXT_STREAM` | + Database (lightweight, cached) | Стриминг-эндпоинты (hot path) |
|
||
| `CONTEXT_ADMIN` | + Database + LegacyInitializer + Redis + API + ResellerAPI + Translator + MobileDetect + session | Админ/реселлер-панель |
|
||
|
||
---
|
||
|
||
## 4. Система модулей
|
||
|
||
### 4.1. Контракт модуля
|
||
|
||
```php
|
||
interface ModuleInterface {
|
||
public function getName(): string;
|
||
public function getVersion(): string;
|
||
public function boot(ServiceContainer $container): void;
|
||
public function registerRoutes(Router $router): void;
|
||
public function registerCommands(CommandRegistry $registry): void;
|
||
public function getEventSubscribers(): array;
|
||
public function install(): void;
|
||
public function uninstall(): void;
|
||
}
|
||
```
|
||
|
||
```json
|
||
// module.json
|
||
{
|
||
"name": "ministra",
|
||
"version": "1.0.0",
|
||
"description": "Ministra portal integration module",
|
||
"requires_core": ">=2.0"
|
||
}
|
||
```
|
||
|
||
### 4.2. Жизненный цикл
|
||
|
||
1. Модуль размещается в `modules/{name}/`
|
||
2. `ModuleLoader` сканирует `modules/*/module.json`
|
||
3. `config/modules.php` проверяется на overrides (`enabled => false`)
|
||
4. `boot(ServiceContainer)` → регистрация сервисов
|
||
5. `registerRoutes(Router)` → HTTP-маршруты
|
||
6. `registerCommands(CommandRegistry)` → CLI-команды и кроны
|
||
7. `getEventSubscribers()` → подписки на события
|
||
8. Установка: `install()` — создание таблиц, начальные данные
|
||
9. Удаление: `uninstall()` → убрать из `modules.php` → удалить папку
|
||
|
||
### 4.3. Правила изоляции
|
||
|
||
```
|
||
✅ Модуль МОЖЕТ:
|
||
- Использовать сервисы из core/ через constructor injection
|
||
- Вызывать Service/Repository из domain/
|
||
- Регистрировать свои маршруты, команды, кроны
|
||
- Подписываться на события ядра
|
||
- Иметь свои views, assets, конфиги
|
||
|
||
❌ Модуль НЕ МОЖЕТ:
|
||
- Модифицировать файлы core/ или domain/
|
||
- Обращаться к БД мимо Repository
|
||
- Зависеть от другого модуля без декларации в module.json
|
||
- Переопределять маршруты или сервисы ядра
|
||
```
|
||
|
||
### 4.4. События-хуки
|
||
|
||
События используются **только для модульных хуков**, а не внутри обычного CRUD.
|
||
|
||
- **Когда использовать:** Модуль хочет отреагировать на действие ядра (DVR-запись при запуске потока)
|
||
- **Когда НЕ использовать:** Внутри CRUD-операций — это прямой вызов в Service
|
||
|
||
---
|
||
|
||
## 5. Варианты сборки: MAIN vs LoadBalancer
|
||
|
||
### 5.1. Два артефакта из одной кодовой базы
|
||
|
||
| Артефакт | Назначение | Что включает | Что исключает |
|
||
|----------|-----------|--------------|---------------|
|
||
| **MAIN** | Основной сервер (admin + streaming) | Всё содержимое `src/` | Ничего |
|
||
| **LoadBalancer (LB)** | Стриминг-сервер без управления | Только стриминг + инфраструктура | Админ-панель, модули, player |
|
||
|
||
**Принцип:** LB — подмножество MAIN. Код не форкается, а фильтруется при сборке.
|
||
|
||
### 5.2. Конфигурация сборки
|
||
|
||
LB собирается из следующих директорий и файлов (определены в `Makefile`):
|
||
|
||
**Директории (`LB_DIRS`):** `bin`, `cli`, `config`, `content`, `core`, `domain`, `includes`, `infrastructure`, `resources`, `signals`, `streaming`, `tmp`, `www`
|
||
|
||
**Root-файлы (`LB_ROOT_FILES`):** `autoload.php`, `bootstrap.php`, `console.php`, `service`, `update`
|
||
|
||
**Исключения (`LB_DIRS_TO_REMOVE`):** `bin/install`, `bin/redis`, `bin/nginx/conf/codes`, `includes/api`, `includes/libs/resources`, `domain/User`, `domain/Device`, `domain/Auth`, `resources/langs`, `resources/libs`
|
||
|
||
**Отдельные файлы, удаляемые из LB (`LB_FILES_TO_REMOVE`):** `infrastructure/legacy/reseller_api.php`, `www/probe.php`, `www/playlist.php`, `www/player_api.php`, `www/epg.php`, `www/enigma2.php`, `www/stream/auth.php`, `www/admin/proxy_api.php`, `www/admin/api.php`, `config/rclone.conf`, ряд CLI-команд и CronJob'ов (admin-only), `domain/Epg/EPG.php`, `bin/nginx/conf/gzip.conf`
|
||
|
||
**Полностью исключены из LB:** `public/`, `modules/`, `ministra/`
|
||
|
||
### 5.3. Правила для разработки с учётом LB
|
||
|
||
1. Код в `domain/`, используемый через streaming, **не должен** тянуть admin-only зависимости
|
||
2. При добавлении новых root-директорий — обновить `LB_DIRS` в Makefile
|
||
3. `domain/` частично нужен LB — нельзя целиком исключать, только admin-specific поддомены
|
||
4. Все модули — это admin-функциональность, в LB не попадают
|
||
5. Проверка сборки: `make new && make lb`
|
||
|
||
### 5.4. Пакетная диаграмма зависимостей
|
||
|
||
```
|
||
┌──────────────┐
|
||
│ public/ │ ← HTTP (Controllers, Views, Routes)
|
||
└──────┬───────┘
|
||
│ depends on
|
||
┌────────────┼────────────┐
|
||
▼ ▼ ▼
|
||
┌──────────┐ ┌──────────┐ ┌──────────┐
|
||
│ domain/ │ │streaming/│ │ modules/ │
|
||
└────┬─────┘ └────┬─────┘ └────┬─────┘
|
||
│ │ │
|
||
└────────────┼────────────┘
|
||
▼
|
||
┌──────────────────────────────────┐
|
||
│ core/ │
|
||
└──────────────────────────────────┘
|
||
│
|
||
▼
|
||
┌──────────────────────────────────┐
|
||
│ infrastructure/ │
|
||
└──────────────────────────────────┘
|
||
```
|
||
|
||
**Стрелки всегда направлены вниз.** Ни один нижний слой не знает о верхних.
|
||
|
||
---
|
||
|
||
## 6. Транзакции и производительность
|
||
|
||
### 6.1. Кто управляет транзакциями
|
||
|
||
**Правило:** Транзакцией управляет **Service**. Контроллер и Repository не открывают транзакции.
|
||
|
||
| Контекст | Транзакция | Кто управляет |
|
||
|----------|-----------|---------------|
|
||
| **Admin CRUD** | Одна операция = одна транзакция | Service |
|
||
| **Mass edit** | Весь batch = одна транзакция | Service |
|
||
| **Import** | Chunk по 100 записей | Service |
|
||
| **Cron** | Каждая итерация = отдельная транзакция | CronJob |
|
||
| **Streaming** | Нет транзакций | — (hot path не мутирует через транзакции) |
|
||
|
||
### 6.2. Внешние процессы
|
||
|
||
Операция «создать поток + запустить ffmpeg + обновить nginx» — не атомарна. FFmpeg/nginx — внешние процессы.
|
||
|
||
**Паттерн:** DB-операции в транзакции → внешние процессы после commit → при сбое обновить статус (`status = 'error'`).
|
||
|
||
### 6.3. Два режима работы
|
||
|
||
| Режим | Путь | Частота | Допустимая latency |
|
||
|-------|------|---------|-------------------|
|
||
| **Hot path** (streaming) | `www/stream/*.php` | ~10K–100K req/min | < 50ms p99 |
|
||
| **Cold path** (admin) | `public/index.php`, API | ~1–100 req/min | < 500ms p99 |
|
||
|
||
### 6.4. Бюджет hot path
|
||
|
||
```
|
||
Bootstrap: < 5ms (autoload + constants + DB)
|
||
Auth: < 10ms (token + Redis + bruteforce)
|
||
Stream lookup: < 5ms (Redis cache) | < 15ms (DB fallback)
|
||
Delivery: < 10ms (redirect + headers)
|
||
─────────────────────────────────────
|
||
Total: < 30ms (target) | < 50ms (max)
|
||
```
|
||
|
||
**НЕЛЬЗЯ загружать в hot path:** Router, EventDispatcher с подписчиками, ServiceContainer полный boot.
|
||
**МОЖНО:** Database (persistent), Redis (single connection), GeoIP (mmap), `streaming/*`.
|