Skip to content

Commit 3533410

Browse files
author
Tihomir Surdilovic
authored
Merge pull request #33 from tsurdilo/addcompensation
[1.0.x] Workflow compensation impl
2 parents 4a10779 + b5b3fd9 commit 3533410

File tree

15 files changed

+219
-3
lines changed

15 files changed

+219
-3
lines changed

api/src/main/java/io/serverlessworkflow/api/interfaces/State.java

+2
Original file line numberDiff line numberDiff line change
@@ -48,5 +48,7 @@ public interface State {
4848

4949
List<Error> getOnErrors();
5050

51+
String getCompensatedBy();
52+
5153
Map<String, String> getMetadata();
5254
}

api/src/main/resources/schema/end/end.json

+5
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@
2020
"type": "object",
2121
"$ref": "../produce/produceevent.json"
2222
}
23+
},
24+
"compensate": {
25+
"type": "boolean",
26+
"default": false,
27+
"description": "If set to true, triggers workflow compensation when before workflow executin completes. Default is false"
2328
}
2429
},
2530
"required": [

api/src/main/resources/schema/states/callbackstate.json

+5
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@
2424
"eventDataFilter": {
2525
"description": "Callback event data filter definition",
2626
"$ref": "../filters/eventdatafilter.json"
27+
},
28+
"usedForCompensation": {
29+
"type": "boolean",
30+
"default": false,
31+
"description": "If true, this state is used to compensate another state. Default is false"
2732
}
2833
},
2934
"required": [

api/src/main/resources/schema/states/defaultstate.json

+5
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,11 @@
6464
"type": "object",
6565
"$ref": "../error/error.json"
6666
}
67+
},
68+
"compensatedBy": {
69+
"type": "string",
70+
"minLength": 1,
71+
"description": "Unique Name of a workflow state which is responsible for compensation of this state"
6772
}
6873
},
6974
"required": [

api/src/main/resources/schema/states/delaystate.json

+5
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@
1212
"timeDelay": {
1313
"type": "string",
1414
"description": "Amount of time (ISO 8601 format) to delay"
15+
},
16+
"usedForCompensation": {
17+
"type": "boolean",
18+
"default": false,
19+
"description": "If true, this state is used to compensate another state. Default is false"
1520
}
1621
},
1722
"required": [

api/src/main/resources/schema/states/foreachstate.json

+5
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,11 @@
3838
"workflowId": {
3939
"type": "string",
4040
"description": "Unique Id of a workflow to be executed for each of the elements of inputCollection"
41+
},
42+
"usedForCompensation": {
43+
"type": "boolean",
44+
"default": false,
45+
"description": "If true, this state is used to compensate another state. Default is false"
4146
}
4247
},
4348
"oneOf": [

api/src/main/resources/schema/states/injectstate.json

+5
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@
1313
"type": "object",
1414
"description": "JSON object which can be set as states data input and can be manipulated via filters",
1515
"existingJavaType": "com.fasterxml.jackson.databind.JsonNode"
16+
},
17+
"usedForCompensation": {
18+
"type": "boolean",
19+
"default": false,
20+
"description": "If true, this state is used to compensate another state. Default is false"
1621
}
1722
},
1823
"required": [

api/src/main/resources/schema/states/operationstate.json

+5
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@
2424
"type": "object",
2525
"$ref": "../actions/action.json"
2626
}
27+
},
28+
"usedForCompensation": {
29+
"type": "boolean",
30+
"default": false,
31+
"description": "If true, this state is used to compensate another state. Default is false"
2732
}
2833
},
2934
"required": [

api/src/main/resources/schema/states/parallelstate.json

+5
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@
2727
"type": "string",
2828
"default": "0",
2929
"description": "Used when completionType is set to 'n_of_m' to specify the 'N' value"
30+
},
31+
"usedForCompensation": {
32+
"type": "boolean",
33+
"default": false,
34+
"description": "If true, this state is used to compensate another state. Default is false"
3035
}
3136
},
3237
"required": [

api/src/main/resources/schema/states/subflowstate.json

+5
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@
1717
"workflowId": {
1818
"type": "string",
1919
"description": "Sub-workflow unique id."
20+
},
21+
"usedForCompensation": {
22+
"type": "boolean",
23+
"default": false,
24+
"description": "If true, this state is used to compensate another state. Default is false"
2025
}
2126
},
2227
"required": [

api/src/main/resources/schema/states/switchstate.json

+5
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@
3232
"default": {
3333
"description": "Default transition of the workflow if there is no matching data conditions. Can include a transition or end definition",
3434
"$ref": "../default/defaultdef.json"
35+
},
36+
"usedForCompensation": {
37+
"type": "boolean",
38+
"default": false,
39+
"description": "If true, this state is used to compensate another state. Default is false"
3540
}
3641
},
3742
"required": [

api/src/main/resources/schema/transitions/transition.json

+5
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@
1818
"type": "string",
1919
"description": "State to transition to next",
2020
"minLength": 1
21+
},
22+
"compensate": {
23+
"type": "boolean",
24+
"default": false,
25+
"description": "If set to true, triggers workflow compensation before this transition is taken. Default is false"
2126
}
2227
},
2328
"required": [

api/src/test/java/io/serverlessworkflow/api/test/MarkupToWorkflowTest.java

+30-3
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,14 @@
1717
package io.serverlessworkflow.api.test;
1818

1919
import io.serverlessworkflow.api.Workflow;
20+
import io.serverlessworkflow.api.interfaces.State;
21+
import io.serverlessworkflow.api.states.EventState;
22+
import io.serverlessworkflow.api.states.OperationState;
2023
import io.serverlessworkflow.api.test.utils.WorkflowTestUtils;
2124
import org.junit.jupiter.params.ParameterizedTest;
2225
import org.junit.jupiter.params.provider.ValueSource;
2326

24-
import static org.junit.jupiter.api.Assertions.assertNotNull;
25-
import static org.junit.jupiter.api.Assertions.assertTrue;
27+
import static org.junit.jupiter.api.Assertions.*;
2628

2729
public class MarkupToWorkflowTest {
2830

@@ -74,7 +76,7 @@ public void testSpecFreatureFunctionRef(String workflowLocation) {
7476
}
7577

7678
@ParameterizedTest
77-
@ValueSource(strings = {"/features/vetappointment.json"})
79+
@ValueSource(strings = {"/features/vetappointment.json", "/features/vetappointment.yml"})
7880
public void testSpecFreatureEventRef(String workflowLocation) {
7981
Workflow workflow = Workflow.fromSource(WorkflowTestUtils.readWorkflowFile(workflowLocation));
8082

@@ -90,4 +92,29 @@ public void testSpecFreatureEventRef(String workflowLocation) {
9092
assertNotNull(workflow.getRetries());
9193
assertTrue(workflow.getRetries().getRetryDefs().size() == 1);
9294
}
95+
96+
@ParameterizedTest
97+
@ValueSource(strings = {"/features/compensationworkflow.json", "/features/compensationworkflow.yml"})
98+
public void testSpecFreatureCompensation(String workflowLocation) {
99+
Workflow workflow = Workflow.fromSource(WorkflowTestUtils.readWorkflowFile(workflowLocation));
100+
101+
assertNotNull(workflow);
102+
assertNotNull(workflow.getId());
103+
assertNotNull(workflow.getName());
104+
assertNotNull(workflow.getStates());
105+
106+
assertNotNull(workflow.getStates());
107+
assertTrue(workflow.getStates().size() == 2);
108+
109+
State firstState = workflow.getStates().get(0);
110+
assertTrue(firstState instanceof EventState);
111+
assertNotNull(firstState.getCompensatedBy());
112+
assertEquals("CancelPurchase", firstState.getCompensatedBy());
113+
114+
State secondState = workflow.getStates().get(1);
115+
assertTrue(secondState instanceof OperationState);
116+
OperationState operationState = (OperationState) secondState;
117+
118+
assertTrue(operationState.isUsedForCompensation());
119+
}
93120
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
{
2+
"id": "CompensationWorkflow",
3+
"name": "Compensation Workflow",
4+
"version": "1.0",
5+
"states": [
6+
{
7+
"name": "NewItemPurchase",
8+
"type": "event",
9+
"onEvents": [
10+
{
11+
"eventRefs": [
12+
"NewPurchase"
13+
],
14+
"actions": [
15+
{
16+
"functionRef": {
17+
"refName": "DebitCustomerFunction",
18+
"parameters": {
19+
"customerid": "{{ $.purchase.customerid }}",
20+
"amount": "{{ $.purchase.amount }}"
21+
}
22+
}
23+
},
24+
{
25+
"functionRef": {
26+
"refName": "SendPurchaseConfirmationEmailFunction",
27+
"parameters": {
28+
"customerid": "{{ $.purchase.customerid }}"
29+
}
30+
}
31+
}
32+
]
33+
}
34+
],
35+
"compensatedBy": "CancelPurchase",
36+
"transition": {
37+
"nextState": "SomeNextWorkflowState"
38+
}
39+
},
40+
{
41+
"name": "CancelPurchase",
42+
"type": "operation",
43+
"usedForCompensation": true,
44+
"actions": [
45+
{
46+
"functionRef": {
47+
"refName": "CreditCustomerFunction",
48+
"parameters": {
49+
"customerid": "{{ $.purchase.customerid }}",
50+
"amount": "{{ $.purchase.amount }}"
51+
}
52+
}
53+
},
54+
{
55+
"functionRef": {
56+
"refName": "SendPurchaseCancellationEmailFunction",
57+
"parameters": {
58+
"customerid": "{{ $.purchase.customerid }}"
59+
}
60+
}
61+
}
62+
]
63+
}
64+
],
65+
"events": [
66+
{
67+
"name": "NewItemPurchase",
68+
"source": "purchasesource",
69+
"type": "org.purchases"
70+
}
71+
],
72+
"functions": [
73+
{
74+
"name": "DebitCustomerFunction",
75+
"operation": "http://myapis.org/application.json#debit"
76+
},
77+
{
78+
"name": "SendPurchaseConfirmationEmailFunction",
79+
"operation": "http://myapis.org/application.json#confirmationemail"
80+
},
81+
{
82+
"name": "SendPurchaseCancellationEmailFunction",
83+
"operation": "http://myapis.org/application.json#cancellationemail"
84+
}
85+
]
86+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
id: CompensationWorkflow
2+
name: Compensation Workflow
3+
version: '1.0'
4+
states:
5+
- name: NewItemPurchase
6+
type: event
7+
onEvents:
8+
- eventRefs:
9+
- NewPurchase
10+
actions:
11+
- functionRef:
12+
refName: DebitCustomerFunction
13+
parameters:
14+
customerid: "{{ $.purchase.customerid }}"
15+
amount: "{{ $.purchase.amount }}"
16+
- functionRef:
17+
refName: SendPurchaseConfirmationEmailFunction
18+
parameters:
19+
customerid: "{{ $.purchase.customerid }}"
20+
compensatedBy: CancelPurchase
21+
transition:
22+
nextState: SomeNextWorkflowState
23+
- name: CancelPurchase
24+
type: operation
25+
usedForCompensation: true
26+
actions:
27+
- functionRef:
28+
refName: CreditCustomerFunction
29+
parameters:
30+
customerid: "{{ $.purchase.customerid }}"
31+
amount: "{{ $.purchase.amount }}"
32+
- functionRef:
33+
refName: SendPurchaseCancellationEmailFunction
34+
parameters:
35+
customerid: "{{ $.purchase.customerid }}"
36+
events:
37+
- name: NewItemPurchase
38+
source: purchasesource
39+
type: org.purchases
40+
functions:
41+
- name: DebitCustomerFunction
42+
operation: http://myapis.org/application.json#debit
43+
- name: SendPurchaseConfirmationEmailFunction
44+
operation: http://myapis.org/application.json#confirmationemail
45+
- name: SendPurchaseCancellationEmailFunction
46+
operation: http://myapis.org/application.json#cancellationemail

0 commit comments

Comments
 (0)