Перенес workflow

This commit is contained in:
2026-03-05 11:46:05 +03:00
parent 4a0646bb14
commit 89c0d21e88
65 changed files with 1271 additions and 1640 deletions

617
README.md
View File

@@ -1,481 +1,206 @@
# PLBA
## 1. Общее описание сервиса и его назначение
`PLBA` (`Platform Runtime for Business Applications`) - runtime для бизнес-приложений.
`PLBA` (`Platform Runtime for Business Applications`) - это платформенный runtime для бизнес-приложений. Библиотека выносит из прикладного кода типовые инфраструктурные задачи: жизненный цикл приложения, запуск и остановку фоновых воркеров, загрузку конфигурации, health-check, tracing, логирование и control plane.
Платформа берет на себя инфраструктурные обязанности:
- lifecycle приложения
- запуск и остановку воркеров
- health/status
- tracing
- logging
- control plane
- загрузку конфигурации
Назначение сервиса:
- дать единый каркас для запуска бизнес-модулей;
- отделить платформенные обязанности от предметной логики;
- упростить разработку сервисов с очередями, polling-обработчиками и фоновыми процессами;
- обеспечить наблюдаемость и управляемость runtime через health/status и HTTP control endpoints.
Бизнес-приложение на базе `plba` собирается вокруг трех уровней:
- `ApplicationModule` собирает приложение и регистрирует воркеры
- `Worker` управляет исполнением и lifecycle
- `Routine` реализует бизнес-функцию
Текущая модель работы выглядит так:
1. приложение создаёт `RuntimeManager`;
2. runtime загружает конфигурацию;
3. применяется logging-конфигурация;
4. модуль приложения регистрирует очереди, обработчики, воркеры и health contributors;
5. `WorkerSupervisor` запускает все рабочие компоненты;
6. runtime агрегирует состояние, health и tracing;
7. control plane предоставляет снимок состояния и команды запуска/остановки.
`Routine` не является контрактом `plba`. Это рекомендуемый архитектурный паттерн для прикладного кода.
`PLBA` особенно полезен для:
- почтовых ботов и интеграционных сервисов;
- event-driven и queue-driven приложений;
- фоновых бизнес-процессов;
- внутренних платформенных сервисов с единым operational-контуром.
Правила построения приложений на платформе собраны отдельно в [application_guidelines.md](/Users/alex/Dev_projects_v2/apps/plba/requirements/application_guidelines.md).
## 2. Архитектура - диаграмма классов
## Runtime model
```mermaid
classDiagram
class RuntimeManager {
+register_module(module)
+add_config_file(path)
+start()
+stop(timeout, force, stop_control_plane)
+status()
+current_health()
}
1. приложение объявляет `ApplicationModule`
2. модуль регистрирует один или несколько `Worker`
3. `RuntimeManager` запускает все воркеры
4. каждый `Worker` запускает свою бизнес-активность
5. runtime агрегирует health и status
6. runtime останавливает воркеры graceful или forcefully
class ConfigurationManager {
+add_provider(provider)
+load()
+reload()
+get()
+section(name, default)
}
## Main contracts
class ServiceContainer {
+register(name, service)
+get(name)
+require(name, expected_type)
+snapshot()
}
### `ApplicationModule`
class ModuleRegistry {
+register_module(name)
+add_queue(name, queue)
+add_handler(name, handler)
+add_worker(worker)
+add_health_contributor(contributor)
}
Описывает, из чего состоит приложение.
class ApplicationModule {
<<abstract>>
+name
+register(registry)
}
Ответственность:
- дать имя модуля
- зарегистрировать воркеры
- зарегистрировать health contributors при необходимости
- собрать прикладные зависимости
class WorkerSupervisor {
+register(worker)
+start()
+stop(timeout, force)
+snapshot()
+healths()
+statuses()
}
### `Worker`
class Worker {
<<abstract>>
+start()
+stop(force)
+health()
+status()
}
Главный runtime-контракт платформы.
class QueueWorker {
+name
+critical
+start()
+stop(force)
+health()
+status()
}
Контракт:
- `name`
- `critical`
- `start()`
- `stop(force=False)`
- `health()`
- `status()`
class TaskQueue {
<<abstract>>
+publish(task)
+consume(timeout)
+ack(task)
+nack(task, retry_delay)
+stats()
}
`Worker` отвечает только за runtime-поведение:
- как запускается бизнес-активность
- в одном потоке или нескольких
- single-run или loop
- graceful shutdown
- интерпретацию ошибок в `health/status`
class InMemoryTaskQueue {
+publish(task)
+consume(timeout)
+ack(task)
+nack(task, retry_delay)
+stats()
}
### `Routine`
class TaskHandler {
<<abstract>>
+handle(task)
}
Рекомендуемый application-level паттерн.
class TraceService {
+create_context(...)
+open_context(...)
+resume(task_metadata, operation)
+attach(task_metadata, context)
+info(message, status, attrs)
+warning(message, status, attrs)
+error(message, status, attrs)
}
`Routine` описывает бизнес-функцию:
- что читать
- какие сервисы вызывать
- какие бизнес-решения принимать
- что сохранять или отправлять наружу
class HealthRegistry {
+register(contributor)
+snapshot(worker_healths)
+payload(state, worker_healths)
}
Обычно воркер получает одну routine через конструктор и вызывает ее в `start()` или во внутренних helper-методах.
class ControlPlaneService {
+register_channel(channel)
+start(runtime)
+stop()
+snapshot(runtime)
}
class LogManager {
+apply_config(config)
}
class FileConfigProvider {
+load()
}
RuntimeManager --> ConfigurationManager
RuntimeManager --> ServiceContainer
RuntimeManager --> ModuleRegistry
RuntimeManager --> WorkerSupervisor
RuntimeManager --> TraceService
RuntimeManager --> HealthRegistry
RuntimeManager --> ControlPlaneService
RuntimeManager --> LogManager
RuntimeManager --> ApplicationModule
ModuleRegistry --> ServiceContainer
ModuleRegistry --> Worker
ModuleRegistry --> TaskQueue
ModuleRegistry --> TaskHandler
WorkerSupervisor --> Worker
QueueWorker --|> Worker
QueueWorker --> TaskQueue
QueueWorker --> TaskHandler
QueueWorker --> TraceService
InMemoryTaskQueue --|> TaskQueue
ConfigurationManager --> FileConfigProvider
```
### Архитектурные слои
- `app_runtime.core` - оркестрация runtime, контейнер сервисов, регистрация модулей, типы состояния.
- `app_runtime.contracts` - абстракции для интеграции бизнес-приложений.
- `app_runtime.workers`, `queue`, `config`, `logging`, `health`, `tracing`, `control` - инфраструктурные адаптеры и платформенные сервисы.
- `plba` - публичный фасад, который реэкспортирует ключевые классы как API пакета.
## 3. Описание доступных модулей, их назначение, краткое устройство, примеры применения в бизнес приложениях
### `plba`
Публичный API пакета. Реэкспортирует `RuntimeManager`, `ApplicationModule`, `QueueWorker`, `InMemoryTaskQueue`, `TraceService`, `HealthRegistry`, `ControlPlaneService` и другие классы.
Краткое устройство:
- служит фасадом над `app_runtime`;
- упрощает импорт для прикладного кода;
- позволяет использовать пакет как библиотеку без знания внутренней структуры.
Пример применения:
- бизнес-сервис импортирует `create_runtime` и `ApplicationModule`, собирает свой модуль и запускает runtime.
### `app_runtime.core`
Основной orchestration-слой.
Ключевые классы:
- `RuntimeManager` - центральная точка запуска и остановки;
- `ConfigurationManager` - загрузка и merge конфигурации;
- `ServiceContainer` - DI-like контейнер платформенных сервисов;
- `ModuleRegistry` - регистрация очередей, обработчиков, воркеров и health contributors.
Краткое устройство:
- `RuntimeManager` создаёт и связывает инфраструктурные сервисы;
- при старте регистрирует health contributors, воркеры и поднимает control plane;
- `ModuleRegistry` связывает бизнес-модуль с runtime без жёсткой зависимости на конкретные реализации.
Примеры применения в бизнес-приложениях:
- CRM-интеграция с несколькими фоновых воркерами;
- сервис обработки заявок, где один модуль регистрирует очередь, handler и worker pool;
- back-office процесс с управляемым graceful shutdown.
### `app_runtime.contracts`
Набор абстракций для расширения платформы.
Ключевые контракты:
- `ApplicationModule`;
- `Worker`;
- `TaskQueue`;
- `TaskHandler`;
- `ConfigProvider`;
- `HealthContributor`;
- trace-related контракты.
Краткое устройство:
- бизнес-код реализует интерфейсы, а runtime работает только через контракты;
- это позволяет менять инфраструктуру без переписывания прикладной логики.
Примеры применения в бизнес-приложениях:
- реализовать свой `ApplicationModule` для почтового бота;
- подключить собственный `ConfigProvider` для БД или secrets storage;
- реализовать кастомный `Worker` для long-running polling процесса.
### `app_runtime.workers`
Модуль управления рабочими процессами.
Ключевые классы:
- `WorkerSupervisor` - запускает и останавливает набор воркеров;
- `QueueWorker` - стандартный worker для обработки задач из очереди.
Краткое устройство:
- `WorkerSupervisor` агрегирует health/status всех воркеров;
- `QueueWorker` поднимает нужное число потоков, читает задачи из `TaskQueue`, вызывает `TaskHandler`, делает `ack/nack` и обновляет operational-метрики.
Примеры применения в бизнес-приложениях:
- параллельная обработка входящих писем;
- обработка очереди заказов;
- фоновая генерация документов или актов.
### `app_runtime.queue`
Очереди задач.
Ключевой класс:
- `InMemoryTaskQueue`.
Краткое устройство:
- использует стандартный `Queue` из Python;
- хранит счётчики `published`, `acked`, `nacked`, `queued`;
- подходит как базовая реализация для разработки, тестов и простых сценариев.
Примеры применения в бизнес-приложениях:
- локальная очередь задач в небольшом внутреннем сервисе;
- тестовая среда без внешнего брокера;
- staging-сценарии для отладки worker pipeline.
### `app_runtime.config`
Подсистема загрузки конфигурации.
Ключевые классы:
- `FileConfigProvider`;
- `ConfigFileLoader`.
Краткое устройство:
- `ConfigurationManager` собирает данные из провайдеров;
- текущая штатная реализация читает YAML-файл;
- поддерживается глубокое слияние секций конфигурации.
Примеры применения в бизнес-приложениях:
- конфигурация платформы и прикладных модулей из `config.yml`;
- раздельное хранение `platform`, `log` и app-specific секций;
- подключение нескольких источников конфигурации с последующим merge.
### `app_runtime.logging`
Управление логированием.
Ключевой класс:
- `LogManager`.
Краткое устройство:
- применяет `dictConfig` из секции `log`;
- хранит последнюю валидную конфигурацию и пытается восстановиться при ошибке.
Примеры применения в бизнес-приложениях:
- единообразная настройка JSON-логов;
- переключение уровней логирования между окружениями;
- централизованная logging-конфигурация для нескольких модулей.
### `app_runtime.health`
Подсистема health aggregation.
Ключевой класс:
- `HealthRegistry`.
Краткое устройство:
- собирает health от воркеров и дополнительных contributors;
- агрегирует статус в `ok`, `degraded`, `unhealthy`;
- формирует payload для readiness/liveness и operational snapshot.
Примеры применения в бизнес-приложениях:
- показывать degraded, если обработка идёт с ошибками;
- маркировать сервис unhealthy при падении критичного worker;
- добавлять health внешней зависимости, например IMAP или ERP API.
### `app_runtime.tracing`
Подсистема трассировки выполнения.
Ключевые классы:
- `TraceService`;
- `TraceContextStore`;
- `NoOpTraceTransport`.
Краткое устройство:
- создаёт trace contexts;
- связывает source/queue/worker/handler через metadata;
- пишет контексты и сообщения через транспортный слой.
Примеры применения в бизнес-приложениях:
- трассировка обработки письма от polling до бизнес-handler;
- аудит прохождения заказа по pipeline;
- отладка проблемных задач в фоне.
### `app_runtime.control`
Control plane и HTTP-канал управления.
Ключевые классы:
- `ControlPlaneService`;
- `HttpControlChannel`;
- `ControlActionSet`.
Краткое устройство:
- публикует health/status и команды управления runtime;
- может поднимать HTTP endpoints для start/stop/status;
- строит snapshot состояния на основе `RuntimeManager`.
Примеры применения в бизнес-приложениях:
- административный endpoint для оператора;
- health endpoint для Kubernetes/nomad;
- runtime status для monitoring dashboard.
## 4. Установка - `git@git.lesha.spb.ru:alex/plba.git`
### Требования
- Python `3.12+`
- `pip`
- SSH-доступ к `git.lesha.spb.ru`
### Установка напрямую через `pip` из Git-репозитория
```bash
pip install "plba @ git+ssh://git@git.lesha.spb.ru/alex/plba.git"
```
При такой установке `pip` ставит не только сам пакет `plba`, но и все его зависимости, объявленные в [pyproject.toml](/Users/alex/Dev_projects_v2/apps/plba/pyproject.toml), например `fastapi`, `uvicorn` и `PyYAML`.
Если нужна установка из конкретной ветки:
```bash
pip install "plba @ git+ssh://git@git.lesha.spb.ru/alex/plba.git@main"
```
Если нужна установка из конкретного тега или commit hash:
```bash
pip install "plba @ git+ssh://git@git.lesha.spb.ru/alex/plba.git@v0.1.0"
```
или
```bash
pip install "plba @ git+ssh://git@git.lesha.spb.ru/alex/plba.git@<commit-hash>"
```
### Установка в виртуальное окружение
```bash
python -m venv .venv
source .venv/bin/activate
pip install --upgrade pip
pip install "plba @ git+ssh://git@git.lesha.spb.ru/alex/plba.git"
```
### Подключение `plba` в бизнес-приложении
Чтобы при установке бизнес-приложения автоматически подтягивались зависимости `plba`, нужно добавить `plba` в зависимости самого бизнес-приложения как Git dependency.
Пример для `requirements.txt`:
```txt
plba @ git+ssh://git@git.lesha.spb.ru/alex/plba.git
```
Пример для `pyproject.toml`:
```toml
[project]
dependencies = [
"plba @ git+ssh://git@git.lesha.spb.ru/alex/plba.git",
]
```
Если бизнес-приложение собирается в Docker, достаточно чтобы на этапе сборки выполнялся обычный `pip install`, например:
```dockerfile
COPY pyproject.toml .
RUN pip install .
```
или при использовании `requirements.txt`:
```dockerfile
COPY requirements.txt .
RUN pip install -r requirements.txt
```
В обоих случаях `pip` установит `plba` из Git и автоматически подтянет его транзитивные зависимости.
### Локальная разработка
Если пакет нужно не только использовать, но и разрабатывать:
```bash
git clone git@git.lesha.spb.ru:alex/plba.git
cd plba
python -m venv .venv
source .venv/bin/activate
pip install -e .
```
### Быстрая проверка
```bash
python -c "import plba; print(plba.__all__[:5])"
```
### Минимальный пример использования
## Minimal example
```python
from plba import ApplicationModule, InMemoryTaskQueue, QueueWorker, RuntimeManager, Task, TaskHandler
from threading import Event, Lock, Thread
from time import sleep
from plba import (
ApplicationModule,
Worker,
WorkerHealth,
WorkerStatus,
create_runtime,
)
class PrintHandler(TaskHandler):
def handle(self, task: Task) -> None:
print(task.payload)
class OrdersRoutine:
def __init__(self, service) -> None:
self._service = service
def run(self) -> None:
self._service.process_new_orders()
class DemoModule(ApplicationModule):
class OrdersWorker(Worker):
def __init__(self, routine: OrdersRoutine, interval: float = 1.0) -> None:
self._routine = routine
self._interval = interval
self._thread: Thread | None = None
self._stop_requested = Event()
self._lock = Lock()
self._in_flight = 0
self._failures = 0
@property
def name(self) -> str:
return "demo"
return "orders-worker"
@property
def critical(self) -> bool:
return True
def start(self) -> None:
if self._thread and self._thread.is_alive():
return
self._stop_requested.clear()
self._thread = Thread(target=self._run_loop, daemon=True)
self._thread.start()
def stop(self, force: bool = False) -> None:
del force
self._stop_requested.set()
def health(self) -> WorkerHealth:
if self._failures > 0:
return WorkerHealth(self.name, "degraded", self.critical)
return WorkerHealth(self.name, "ok", self.critical)
def status(self) -> WorkerStatus:
alive = self._thread is not None and self._thread.is_alive()
state = "busy" if self._in_flight else "idle"
if not alive:
state = "stopped"
elif self._stop_requested.is_set():
state = "stopping"
return WorkerStatus(name=self.name, state=state, in_flight=self._in_flight)
def _run_loop(self) -> None:
while not self._stop_requested.is_set():
with self._lock:
self._in_flight += 1
try:
self._routine.run()
except Exception:
self._failures += 1
finally:
with self._lock:
self._in_flight -= 1
sleep(self._interval)
class OrdersModule(ApplicationModule):
@property
def name(self) -> str:
return "orders"
def register(self, registry) -> None:
queue = InMemoryTaskQueue()
traces = registry.services.get("traces")
handler = PrintHandler()
queue.publish(Task(name="demo-task", payload={"id": 1}, metadata={}))
registry.add_worker(QueueWorker("demo-worker", queue, handler, traces))
service = OrderService()
routine = OrdersRoutine(service)
registry.add_worker(OrdersWorker(routine))
runtime = RuntimeManager()
runtime.register_module(DemoModule())
runtime = create_runtime(OrdersModule(), config_path="config.yml")
runtime.start()
runtime.stop()
```
## Health and status
Практика такая:
- `Routine` выполняет бизнес-работу
- `Worker` ловит ее ошибки
- `Worker` интерпретирует outcome в `health()` и `status()`
То есть routine не выставляет health напрямую.
## In-memory queue
`InMemoryTaskQueue` остается в платформе как простой in-memory utility.
Это не базовый платформенный контракт и не обязательный паттерн архитектуры.
Ее можно использовать в прикладном коде как локальный буфер между компонентами, если это действительно помогает.
```python
from plba import InMemoryTaskQueue
queue = InMemoryTaskQueue[str]()
queue.put("payload")
item = queue.get(timeout=0.1)
```
## Public API
Основные публичные сущности:
- `ApplicationModule`
- `Worker`
- `WorkerHealth`
- `WorkerStatus`
- `RuntimeManager`
- `WorkerSupervisor`
- `TraceService`
- `HealthRegistry`
- `InMemoryTaskQueue`
- `create_runtime(...)`