go-bpm RU

Code generator

go-bpm-gen validates BPMN and DMN files, produces typed Go code and compiles the generated packages. Handwritten methods and handlers remain part of your service.

Install and run#

go get -tool github.com/nicolas-vp/go-bpm/cmd/go-bpm-gen@latest
go tool go-bpm-gen

Use the same engine and generator version. Default paths are relative to the module's go.mod: input is process/, output is internal/bpmgen/. Run inside a Go module, or specify both input and output paths with the generator flags.

Add a generation directive to a package in your service:

package gen

//go:generate go tool go-bpm-gen

Then run:

go generate ./...

Generated layout#

process/bpmn-gen.go                 embedded BPMN and DMN
internal/bpmgen/
  bpm-gen.go                       process registry
  queue-gen.go                     handler queues
  model/data_model.go              your typed variables
  services/services.go             your dependencies
  handlers/notify_client.go        your service task handler
  order_approval/
    flow-gen.go                    generated process graph
    method_check_order.go          your synchronous method
    condition_*.go                 generated conditions
    decision_*.go                  generated DMN functions

Files marked Code generated by go-bpm-gen. DO NOT EDIT. are regenerated. Model, service, method and handler stubs are created once and then preserved. Keep handwritten logic in those files.

Data model#

type DataModel struct {
    Amount   float64 `json:"amount"`
    Checked  bool    `json:"checked"`
    Notified bool    `json:"notified"`
}

The JSON tag defines the process variable name. One model is shared across processes. Nested structs support nested variable paths. Missing fields stop generation with a suggested field declaration; review the inferred type before adding it.

Methods see typed fields. After a method runs, fields merge back into variables. Unknown variables survive, and omitempty controls whether zero-valued fields are omitted from the merge.

Synchronous methods#

An ordinary task calls a method within the current process step:

func CheckOrder(ctx context.Context, svc *services.Services, data *model.DataModel) error {
    if data.Amount <= 0 {
        return fmt.Errorf("order amount must be positive: %v", data.Amount)
    }
    data.Checked = true
    return nil
}

A later error in the step rolls back its database effects and causes the whole step to repeat. Keep irreversible external operations in idempotent service task handlers.

Service task handlers#

A service task uses a jobType, such as notifyClient. The generator creates its queue registration and handler stub:

func NotifyClient(ctx context.Context, svc *services.Services, job bpm.Job, data *model.DataModel) error {
    slog.InfoContext(ctx, "notifying customer", "instance", job.InstanceUid)
    data.Notified = true
    return nil
}

The handler runs in a separate queue task with BPMN retry settings. Put external clients in services.Services; use interfaces to substitute test doubles.

Conditions, scripts and decisions#

Gateway expressions and scripts become Go functions over DataModel. DMN tables become functions in each calling process package, with typed results and matched rule IDs. Compilation catches missing fields and invalid operations. See BPMN and DMN.

Changing a diagram#

ChangeGenerator behavior
Add a taskCreate a method or handler stub
Remove a taskRemove it from the graph; retain handwritten files
Rename an element IDCreate a new stub; move your code manually
Remove a job typeRetain its handwritten handler
Remove a processRemove generated files; retain handwritten files with a warning
Edit a condition, script or decision tableRegenerate the implementation

Delete obsolete handwritten stubs when they are no longer used.

Running instances and process versions#

Tokens store an element ID and variables. Removing or renaming an element with waiting tokens can break existing instances after deployment. Retain it until those instances have passed, or run old and new service versions with distinct queue KindPrefix settings. See operations.

Troubleshooting#

  • Missing variables: add the suggested model fields and regenerate.
  • Invalid expression: fix its Go-compatible syntax and types.
  • Unsupported BPMN: replace it with a supported element.
  • Several outgoing task flows: insert a gateway.
  • Missing service job type, DMN reference or result variable: complete its properties.
  • Duplicate generated names: choose distinct process and element IDs.
  • Invalid timer: use an ISO 8601 duration without years/months or a zoned RFC 3339 date.
  • Broken XML connections: correct sourceRef / targetRef and any explicit incoming/outgoing declarations.

CI check#

go generate ./...
git diff --exit-code

This catches diagrams committed without regenerated code and accidental edits to generated files.

Suggest an edit on GitHub