Workflow definitions and approval processes
Page ID and namespace are not decided yet - fix the internal links below before publishing.
An approval process decides whether a request (assignment of a role, change of an assigned role, recertification, …) has to be approved and by whom. It binds three things together: the event that triggers it, the workflow definition that describes the approval graph, and an optional condition that narrows down when it applies.
Since IdM 16 the product uses its own workflow engine (agenda Workflows) instead of Activiti. IdM 16.1 adds a set of workflow definitions and approval processes that are delivered with the product, so that an upgraded instance keeps approving role requests the way it did before the engine was replaced.
Version
| Version | Compatible with product | Notes |
|---|---|---|
| 16.1.0 | 16.1.0 | Workflows delivered with the product, default approval processes, read only system workflows, copy of a workflow, unique code of a definition |
| 16.0.0 | 16.0.0 | New workflow engine, agenda Workflows, versioning of a definition |
Compatible with the product from the version stated above.
Concepts
- Workflow definition - the approval graph: nodes (start, condition, approval, end), their ports and the edges between them. Stored in the agenda Workflows.
- Approval process (
IdmBusinessProcess) - configuration record that says "when event X happens, run workflow Y". It carries the triggering workflow event type, the workflow, an optional named condition, the order and a disabled flag. - Named condition - a condition attached to one approval process. It belongs to that process only and is created and removed together with it.
- Workflow instance - one run of a definition. Instances keep the version of the definition they started with.
Workflows delivered with the product
@since 16.1.0 Three workflow definitions ship inside the product and are deployed on every application start:
| Code | What it does |
|---|---|
| `system-workflow-approve-by-manager/guarantor` | Approved by the manager of the applicant or by the guarantor |
| `system-workflow-approve-by-manager/guarantor–role-criticality-1+` | The same, but only for roles with criticality 1 and higher. Used by the default approval processes |
| `system-workflow-approve-by-role-guarantor` | Approved by the guarantor of the requested role |
They are read only
A workflow delivered with the product is managed by the application. It cannot be changed and it cannot be deleted - not through the user interface, not over REST and not by an import. The editor opens it in a read only mode: no palette, no Save, the fields are greyed out, a node cannot be moved or deleted. Clicking a node still shows its properties, read only.
The
systemWorkflow property is read only on REST, so it cannot be sent from a client, and an import never grants it either. The only write path that marks a workflow as delivered with the product is the deployment described below. Want your own variant? Use Duplicate workflow and edit the copy. That is the supported way to build on what the product delivers.
What happens on every start
The deployment compares the shipped content with what is in the database and acts per workflow:
| Situation in the database | Result of the start |
|---|---|
| The workflow is missing | It is created |
| It is there and unchanged | Nothing is written |
| Its content differs | It is repaired back to the shipped content |
| It was deleted | It is created again |
| It is archived and no current version exists | The archived version is restored as the current one |
| Another workflow occupies the shipped identifier | The start fails, an administrator has to resolve it |
| Two current workflows share the shipped code | A warning is logged and the workflow is skipped |
An unchanged workflow is deliberately not saved again: a save of a definition that has instances creates a new version (see below), so writing on every start would produce a new version per restart. The comparison uses the content of the graph - its natural keys, node attribute values and edges. The layout is not part of it, so moving a node in the editor is not a content change.
When the deployment fails
The deployment runs as an initialization processor (core-init-system-workflow-processor). An enabled initialization processor that fails prevents the application from starting - that is the standard behaviour and it is intentional here: an instance without the shipped workflows would silently approve nothing. Data situations the processor recognises (an ambiguous code) are answered with a warning instead.
If starting without the shipped workflows is preferred, disable the processor in the configuration:
# Deployment of the workflows delivered with the product idm.sec.core.processor.core-init-system-workflow-processor.enabled=true
Versioning of a definition
A definition that has no instance is updated in place. A definition that has an instance is versioned by copy on write:
- the current row is archived and keeps its graph, so running instances still see what they started with,
- a new row is created with the same code, the same version group and the version increased by one,
- approval processes that referenced the old version are re-pointed to the new one.
Archived versions therefore share the code with the current one by design. Deleting a definition that has instances archives it as well, it does not remove it.
Uniqueness of the code
@since 16.1.0 The code of a current (not archived) workflow definition is unique. Everything that resolves a workflow by its code - the deployment above, the default approval processes, pairing of an import - expects exactly one row, and before 16.1 two current definitions could share a code even through the user interface.
The rule is enforced twice: by a partial unique index in the database (ux\_wfe\_definition\_code, only over rows that are not archived) and by a validation in the service, so a client gets a readable error (WORKFLOW\_CODE\_NOT\_UNIQUE) instead of a constraint violation.
A definition without a code is allowed and is not checked - the graph can be built before the code is filled in.
Copying a workflow
@since 16.1.0 The bulk action Duplicate workflow in the agenda Workflows creates an independent copy of the selected definitions, including the whole graph. The copy:
- gets new identifiers - the definition and every node, port and edge,
- gets the first free code in the form `<original code>_1`, `_2`, … and the name with ` (1)` appended,
- starts its own group of versions at version one, so it is a new workflow and not another version of the original,
- is never a workflow delivered with the product, even when the original is.
Export and import
A definition can be exported and imported through the standard export and import batches. Two things are specific to this agenda:
- Pairing. An imported definition is paired with an existing one by code, so an import updates the workflow of the same code instead of creating a second one.
- References travel as natural keys. Node types, referenced roles and script codes are exported by their code and resolved in the target environment, because their identifiers differ per instance.
An import cannot change a workflow delivered with the product and cannot grant that flag either. An import of an unchanged shipped workflow is a no operation.
When an imported item violates a business rule, the import manager reports a transaction error instead of the localized reason - the reason itself is in the log. That is how the import manager reports any violated rule, it is not specific to workflows.
Approval processes
Approval processes live in the agenda Business processes. Each one binds a triggering event to a workflow.
What the product delivers
@since 16.1.0 After an upgrade the product creates the approval configuration an instance had before the engine was replaced:
| Code | Triggering event | Workflow used |
|---|---|---|
| `system-process-assign-role` | Assignment of a role | Approve by manager or guarantor, role criticality 1 and higher |
| `system-process-change-assigned-role` | Change of an assigned role | The same |
| `system-process-recertificate-role` | Recertification of an assigned role (module rec) | The same |
A process is created only when it is missing. An existing configuration is never changed, so an administrator can replace the default or switch it off.
A disabled process stays disabled - the deployment counts it as an existing configuration and does not touch it.
A deleted process is created again by the next start, with a new identifier.
Order and who claims a request
Approval processes are started in the order given by the order column, and the first process that reaches a concept of the request claims it - a later process is not started for the same concept. The processes delivered with the product therefore use the order `1000`, so that a process created by an administrator (order `0` unless it is filled in) always wins.
Condition
An approval process can carry a named condition that decides whether the process applies at all (for example only for identities with a given form projection). The condition belongs to its process: it is created with it and @since 16.1.0 removed with it, including its extended attributes.
Uniqueness of the code
@since 16.1.0 The code of an approval process is unique. The database has enforced it since the agenda was introduced, but a reused code ended as an internal error - now it is refused with BUSINESS\_PROCESS\_CODE\_NOT\_UNIQUE.
A process the application cannot resolve
When the triggering event of a process is not known to the application - typically after a module was uninstalled - the process is ignored, a warning is logged and the requests of other events are not affected. The same holds for a process whose workflow no longer exists.
Configuration
In the application profile (application.properties) - overloadable via ConfigurationService.
# Product initialization data. The default approval processes are init data, so they are # not created when this is switched off. The shipped workflows are deployed regardless - # they are an artifact, like scripts and notification templates. # @since 16.1.0 idm.pri.core.init.data.enabled=true # Deployment of the workflows delivered with the product # @since 16.1.0 idm.sec.core.processor.core-init-system-workflow-processor.enabled=true # Creation of the default approval processes # @since 16.1.0 idm.sec.core.processor.core-init-business-process-processor.enabled=true
idm.sec.core.wf.approval.* and idm.sec.core.wf.role.approval.* does not configure the approval of IdM 16. Those keys belong to the removed Activiti engine and are read only by artifacts that are no longer deployed. The approval of IdM 16 is configured by the approval processes described above. Do not add anything there.
Known limitations
- A workflow delivered with the product can still be selected into the delete bulk action. The server refuses the item, so the workflow survives and the item ends as failed with the localized reason. This is a limitation of the selection, not a defect.
- The user interface saves the whole definition with a single POST - there is no partial update of a graph.
Developer guide
Services
| Service | Method | What it is for |
|---|---|---|
WfeDefinitionService | upsert(dto) | Standard save of the whole aggregate. Refuses a change of a workflow delivered with the product, validates the uniqueness of the code, versions the definition when it has instances |
WfeDefinitionService | deploySystemWorkflow(dto) | The only write path that marks a workflow as delivered with the product. Used by the deployment |
WfeDefinitionService | duplicate(id) | Independent copy, see Copying a workflow |
IdmBusinessProcessService | supports, shouldBeStarted | Decides whether a process applies to an event and whether it claims a concept |
WorkflowEventTypeManager | findByCode(code) | Resolves the triggering event. Returns an empty Optional for an unknown code instead of failing |
Result codes
| Code | Status | When |
|---|---|---|
SYSTEM\_WORKFLOW\_DELETE\_FAILED | 400 | Delete of a workflow delivered with the product |
SYSTEM\_WORKFLOW\_UPDATE\_FAILED | 400 | Change of a workflow delivered with the product outside the deployment |
SYSTEM\_WORKFLOW\_CODE\_CHANGE\_FAILED | 400 | Change of the code of a workflow delivered with the product |
SYSTEM\_WORKFLOW\_ID\_OCCUPIED | 400 | Another workflow occupies the identifier of a shipped one. Raised during the deployment, so it prevents the start |
WORKFLOW\_CODE\_NOT\_UNIQUE | 400 | Another current definition already uses the code |
BUSINESS\_PROCESS\_CODE\_NOT\_UNIQUE | 400 | Another approval process already uses the code |
Extension points
- Content comparison -
WfeDefinitionContentSignatureexpresses the content of a definition by its natural keys, so two definitions can be compared regardless of their identifiers. The layout is deliberately outside the signature. - Portable references - references that differ per instance are exported as natural keys and resolved back on import. A new kind of reference has to be added there, otherwise an exported workflow does not survive the transfer.
- Initialization -
InitSystemWorkflowProcessordeploys the shipped batch,InitBusinessProcessProcessorcreates the default approval. A module that ships its own default approval follows the second one (see the module rec) and reusesIdmBusinessProcessService.DEFAULT\_APPROVAL\_WORKFLOW\_CODEandDEFAULT\_APPROVAL\_PROCESS\_ORDER.
- A workflow delivered with the product can still be selected into the delete bulk action. The server refuses the item, so the workflow survives and the item ends as failed with the localized reason. This is a limitation of the selection, not a defect.
- The user interface saves the whole definition with a single POST - there is no partial update of a graph.
Developer guide
Services
| Service | Method | What it is for |
|---|---|---|
WfeDefinitionService | upsert(dto) | Standard save of the whole aggregate. Refuses a change of a workflow delivered with the product, validates the uniqueness of the code, versions the definition when it has instances |
WfeDefinitionService | deploySystemWorkflow(dto) | The only write path that marks a workflow as delivered with the product. Used by the deployment |
WfeDefinitionService | duplicate(id) | Independent copy, see Copying a workflow |
IdmBusinessProcessService | supports, shouldBeStarted | Decides whether a process applies to an event and whether it claims a concept |
WorkflowEventTypeManager | findByCode(code) | Resolves the triggering event. Returns an empty Optional for an unknown code instead of failing |
Result codes
| Code | Status | When |
|---|---|---|
SYSTEM\_WORKFLOW\_DELETE\_FAILED | 400 | Delete of a workflow delivered with the product |
SYSTEM\_WORKFLOW\_UPDATE\_FAILED | 400 | Change of a workflow delivered with the product outside the deployment |
SYSTEM\_WORKFLOW\_CODE\_CHANGE\_FAILED | 400 | Change of the code of a workflow delivered with the product |
SYSTEM\_WORKFLOW\_ID\_OCCUPIED | 400 | Another workflow occupies the identifier of a shipped one. Raised during the deployment, so it prevents the start |
WORKFLOW\_CODE\_NOT\_UNIQUE | 400 | Another current definition already uses the code |
BUSINESS\_PROCESS\_CODE\_NOT\_UNIQUE | 400 | Another approval process already uses the code |
Extension points
- Content comparison -
WfeDefinitionContentSignatureexpresses the content of a definition by its natural keys, so two definitions can be compared regardless of their identifiers. The layout is deliberately outside the signature. - Portable references - references that differ per instance are exported as natural keys and resolved back on import. A new kind of reference has to be added there, otherwise an exported workflow does not survive the transfer.
- Initialization -
InitSystemWorkflowProcessordeploys the shipped batch,InitBusinessProcessProcessorcreates the default approval. A module that ships its own default approval follows the second one (see the module rec) and reusesIdmBusinessProcessService.DEFAULT\_APPROVAL\_WORKFLOW\_CODEandDEFAULT\_APPROVAL\_PROCESS\_ORDER.
Things that are easy to get wrong
- The whole initialization runs in ONE transaction. An exception from any initialization processor rolls the whole event back, so a per item try and catch does not isolate anything. This is why the deployment refuses to write on a code it cannot resolve unambiguously.
- An eventable service needs its delete processor.
IdmNamedConditionServicepublishes a delete event and the row is removed byIdmNamedConditionDeleteProcessor. Without such a processor a delete is a silent no operation - no exception and nothing deleted. - Archiving has to reach the database before the new version is inserted. A new version carries the code of the original, and Hibernate executes inserts before updates within one flush, so the insert would meet a row that is not archived yet and collide with the unique index of the code.
- A read only property is invisible in both directions of a round trip.
systemWorkflowis serialized but never deserialized, so reading a response back into the DTO always yields `false`. Assert such a property against the raw json.
Notes for tests
- The workflows and the default approval processes delivered with the product are present in every test context. A test that builds its own approval configuration has to clear the existing one first, otherwise the shipped process claims the concept and the own process is never started.
- A fixture that sits on a shipped identifier or holds a shipped code needs its own clean up - the clean up by the flag does not find an unflagged row, and an unflagged row with a shipped code is adopted by the next test.
- The long running task is synchronous in the test profile, so a refused item of a bulk action marks the shared transaction rollback only and the whole action ends with a transaction error. In production the task runs asynchronously and every item gets its own transaction.