Data Objects¶
A DataObject is a diagram-visible, named data container that lives in an
instance's scope. Where a Property is hidden engine state you seed at build
time, a DataObject is a variable a node writes at run time — a task's output
flows into it through a data association, and from then on it is an ordinary
scope-resident value reachable by name: from another node, an expression, or
the instance handle. It is per-instance — each instance clones its own copy, so
concurrent instances never share DataObject state.
This page is the data-model reference: the type, its constructor, the association methods you call to wire it, and its runtime behavior.
Taxonomy¶
| BPMN category | Data → Data Object (§10.4.1) |
| Package | github.com/dr-dobermann/gobpm/pkg/model/data_objects (imported as dataobjects) |
| Type | dataobjects.DataObject |
| Embeds | flow.BaseElement, data.ItemAwareElement |
| Element type | flow.DataObjectElement ("DataObject") — via EType(), so it can be proc.Add-ed to a Process/SubProcess |
| The work | holds one item value; fed by a DataAssociation from a producing node |
A DataObject is item-aware (it carries an ItemDefinition and a DataState),
which places it in the same family as Property and the task I/O parameters —
see Item definitions & item-aware elements.
Constructor¶
func New(
name string,
idef *data.ItemDefinition,
state *data.SrcState,
baseOpts ...options.Option,
) (*DataObject, error)
| Parameter | Meaning |
|---|---|
name |
the object's diagram name and its by-name lookup key. |
idef |
the item it holds — an *data.ItemDefinition carrying the value and the id that ties an association to it. Build one with data.MustItemDefinition(values.NewVariable(zero), foundation.WithID(id)). |
state |
an optional *data.SrcState (BPMN Data State); pass nil for none. |
baseOpts |
zero or more options.Option (e.g. foundation.WithID). |
It returns an error — never panics — on an invalid item definition or option combination.
Note:
data.CreateDefaultStates()must run once before building any item-aware element — it registers the standard data states (Ready,Unavailable, …). Skip it and construction fails.
Wiring: the association methods¶
A DataObject does not read process data on its own — a producing node's output
is copied into it along a data association. The DataObject is the
association's target; AssociateSource builds that link:
| Method | Role |
|---|---|
AssociateSource(n flow.AssociationSource, sourceIDs []string, transformation data.FormalExpression, shape ...options.Option) error |
make node n's outputs (named by sourceIDs) the source; the DataObject is the target. The common case. |
AssociateTarget(n flow.AssociationTarget, transformation data.FormalExpression, shape ...options.Option) error |
the reverse — feed the DataObject's value into node n's input. |
AssociateTargetInput(n flow.AssociationTarget, inputID string, transformation data.FormalExpression, shape ...options.Option) error |
the reverse, naming the input by its id — for a throw event, whose input carries its definition's item (Event data). |
Update(ctx context.Context) error |
recompute the object's state. |
func (do *DataObject) AssociateSource(
n flow.AssociationSource,
sourceIDs []string,
transformation data.FormalExpression,
shape ...options.Option,
) error
| Parameter | Meaning |
|---|---|
n |
the producing node — anything implementing flow.AssociationSource (e.g. a ServiceTask). |
sourceIDs |
the ids of n's output parameters whose values flow into the object. The id is the wiring: it must match the DataObject's item-definition id. |
transformation |
a data.FormalExpression applied as the value flows in; nil for a straight copy. |
shape |
the association's other two shapes — data.WithAssignments(...) for field-level writes, data.WithSources(...) for a second source. Optional; see below. |
The three shapes¶
BPMN gives an association exactly one execution shape (§10.4.2), and gobpm accepts one at a time:
- a transformation — one expression produces the whole target value. Pass it positionally; the sources are what it may read.
- assignments —
data.WithAssignments(data.MustAssignment(from, "quote.status"))writes eachfrominto the path itstonames, leaving the rest of the target untouched. - a plain copy — neither, and then exactly one source, whose value is copied as it is.
Passing both a transformation and assignments is refused when the association
is built, not at run time. Several sources (data.WithSources) are only
meaningful under a transformation or assignments — a plain copy has nothing to
combine them with. A worked example of the first two is
examples/association-expressions/.
Build it¶
Three pieces line up by a shared id (resID): the task output parameter,
the DataObject's item definition, and the association. From
examples/process-data/:
Declare the task output the operation fills —
outParam := data.MustParameter(name+" result",
data.MustItemAwareElement(
data.MustItemDefinition(
values.NewVariable(""),
foundation.WithID(resID)),
data.UnavailableDataState))
st, err := activities.NewServiceTask(name, op,
activities.WithParameters(data.Output, outParam))
Create the DataObject over the same id, then associate the task as its source —
resDO, err := dataobjects.New(name+"-result",
data.MustItemDefinition(
values.NewVariable(""),
foundation.WithID(resID)),
nil)
// task output (resID) → DataObject
err = resDO.AssociateSource(st, []string{resID}, nil)
Register the DataObject on the process alongside the flow nodes, so every instance seeds its own copy into scope —
for _, e := range []flow.Element{
start, split, greetA, greetB, endA, endB, resultA, resultB,
} {
_ = proc.Add(e)
}
Run it¶
cd examples/process-data && go run .
Two parallel branches each produce a greeting and land it in their own per-instance DataObject; the program reads each one back by name through the instance handle:
▶ greet-a produced "Hello, dr.Dobermann!" (instance started 2026-07-27 …)
▶ greet-b produced "Welcome, dr.Dobermann!" (instance started 2026-07-27 …)
✓ greet-a-result = "Hello, dr.Dobermann!"
✓ greet-b-result = "Welcome, dr.Dobermann!"
✓ data-demo completed: the property fed both branches through their frames;
each result reached its per-instance DataObject in scope, read back by name
Reading a DataObject back is the ordinary by-name resolve — nothing DataObject-specific:
d, err := h.Data().GetData(res.do.Name())
got, _ := d.Value().Get(ctx).(string)
Runtime behavior¶
The engine drives the association; a developer needs to know these:
- Registration seeds per-instance copies. Because the DataObject is
proc.Add-ed to the process, each instance clones it (CloneDataObjects) into its own scope at start; the branch results land in that instance's objects, not a shared one. This scope-tree residency is what makes it resolvable by name (ADR-030 §2.1) — the difference from aPropertyis visibility, not mechanism. - The id is the wiring.
AssociateSource(st, []string{resID}, nil)matches the object's source to the task output whose id isresID. Mismatch the ids and nothing flows. - Commit is asynchronous. The value reaches the object when the producing
frame commits (its
UploadDatastage), not when the operation returns — the example waits for both branches, then a brief grace, before reading the objects back. - Lifecycle follows the parent scope. A DataObject is instantiated when its parent scope opens and disposed when it closes (BPMN §10.4.1) — a Process-level object lives for the whole instance; a Sub-Process one for that sub-process's activation.
DataObject vs its neighbours¶
| Seeded when | Lifetime | Written by | |
|---|---|---|---|
Property |
build time (data.WithProperties) |
its scope | seeded, then nodes |
| DataObject | instance start (registered on the process) | its scope (dies with the instance) | a node, at run time, via association |
| Data Store | engine construction | engine-global, outlives every instance | a node, via the same association wiring |
All three are read by name through the same walk-up resolver; the association
wiring (AssociateSource / AssociateTarget) is identical for a DataObject and
a Data Store — only the residency differs.
See also¶
- Full example:
examples/process-data/ - Related guides: Working with data — overview · Item definitions · Data Store · Reading & writing by path
- Design: ADR-030 — Data Objects & Data Store
- Full API:
go doc github.com/dr-dobermann/gobpm/pkg/model/data_objects