Skip to content

Activities

An activity is a unit of work in a process: the engine activates it, runs its behavior, and moves the token on. Every member of the family lives in github.com/dr-dobermann/gobpm/pkg/model/activities and is either a Task (an atomic step — your Go code, a human step, a script, a rule table, a message), a Sub-Process (a container of its own inner graph), or a Call Activity (a reference to a separately registered process). They all share the same base — the Activity attributes and associations: I/O parameters, boundary events, loop/multi-instance characteristics, and compensation — so the shared options below apply to any of them. This page is the family map; each member has its own reference page.

The family

classDiagram
    class Activity {
      I/O parameters
      boundary events
      loop / multi-instance
      compensation
    }
    class Task
    class SubProcess
    class CallActivity
    Activity <|-- Task
    Activity <|-- SubProcess
    Activity <|-- CallActivity
    Task <|-- ServiceTask
    Task <|-- UserTask
    Task <|-- ScriptTask
    Task <|-- BusinessRuleTask
    Task <|-- SendTask
    Task <|-- ReceiveTask
    Task <|-- ManualTask

The three branches map to the flow.ActivityType a member reports from ActivityType():

flow.ActivityType Members
TaskActivity ("Task") Service, User, Script, Business Rule, Send, Receive, Manual
SubProcessActivity ("SubProcess") SubProcess (embedded, event, transaction)
CallActivity ("CallActivity") CallActivity

Most-used members

The steps most processes reach for first:

Type Role Page
ServiceTask run your own Go code — in-process or dispatched to a worker. Service Task
UserTask a human step: assign, list, claim, complete. User Task
SubProcess group inner flow as one collapsible step. Embedded Sub-Process
CallActivity invoke a separately registered process as a child. Call Activity

Every member

Type Constructor Role Page
ServiceTask NewServiceTask(name, operation, opts…) automated work — a service.Operation in-process, or a worker topic. Service Task
UserTask NewUserTask(name, opts…) a human step gated by assignee / candidate users & groups, then claimed for exclusive hold — only the holder may complete it. User Task
ScriptTask NewScriptTask(name, format, body, opts…) evaluate an inline script/expression body. Script Task
BusinessRuleTask NewBusinessRuleTask(name, decisionRef, opts…) evaluate a decision table by reference. Business Rule Task
SendTask NewSendTask(name, msg, opts…) throw a message out of the process. Send / Receive Task
ReceiveTask NewReceiveTask(name, msg, opts…) wait for a matching message (optionally instantiating). Send / Receive Task
ManualTask NewManualTask(name, opts…) non-operational; a no-op pass-through in gobpm. Manual Task
SubProcess NewSubProcess(name, opts…) container of an inner graph; embedded, event, or transaction. Embedded Sub-Process
CallActivity NewCallActivity(name, calledKey, opts…) invoke a registered process as an isolated child instance. Call Activity

Every constructor takes ...options.Option and returns (*T, error) — it rejects an invalid combination rather than panicking. The task types share the Activity options below; each also has its own typed option family covered on its page (SrvTaskOption, UsrTaskOption, RcvTaskOption, SndTaskOption, SubProcessOption, CallActivityOption).

Shared activity options

Every member accepts these ActivityOptions — they configure the common Activity base. Most activities need only I/O declaration:

Option When you reach for it
WithParameters(dir, params…) declare typed data.Input / data.Output parameters.
WithoutParams() declare no parameters — the activity reads/writes process data by name instead.

The full set:

Option Effect
WithParameters(d data.Direction, params ...*data.Parameter) declare the activity's inputs or outputs for a direction; accumulates across calls, skips ids already present.
WithoutParams() declare an empty input and output set; ignores any WithParameters.
WithCompensation() set the isForCompensation flag — the activity is a compensation handler (armed, off the normal flow).
WithLoop(lc LoopCharacteristics) attach loop / multi-instance characteristics so the activity iterates (a later WithLoop replaces an earlier one).
WithStartQuantity(qty int) BPMN start-token quantity (default 1).
WithCompletionQuantity(qty int) BPMN completion-token quantity (default 1).

