diff --git a/api/src/main/java/io/serverlessworkflow/api/interfaces/State.java b/api/src/main/java/io/serverlessworkflow/api/interfaces/State.java index ec12b4ff..a8b0a519 100644 --- a/api/src/main/java/io/serverlessworkflow/api/interfaces/State.java +++ b/api/src/main/java/io/serverlessworkflow/api/interfaces/State.java @@ -48,5 +48,7 @@ public interface State { List getOnErrors(); + String getCompensatedBy(); + Map getMetadata(); } \ No newline at end of file diff --git a/api/src/main/resources/schema/end/end.json b/api/src/main/resources/schema/end/end.json index 9c1c5e35..ebf3ceed 100644 --- a/api/src/main/resources/schema/end/end.json +++ b/api/src/main/resources/schema/end/end.json @@ -20,6 +20,11 @@ "type": "object", "$ref": "../produce/produceevent.json" } + }, + "compensate": { + "type": "boolean", + "default": false, + "description": "If set to true, triggers workflow compensation when before workflow executin completes. Default is false" } }, "required": [ diff --git a/api/src/main/resources/schema/states/callbackstate.json b/api/src/main/resources/schema/states/callbackstate.json index 599576a4..3cef4586 100644 --- a/api/src/main/resources/schema/states/callbackstate.json +++ b/api/src/main/resources/schema/states/callbackstate.json @@ -24,6 +24,11 @@ "eventDataFilter": { "description": "Callback event data filter definition", "$ref": "../filters/eventdatafilter.json" + }, + "usedForCompensation": { + "type": "boolean", + "default": false, + "description": "If true, this state is used to compensate another state. Default is false" } }, "required": [ diff --git a/api/src/main/resources/schema/states/defaultstate.json b/api/src/main/resources/schema/states/defaultstate.json index ec70d4a8..6d922e19 100644 --- a/api/src/main/resources/schema/states/defaultstate.json +++ b/api/src/main/resources/schema/states/defaultstate.json @@ -64,6 +64,11 @@ "type": "object", "$ref": "../error/error.json" } + }, + "compensatedBy": { + "type": "string", + "minLength": 1, + "description": "Unique Name of a workflow state which is responsible for compensation of this state" } }, "required": [ diff --git a/api/src/main/resources/schema/states/delaystate.json b/api/src/main/resources/schema/states/delaystate.json index 39e62518..3d0eab4c 100644 --- a/api/src/main/resources/schema/states/delaystate.json +++ b/api/src/main/resources/schema/states/delaystate.json @@ -12,6 +12,11 @@ "timeDelay": { "type": "string", "description": "Amount of time (ISO 8601 format) to delay" + }, + "usedForCompensation": { + "type": "boolean", + "default": false, + "description": "If true, this state is used to compensate another state. Default is false" } }, "required": [ diff --git a/api/src/main/resources/schema/states/foreachstate.json b/api/src/main/resources/schema/states/foreachstate.json index 1496c143..897dabe1 100644 --- a/api/src/main/resources/schema/states/foreachstate.json +++ b/api/src/main/resources/schema/states/foreachstate.json @@ -38,6 +38,11 @@ "workflowId": { "type": "string", "description": "Unique Id of a workflow to be executed for each of the elements of inputCollection" + }, + "usedForCompensation": { + "type": "boolean", + "default": false, + "description": "If true, this state is used to compensate another state. Default is false" } }, "oneOf": [ diff --git a/api/src/main/resources/schema/states/injectstate.json b/api/src/main/resources/schema/states/injectstate.json index 1101b6e1..d0d10589 100644 --- a/api/src/main/resources/schema/states/injectstate.json +++ b/api/src/main/resources/schema/states/injectstate.json @@ -13,6 +13,11 @@ "type": "object", "description": "JSON object which can be set as states data input and can be manipulated via filters", "existingJavaType": "com.fasterxml.jackson.databind.JsonNode" + }, + "usedForCompensation": { + "type": "boolean", + "default": false, + "description": "If true, this state is used to compensate another state. Default is false" } }, "required": [ diff --git a/api/src/main/resources/schema/states/operationstate.json b/api/src/main/resources/schema/states/operationstate.json index 1775a568..8d8211a9 100644 --- a/api/src/main/resources/schema/states/operationstate.json +++ b/api/src/main/resources/schema/states/operationstate.json @@ -24,6 +24,11 @@ "type": "object", "$ref": "../actions/action.json" } + }, + "usedForCompensation": { + "type": "boolean", + "default": false, + "description": "If true, this state is used to compensate another state. Default is false" } }, "required": [ diff --git a/api/src/main/resources/schema/states/parallelstate.json b/api/src/main/resources/schema/states/parallelstate.json index 2f82264a..824f3ff5 100644 --- a/api/src/main/resources/schema/states/parallelstate.json +++ b/api/src/main/resources/schema/states/parallelstate.json @@ -27,6 +27,11 @@ "type": "string", "default": "0", "description": "Used when completionType is set to 'n_of_m' to specify the 'N' value" + }, + "usedForCompensation": { + "type": "boolean", + "default": false, + "description": "If true, this state is used to compensate another state. Default is false" } }, "required": [ diff --git a/api/src/main/resources/schema/states/subflowstate.json b/api/src/main/resources/schema/states/subflowstate.json index 8c6da3b1..4a208691 100644 --- a/api/src/main/resources/schema/states/subflowstate.json +++ b/api/src/main/resources/schema/states/subflowstate.json @@ -17,6 +17,11 @@ "workflowId": { "type": "string", "description": "Sub-workflow unique id." + }, + "usedForCompensation": { + "type": "boolean", + "default": false, + "description": "If true, this state is used to compensate another state. Default is false" } }, "required": [ diff --git a/api/src/main/resources/schema/states/switchstate.json b/api/src/main/resources/schema/states/switchstate.json index 8ce641cb..e421397d 100644 --- a/api/src/main/resources/schema/states/switchstate.json +++ b/api/src/main/resources/schema/states/switchstate.json @@ -32,6 +32,11 @@ "default": { "description": "Default transition of the workflow if there is no matching data conditions. Can include a transition or end definition", "$ref": "../default/defaultdef.json" + }, + "usedForCompensation": { + "type": "boolean", + "default": false, + "description": "If true, this state is used to compensate another state. Default is false" } }, "required": [ diff --git a/api/src/main/resources/schema/transitions/transition.json b/api/src/main/resources/schema/transitions/transition.json index c3008fb9..5ba61b51 100644 --- a/api/src/main/resources/schema/transitions/transition.json +++ b/api/src/main/resources/schema/transitions/transition.json @@ -18,6 +18,11 @@ "type": "string", "description": "State to transition to next", "minLength": 1 + }, + "compensate": { + "type": "boolean", + "default": false, + "description": "If set to true, triggers workflow compensation before this transition is taken. Default is false" } }, "required": [ diff --git a/api/src/test/java/io/serverlessworkflow/api/test/MarkupToWorkflowTest.java b/api/src/test/java/io/serverlessworkflow/api/test/MarkupToWorkflowTest.java index c4a0b36c..6d770361 100644 --- a/api/src/test/java/io/serverlessworkflow/api/test/MarkupToWorkflowTest.java +++ b/api/src/test/java/io/serverlessworkflow/api/test/MarkupToWorkflowTest.java @@ -17,12 +17,14 @@ package io.serverlessworkflow.api.test; import io.serverlessworkflow.api.Workflow; +import io.serverlessworkflow.api.interfaces.State; +import io.serverlessworkflow.api.states.EventState; +import io.serverlessworkflow.api.states.OperationState; import io.serverlessworkflow.api.test.utils.WorkflowTestUtils; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.*; public class MarkupToWorkflowTest { @@ -74,7 +76,7 @@ public void testSpecFreatureFunctionRef(String workflowLocation) { } @ParameterizedTest - @ValueSource(strings = {"/features/vetappointment.json"}) + @ValueSource(strings = {"/features/vetappointment.json", "/features/vetappointment.yml"}) public void testSpecFreatureEventRef(String workflowLocation) { Workflow workflow = Workflow.fromSource(WorkflowTestUtils.readWorkflowFile(workflowLocation)); @@ -90,4 +92,29 @@ public void testSpecFreatureEventRef(String workflowLocation) { assertNotNull(workflow.getRetries()); assertTrue(workflow.getRetries().getRetryDefs().size() == 1); } + + @ParameterizedTest + @ValueSource(strings = {"/features/compensationworkflow.json", "/features/compensationworkflow.yml"}) + public void testSpecFreatureCompensation(String workflowLocation) { + Workflow workflow = Workflow.fromSource(WorkflowTestUtils.readWorkflowFile(workflowLocation)); + + assertNotNull(workflow); + assertNotNull(workflow.getId()); + assertNotNull(workflow.getName()); + assertNotNull(workflow.getStates()); + + assertNotNull(workflow.getStates()); + assertTrue(workflow.getStates().size() == 2); + + State firstState = workflow.getStates().get(0); + assertTrue(firstState instanceof EventState); + assertNotNull(firstState.getCompensatedBy()); + assertEquals("CancelPurchase", firstState.getCompensatedBy()); + + State secondState = workflow.getStates().get(1); + assertTrue(secondState instanceof OperationState); + OperationState operationState = (OperationState) secondState; + + assertTrue(operationState.isUsedForCompensation()); + } } diff --git a/api/src/test/resources/features/compensationworkflow.json b/api/src/test/resources/features/compensationworkflow.json new file mode 100644 index 00000000..5f979f68 --- /dev/null +++ b/api/src/test/resources/features/compensationworkflow.json @@ -0,0 +1,86 @@ +{ + "id": "CompensationWorkflow", + "name": "Compensation Workflow", + "version": "1.0", + "states": [ + { + "name": "NewItemPurchase", + "type": "event", + "onEvents": [ + { + "eventRefs": [ + "NewPurchase" + ], + "actions": [ + { + "functionRef": { + "refName": "DebitCustomerFunction", + "parameters": { + "customerid": "{{ $.purchase.customerid }}", + "amount": "{{ $.purchase.amount }}" + } + } + }, + { + "functionRef": { + "refName": "SendPurchaseConfirmationEmailFunction", + "parameters": { + "customerid": "{{ $.purchase.customerid }}" + } + } + } + ] + } + ], + "compensatedBy": "CancelPurchase", + "transition": { + "nextState": "SomeNextWorkflowState" + } + }, + { + "name": "CancelPurchase", + "type": "operation", + "usedForCompensation": true, + "actions": [ + { + "functionRef": { + "refName": "CreditCustomerFunction", + "parameters": { + "customerid": "{{ $.purchase.customerid }}", + "amount": "{{ $.purchase.amount }}" + } + } + }, + { + "functionRef": { + "refName": "SendPurchaseCancellationEmailFunction", + "parameters": { + "customerid": "{{ $.purchase.customerid }}" + } + } + } + ] + } + ], + "events": [ + { + "name": "NewItemPurchase", + "source": "purchasesource", + "type": "org.purchases" + } + ], + "functions": [ + { + "name": "DebitCustomerFunction", + "operation": "http://myapis.org/application.json#debit" + }, + { + "name": "SendPurchaseConfirmationEmailFunction", + "operation": "http://myapis.org/application.json#confirmationemail" + }, + { + "name": "SendPurchaseCancellationEmailFunction", + "operation": "http://myapis.org/application.json#cancellationemail" + } + ] +} \ No newline at end of file diff --git a/api/src/test/resources/features/compensationworkflow.yml b/api/src/test/resources/features/compensationworkflow.yml new file mode 100644 index 00000000..81506399 --- /dev/null +++ b/api/src/test/resources/features/compensationworkflow.yml @@ -0,0 +1,46 @@ +id: CompensationWorkflow +name: Compensation Workflow +version: '1.0' +states: + - name: NewItemPurchase + type: event + onEvents: + - eventRefs: + - NewPurchase + actions: + - functionRef: + refName: DebitCustomerFunction + parameters: + customerid: "{{ $.purchase.customerid }}" + amount: "{{ $.purchase.amount }}" + - functionRef: + refName: SendPurchaseConfirmationEmailFunction + parameters: + customerid: "{{ $.purchase.customerid }}" + compensatedBy: CancelPurchase + transition: + nextState: SomeNextWorkflowState + - name: CancelPurchase + type: operation + usedForCompensation: true + actions: + - functionRef: + refName: CreditCustomerFunction + parameters: + customerid: "{{ $.purchase.customerid }}" + amount: "{{ $.purchase.amount }}" + - functionRef: + refName: SendPurchaseCancellationEmailFunction + parameters: + customerid: "{{ $.purchase.customerid }}" +events: + - name: NewItemPurchase + source: purchasesource + type: org.purchases +functions: + - name: DebitCustomerFunction + operation: http://myapis.org/application.json#debit + - name: SendPurchaseConfirmationEmailFunction + operation: http://myapis.org/application.json#confirmationemail + - name: SendPurchaseCancellationEmailFunction + operation: http://myapis.org/application.json#cancellationemail pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy