Data associations¶
A data association is the non-ordering edge that moves a value — as
opposed to a sequence flow, which moves a token. It binds a node's declared
input or output to a data element (a DataObject or a DataStoreReference), so
that when the activity runs, the engine reads the source into the node's input
before it executes, and pushes the node's output to the target after. This page
is the developer reference for data.Association and the AssociateSource /
AssociateTarget API you actually call.
Taxonomy¶
| BPMN category | Data Association (§10.3.1 activity I/O, §10.4.2 semantics) |
| Package | github.com/dr-dobermann/gobpm/pkg/model/data |
| Type | data.Association (embeds flow.BaseElement) |
| Built by | AssociateSource / AssociateTarget on the data element (DataObject, DataStoreReference) |
| Node endpoint | flow.AssociationSource (has outputs) / flow.AssociationTarget (has inputs) |
| Direction | data.Input (data → node) or data.Output (node → data) |
You rarely call data.NewAssociation directly — the data element's
Associate* methods build the Association and register it on the node.
The two directions¶
Associations are directional, and the method name is from the node's role, which is easy to get backwards — read the table:
| You call (on the DataObject / DataStoreReference) | The node is | Value flows | BPMN |
|---|---|---|---|
AssociateSource(node, sourceIDs, xform) |
the value source — it produces the value | node output → data element | DataOutputAssociation (Node → data) |
AssociateTarget(node, xform) |
the value target — it consumes the value | data element → node input | DataInputAssociation (data → Node) |
So a task that fills a DataObject uses AssociateSource (the task is the
source); a task that reads one uses AssociateTarget (the task is the target).
Building an association¶
Both methods live on the data element and take the node plus an optional transformation:
func (do *DataObject) AssociateSource(
n flow.AssociationSource, // a node with outputs
sourceIDs []string, // which of the node's outputs feed this element
transformation data.FormalExpression,
) error
func (do *DataObject) AssociateTarget(
n flow.AssociationTarget, // a node with inputs
transformation data.FormalExpression,
) error
DataStoreReference exposes the identical pair. The node must implement the
matching endpoint contract:
type AssociationSource interface {
Node
Outputs() []*data.ItemAwareElement
BindOutgoing(oa *data.Association) error
}
type AssociationTarget interface {
Node
Inputs() []*data.ItemAwareElement
BindIncoming(ia *data.Association) error
}
Real calls from the examples:
// examples/data-store — a task output writes the engine store (output assoc):
ref.AssociateSource(writer, []string{itemID}, nil)
// …and a separate task reads it back (input assoc):
ref.AssociateTarget(reader, nil)
// examples/process-data — a task output fills a per-instance DataObject:
resDO.AssociateSource(st, []string{resID}, nil)
Routing — per-instance scope vs the engine store¶
The same association surface serves two backings, and the association itself carries the routing:
- A
DataObjectassociation moves the value through the per-instance scope (each instance its own copy). - A
DataStoreReferenceassociation moves it through the engine-global Data Store; the association carries thedataStoreRef(Association.DataStoreRef()), and the task reroute branches on it — store vs scope — with no endpoint type-switch.
See Data Objects and Data Store for the two backings, and Scope & the data plane for name resolution.
Transformation¶
The optional transformation (data.FormalExpression) computes the moved value
rather than copying it — the standard's DataAssociation transformation. Pass
nil for a plain copy (the common case); pass an expression to derive the
target from the source(s). See Expressions.
The Association type¶
Once built, the association exposes its routing and value:
| Method | Role |
|---|---|
TargetName() |
the target item's name (the store key / DataObject name). |
SourceNames() / SourcesIDs() |
the source item name(s) / id(s). |
HasSourceID(id) |
whether id is one of the sources (the reroute uses this to match a node output). |
DataStoreRef() |
the engine-store ref, or "" for a scope-backed (DataObject) association. |
Value(ctx) / Find(ctx, name) |
the resolved value / a named source value. |
IsReady() |
whether the source value is available. |
Runtime — how a value moves¶
Associations are evaluated synchronous to the activity lifecycle (§10.4.2), by the task's data phases:
- Before
Exec—LoadData. For each input association, the engine resolves the source (a DataObject by name from scope, or a Data Store value by key) and fills the node's input parameter. A required input that can't be filled fails fast — gobpm never waits for data. - After
Exec—UploadData. For each output association, the engine takes the produced output and writes it to the target — the per-instance DataObject (an in-place scope write) or the engine Data Store (a clonedPut). ThedataStoreRefon the association selects which.
Both phases carry the association's transformation when present. The data-plane mechanics behind resolution live in How a process executes and Scope & the data plane.
See also¶
- The data backings: Data Objects · Data Store
- Where associations are declared: Service Task (its
WithParametersI/O) - The other edge: Sequence flows
- Design: ADR-011 — process data flow · ADR-030 — data objects & store
- Full API:
go doc github.com/dr-dobermann/gobpm/pkg/model/data