go-bpm EN

Открытый код · Go · PostgreSQL

Процессы рисуются в BPMN.
Исполняются как Go-код.

go-bpm превращает схемы BPMN и таблицы DMN в типизированный Go-код и исполняет его на очереди задач в PostgreSQL — в той же базе, что и данные сервиса. Отдельного сервера процессов нет: движок — библиотека внутри вашего сервиса. Методы и решения проверяются обычным go test.

Проверено на Astra Linux Special Edition 1.8.1.6 →

$ go get github.com/nicolas-vp/go-bpm
process/order.bpmn
Процесс согласования заказа: проверка, шлюз «Крупный заказ?», согласование менеджером и уведомление клиента
order_approval/method_check_order.go
func CheckOrder(ctx context.Context, svc *services.Services,
	data *model.DataModel) error {
	if data.Amount <= 0 {
		return fmt.Errorf("сумма должна быть положительной")
	}
	data.Checked = true
	return nil
}

go-bpm за две минуты

Моделер, генерация кода, шаг в одной транзакции, скорость и история экземпляра в консоли — коротко обо всём, что подробнее ниже на странице.

Как это устроено

  1. 1

    Нарисуйте процесс

    В go-bpm modeler — своём редакторе: в палитре только то, что исполнит go-bpm, схема проверяется правилами генератора. Схемы из Camunda Modeler тоже подходят: go-bpm читает расширения gobpm:* и zeebe:*. Схемы BPMN и таблицы DMN лежат в process/ репозитория сервиса и проходят ревью вместе с кодом.

  2. 2

    Сгенерируйте код

    go-bpm-gen проверяет схему и создаёт Go: переходы, условия шлюзов, скрипты, решения DMN и заготовки ваших методов. Опечатку в условии покажет компилятор.

  3. 3

    Запустите сервис

    Движок исполняет процесс на очереди задач в PostgreSQL. Каждый шаг — задача очереди, все его эффекты пишутся одной транзакцией.

Почему go-bpm

Процесс — это код

Условия шлюзов, скрипты и таблицы DMN генерируются в Go-функции до запуска. Нет интерпретатора выражений в рантайме: условие шлюза — обычный if.

Шаг — одна транзакция

Переход токена, история и новые задачи пишутся вместе с завершением задачи очереди. Упал сервис посередине — шаг повторится целиком, токен не потеряется и не раздвоится.

Данные — Go-структура

Переменные процесса читаются в вашу DataModel. Методы работают с полями, а не с map[string]any.

Нужен только PostgreSQL

Очередь, история, экземпляры и задачи пользователей — таблицы bpm_* в вашей базе. Сервис масштабируется несколькими экземплярами на одной базе.

Старт в транзакции сервиса

StartProcessTx запускает процесс в той же транзакции, где сервис пишет свои данные: заказ и его процесс появятся вместе или не появятся вовсе.

Консоль из коробки

/ui/: процессы, запуск, поиск экземпляров, задачи пользователей, история экземпляра поверх диаграммы bpmn-js со сработавшими правилами DMN, термокарта и динамика процесса.

Тестирование

Тесты — обычный go test

Логика процесса — это ваши Go-функции, а не выражения внутри XML. Поэтому её проверяют так же, как остальной код сервиса: без моделера, движка и специального тестового фреймворка.

  • Методы задач — юнит-тест: вызвать функцию с *model.DataModel и проверить поля.
  • Внешние зависимости — интерфейсы в services.Services: в тесте подставляются заглушки.
  • Решения DMN — сгенерированные функции decide<Id>, тестируются из пакета процесса.
  • Процесс целиком — интеграционный тест на настоящем PostgreSQL: запустить процесс и дождаться статуса экземпляра.
  • Ошибки в схеме — ещё раньше, на генерации: опечатку в условии или переменную не того типа покажет компилятор.
// internal/bpmgen/order_approval/method_check_order_test.go
func TestCheckOrder(t *testing.T) {
	ctx, svc := context.Background(), &services.Services{}

	data := &model.DataModel{Amount: 500}
	if err := CheckOrder(ctx, svc, data); err != nil {
		t.Fatal(err)
	}
	if !data.Checked {
		t.Error("заказ не отмечен проверенным")
	}
	if err := CheckOrder(ctx, svc, &model.DataModel{}); err == nil {
		t.Error("пустой заказ прошёл проверку")
	}
}

Настройка и расширение

Подстраивается под ваш проект

go-bpm — библиотека, а не платформа: движок, API и консоль собираются из частей внутри вашего сервиса, и каждую можно настроить или заменить своей.

Ваши зависимости

Клиенты баз, API и очередей — поля services.Services. Они доступны во всех методах и обработчиках, а типы проверяет компилятор.

