Skip to content

[1.0.x] Workflow compensation impl #33

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Dec 11, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -48,5 +48,7 @@ public interface State {

List<Error> getOnErrors();

String getCompensatedBy();

Map<String, String> getMetadata();
}
5 changes: 5 additions & 0 deletions api/src/main/resources/schema/end/end.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": [
Expand Down
5 changes: 5 additions & 0 deletions api/src/main/resources/schema/states/callbackstate.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": [
Expand Down
5 changes: 5 additions & 0 deletions api/src/main/resources/schema/states/defaultstate.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": [
Expand Down
5 changes: 5 additions & 0 deletions api/src/main/resources/schema/states/delaystate.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": [
Expand Down
5 changes: 5 additions & 0 deletions api/src/main/resources/schema/states/foreachstate.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": [
Expand Down
5 changes: 5 additions & 0 deletions api/src/main/resources/schema/states/injectstate.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": [
Expand Down
5 changes: 5 additions & 0 deletions api/src/main/resources/schema/states/operationstate.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": [
Expand Down
5 changes: 5 additions & 0 deletions api/src/main/resources/schema/states/parallelstate.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": [
Expand Down
5 changes: 5 additions & 0 deletions api/src/main/resources/schema/states/subflowstate.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": [
Expand Down
5 changes: 5 additions & 0 deletions api/src/main/resources/schema/states/switchstate.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": [
Expand Down
5 changes: 5 additions & 0 deletions api/src/main/resources/schema/transitions/transition.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand Down Expand Up @@ -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));

Expand All @@ -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());
}
}
86 changes: 86 additions & 0 deletions api/src/test/resources/features/compensationworkflow.json
Original file line number Diff line number Diff line change
@@ -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"
}
]
}
46 changes: 46 additions & 0 deletions api/src/test/resources/features/compensationworkflow.yml
Original file line number Diff line number Diff line change
@@ -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