Files
plba/README.md
T
2026-03-05 11:46:05 +03:00

207 lines
6.5 KiB
Markdown

# PLBA
`PLBA` (`Platform Runtime for Business Applications`) - runtime для бизнес-приложений.
Платформа берет на себя инфраструктурные обязанности:
- lifecycle приложения
- запуск и остановку воркеров
- health/status
- tracing
- logging
- control plane
- загрузку конфигурации
Бизнес-приложение на базе `plba` собирается вокруг трех уровней:
- `ApplicationModule` собирает приложение и регистрирует воркеры
- `Worker` управляет исполнением и lifecycle
- `Routine` реализует бизнес-функцию
`Routine` не является контрактом `plba`. Это рекомендуемый архитектурный паттерн для прикладного кода.
Правила построения приложений на платформе собраны отдельно в [application_guidelines.md](/Users/alex/Dev_projects_v2/apps/plba/requirements/application_guidelines.md).
## Runtime model
1. приложение объявляет `ApplicationModule`
2. модуль регистрирует один или несколько `Worker`
3. `RuntimeManager` запускает все воркеры
4. каждый `Worker` запускает свою бизнес-активность
5. runtime агрегирует health и status
6. runtime останавливает воркеры graceful или forcefully
## Main contracts
### `ApplicationModule`
Описывает, из чего состоит приложение.
Ответственность:
- дать имя модуля
- зарегистрировать воркеры
- зарегистрировать health contributors при необходимости
- собрать прикладные зависимости
### `Worker`
Главный runtime-контракт платформы.
Контракт:
- `name`
- `critical`
- `start()`
- `stop(force=False)`
- `health()`
- `status()`
`Worker` отвечает только за runtime-поведение:
- как запускается бизнес-активность
- в одном потоке или нескольких
- single-run или loop
- graceful shutdown
- интерпретацию ошибок в `health/status`
### `Routine`
Рекомендуемый application-level паттерн.
`Routine` описывает бизнес-функцию:
- что читать
- какие сервисы вызывать
- какие бизнес-решения принимать
- что сохранять или отправлять наружу
Обычно воркер получает одну routine через конструктор и вызывает ее в `start()` или во внутренних helper-методах.
## Minimal example
```python
from threading import Event, Lock, Thread
from time import sleep
from plba import (
ApplicationModule,
Worker,
WorkerHealth,
WorkerStatus,
create_runtime,
)
class OrdersRoutine:
def __init__(self, service) -> None:
self._service = service
def run(self) -> None:
self._service.process_new_orders()
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 "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:
service = OrderService()
routine = OrdersRoutine(service)
registry.add_worker(OrdersWorker(routine))
runtime = create_runtime(OrdersModule(), config_path="config.yml")
runtime.start()
```
## 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(...)`