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

207 lines
6.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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(...)`