WithMultyInstance() returns a bare options.Option (not an ActivityOption) and sets the task's multi-instance flag. For real multi-instance semantics build a LoopCharacteristics and pass it via WithLoop — see Iteration.

Boundary events are not a construction option — attach them after the fact with the AddBoundaryEvent method on the activity. See Boundary events.

Loop characteristics

WithLoop takes a LoopCharacteristics — a sealed interface whose concrete kind selects the iteration mechanism. Build one and hand it to any activity:

Constructor Kind Page
NewStandardLoop(loopCondition, opts…) Standard Loop — repeat while a condition holds. Standard Loop
NewMultiInstance(opts…) Multi-Instance — one iteration per collection item, sequential or parallel. Multi-Instance
loop, _ := activities.NewStandardLoop(cond, activities.WithTestBefore())
task, _ := activities.NewServiceTask("retry", op,
    activities.WithLoop(loop),
    activities.WithoutParams())

Sub-process & call flavors

SubProcess and CallActivity carry their own typed options that shape which kind of container / reference you get:

Option Family Effect
WithTriggeredByEvent() SubProcessOption make it an Event Sub-Process — a handler entered by its triggered Start Event, not a sequence flow.
WithTransaction(opts...) SubProcessOption make it a Transaction Sub-Process — a Cancel End inside triggers an ACID-like abort (mutually exclusive with WithTriggeredByEvent).
WithCalledVersion(v int) CallActivityOption pin the call to an exact registered version; without it the call binds latest-at-launch.

See Composition for the container/reuse family.

Reusing tasks

BPMN has a GlobalTask — a task defined once, outside any process, called from many places. gobpm has no such element, and does not need one for authoring: it exists because XML has no functions. A file cannot call a builder, so the standard needs a named, referenceable definition to get reuse at all. You have functions.

Write a constructor. The reusable definition is a Go function returning a configured task:

// ApprovalTask is the reusable definition: every process that needs an approval
// step calls this, and each call yields its own configured task.
func ApprovalTask(name string, approvers ...string) (*activities.UserTask, error) {
    return activities.NewUserTask(name,
        activities.WithCandidateUsers(approvers...),
        activities.WithOutput("decision", "string", true),
        activities.WithoutParams())
}

Then use it wherever you need it:

review, err := ApprovalTask("review", "alice", "bob")
sign,   err := ApprovalTask("sign", "carol")

This is strictly more capable than GlobalTask: the definition takes parameters, so one constructor covers a family of related tasks that XML would need a separate <globalTask> for each of.

Give each task its own node. A task object belongs to one container — add the same object to two processes and the second Add fails. Call the constructor once per use site; that is the point of it being a constructor.

Reuse by copy vs by reference. The pattern above is reuse by copy: each call site gets its own node, built from one definition in your code. BPMN's GlobalTask is reuse by reference — one registered definition, many callers. That needs a registry of callable definitions, and the process registry is one: a global task is a callable process whose body is that one task, so the by-reference path is CallActivity against the process registry, and it launches a child instance:

// by reference — but the target is a registered PROCESS, so this call
// creates a child instance with its own lifecycle and id.
call, err := activities.NewCallActivity("do-approval", "approval-process")

Wrapping a single task in a one-activity process is a legitimate model, but be aware of what it costs: an instance per call, with its own scope and fact stream. For a step used in many places in your own code, prefer the constructor — it is cheaper and can be parameterized.

Reuse by reference is what a document needs, because XML has no functions: a .bpmn file cannot call a Go constructor, so the standard gives it GlobalTask instead. An imported <globalTask> (and its four siblings) therefore becomes exactly the one-activity process described above, built for you and registered under the global task's own id — see examples/bpmn-callable.

What every member implements

An activity is a flow.Node and a flow.ActivityNode (it adds ActivityType() flow.ActivityType). Beyond that the engine drives it through a common surface — you rarely call these directly:

Method Role
ActivityType() flow.ActivityType which of the three branches this member is.
Exec(ctx, re) ([]*flow.SequenceFlow, error) run the behavior and return the outgoing flows.
AddBoundaryEvent(be) / BoundaryEvents() attach / inspect boundary events.
ForCompensation() bool whether the activity is a compensation handler.
Clone() (flow.Node, error) per-instance copy taken from the snapshot.

See also