go-bpm

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

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

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

$ 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
}

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

  1. 1

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

    В Camunda Modeler, платформа Camunda 8. Схемы 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, история с флагами, которые переключаются на лету, и префикс очереди для версий процессов.

Все настройки движка

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

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

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

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

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

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

Движок, 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 сработало.