Ваша модель данных

Переменные процесса — поля вашей DataModel, включая вложенные структуры. Генератор подскажет, каких полей не хватает.

Ваш код не перезаписывается

Генератор создаёт заготовки методов, обработчиков и модели один раз и больше их не трогает. Пересоздаются только файлы с пометкой Code generated by go-bpm-gen.

Ваш HTTP и авторизация

API и консоль — обычные обработчики на вашем http.ServeMux. Подключайте только то, что нужно, и закрывайте своим middleware.

Ваши миграции

migrations.Up применяет схему сам, а для goose, migrate или liquibase SQL берётся из migrations.List().

Тонкая настройка

Воркеры, попытки, таймауты и задержки повторов, логгер slog, история с флагами, которые переключаются на лету, и префикс очереди для версий процессов.

Метрики Prometheus

metrics.Collector регистрируется в реестре сервиса: очередь по статусам, возраст самой старой задачи, упавшие экземпляры, открытые задачи пользователей, время попыток.

Хуки и трассировка

engine.Hooks: события элементов, обёртка вокруг вызова вашего кода — место для span OpenTelemetry — и падение экземпляра, чтобы поднять алерт.

Срок хранения истории

engine.Retention удаляет завершённые и отменённые экземпляры старше срока вместе с историей и задачами. Упавшие не трогает — они ждут разбора.

Все настройки движка, хуки и метрики

Что умеет движок

Генератор проверяет BPMN и отвергает то, что не поддерживается, — на этапе генерации, а не в рантайме.

  • Task, script task, service task с повторами
  • Exclusive и parallel gateway
  • Business rule task — решения DMN, скомпилированные в Go
  • AI task: запрос к модели из схемы, ответ — текстом или JSON в модель данных
  • User task как в Camunda 7: исполнитель, кандидаты, группы
  • Таймеры по длительности и до даты, граничные события таймера и ошибки
  • Эскалация задачи пользователя граничным таймером
  • Receive task и события сообщений с корреляцией по ключу
  • Последовательный multi-instance: число кругов и условие выхода
  • Встроенные подпроцессы и call activity
  • Экземпляры с бизнес-ключом и историей в базе
  • HTTP API процессов, экземпляров и задач
  • Повтор упавших шагов и отмена экземпляров — из консоли и API
  • Термокарта и динамика процесса по истории

Все элементы и ограничения

Поддерживаемые элементы BPMN: события таймера и сообщения, шлюзы, виды задач, граничные события, multi-instance, подпроцесс

Встроить в сервис

Движок, API и консоль подключаются к вашему http.ServeMux и пулу pgx.

go get github.com/nicolas-vp/go-bpm
go get -tool github.com/nicolas-vp/go-bpm/cmd/go-bpm-gen
q := queue.New(pool, queue.Options{})
e, _ := engine.New(q, engine.Config{
	History:      engine.HistoryConfig{ToDB: on}, // экземпляры и история в базе
	RulesHistory: engine.HistoryConfig{ToDB: on}, // сработавшие правила DMN
}, bpmgen.Processes(svc), bpmgen.Queues(svc))

httpapi.Register(mux, e, history) // /api/v1/processes, /api/v1/instances
webui.Register(mux, history)      // /ui/ — консоль и история экземпляра

q.Start(ctx)

Свой сервис с нуля, пошагово

История экземпляра на схеме

Где прошёл токен, какие переменные были у каждой задачи, какое правило DMN сработало, а по всем экземплярам — где процесс проводит время.

Сопровождение

Упавшее чинится без SQL, медленное видно на схеме

Всё это — в консоли /ui/, в HTTP API и в Go-методах: можно встроить в свои инструменты.

Повтор упавшего шага

В истории экземпляра — упавшие задачи его и дочерних экземпляров: элемент, попытки, текст ошибки. «Повторить» возвращает задачу в очередь после выкладки исправления, экземпляр и родители снова выполняются.

Исправление переменных

Если шаг упал на данных, переменные токена открываются JSON-ом: поправьте поле, и задача выполнится с исправленными.

Отмена экземпляра

Корневой экземпляр отменяется вместе с дочерними: снимаются задачи очереди, таймеры и открытые задачи пользователей, статус — cancelled.

Термокарта

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

Динамика

Экземпляры по дням запуска и чем они закончились: завершены, выполняются, упали, отменены. Медиана и p95 длительности завершённых.

Для своих инструментов

GET /instances/{id}/failures, POST /failures/{taskId}/retry, POST /instances/{id}/cancel, /processes/{name}/heatmap и /stats; из Go — engine.InstanceOperations.

Упавшие задачи и отмена в API