Skip to content
bitzorcas
中EN

Guide

Defining and Designing Workflows

Current JSON DSL guide to nodes, edges, expressions, business binding, form fields, participants, multi-instance, timers, listeners, validation, and simulation.

Last updated

A definition is JSON DSL, not BPMN XML. DefinitionCompiler uses System.Text.Json with case-insensitive properties and camelCase enums. Deployment stores the original JSON, tenant, version, and SHA-256 checksum. Instances reference immutable snapshots.

invalidvalid

Edit JSON DSL

Structural and semantic validation

Fix reported issues

Simulate representative variables

Run dual-ORM integration tests

Deploy immutable Definition

Publish Deployment binding

The pipeline separates creating an immutable definition version from changing the tenant or office binding used by new instances. A successful simulation is evidence for one path, not a substitute for persistence, concurrency, and authorization tests.

1. Seven built-in nodes

typeSemanticsImportant fields
startEventsingle entryname, nodePrefix
endEventterminal, multiple allowedname
userTaskcreates human work and suspends Executionparticipants, multiInstance, rollback, config
serviceTaskinvokes a host handlerimplementation
exclusiveGatewayfirst matching branchedge condition and sort
parallelGatewayall branches and scoped joinunconditional edges
inclusiveGatewayevery matching branchconditions and join scope

2. Minimal deployable approval

matter-intake-approval.json
{
"key": "matter-intake-approval",
"name": "Matter intake approval",
"variables": {
"disputeAmount": 0,
"crossBorder": false
},
"nodes": [
{
"id": "start",
"type": "startEvent",
"name": "Submit",
"nodePrefix": "A"
},
{
"id": "partner",
"type": "userTask",
"name": "Lead partner approval",
"nodePrefix": "B",
"participants": {
"roles": ["litigation-partner"],
"positions": [],
"orgUnits": [],
"userIds": [],
"virtualRoles": []
},
"config": {
"requireComment": true,
"allowAddParticipant": true,
"allowTransfer": true,
"allowDelegate": true,
"quickReplies": ["Open the matter", "Conflict check pending"]
}
},
{
"id": "end",
"type": "endEvent",
"name": "Completed"
}
],
"edges": [
{ "source": "start", "target": "partner", "sort": 0 },
{ "source": "partner", "target": "end", "sort": 0 }
]
}

DefinitionCompiler only guarantees basic deserialization. Graph validation is a separate operation. DeployAsync parses but does not call DefinitionValidator, so production release must validate first.

3. Expressions

DefaultExpressionEvaluator supports variables, literals, comparisons, AND, and OR. It does not execute methods, arbitrary scripts, SQL, or object navigation.

Major cross-border escalation route
{
"nodes": [{ "id": "route", "type": "exclusiveGateway", "name": "Matter grading" }],
"edges": [
{
"source": "route",
"target": "committee",
"condition": "${disputeAmount >= 1000000 && crossBorder == true}",
"sort": 10
},
{
"source": "route",
"target": "partner",
"sort": 99
}
]
}

At most one edge should be unconditional as the default. Calculate complex domain decisions before start (for example an investor-state arbitration clause flag) and pass stable variables instead of embedding business policy in DSL.

4. Participants

ParticipantRule supports roles, positions, organizational units, explicit users, virtual roles, and a resolver key. A named IParticipantResolver is used first and explicit users are merged. Otherwise IParticipantProvider expands directory rules.

If the resolved list is empty, UserTaskBehavior leaves the node automatically. A critical approval can therefore auto-pass after directory misconfiguration. Release tests must exercise zero-assignee behavior with real tenant directory data.

5. Multi-instance

Parallel creates work for every participant; Sequential creates one at a time. CompletionCondition can read nrOfInstances, nrOfCompletedInstances, and nrOfActiveInstances.

Conflict committee: two of three
{
"id": "conflict-committee",
"type": "userTask",
"name": "Conflict-of-interest committee",
"participants": {
"userIds": ["101", "102", "103"]
},
"multiInstance": {
"mode": "parallel",
"completionCondition": "${nrOfCompletedInstances >= 2}"
}
}

Test simultaneous threshold completion, sibling cancellation, variable merge, and nesting under parallel gateways.

6. Rollback, task config, and data scope

RollbackRule supports start, prev, selected, and target lists. A Reject request can supply targetNodeId. UserTaskConfig declares required comment, quick replies, auto-commit, handoff switches, withdrawal time limit, and CC recipients.

FlowDataScopeConfig is present in the model, but the platform checker currently receives only View/Operate/Manage plus task/instance identity. DSL rules are not passed into that checker.

7. Timers

TimerConfig accepts ISO 8601 duration and remind, escalate, or transfer actions. The JobHost workflow-timer job scans due entries every five minutes by default (BackgroundJobs:workflow-timer:IntervalSeconds overrides), and its execution scope already registers INotificationChannel → WorkflowNotificationAdapter, so reminder notifications commit atomically with Fired state.

Escalate assignment after two days
{
"timers": [
{
"type": "boundary",
"duration": "P2D",
"action": "escalate",
"escalateTo": "managing-partner"
}
]
}

Timer reliability is still the known weak spot: processing saves Fired before executing the action, so a crash between the two steps leaves no automatic recovery to Pending, and there is no cross-instance claim/lease. Use timers for reminders and escalation, not as a hard SLA.

8. Node listeners

Listener types are expression, notification, and callback. Expression only evaluates a variable; it does not mutate context. Notification recipients come from __listenerRecipients. Callback reuses the business status port. The NodeListenerDispatcher logs each failure and then rethrows — listeners now participate in transactional correctness rather than being silently isolated, so a listener exception fails the whole transition command. Definition authors must give every listener implementation reliability matching the main transaction.

9. Validation and simulation

Validator checks IDs, dangling edges, self-loops, start/end nodes, ingress/egress, gateway conditions, user-task participants, and reachability. Unreachable nodes are warnings. It does not statically verify every timer action or configuration policy.

Simulator stops at UserTask, caps depth at 100, selects only one branch for exclusive/inclusive preview, and only the first parallel branch. It is a designer preview rather than coverage proof.

Release gate
DefinitionValidationResult validation =
await repository.ValidateDefinitionAsync(json, cancellationToken);
// Error issues block release; reachability warnings still require an explicit review.
if (!validation.IsValid)
return RejectWithIssues(validation.Issues);
Result<SimulationResult> simulation =
await repository.SimulateAsync(
json,
new Dictionary<string, object?>
{
["disputeAmount"] = 1200000m,
["crossBorder"] = true
},
cancellationToken);
// Simulation covers this input only; integration tests exercise persisted execution.
return await repository.DeployAsync(
"matter-intake-approval",
"Matter intake approval",
json,
currentUserId,
tenantId,
cancellationToken);

10. Business binding and visual authoring

A definition root can now declare businessBinding.entityName, and a UserTask can declare formFields. Bindable entities and fields come from compile-time [BitzWorkflow] and [BitzFlowScope] metadata instead of a browser-maintained catalog. A formFields item currently has only fieldName and visibility, where visibility is editable, readOnly, or hidden. Requiredness comes from FlowScope metadata and is not a required DSL property.

The full-page frontend designer consumes the server designer-schema and business metadata catalog, and drafts use Revision-based optimistic concurrency. Its /draft/publish path repeats authoritative validation, while the legacy POST /api/workflow/definitions/ path still requires only compilable JSON. See Visual designer for endpoints, permissions, version visibility, and browser recovery boundaries.

11. Definition release checklist

  • Stable key with one business meaning.
  • Every user task resolves assignees in real tenant data.
  • Defaults, missing variables, and type mismatches are tested.
  • Parallel and inclusive joins cannot deadlock or finish early.
  • Variables contain no secrets or oversized objects.
  • DefinitionId, JSON, version, and checksum enter release evidence.
  • Validation, simulation matrix, and database integration tests pass.

To produce this DSL from the BitzOrcas five-table set or a Saury step tree, use the Workflow Migrator. The tool emits structure and a sidecar state map; it does not implement business callbacks.

Next: visual designer · Deployment and versioning

100%

Scroll or use controls to zoom · drag when enlarged · double-click for 100% / 200%