207 lines
6.5 KiB
Markdown
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(...)`
|