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.

Installation

Установка plba через pip из git-репозитория:

pip install "plba @ git+https://git.lesha.spb.ru/alex/plba.git"

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

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.

Это не базовый платформенный контракт и не обязательный паттерн архитектуры. Ее можно использовать в прикладном коде как локальный буфер между компонентами, если это действительно помогает.

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(...)
Description
No description provided
Readme 260 KiB
Languages
Python 100%