Skip to content

Commit bc7e832

Browse files
authored
Merge pull request #7 from IntentFile/spec/process-parallel
spec(processes): parallel fork/join step (kind: parallel)
2 parents fcf84a7 + 4d95ed3 commit bc7e832

1 file changed

Lines changed: 32 additions & 1 deletion

File tree

β€Ždocs/spec/processes.mdβ€Ž

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ processes:
2121
2222
Generates one process definition per `processes[]` entry (a standard workflow model plus its diagram layout, so a modeller renders it).
2323

24-
Step kinds: `userTask`, `serviceTask`, `decision`, `script`, `wait`, `end`.
24+
Step kinds: `userTask`, `serviceTask`, `decision`, `script`, `wait`, `parallel`, `end`.
2525

2626
### Step routing β€” the linear chain and `next:`
2727

@@ -39,6 +39,37 @@ A `notify` service task stands alone: it cannot carry another action (`setField`
3939

4040
A decision condition may walk **one hop** off the trigger entity (`customer.creditLimit > 10000`): a resolver step is generated before the gateway to load the related entity and rewrite the condition.
4141

42+
### parallel β€” concurrent branches (fork/join)
43+
44+
A `parallel` step runs several branch steps **at the same time** and rejoins before the next step β€” two independent reviews of one order, say, instead of one after the other. It declares the `branches` to run concurrently and the `next` step to continue at once every branch is done:
45+
46+
```yaml
47+
- { name: reviews, kind: parallel, args: { branches: [techReview, commercialReview], next: consolidate } }
48+
- { name: techReview, kind: userTask, args: { assignee: engineer, form: ReviewOrder } }
49+
- { name: commercialReview, kind: userTask, args: { assignee: sales, form: ReviewOrder } }
50+
- { name: consolidate, kind: serviceTask, args: { setRelationField: Status, value: 2, next: done } }
51+
```
52+
53+
It is a **parallel-gateway fork/join**: a diverging gateway fans an unconditioned flow to each branch, and a converging gateway waits for **all** branches before continuing.
54+
55+
A branch is a **chain**, not a single step. It starts at the declared branch step and continues through that step's own routing β€” its `next`, a decision's `then` / `else`, a boundary `timeout` / `expire` branch β€” and it may itself be a nested `parallel`, which contributes its own fork/join pair. Everything reachable that way belongs to the branch and runs concurrently with the sibling branches:
56+
57+
```yaml
58+
- { name: reviews, kind: parallel, args: { branches: [techReview, commercial], next: consolidate } }
59+
# a two-step chain: the second step declares no routing, so it joins
60+
- { name: techReview, kind: userTask, args: { assignee: engineer, form: ReviewOrder, next: techSignoff } }
61+
- { name: techSignoff, kind: serviceTask, args: { setRelationField: TechStatus, value: 2 } }
62+
# a nested fork: it declares no `next`, so its join flows into the enclosing one
63+
- { name: commercial, kind: parallel, args: { branches: [pricing, legal] } }
64+
- { name: pricing, kind: decision, args: { if: "amount > 1000", then: escalate, else: join } }
65+
- { name: escalate, kind: userTask, args: { assignee: manager, form: ReviewOrder } }
66+
- { name: legal, kind: userTask, args: { assignee: legal, form: ReviewOrder } }
67+
```
68+
69+
A branch and everything it reaches are off the linear chain (like decision targets), so their declaration order carries no meaning β€” and inside a branch there is **no positional fall-through**. A step routes explicitly, or, declaring no routing at all, is a branch **terminal** and flows into the join. The routing literal **`join`** converges on the innermost enclosing join gateway, which is how a decision inside a branch rejoins from both arms.
70+
71+
Rules: at least two distinct `branches`, each a declared step other than the fork itself. `join` is valid only inside a branch, and no step may be named `join`. A branch must never route to `end` β€” the join would wait forever for a token that ended; end the process after the fork instead. A step may belong to only one branch: a step two concurrent tokens reach runs twice and still leaves the join waiting. A branch is entered through its fork only, so a branch converges on `join`, never on the fork's own `next` directly. A top-level fork declares `next` (a declared step or `end`); a **nested** fork may omit it, and then joins into its own enclosing join.
72+
4273
### wait β€” park the process on a data event
4374

4475
A `wait` step parks the process until an entity lifecycle event resumes it β€” a case waiting for a reply, a flow waiting for a payment, an order waiting for its goods receipt:

0 commit comments

Comments
Β (0)