@@ -1,214 +1,319 @@
# PLBA
`PLBA` (`Platform Runtime for Business Applications` ) - runtime для бизнес-приложений.
## 1. Назначение платформы
PLBA (`Platform Runtime for Business Applications` ) - это runtime-слой для бизнес-приложений, который забирает на себя инфраструктурную часть исполнения. Платформа стандартизирует запуск и остановку рабочих процессов, контроль состояния приложения и эксплуатационные сервисы вокруг них. З а счет этого прикладной код концентрируется на бизнес-логике, а не на lifecycle, диагностике и служебных механизмах. Базовая модель использования строится вокруг `ApplicationModule` , `Worker` и прикладной `Routine` , где каждый уровень отвечает за свою часть ответственности. В результате приложение получается предсказуемым в эксплуатации, проще в сопровождении и масштабировании.
Платформа берет на себя инфраструктурные обязанности:
- lifecycle приложения
- запуск и остановку воркеров
- health/status
- tracing
- logging
- control plane
- загрузку конфигурации
## 2. Концепция использования
`ApplicationModule` - точка сборки приложения: здесь создаются зависимости, собираются рутины, создаются воркеры и регистрируются дополнительные health-контрибьюторы.
Бизнес-приложение на базе `plba` собирается вокруг трех уровней:
- `ApplicationModule` собирает приложение и регистрирует воркеры
- `Worker` управляет исполнением и lifecycle
- `Routine` реализует бизнес-функцию
`Worker` - основной runtime-контракт платформы: он управляет исполнением (потоки, цикл, стратегия остановки), возвращает `health()` и `status()` , а также определяет критичность компонента для общего health.
`Routine` не является контрактом `plba` . Это рекомендуемый архитектурный паттерн для прикладного кода .
`Routine` - прикладной паттерн (не обязательный контракт платформы): класс или набор классов, где сосредоточена бизнес-логика, которую воркер регулярно или однократно запускает .
Правила построения приложений на платформе собраны отдельно в [application_guidelines.md ](/Users/alex/Dev_projects_v2/apps/plba/requirements/application_guidelines.md ).
Вспомогательные сервисы платформы дополняют core-модель:
- `tracing` (`TraceService` , транспорты) - операционная трассировка контекстов и сообщений.
- `logging` (`LogManager` ) - применение конфигурации логирования из runtime-конфига.
- `health` (`HealthRegistry` ) - агрегирование здоровья воркеров и дополнительных компонентов.
- `workflow` (`WorkflowEngine` и persistence-слой) - исполнение шагов бизнес-процесса с переходами и фиксацией состояния.
- `control plane` (`ControlPlaneService` , `HttpControlChannel` ) - внешние health/action endpoints.
- `queue` (`InMemoryTaskQueue` ) - локальный in-memory буфер как утилита прикладного уровня.
## Installation
## 3. Архитектура
``` mermaid
classDiagram
class ApplicationModule {
<<abstract>>
+name: str
+register(registry)
}
class ModuleRegistry {
+add_worker(worker)
+add_health_contributor(contributor)
+register_module(name)
}
class RuntimeManager {
+register_module(module)
+add_config_file(path)
+start()
+stop(timeout, force)
+status()
+current_health()
}
class WorkerSupervisor {
+register(worker)
+start()
+stop(timeout, force)
+statuses()
+healths()
}
class Worker {
<<abstract>>
+name: str
+critical: bool
+start()
+stop(force)
+health()
+status()
}
class Routine {
<<pattern>>
+run()
}
Установка `plba` через `pip` из git-репозитория:
class ConfigurationManager
class LogManager
class HealthRegistry
class TraceService
class ControlPlaneService
class WorkflowRuntimeFactory
class WorkflowEngine
class WorkflowPersistence
class WorkflowRepository
class CheckpointRepository
class InMemoryTaskQueue
class MySqlTraceTransport
``` bash
pip install "plba @ git+https://git.lesha.spb.ru/alex/plba.git"
ApplicationModule --> ModuleRegistry : register(...)
RuntimeManager --> ApplicationModule : register_module(...)
RuntimeManager --> ModuleRegistry
RuntimeManager --> ConfigurationManager
RuntimeManager --> LogManager
RuntimeManager --> HealthRegistry
RuntimeManager --> TraceService
RuntimeManager --> WorkerSupervisor
RuntimeManager --> ControlPlaneService
WorkerSupervisor --> Worker
Worker --> Routine : invokes
WorkflowRuntimeFactory --> WorkflowEngine : create_engine(...)
WorkflowEngine --> WorkflowPersistence
WorkflowPersistence --> WorkflowRepository
WorkflowPersistence --> CheckpointRepository
TraceService --> MySqlTraceTransport
```
## Runtime model
## 4. Описание компонентов
1. приложение объявляет `ApplicationModule`
2. модуль регистрирует один или несколько `Worker`
3. `RuntimeManager` запускает все воркеры
4. каждый `Worker` запускает свою бизнес-активность
5. runtime агрегирует health и status
6. runtime останавливает воркеры graceful или forcefully
### 4.1 Core модули
## Main contracts
#### ApplicationModule
- Назначение: композиция прикладного приложения и регистрация runtime-компонентов.
- Реализация: контракт `app_runtime.contracts.application.ApplicationModule` , регистрация через `app_runtime.core.registration.ModuleRegistry` .
- Как работает / API / вызовы / таблицы:
- API: `name` , `register(registry)` .
- Типичные вызовы: `registry.add_worker(worker)` , `registry.add_health_contributor(contributor)` .
- `RuntimeManager.register_module()` вызывает `module.register(...)` и добавляет имя модуля в снимок runtime.
- В БД напрямую не пишет.
- Типовая схема использования:
- Создать инфраструктурные и доменные сервисы.
- Создать `Routine` .
- Создать `Worker` с зависимостью на рутину.
- Зарегистрировать воркер и опциональные health contributors.
### `ApplicationModule`
#### Worker
- Назначение: runtime-исполнение бизнес-активности, управление lifecycle, интерпретация health/status.
- Реализация: контракт `app_runtime.contracts.worker.Worker` , оркестрация через `app_runtime.workers.supervisor.WorkerSupervisor` .
- Как работает / API / вызовы / таблицы:
- API: `start()` , `stop(force=False)` , `health() -> WorkerHealth` , `status() -> WorkerStatus` , свойства `name` , `critical` .
- `WorkerSupervisor.start()` запускает все воркеры, `stop()` останавливает и ждет state=`stopped` .
- `RuntimeManager` получает агрегированные `statuses()` /`healths()` у супервизора.
- В БД напрямую не пишет (если прикладной worker сам не реализует persistence).
- Типовая схема использования:
- Внутри `start()` поднимать поток/пул или запускать одноразовую задачу.
- В цикле вызывать `routine.run()` .
- Ошибки бизнес-логики транслировать в `health()` /`status()` .
Описывает, из чего состоит приложение.
#### Routine
- Назначение: изоляция прикладной бизнес-логики от runtime-обвязки.
- Реализация: прикладной класс (паттерн), обычно с методом `run()` ; платформой не навязывается отдельный интерфейс.
- Как работает / API / вызовы / таблицы:
- Типичный API: `run()` + дополнительные domain-методы.
- Вызывается воркером в выбранной стратегии выполнения (single-run/loop, 1..N потоков).
- Может вызывать сервисы интеграций, workflow, очереди, доменные репозитории.
- Запись в таблицы определяется прикладными сервисами, а не самой платформой.
- Типовая схема использования:
- Оставлять рутину тонким оркестратором бизнес-действий.
- Сложную логику выносить в отдельные доменные сервисы.
Ответственность:
- дать имя модуля
- зарегистрировать воркеры
- зарегистрировать health contributors при необходимости
- собрать прикладные зависимости
### 4.2 Service модули
### `Worker`
#### Configuration
- Назначение: централизованная загрузка и слияние runtime-конфигурации.
- Реализация: `ConfigurationManager` , `FileConfigProvider` , `ConfigFileLoader` .
- Как работает / API / вызовы / таблицы:
- API: `add_provider()` , `load()` , `reload()` , `get()` , `section()` .
- `RuntimeManager.start()` вызывает `configuration.load()` .
- Поддерживается YAML/JSON через `ConfigFileLoader.parse()` .
- В БД не пишет.
- Типовая схема использования:
- В bootstrap добавить файл: `runtime.add_config_file("config.yml")` .
- Читать секции из `runtime.configuration.section("...")` в прикладных фабриках.
Главный runtime-контракт платформы.
#### Logging
- Назначение: применение и восстановление валидной конфигурации логирования.
- Реализация: `LogManager` .
- Как работает / API / вызовы / таблицы:
- API: `apply_config(config)` .
- `RuntimeManager.start()` вызывает `logs.apply_config(config)` .
- Если секция `log` некорректна, сервис пытается восстановить предыдущую валидную конфигурацию.
- В БД не пишет.
- Типовая схема использования:
- Передать `log` секцию в конфиг и запускать runtime стандартно через `start()` .
Контракт:
- `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
#### Health
- Назначение: сводный health приложения по воркерам и дополнительным контрибьюторам.
- Реализация: `HealthRegistry` + контрибьюторы по контракту `HealthContributor ` .
- Как работает / API / вызовы / таблицы:
- API: `register()` , `snapshot(worker_healths)` , `payload(state, worker_healths )` .
- Агрегация: `unhealthy` при критичном `unhealthy` , `degraded` при любой деградации, иначе `ok` .
- `RuntimeManager.current_health()` использует `HealthRegistry.payload(... )` .
- В БД не пишет.
- Типовая схема использова ния :
- Зарегистрировать contributor в `ApplicationModule.register(...)` .
- Отдавать health наружу через control plane `/health` .
#### Tracing
- Назначение: фиксация контекстов выполнения и событий по шагам операций.
- Реализация: `TraceService` , `TraceContextStore` , `NoOpTraceTransport` , `MySqlTraceTransport` .
- Как работает / API / вызовы / таблицы:
- API: `create_context()` , `open_context()` , `step()` , `info()/warning()/error()/exception()` , `new_root()` , `child_of()` , `attach()` , `resume()` .
- `TraceService` пишет записи через transport; ошибки транспорта изолируются в логах.
- При `MySqlTraceTransport` записи идут в таблицы:
- `trace_contexts`
- `trace_messages`
- Эталонная схема и migration для MySQL: `requirements/sql/trace_mysql_schema.sql` .
- Trace Context:
- Что это: запись о начале логического контекста выполнения (операция, воркер, подоперация), к которой потом привязываются trace-сообщения.
- Для чего нужен: позволяет собрать дерево исполнения и связать все сообщения конкретной бизнес-операции.
- Атрибуты контекста (`TraceContextRecord` ): `trace_id` , `alias` , `parent_id` , `type` , `event_time` , `attrs` .
- Иерархия: `parent_id` указывает на родительский контекст; так строится цепочка root -> child.
- Таблица: `trace_contexts` .
- Как объявляется в коде:
``` python
from threading import Event , Lock , Thread
from time import sleep
with traces . open_context ( alias = " orders-worker " , kind = " worker " , attrs = { " routine " : " orders " } ) as trace_id :
. . .
```
``` python
root = traces . new_root ( " orders.sync " )
child = traces . child_of ( root , " orders.process_batch " )
```
- Trace Message:
- Что это: событие внутри активного context (статус шага, предупреждение, ошибка, служебная информация).
- Роль `step` : текущая стадия операции (`parse` , `validate` , `persist` и т.д.), которую выставляют через `traces.step("...")` .
- Уровни сообщений: `INFO` , `WARNING` , `ERROR` ; `exception(...)` пишет сообщение уровня `ERROR` .
- Атрибуты сообщения (`TraceLogMessage` ): `trace_id` , `step` , `status` , `message` , `level` , `event_time` , `attrs` .
- Связь с context: каждое сообщение обязательно связано с текущим активным `trace_id` ; без активного контекста `TraceService` выбрасывает ошибку.
- Таблица: `trace_messages` .
- Как правильно применять в проекте:
- Открывать один root-context на единицу бизнес-работы (например, обработка одного сообщения/заказа).
- Перед важными этапами явно менять `step` .
- `info` использовать для нормального прогресса, `warning` для recoverable ситуаций (retry/fallback), `error` /`exception` для сбоев.
- В `attrs` класть диагностические ключи (`entity_id` , `attempt` , `integration` , `duration_ms` ), чтобы ускорить расследование инцидентов.
- Типовая схема использования:
- В воркере открыть контекст (`open_context` ) и на каждом шаге рутины писать `step()/info()` .
from plba import (
ApplicationModule ,
Worker ,
WorkerHealth ,
WorkerStatus ,
create_runtime ,
)
#### Workflow
- Назначение: исполнение step-based workflow с переходами и сохранением прогресса.
- Реализация: `WorkflowRuntimeFactory` , `WorkflowEngine` , `WorkflowPersistence` , `WorkflowRepository` , `CheckpointRepository` .
- Как работает / API / вызовы / таблицы:
- API верхнего уровня: `WorkflowRuntimeFactory.create_engine(workflow)` , `WorkflowEngine.run(context)` .
- `WorkflowEngine` по шагам вызывает `WorkflowStep.run(context)` , применяет `TransitionResolver` , вызывает hooks.
- `WorkflowPersistence` пишет состояние run/step/checkpoint через репозитории.
- При настроенном подключении к БД записи идут в таблицы:
- `workflow_runs`
- `workflow_steps`
- `workflow_checkpoints`
- Без подключения работает in-memory fallback репозиториев.
- Типовая схема использования:
- Описать `WorkflowDefinition` (узлы и transitions).
- Создать engine через фабрику.
- Запускать из рутины или воркера: `engine.run(context)` .
#### Control Plane
- Назначение: внешний канал управления runtime и чтения health/status.
- Реализация: `ControlPlaneService` , `HttpControlChannel` , `HttpControlAppFactory` .
- Как работает / API / вызовы / таблицы:
- API: `register_channel()` , `start(runtime)` , `stop()` , `snapshot(runtime)` .
- HTTP-канал поднимает endpoint'ы:
- `GET /health`
- `GET|POST /actions/{action}` (`start` , `stop` , `status` )
- Колбэки action'ов вызывают async-методы `RuntimeManager` .
- В БД не пишет.
- Типовая схема использования:
- При создании runtime включить `enable_http_control=True` .
- Использовать `/health` для readiness/liveness и `/actions/*` для операционного контроля.
#### Queue
- Назначение: простой in-memory буфер задач/сообщений внутри приложения.
- Реализация: `InMemoryTaskQueue[T]` .
- Как работает / API / вызовы / таблицы:
- API: `put(item)` , `get(timeout)` , `task_done()` , `qsize()` , `stats()` .
- Может использоваться между воркерами/сервисами как локальная очередь.
- В БД не пишет.
- Типовая схема использования:
- Producer в рутине кладет элементы через `put` .
- Consumer-воркер извлекает через `get(timeout)` и обрабатывает.
## 5. MVP бизнес-приложения
Минимальная конфигурация запуска:
1. Создать один `ApplicationModule` .
2. В модуле собрать одну `Routine` и один `Worker` (1 worker -> 1 routine).
3. Зарегистрировать воркер через `registry.add_worker(...)` .
4. Создать runtime: `create_runtime(module, config_path="config.yml")` .
5. Вызвать `runtime.start()` .
Минимальный пример:
``` python
from plba import ApplicationModule , Worker , WorkerHealth , WorkerStatus , create_runtime
from app_runtime . core . registration import ModuleRegistry
class Orders Routine:
def __init__ ( self , service ) - > None :
self . _service = service
class Demo Routine:
def run ( self ) - > None :
self . _service . process_new_orders ( )
print ( " business action " )
class Orders Worker( Worker ) :
def __init__ ( self , routine : Orders Routine, interval : float = 1.0 ) - > None :
class Demo Worker( Worker ) :
def __init__ ( self , routine : Demo Routine) - > 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
self . _started = False
@property
def name ( self ) - > str :
return " orders -worker"
return " demo -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 ( )
self . _started = True
self . _routine . run ( )
def stop ( self , force : bool = False ) - > None :
del force
self . _stop_requested . set ( )
self . _started = False
def health ( self ) - > WorkerHealth :
if self . _failures > 0 :
return WorkerHealth ( self . name , " degraded " , self . critical )
return WorkerHealth ( self . name , " ok " , self . critical )
return WorkerHealth ( name = self . name , status = " ok " , critical = self . critical )
def status ( self ) - > WorkerStatus :
aliv e = 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 )
return WorkerStatus ( nam e= self . name , state = " idle " if self . _started else " stopped " )
class Orders Module( ApplicationModule ) :
class Demo Module( ApplicationModule ) :
@property
def name ( self ) - > str :
return " orders "
return " demo "
def register ( self , registry ) - > None :
service = OrderService ( )
routine = OrdersRoutine ( service )
registry . add_worker ( OrdersWorker ( routine ) )
def register ( self , registry : ModuleRegistry ) - > None :
registry . add_worker ( DemoWorker ( DemoRoutine ( ) ) )
runtime = create_runtime ( Orders Module( ) , config_path = " config.yml " )
runtime = create_runtime ( Demo Module( ) , 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(...)`
Для production-сценария после MVP обычно добавляют `tracing` , `health contributors` , `workflow` и HTTP control plane, но базовый запуск не требует этих расширений.