This is an automated email from the ASF dual-hosted git repository.
yixia pushed a commit to branch feature/saga
in repository https://gitbox.apache.org/repos/asf/incubator-seata-go.git
The following commit(s) were added to refs/heads/feature/saga by this push:
new 9c4c0f44 feature: add serverice task parse in statelang (#650)
9c4c0f44 is described below
commit 9c4c0f44a0d59a51bdd861601cce30f811f3df39
Author: Jingliu Xiong <[email protected]>
AuthorDate: Fri Feb 2 10:48:10 2024 +0800
feature: add serverice task parse in statelang (#650)
---
pkg/saga/statemachine/constant/constant.go | 28 ++
pkg/saga/statemachine/constant/contant.go | 20 -
.../statelang/parser/choice_state_json_parser.go | 10 +-
.../parser/compensation_trigger_state_parser.go | 31 ++
.../statelang/parser/end_state_parser.go | 66 ++++
.../statelang/parser/statemachine_json_parser.go | 42 +-
.../parser/statemachine_json_parser_test.go | 36 +-
.../statelang/parser/statemachine_parser.go | 133 ++++++-
.../statelang/parser/sub_state_machine_parser.go | 84 ++++
.../statelang/parser/task_state_json_parser.go | 330 ++++++++++++++++
pkg/saga/statemachine/statelang/state.go | 4 +
.../statemachine/statelang/state/choice_state.go | 5 +-
.../statelang/state/compensation_trigger_state.go | 22 ++
pkg/saga/statemachine/statelang/state/end_state.go | 64 ++++
.../statelang/state/sub_state_machine.go | 69 ++++
.../statemachine/statelang/state/task_state.go | 407 +++++++++++++++++++-
.../statelang/simple_statelang_with_choice.json | 38 ++
testdata/saga/statelang/simple_statemachine.json | 138 +++++++
.../saga/statelang/state_machine_new_designer.json | 424 +++++++++++++++++++++
19 files changed, 1909 insertions(+), 42 deletions(-)
diff --git a/pkg/saga/statemachine/constant/constant.go
b/pkg/saga/statemachine/constant/constant.go
new file mode 100644
index 00000000..f9e1eeba
--- /dev/null
+++ b/pkg/saga/statemachine/constant/constant.go
@@ -0,0 +1,28 @@
+package constant
+
+const (
+ VarNameProcessType string = "_ProcessType_"
+ VarNameOperationName string = "_operation_name_"
+ OperationNameStart string = "start"
+ VarNameAsyncCallback string = "_async_callback_"
+ VarNameStateMachineInst string =
"_current_statemachine_instance_"
+ VarNameStateMachine string = "_current_statemachine_"
+ VarNameStateMachineEngine string =
"_current_statemachine_engine_"
+ VarNameStateMachineConfig string = "_statemachine_config_"
+ VarNameStateMachineContext string = "context"
+ VarNameIsAsyncExecution string = "_is_async_execution_"
+ VarNameStateInst string = "_current_state_instance_"
+ SeqEntityStateMachineInst string = "STATE_MACHINE_INST"
+ VarNameBusinesskey string = "_business_key_"
+ VarNameParentId string = "_parent_id_"
+ StateTypeServiceTask string = "ServiceTask"
+ StateTypeChoice string = "Choice"
+ StateTypeSubStateMachine string = "SubStateMachine"
+ CompensateSubMachine string = "CompensateSubMachine"
+ StateTypeSucceed string = "Succeed"
+ StateTypeFail string = "Fail"
+ StateTypeCompensationTrigger string = "CompensationTrigger"
+ StateTypeScriptTask string = "ScriptTask"
+ CompensateSubMachineStateNamePrefix string =
"_compensate_sub_machine_state_"
+ DefaultScriptType string = "groovy"
+)
diff --git a/pkg/saga/statemachine/constant/contant.go
b/pkg/saga/statemachine/constant/contant.go
deleted file mode 100644
index 39cb2924..00000000
--- a/pkg/saga/statemachine/constant/contant.go
+++ /dev/null
@@ -1,20 +0,0 @@
-package constant
-
-const (
- VarNameProcessType string = "_ProcessType_"
- VarNameOperationName string = "_operation_name_"
- OperationNameStart string = "start"
- VarNameAsyncCallback string = "_async_callback_"
- VarNameStateMachineInst string = "_current_statemachine_instance_"
- VarNameStateMachine string = "_current_statemachine_"
- VarNameStateMachineEngine string = "_current_statemachine_engine_"
- VarNameStateMachineConfig string = "_statemachine_config_"
- VarNameStateMachineContext string = "context"
- VarNameIsAsyncExecution string = "_is_async_execution_"
- VarNameStateInst string = "_current_state_instance_"
- SeqEntityStateMachineInst string = "STATE_MACHINE_INST"
- VarNameBusinesskey string = "_business_key_"
- VarNameParentId string = "_parent_id_"
- StateTypeServiceTask string = "ServiceTask"
- StateTypeChoice string = "Choice"
-)
diff --git a/pkg/saga/statemachine/statelang/parser/choice_state_json_parser.go
b/pkg/saga/statemachine/statelang/parser/choice_state_json_parser.go
index 04729c57..9b4d51cd 100644
--- a/pkg/saga/statemachine/statelang/parser/choice_state_json_parser.go
+++ b/pkg/saga/statemachine/statelang/parser/choice_state_json_parser.go
@@ -3,21 +3,23 @@ package parser
import (
"fmt"
"github.com/pkg/errors"
- "github.com/seata/seata-go/pkg/saga/statemachine/engine"
+ "github.com/seata/seata-go/pkg/saga/statemachine/constant"
"github.com/seata/seata-go/pkg/saga/statemachine/statelang"
"github.com/seata/seata-go/pkg/saga/statemachine/statelang/state"
)
type ChoiceStateParser struct {
- BaseStateParser
+ *BaseStateParser
}
func NewChoiceStateParser() *ChoiceStateParser {
- return &ChoiceStateParser{}
+ return &ChoiceStateParser{
+ &BaseStateParser{},
+ }
}
func (c ChoiceStateParser) StateType() string {
- return engine.StateTypeChoice
+ return constant.StateTypeChoice
}
func (c ChoiceStateParser) Parse(stateName string, stateMap
map[string]interface{}) (statelang.State, error) {
diff --git
a/pkg/saga/statemachine/statelang/parser/compensation_trigger_state_parser.go
b/pkg/saga/statemachine/statelang/parser/compensation_trigger_state_parser.go
new file mode 100644
index 00000000..d41a6d29
--- /dev/null
+++
b/pkg/saga/statemachine/statelang/parser/compensation_trigger_state_parser.go
@@ -0,0 +1,31 @@
+package parser
+
+import (
+ "github.com/seata/seata-go/pkg/saga/statemachine/constant"
+ "github.com/seata/seata-go/pkg/saga/statemachine/statelang"
+ "github.com/seata/seata-go/pkg/saga/statemachine/statelang/state"
+)
+
+type CompensationTriggerStateParser struct {
+ *BaseStateParser
+}
+
+func NewCompensationTriggerStateParser() *CompensationTriggerStateParser {
+ return &CompensationTriggerStateParser{
+ &BaseStateParser{},
+ }
+}
+
+func (c CompensationTriggerStateParser) StateType() string {
+ return constant.StateTypeCompensationTrigger
+}
+
+func (c CompensationTriggerStateParser) Parse(stateName string, stateMap
map[string]interface{}) (statelang.State, error) {
+ compensateSubStateMachineStateImpl :=
state.NewCompensationTriggerStateImpl()
+ err := c.ParseBaseAttributes(stateName,
compensateSubStateMachineStateImpl, stateMap)
+ if err != nil {
+ return nil, err
+ }
+
+ return compensateSubStateMachineStateImpl, nil
+}
diff --git a/pkg/saga/statemachine/statelang/parser/end_state_parser.go
b/pkg/saga/statemachine/statelang/parser/end_state_parser.go
new file mode 100644
index 00000000..823ee8df
--- /dev/null
+++ b/pkg/saga/statemachine/statelang/parser/end_state_parser.go
@@ -0,0 +1,66 @@
+package parser
+
+import (
+ "github.com/seata/seata-go/pkg/saga/statemachine/constant"
+ "github.com/seata/seata-go/pkg/saga/statemachine/statelang"
+ "github.com/seata/seata-go/pkg/saga/statemachine/statelang/state"
+)
+
+type SucceedEndStateParser struct {
+ *BaseStateParser
+}
+
+func NewSucceedEndStateParser() *SucceedEndStateParser {
+ return &SucceedEndStateParser{
+ &BaseStateParser{},
+ }
+}
+
+func (s SucceedEndStateParser) StateType() string {
+ return constant.StateTypeSucceed
+}
+
+func (s SucceedEndStateParser) Parse(stateName string, stateMap
map[string]interface{}) (statelang.State, error) {
+ succeedEndStateImpl := state.NewSucceedEndStateImpl()
+ err := s.ParseBaseAttributes(stateName, succeedEndStateImpl, stateMap)
+ if err != nil {
+ return nil, err
+ }
+
+ return succeedEndStateImpl, nil
+}
+
+type FailEndStateParser struct {
+ *BaseStateParser
+}
+
+func NewFailEndStateParser() *FailEndStateParser {
+ return &FailEndStateParser{
+ &BaseStateParser{},
+ }
+}
+
+func (f FailEndStateParser) StateType() string {
+ return constant.StateTypeFail
+}
+
+func (f FailEndStateParser) Parse(stateName string, stateMap
map[string]interface{}) (statelang.State, error) {
+ failEndStateImpl := state.NewFailEndStateImpl()
+ err := f.ParseBaseAttributes(stateName, failEndStateImpl, stateMap)
+ if err != nil {
+ return nil, err
+ }
+
+ errorCode, err := f.GetStringOrDefault(stateName, stateMap,
"ErrorCode", "")
+ if err != nil {
+ return nil, err
+ }
+ failEndStateImpl.SetErrorCode(errorCode)
+
+ message, err := f.GetStringOrDefault(stateName, stateMap, "Message", "")
+ if err != nil {
+ return nil, err
+ }
+ failEndStateImpl.SetMessage(message)
+ return failEndStateImpl, nil
+}
diff --git a/pkg/saga/statemachine/statelang/parser/statemachine_json_parser.go
b/pkg/saga/statemachine/statelang/parser/statemachine_json_parser.go
index c0977034..4bcfdcbc 100644
--- a/pkg/saga/statemachine/statelang/parser/statemachine_json_parser.go
+++ b/pkg/saga/statemachine/statelang/parser/statemachine_json_parser.go
@@ -3,14 +3,19 @@ package parser
import (
"encoding/json"
"github.com/pkg/errors"
+ "github.com/seata/seata-go/pkg/saga/statemachine/constant"
"github.com/seata/seata-go/pkg/saga/statemachine/statelang"
+ "github.com/seata/seata-go/pkg/saga/statemachine/statelang/state"
)
type JSONStateMachineParser struct {
+ *BaseStateParser
}
func NewJSONStateMachineParser() *JSONStateMachineParser {
- return &JSONStateMachineParser{}
+ return &JSONStateMachineParser{
+ &BaseStateParser{},
+ }
}
func (stateMachineParser JSONStateMachineParser) GetType() string {
@@ -77,15 +82,40 @@ func (stateMachineParser JSONStateMachineParser)
Parse(content string) (statelan
stateMachine.States()[stateName] = state
}
- //TODO setCompensateState
- //for stateName, state := range stateMachine.GetStates() {
- //
- //}
- //
+ for _, stateValue := range stateMachine.States() {
+ if stateMachineParser.isTaskState(stateValue.Type()) {
+ stateMachineParser.setForCompensation(stateValue,
stateMachine)
+ }
+ }
return stateMachine, nil
}
+func (stateMachineParser JSONStateMachineParser) setForCompensation(stateValue
statelang.State, stateMachine *statelang.StateMachineImpl) {
+ switch stateValue.Type() {
+ case stateValue.Type():
+ serviceTaskStateImpl, ok :=
stateValue.(*state.ServiceTaskStateImpl)
+ if ok {
+ if serviceTaskStateImpl.CompensateState() != "" {
+ compState :=
stateMachine.States()[serviceTaskStateImpl.CompensateState()]
+ if
stateMachineParser.isTaskState(compState.Type()) {
+ compStateImpl, ok :=
compState.(state.ServiceTaskStateImpl)
+ if ok {
+
compStateImpl.SetForCompensation(true)
+ }
+ }
+ }
+ }
+ }
+}
+
+func (stateMachineParser JSONStateMachineParser) isTaskState(stateType string)
bool {
+ if stateType == constant.StateTypeServiceTask {
+ return true
+ }
+ return false
+}
+
type StateMachineJsonObject struct {
Name string `json:"Name"`
Comment string `json:"Comment"`
diff --git
a/pkg/saga/statemachine/statelang/parser/statemachine_json_parser_test.go
b/pkg/saga/statemachine/statelang/parser/statemachine_json_parser_test.go
index c6d00b75..0028309a 100644
--- a/pkg/saga/statemachine/statelang/parser/statemachine_json_parser_test.go
+++ b/pkg/saga/statemachine/statelang/parser/statemachine_json_parser_test.go
@@ -1,12 +1,44 @@
package parser
import (
+ "os"
"testing"
)
func TestParseChoice(t *testing.T) {
- var content = "{\n \"Name\":\"ChoiceTest\",\n
\"Comment\":\"ChoiceTest\",\n \"StartState\":\"ChoiceState\",\n
\"Version\":\"0.0.1\",\n \"States\":{\n \"ChoiceState\":{\n
\"Type\":\"Choice\",\n \"Choices\":[\n {\n
\"Expression\":\"[a] == 1\",\n
\"Next\":\"SecondState\"\n },\n {\n
\"Expression\":\"[a] == 2\",\n \"Next\":\"Thir [...]
- _, err := NewJSONStateMachineParser().Parse(content)
+ filePath :=
"../../../../../testdata/saga/statelang/simple_statelang_with_choice.json"
+ fileContent, err := os.ReadFile(filePath)
+ if err != nil {
+ t.Error("parse fail: " + err.Error())
+ return
+ }
+ _, err = NewJSONStateMachineParser().Parse(string(fileContent))
+ if err != nil {
+ t.Error("parse fail: " + err.Error())
+ }
+}
+
+func TestParseServiceTaskForSimpleStateMachine(t *testing.T) {
+ filePath :=
"../../../../../testdata/saga/statelang/simple_statemachine.json"
+ fileContent, err := os.ReadFile(filePath)
+ if err != nil {
+ t.Error("parse fail: " + err.Error())
+ return
+ }
+ _, err = NewJSONStateMachineParser().Parse(string(fileContent))
+ if err != nil {
+ t.Error("parse fail: " + err.Error())
+ }
+}
+
+func TestParseServiceTaskForNewDesigner(t *testing.T) {
+ filePath :=
"../../../../../testdata/saga/statelang/state_machine_new_designer.json"
+ fileContent, err := os.ReadFile(filePath)
+ if err != nil {
+ t.Error("parse fail: " + err.Error())
+ return
+ }
+ _, err = NewJSONStateMachineParser().Parse(string(fileContent))
if err != nil {
t.Error("parse fail: " + err.Error())
}
diff --git a/pkg/saga/statemachine/statelang/parser/statemachine_parser.go
b/pkg/saga/statemachine/statelang/parser/statemachine_parser.go
index 99ee95e1..a74c79ff 100644
--- a/pkg/saga/statemachine/statelang/parser/statemachine_parser.go
+++ b/pkg/saga/statemachine/statelang/parser/statemachine_parser.go
@@ -3,6 +3,8 @@ package parser
import (
"github.com/pkg/errors"
"github.com/seata/seata-go/pkg/saga/statemachine/statelang"
+ "strconv"
+ "strings"
"sync"
)
@@ -19,20 +21,23 @@ type StateParser interface {
type BaseStateParser struct {
}
+func NewBaseStateParser() *BaseStateParser {
+ return &BaseStateParser{}
+}
+
func (b BaseStateParser) ParseBaseAttributes(stateName string, state
statelang.State, stateMap map[string]interface{}) error {
state.SetName(stateName)
- comment, err := b.GetString(stateName, stateMap, "Comment")
+ comment, err := b.GetStringOrDefault(stateName, stateMap, "Comment", "")
if err != nil {
return err
}
state.SetComment(comment)
- next, err := b.GetString(stateName, stateMap, "Next")
+ next, err := b.GetStringOrDefault(stateName, stateMap, "Next", "")
if err != nil {
return err
}
-
state.SetNext(next)
return nil
}
@@ -52,9 +57,21 @@ func (b BaseStateParser) GetString(stateName string,
stateMap map[string]interfa
return valueAsString, nil
}
-func (b BaseStateParser) GetSlice(stateName string, stateMap
map[string]interface{}, key string) ([]interface{}, error) {
+func (b BaseStateParser) GetStringOrDefault(stateName string, stateMap
map[string]interface{}, key string, defaultValue string) (string, error) {
value := stateMap[key]
+ if value == nil {
+ return defaultValue, nil
+ }
+ valueAsString, ok := value.(string)
+ if !ok {
+ return defaultValue, errors.New("State [" + stateName + "] " +
key + " illegal, required string")
+ }
+ return valueAsString, nil
+}
+
+func (b BaseStateParser) GetSlice(stateName string, stateMap
map[string]interface{}, key string) ([]interface{}, error) {
+ value := stateMap[key]
if value == nil {
var result []interface{}
return result, errors.New("State [" + stateName + "] " + key +
" not exist")
@@ -62,12 +79,103 @@ func (b BaseStateParser) GetSlice(stateName string,
stateMap map[string]interfac
valueAsSlice, ok := value.([]interface{})
if !ok {
- var result []interface{}
- return result, errors.New("State [" + stateName + "] " + key +
" illegal, required slice")
+ var slice []interface{}
+ return slice, errors.New("State [" + stateName + "] " + key + "
illegal, required []interface{}")
}
return valueAsSlice, nil
}
+func (b BaseStateParser) GetSliceOrDefault(stateName string, stateMap
map[string]interface{}, key string, defaultValue []interface{}) ([]interface{},
error) {
+ value := stateMap[key]
+
+ if value == nil {
+ return defaultValue, nil
+ }
+
+ valueAsSlice, ok := value.([]interface{})
+ if !ok {
+ return defaultValue, errors.New("State [" + stateName + "] " +
key + " illegal, required []interface{}")
+ }
+ return valueAsSlice, nil
+}
+
+func (b BaseStateParser) GetMapOrDefault(stateMap map[string]interface{}, key
string, defaultValue map[string]interface{}) (map[string]interface{}, error) {
+ value := stateMap[key]
+
+ if value == nil {
+ return defaultValue, nil
+ }
+
+ valueAsMap, ok := value.(map[string]interface{})
+ if !ok {
+ return defaultValue, nil
+ }
+ return valueAsMap, nil
+}
+
+func (b BaseStateParser) GetBool(stateName string, stateMap
map[string]interface{}, key string) (bool, error) {
+ value := stateMap[key]
+
+ if value == nil {
+ return false, errors.New("State [" + stateName + "] " + key + "
not exist")
+ }
+
+ valueAsBool, ok := value.(bool)
+ if !ok {
+ return false, errors.New("State [" + stateName + "] " + key + "
illegal, required bool")
+ }
+ return valueAsBool, nil
+}
+
+func (b BaseStateParser) GetBoolOrDefault(stateName string, stateMap
map[string]interface{}, key string, defaultValue bool) (bool, error) {
+ value := stateMap[key]
+
+ if value == nil {
+ return defaultValue, nil
+ }
+
+ valueAsBool, ok := value.(bool)
+ if !ok {
+ return false, errors.New("State [" + stateName + "] " + key + "
illegal, required bool")
+ }
+ return valueAsBool, nil
+}
+
+func (b BaseStateParser) GetIntOrDefault(stateName string, stateMap
map[string]interface{}, key string, defaultValue int) (int, error) {
+ value := stateMap[key]
+
+ if value == nil {
+ return defaultValue, nil
+ }
+
+ // just use float64 to convert, json reader will read all number as
float64
+ valueAsFloat64, ok := value.(float64)
+ if !ok {
+ return defaultValue, errors.New("State [" + stateName + "] " +
key + " illegal, required int")
+ }
+
+ floatStr := strconv.FormatFloat(valueAsFloat64, 'f', -1, 64)
+ if strings.Contains(floatStr, ".") {
+ return defaultValue, errors.New("State [" + stateName + "] " +
key + " illegal, required int")
+ }
+
+ return int(valueAsFloat64), nil
+}
+
+func (b BaseStateParser) GetFloat64OrDefault(stateName string, stateMap
map[string]interface{}, key string, defaultValue float64) (float64, error) {
+ value := stateMap[key]
+
+ if value == nil {
+ return defaultValue, nil
+ }
+
+ valueAsFloat64, ok := value.(float64)
+ if !ok {
+ return defaultValue, errors.New("State [" + stateName + "] " +
key + " illegal, required float64")
+ }
+ return valueAsFloat64, nil
+}
+
type StateParserFactory interface {
RegistryStateParser(stateType string, stateParser StateParser)
@@ -89,8 +197,21 @@ func NewDefaultStateParserFactory()
*DefaultStateParserFactory {
// InitDefaultStateParser init StateParser by default
func (d *DefaultStateParserFactory) InitDefaultStateParser() {
choiceStateParser := NewChoiceStateParser()
+ serviceTaskStateParser := NewServiceTaskStateParser()
+ subStateMachineParser := NewSubStateMachineParser()
+ succeedEndStateParser := NewSucceedEndStateParser()
+ compensationTriggerStateParser := NewCompensationTriggerStateParser()
+ failEndStateParser := NewFailEndStateParser()
+ scriptTaskStateParser := NewScriptTaskStateParser()
d.RegistryStateParser(choiceStateParser.StateType(), choiceStateParser)
+ d.RegistryStateParser(serviceTaskStateParser.StateType(),
serviceTaskStateParser)
+ d.RegistryStateParser(subStateMachineParser.StateType(),
subStateMachineParser)
+ d.RegistryStateParser(succeedEndStateParser.StateType(),
succeedEndStateParser)
+ d.RegistryStateParser(compensationTriggerStateParser.StateType(),
compensationTriggerStateParser)
+ d.RegistryStateParser(compensationTriggerStateParser.StateType(),
compensationTriggerStateParser)
+ d.RegistryStateParser(failEndStateParser.StateType(),
failEndStateParser)
+ d.RegistryStateParser(scriptTaskStateParser.StateType(),
scriptTaskStateParser)
}
func (d *DefaultStateParserFactory) RegistryStateParser(stateType string,
stateParser StateParser) {
diff --git a/pkg/saga/statemachine/statelang/parser/sub_state_machine_parser.go
b/pkg/saga/statemachine/statelang/parser/sub_state_machine_parser.go
new file mode 100644
index 00000000..dc466da5
--- /dev/null
+++ b/pkg/saga/statemachine/statelang/parser/sub_state_machine_parser.go
@@ -0,0 +1,84 @@
+package parser
+
+import (
+ "fmt"
+ "github.com/pkg/errors"
+ "github.com/seata/seata-go/pkg/saga/statemachine/constant"
+ "github.com/seata/seata-go/pkg/saga/statemachine/statelang"
+ "github.com/seata/seata-go/pkg/saga/statemachine/statelang/state"
+)
+
+type SubStateMachineParser struct {
+ *AbstractTaskStateParser
+}
+
+func NewSubStateMachineParser() *SubStateMachineParser {
+ return &SubStateMachineParser{
+ NewAbstractTaskStateParser(),
+ }
+}
+
+func (s SubStateMachineParser) StateType() string {
+ return constant.StateTypeSubStateMachine
+}
+
+func (s SubStateMachineParser) Parse(stateName string, stateMap
map[string]interface{}) (statelang.State, error) {
+ subStateMachineImpl := state.NewSubStateMachineImpl()
+
+ err := s.ParseTaskAttributes(stateName,
subStateMachineImpl.AbstractTaskState, stateMap)
+ if err != nil {
+ return nil, err
+ }
+
+ stateMachineName, err := s.BaseStateParser.GetString(stateName,
stateMap, "StateMachineName")
+ if err != nil {
+ return nil, err
+ }
+ subStateMachineImpl.SetName(stateMachineName)
+
+ if subStateMachineImpl.CompensateState() == "" {
+ // build default SubStateMachine compensate state
+ compensateSubStateMachineStateParser :=
NewCompensateSubStateMachineStateParser()
+ compensateState, err :=
compensateSubStateMachineStateParser.Parse(stateName, nil)
+ if err != nil {
+ return nil, err
+ }
+ compensateStateImpl, ok := compensateState.(state.TaskState)
+ if !ok {
+ return nil, errors.New(fmt.Sprintf("State [name:%s] has
wrong compensateState type", stateName))
+ }
+ subStateMachineImpl.SetCompensateStateImpl(compensateStateImpl)
+
subStateMachineImpl.SetCompensateState(compensateStateImpl.Name())
+ }
+ return subStateMachineImpl, nil
+}
+
+type CompensateSubStateMachineStateParser struct {
+ *AbstractTaskStateParser
+}
+
+func NewCompensateSubStateMachineStateParser()
*CompensateSubStateMachineStateParser {
+ return &CompensateSubStateMachineStateParser{
+ NewAbstractTaskStateParser(),
+ }
+}
+
+func (c CompensateSubStateMachineStateParser) StateType() string {
+ return constant.CompensateSubMachine
+}
+
+func (c CompensateSubStateMachineStateParser) Parse(stateName string, stateMap
map[string]interface{}) (statelang.State, error) {
+ compensateSubStateMachineStateImpl :=
state.NewCompensateSubStateMachineStateImpl()
+ compensateSubStateMachineStateImpl.SetForCompensation(true)
+
+ if stateMap != nil {
+ err := c.ParseTaskAttributes(stateName,
compensateSubStateMachineStateImpl.ServiceTaskStateImpl.AbstractTaskState,
stateMap)
+ if err != nil {
+ return nil, err
+ }
+ }
+ if compensateSubStateMachineStateImpl.Name() == "" {
+
compensateSubStateMachineStateImpl.SetName(constant.CompensateSubMachineStateNamePrefix
+ compensateSubStateMachineStateImpl.Hashcode())
+ }
+ return compensateSubStateMachineStateImpl, nil
+}
diff --git a/pkg/saga/statemachine/statelang/parser/task_state_json_parser.go
b/pkg/saga/statemachine/statelang/parser/task_state_json_parser.go
new file mode 100644
index 00000000..5e702487
--- /dev/null
+++ b/pkg/saga/statemachine/statelang/parser/task_state_json_parser.go
@@ -0,0 +1,330 @@
+package parser
+
+import (
+ "fmt"
+ "github.com/pkg/errors"
+ "github.com/seata/seata-go/pkg/saga/statemachine/constant"
+ "github.com/seata/seata-go/pkg/saga/statemachine/statelang"
+ "github.com/seata/seata-go/pkg/saga/statemachine/statelang/state"
+)
+
+type AbstractTaskStateParser struct {
+ *BaseStateParser
+}
+
+func NewAbstractTaskStateParser() *AbstractTaskStateParser {
+ return &AbstractTaskStateParser{
+ &BaseStateParser{},
+ }
+}
+
+func (a *AbstractTaskStateParser) ParseTaskAttributes(stateName string, state
*state.AbstractTaskState, stateMap map[string]interface{}) error {
+ err := a.ParseBaseAttributes(state.Name(), state.BaseState, stateMap)
+ if err != nil {
+ return err
+ }
+
+ compensateState, err := a.GetStringOrDefault(stateName, stateMap,
"CompensateState", "")
+ if err != nil {
+ return err
+ }
+ state.SetCompensateState(compensateState)
+
+ isForCompensation, err := a.GetBoolOrDefault(stateName, stateMap,
"IsForCompensation", false)
+ if err != nil {
+ return err
+ }
+ state.SetForCompensation(isForCompensation)
+
+ isForUpdate, err := a.GetBoolOrDefault(stateName, stateMap,
"IsForUpdate", false)
+ if err != nil {
+ return err
+ }
+ state.SetForUpdate(isForUpdate)
+
+ isPersist, err := a.GetBoolOrDefault(stateName, stateMap, "IsPersist",
false)
+ if err != nil {
+ return err
+ }
+ state.SetPersist(isPersist)
+
+ isRetryPersistModeUpdate, err := a.GetBoolOrDefault(stateName,
stateMap, "IsRetryPersistModeUpdate", false)
+ if err != nil {
+ return err
+ }
+ state.SetRetryPersistModeUpdate(isRetryPersistModeUpdate)
+
+ isCompensatePersistModeUpdate, err := a.GetBoolOrDefault(stateName,
stateMap, "IsCompensatePersistModeUpdate", false)
+ if err != nil {
+ return err
+ }
+ state.SetCompensatePersistModeUpdate(isCompensatePersistModeUpdate)
+
+ retryInterfaces, err := a.GetSliceOrDefault(stateName, stateMap,
"Retry", nil)
+ if err != nil {
+ return err
+ }
+ if retryInterfaces != nil {
+ retries, err := a.parseRetries(state.Name(), retryInterfaces)
+ if err != nil {
+ return err
+ }
+ state.SetRetry(retries)
+ }
+
+ catchInterfaces, err := a.GetSliceOrDefault(stateName, stateMap,
"Catch", nil)
+ if err != nil {
+ return err
+ }
+ if catchInterfaces != nil {
+ catches, err := a.parseCatches(state.Name(), catchInterfaces)
+ if err != nil {
+ return err
+ }
+ state.SetCatches(catches)
+ }
+
+ inputInterfaces, err := a.GetSliceOrDefault(stateName, stateMap,
"Input", nil)
+ if err != nil {
+ return err
+ }
+ if inputInterfaces != nil {
+ state.SetInput(inputInterfaces)
+ }
+
+ output, err := a.GetMapOrDefault(stateMap, "Output", nil)
+ if err != nil {
+ return err
+ }
+ if output != nil {
+ state.SetOutput(output)
+ }
+
+ statusMap, ok := stateMap["Status"].(map[string]string)
+ if ok {
+ state.SetStatus(statusMap)
+ }
+
+ loopMap, ok := stateMap["Loop"].(map[string]interface{})
+ if ok {
+ loop := a.parseLoop(stateName, loopMap)
+ state.SetLoop(loop)
+ }
+
+ return nil
+}
+
+func (a *AbstractTaskStateParser) parseLoop(stateName string, loopMap
map[string]interface{}) state.Loop {
+ loopImpl := &state.LoopImpl{}
+ parallel, err := a.GetIntOrDefault(stateName, loopMap, "Parallel", 1)
+ if err != nil {
+ return nil
+ }
+ loopImpl.SetParallel(parallel)
+
+ collection, err := a.GetStringOrDefault(stateName, loopMap,
"Collection", "")
+ if err != nil {
+ return nil
+ }
+ loopImpl.SetCollection(collection)
+
+ elementVariableName, err := a.GetStringOrDefault(stateName, loopMap,
"ElementVariableName", "loopElement")
+ if err != nil {
+ return nil
+ }
+ loopImpl.SetElementVariableName(elementVariableName)
+
+ elementIndexName, err := a.GetStringOrDefault(stateName, loopMap,
"ElementIndexName", "loopCounter")
+ if err != nil {
+ return nil
+ }
+ loopImpl.SetElementIndexName(elementIndexName)
+
+ completionCondition, err := a.GetStringOrDefault(stateName, loopMap,
"CompletionCondition", "[nrOfInstances] == [nrOfCompletedInstances]")
+ if err != nil {
+ return nil
+ }
+ loopImpl.SetElementIndexName(completionCondition)
+ return loopImpl
+}
+
+func (a *AbstractTaskStateParser) parseRetries(stateName string,
retryInterfaces []interface{}) ([]state.Retry, error) {
+ retries := make([]state.Retry, 0)
+ for _, retryInterface := range retryInterfaces {
+ retryMap, ok := retryInterface.(map[string]interface{})
+ if !ok {
+
+ return nil, errors.New("State [" + stateName + "] " +
"Retry illegal, require map[string]interface{}")
+ }
+ retry := &state.RetryImpl{}
+ errorTypes, err := a.GetSliceOrDefault(stateName, retryMap,
"Exceptions", nil)
+ if err != nil {
+ return nil, err
+ }
+ if errorTypes != nil {
+ errorTypeNames := make([]string, 0)
+ for _, errorType := range errorTypes {
+ errorTypeNames = append(errorTypeNames,
errorType.(string))
+ }
+ retry.SetErrorTypeNames(errorTypeNames)
+ }
+
+ maxAttempts, err := a.GetIntOrDefault(stateName, retryMap,
"MaxAttempts", 0)
+ if err != nil {
+ return nil, err
+ }
+ retry.SetMaxAttempt(maxAttempts)
+
+ backoffInterval, err := a.GetFloat64OrDefault(stateName,
retryMap, "BackoffInterval", 0)
+ if err != nil {
+ return nil, err
+ }
+ retry.SetBackoffRate(backoffInterval)
+
+ intervalSeconds, err := a.GetFloat64OrDefault(stateName,
retryMap, "IntervalSeconds", 0)
+ if err != nil {
+ return nil, err
+ }
+ retry.SetIntervalSecond(intervalSeconds)
+ retries = append(retries, retry)
+ }
+ return retries, nil
+}
+
+func (a *AbstractTaskStateParser) parseCatches(stateName string,
catchInterfaces []interface{}) ([]state.ErrorMatch, error) {
+ errorMatches := make([]state.ErrorMatch, 0, len(catchInterfaces))
+ for _, catchInterface := range catchInterfaces {
+ catchMap, ok := catchInterface.(map[string]interface{})
+ if !ok {
+ return nil, errors.New("State [" + stateName + "] " +
"Catch illegal, require map[string]interface{}")
+ }
+ errorMatch := &state.ErrorMatchImpl{}
+ errorInterfaces, err := a.GetSliceOrDefault(stateName,
catchMap, "Exceptions", nil)
+ if err != nil {
+ return nil, err
+ }
+ if errorInterfaces != nil {
+ errorNames := make([]string, 0)
+ for _, errorType := range errorInterfaces {
+ errorNames = append(errorNames,
errorType.(string))
+ }
+ errorMatch.SetErrors(errorNames)
+ }
+ next, err := a.GetStringOrDefault(stateName, catchMap, "Next",
"")
+ if err != nil {
+ return nil, err
+ }
+ errorMatch.SetNext(next)
+ errorMatches = append(errorMatches, errorMatch)
+ }
+ return errorMatches, nil
+}
+
+type ServiceTaskStateParser struct {
+ *AbstractTaskStateParser
+}
+
+func NewServiceTaskStateParser() *ServiceTaskStateParser {
+ return &ServiceTaskStateParser{
+ NewAbstractTaskStateParser(),
+ }
+}
+
+func (s ServiceTaskStateParser) StateType() string {
+ return constant.StateTypeServiceTask
+}
+
+func (s ServiceTaskStateParser) Parse(stateName string, stateMap
map[string]interface{}) (statelang.State, error) {
+ serviceTaskStateImpl := state.NewServiceTaskStateImpl()
+
+ err := s.ParseTaskAttributes(stateName,
serviceTaskStateImpl.AbstractTaskState, stateMap)
+ if err != nil {
+ return nil, err
+ }
+
+ serviceName, err := s.GetString(stateName, stateMap, "ServiceName")
+ if err != nil {
+ return nil, err
+ }
+ serviceTaskStateImpl.SetServiceName(serviceName)
+
+ serviceMethod, err := s.GetString(stateName, stateMap, "ServiceMethod")
+ if err != nil {
+ return nil, err
+ }
+ serviceTaskStateImpl.SetServiceMethod(serviceMethod)
+
+ serviceType, err := s.GetStringOrDefault(stateName, stateMap,
"ServiceType", "")
+ if err != nil {
+ return nil, err
+ }
+ serviceTaskStateImpl.SetServiceType(serviceType)
+
+ parameterTypeInterfaces, err := s.GetSliceOrDefault(stateName,
stateMap, "ParameterTypes", nil)
+ if err != nil {
+ return nil, err
+ }
+ if parameterTypeInterfaces != nil {
+ var parameterTypes []string
+ for i := range parameterTypeInterfaces {
+ parameterType, ok := parameterTypeInterfaces[i].(string)
+ if !ok {
+ return nil, errors.New(fmt.Sprintf("State [%s]
parameterType required string", stateName))
+ }
+
+ parameterTypes = append(parameterTypes, parameterType)
+ }
+ serviceTaskStateImpl.SetParameterTypes(parameterTypes)
+ }
+
+ isAsync, err := s.GetBoolOrDefault(stateName, stateMap, "IsAsync",
false)
+ if err != nil {
+ return nil, err
+ }
+ serviceTaskStateImpl.SetIsAsync(isAsync)
+
+ return serviceTaskStateImpl, nil
+}
+
+type ScriptTaskStateParser struct {
+ *AbstractTaskStateParser
+}
+
+func NewScriptTaskStateParser() *ScriptTaskStateParser {
+ return &ScriptTaskStateParser{
+ NewAbstractTaskStateParser(),
+ }
+}
+
+func (s ScriptTaskStateParser) StateType() string {
+ return constant.StateTypeScriptTask
+}
+
+func (s ScriptTaskStateParser) Parse(stateName string, stateMap
map[string]interface{}) (statelang.State, error) {
+ scriptTaskStateImpl := state.NewScriptTaskStateImpl()
+
+ err := s.ParseTaskAttributes(stateName,
scriptTaskStateImpl.AbstractTaskState, stateMap)
+ if err != nil {
+ return nil, err
+ }
+
+ scriptType, err := s.GetStringOrDefault(stateName, stateMap,
"ScriptType", "")
+ if err != nil {
+ return nil, err
+ }
+ if scriptType != "" {
+ scriptTaskStateImpl.SetScriptType(scriptType)
+ }
+
+ scriptContent, err := s.GetStringOrDefault(stateName, stateMap,
"ScriptContent", "")
+ if err != nil {
+ return nil, err
+ }
+ scriptTaskStateImpl.SetScriptContent(scriptContent)
+
+ scriptTaskStateImpl.SetForCompensation(false)
+ scriptTaskStateImpl.SetForUpdate(false)
+ scriptTaskStateImpl.SetPersist(false)
+
+ return scriptTaskStateImpl, nil
+}
diff --git a/pkg/saga/statemachine/statelang/state.go
b/pkg/saga/statemachine/statelang/state.go
index 1e490ac0..29ac6727 100644
--- a/pkg/saga/statemachine/statelang/state.go
+++ b/pkg/saga/statemachine/statelang/state.go
@@ -30,6 +30,10 @@ type BaseState struct {
stateMachine StateMachine
}
+func NewBaseState() *BaseState {
+ return &BaseState{}
+}
+
func (b *BaseState) Name() string {
return b.name
}
diff --git a/pkg/saga/statemachine/statelang/state/choice_state.go
b/pkg/saga/statemachine/statelang/state/choice_state.go
index e1cd973c..c3f48276 100644
--- a/pkg/saga/statemachine/statelang/state/choice_state.go
+++ b/pkg/saga/statemachine/statelang/state/choice_state.go
@@ -21,14 +21,15 @@ type Choice interface {
}
type ChoiceStateImpl struct {
- statelang.BaseState
+ *statelang.BaseState
defaultChoice string `alias:"Default"`
choices []Choice `alias:"Choices"`
}
func NewChoiceStateImpl() *ChoiceStateImpl {
return &ChoiceStateImpl{
- choices: make([]Choice, 0),
+ BaseState: statelang.NewBaseState(),
+ choices: make([]Choice, 0),
}
}
diff --git
a/pkg/saga/statemachine/statelang/state/compensation_trigger_state.go
b/pkg/saga/statemachine/statelang/state/compensation_trigger_state.go
new file mode 100644
index 00000000..cac71420
--- /dev/null
+++ b/pkg/saga/statemachine/statelang/state/compensation_trigger_state.go
@@ -0,0 +1,22 @@
+package state
+
+import (
+ "github.com/seata/seata-go/pkg/saga/statemachine/constant"
+ "github.com/seata/seata-go/pkg/saga/statemachine/statelang"
+)
+
+type CompensationTriggerState interface {
+ statelang.State
+}
+
+type CompensationTriggerStateImpl struct {
+ *statelang.BaseState
+}
+
+func NewCompensationTriggerStateImpl() *CompensationTriggerStateImpl {
+ s := &CompensationTriggerStateImpl{
+ BaseState: statelang.NewBaseState(),
+ }
+ s.SetType(constant.StateTypeCompensationTrigger)
+ return s
+}
diff --git a/pkg/saga/statemachine/statelang/state/end_state.go
b/pkg/saga/statemachine/statelang/state/end_state.go
new file mode 100644
index 00000000..099e2e07
--- /dev/null
+++ b/pkg/saga/statemachine/statelang/state/end_state.go
@@ -0,0 +1,64 @@
+package state
+
+import (
+ "github.com/seata/seata-go/pkg/saga/statemachine/constant"
+ "github.com/seata/seata-go/pkg/saga/statemachine/statelang"
+)
+
+type EndState interface {
+ statelang.State
+}
+
+type SucceedEndState interface {
+ EndState
+}
+
+type SucceedEndStateImpl struct {
+ *statelang.BaseState
+}
+
+func NewSucceedEndStateImpl() *SucceedEndStateImpl {
+ s := &SucceedEndStateImpl{
+ BaseState: statelang.NewBaseState(),
+ }
+ s.SetType(constant.StateTypeSucceed)
+ return s
+}
+
+type FailEndState interface {
+ EndState
+
+ ErrorCode() string
+
+ Message() string
+}
+
+type FailEndStateImpl struct {
+ *statelang.BaseState
+ errorCode string
+ message string
+}
+
+func NewFailEndStateImpl() *FailEndStateImpl {
+ s := &FailEndStateImpl{
+ BaseState: statelang.NewBaseState(),
+ }
+ s.SetType(constant.StateTypeFail)
+ return s
+}
+
+func (f *FailEndStateImpl) ErrorCode() string {
+ return f.errorCode
+}
+
+func (f *FailEndStateImpl) SetErrorCode(errorCode string) {
+ f.errorCode = errorCode
+}
+
+func (f *FailEndStateImpl) Message() string {
+ return f.message
+}
+
+func (f *FailEndStateImpl) SetMessage(message string) {
+ f.message = message
+}
diff --git a/pkg/saga/statemachine/statelang/state/sub_state_machine.go
b/pkg/saga/statemachine/statelang/state/sub_state_machine.go
new file mode 100644
index 00000000..bf0d96d5
--- /dev/null
+++ b/pkg/saga/statemachine/statelang/state/sub_state_machine.go
@@ -0,0 +1,69 @@
+package state
+
+import (
+ "github.com/google/uuid"
+ "github.com/seata/seata-go/pkg/saga/statemachine/constant"
+)
+
+type SubStateMachine interface {
+ TaskState
+
+ StateMachineName() string
+
+ CompensateStateImpl() TaskState
+}
+
+type SubStateMachineImpl struct {
+ *ServiceTaskStateImpl
+ stateMachineName string
+ compensateState TaskState
+}
+
+func NewSubStateMachineImpl() *SubStateMachineImpl {
+ return &SubStateMachineImpl{
+ ServiceTaskStateImpl: NewServiceTaskStateImpl(),
+ }
+}
+
+func (s *SubStateMachineImpl) StateMachineName() string {
+ return s.stateMachineName
+}
+
+func (s *SubStateMachineImpl) SetStateMachineName(stateMachineName string) {
+ s.stateMachineName = stateMachineName
+}
+
+func (s *SubStateMachineImpl) CompensateStateImpl() TaskState {
+ return s.compensateState
+}
+
+func (s *SubStateMachineImpl) SetCompensateStateImpl(compensateState
TaskState) {
+ s.compensateState = compensateState
+}
+
+type CompensateSubStateMachineState interface {
+ ServiceTaskState
+}
+
+type CompensateSubStateMachineStateImpl struct {
+ *ServiceTaskStateImpl
+ hashcode string
+}
+
+func NewCompensateSubStateMachineStateImpl()
*CompensateSubStateMachineStateImpl {
+ uuid := uuid.New()
+ c := &CompensateSubStateMachineStateImpl{
+ ServiceTaskStateImpl: NewServiceTaskStateImpl(),
+ hashcode: uuid.String(),
+ }
+ c.SetType(constant.CompensateSubMachine)
+ return c
+}
+
+func (c *CompensateSubStateMachineStateImpl) Hashcode() string {
+ return c.hashcode
+}
+
+func (c *CompensateSubStateMachineStateImpl) SetHashcode(hashcode string) {
+ c.hashcode = hashcode
+}
diff --git a/pkg/saga/statemachine/statelang/state/task_state.go
b/pkg/saga/statemachine/statelang/state/task_state.go
index a9164d35..63e33f3c 100644
--- a/pkg/saga/statemachine/statelang/state/task_state.go
+++ b/pkg/saga/statemachine/statelang/state/task_state.go
@@ -1,7 +1,9 @@
package state
import (
+ "github.com/seata/seata-go/pkg/saga/statemachine/constant"
"github.com/seata/seata-go/pkg/saga/statemachine/statelang"
+ "reflect"
)
type TaskState interface {
@@ -9,9 +11,39 @@ type TaskState interface {
CompensateState() string
- Status() map[string]string
+ ForCompensation() bool
+
+ ForUpdate() bool
Retry() []Retry
+
+ Catches() []ErrorMatch
+
+ Status() map[string]string
+
+ Loop() Loop
+}
+
+type Loop interface {
+ Parallel() int
+
+ Collection() string
+
+ ElementVariableName() string
+
+ ElementIndexName() string
+
+ CompletionCondition() string
+}
+
+type ErrorMatch interface {
+ Errors() []string
+
+ ErrorTypes() []reflect.Type
+
+ SetErrorTypes(errorTypes []reflect.Type)
+
+ Next() string
}
type Retry interface {
@@ -26,5 +58,376 @@ type Retry interface {
type ServiceTaskState interface {
TaskState
- //TODO add serviceTask
+
+ ServiceType() string
+
+ ServiceName() string
+
+ ServiceMethod() string
+
+ ParameterTypes() []string
+
+ Persist() bool
+
+ RetryPersistModeUpdate() bool
+
+ CompensatePersistModeUpdate() bool
+}
+
+type AbstractTaskState struct {
+ *statelang.BaseState
+ loop Loop
+ catches []ErrorMatch
+ input []interface{}
+ output map[string]interface{}
+ compensatePersistModeUpdate bool
+ retryPersistModeUpdate bool
+ forCompensation bool
+ forUpdate bool
+ persist bool
+ compensateState string
+ status map[string]string
+ retry []Retry
+}
+
+func NewAbstractTaskState() *AbstractTaskState {
+ return &AbstractTaskState{
+ BaseState: &statelang.BaseState{},
+ }
+}
+
+func (a *AbstractTaskState) Input() []interface{} {
+ return a.input
+}
+
+func (a *AbstractTaskState) SetInput(input []interface{}) {
+ a.input = input
+}
+
+func (a *AbstractTaskState) Output() map[string]interface{} {
+ return a.output
+}
+
+func (a *AbstractTaskState) SetOutput(output map[string]interface{}) {
+ a.output = output
+}
+
+func (a *AbstractTaskState) CompensatePersistModeUpdate() bool {
+ return a.compensatePersistModeUpdate
+}
+
+func (a *AbstractTaskState)
SetCompensatePersistModeUpdate(isCompensatePersistModeUpdate bool) {
+ a.compensatePersistModeUpdate = isCompensatePersistModeUpdate
+}
+
+func (a *AbstractTaskState) RetryPersistModeUpdate() bool {
+ return a.retryPersistModeUpdate
+}
+
+func (a *AbstractTaskState) SetRetryPersistModeUpdate(retryPersistModeUpdate
bool) {
+ a.retryPersistModeUpdate = retryPersistModeUpdate
+}
+
+func (a *AbstractTaskState) Persist() bool {
+ return a.persist
+}
+
+func (a *AbstractTaskState) SetPersist(persist bool) {
+ a.persist = persist
+}
+
+func (a *AbstractTaskState) SetLoop(loop Loop) {
+ a.loop = loop
+}
+
+func (a *AbstractTaskState) SetCatches(catches []ErrorMatch) {
+ a.catches = catches
+}
+
+func (a *AbstractTaskState) SetForCompensation(forCompensation bool) {
+ a.forCompensation = forCompensation
+}
+
+func (a *AbstractTaskState) SetForUpdate(forUpdate bool) {
+ a.forUpdate = forUpdate
+}
+
+func (a *AbstractTaskState) SetCompensateState(compensateState string) {
+ a.compensateState = compensateState
+}
+
+func (a *AbstractTaskState) SetStatus(status map[string]string) {
+ a.status = status
+}
+
+func (a *AbstractTaskState) SetRetry(retry []Retry) {
+ a.retry = retry
+}
+
+func (a *AbstractTaskState) ForCompensation() bool {
+ return a.forCompensation
+}
+
+func (a *AbstractTaskState) ForUpdate() bool {
+ return a.forUpdate
+}
+
+func (a *AbstractTaskState) Catches() []ErrorMatch {
+ return a.catches
+}
+
+func (a *AbstractTaskState) Loop() Loop {
+ return a.loop
+}
+
+func (a *AbstractTaskState) CompensateState() string {
+ return a.compensateState
+}
+
+func (a *AbstractTaskState) Status() map[string]string {
+ return a.status
+}
+
+func (a *AbstractTaskState) Retry() []Retry {
+ return a.retry
+}
+
+type ServiceTaskStateImpl struct {
+ *AbstractTaskState
+ serviceType string
+ serviceName string
+ serviceMethod string
+ parameterTypes []string
+ persist bool
+ retryPersistModeUpdate bool
+ compensatePersistModeUpdate bool
+ isAsync bool
+}
+
+func NewServiceTaskStateImpl() *ServiceTaskStateImpl {
+ return &ServiceTaskStateImpl{
+ AbstractTaskState: NewAbstractTaskState(),
+ }
+}
+
+func (s *ServiceTaskStateImpl) IsAsync() bool {
+ return s.isAsync
+}
+
+func (s *ServiceTaskStateImpl) SetIsAsync(isAsync bool) {
+ s.isAsync = isAsync
+}
+
+func (s *ServiceTaskStateImpl) SetServiceType(serviceType string) {
+ s.serviceType = serviceType
+}
+
+func (s *ServiceTaskStateImpl) SetServiceName(serviceName string) {
+ s.serviceName = serviceName
+}
+
+func (s *ServiceTaskStateImpl) SetServiceMethod(serviceMethod string) {
+ s.serviceMethod = serviceMethod
+}
+
+func (s *ServiceTaskStateImpl) SetParameterTypes(parameterTypes []string) {
+ s.parameterTypes = parameterTypes
+}
+
+func (s *ServiceTaskStateImpl) SetPersist(persist bool) {
+ s.persist = persist
+}
+
+func (s *ServiceTaskStateImpl)
SetRetryPersistModeUpdate(retryPersistModeUpdate bool) {
+ s.retryPersistModeUpdate = retryPersistModeUpdate
+}
+
+func (s *ServiceTaskStateImpl)
SetCompensatePersistModeUpdate(compensatePersistModeUpdate bool) {
+ s.compensatePersistModeUpdate = compensatePersistModeUpdate
+}
+
+func (s *ServiceTaskStateImpl) Loop() Loop {
+ return s.loop
+}
+
+func (s *ServiceTaskStateImpl) ServiceType() string {
+ return s.serviceType
+}
+
+func (s *ServiceTaskStateImpl) ServiceName() string {
+ return s.serviceName
+}
+
+func (s *ServiceTaskStateImpl) ServiceMethod() string {
+ return s.serviceMethod
+}
+
+func (s *ServiceTaskStateImpl) ParameterTypes() []string {
+ return s.parameterTypes
+}
+
+func (s *ServiceTaskStateImpl) Persist() bool {
+ return s.persist
+}
+
+func (s *ServiceTaskStateImpl) RetryPersistModeUpdate() bool {
+ return s.retryPersistModeUpdate
+}
+
+func (s *ServiceTaskStateImpl) CompensatePersistModeUpdate() bool {
+ return s.compensatePersistModeUpdate
+}
+
+type LoopImpl struct {
+ parallel int
+ collection string
+ elementVariableName string
+ elementIndexName string
+ completionCondition string
+}
+
+func (l *LoopImpl) SetParallel(parallel int) {
+ l.parallel = parallel
+}
+
+func (l *LoopImpl) SetCollection(collection string) {
+ l.collection = collection
+}
+
+func (l *LoopImpl) SetElementVariableName(elementVariableName string) {
+ l.elementVariableName = elementVariableName
+}
+
+func (l *LoopImpl) SetElementIndexName(elementIndexName string) {
+ l.elementIndexName = elementIndexName
+}
+
+func (l *LoopImpl) SetCompletionCondition(completionCondition string) {
+ l.completionCondition = completionCondition
+}
+
+func (l *LoopImpl) Parallel() int {
+ return l.parallel
+}
+
+func (l *LoopImpl) Collection() string {
+ return l.collection
+}
+
+func (l *LoopImpl) ElementVariableName() string {
+ return l.elementVariableName
+}
+
+func (l *LoopImpl) ElementIndexName() string {
+ return l.elementIndexName
+}
+
+func (l *LoopImpl) CompletionCondition() string {
+ return l.completionCondition
+}
+
+type RetryImpl struct {
+ errorTypeNames []string
+ intervalSecond float64
+ maxAttempt int
+ backoffRate float64
+}
+
+func (r *RetryImpl) SetErrorTypeNames(errorTypeNames []string) {
+ r.errorTypeNames = errorTypeNames
+}
+
+func (r *RetryImpl) SetIntervalSecond(intervalSecond float64) {
+ r.intervalSecond = intervalSecond
+}
+
+func (r *RetryImpl) SetMaxAttempt(maxAttempt int) {
+ r.maxAttempt = maxAttempt
+}
+
+func (r *RetryImpl) SetBackoffRate(backoffRate float64) {
+ r.backoffRate = backoffRate
+}
+
+func (r *RetryImpl) ErrorTypeNames() []string {
+ return r.errorTypeNames
+}
+
+func (r *RetryImpl) IntervalSecond() float64 {
+ return r.intervalSecond
+}
+
+func (r *RetryImpl) MaxAttempt() int {
+ return r.maxAttempt
+}
+
+func (r *RetryImpl) BackoffRate() float64 {
+ return r.backoffRate
+}
+
+type ErrorMatchImpl struct {
+ errors []string
+ errorTypes []reflect.Type
+ next string
+}
+
+func (e *ErrorMatchImpl) SetErrors(errors []string) {
+ e.errors = errors
+}
+
+func (e *ErrorMatchImpl) SetNext(next string) {
+ e.next = next
+}
+
+func (e *ErrorMatchImpl) Errors() []string {
+ return e.errors
+}
+
+func (e *ErrorMatchImpl) ErrorTypes() []reflect.Type {
+ return e.errorTypes
+}
+
+func (e *ErrorMatchImpl) SetErrorTypes(errorTypes []reflect.Type) {
+ e.errorTypes = errorTypes
+}
+
+func (e *ErrorMatchImpl) Next() string {
+ return e.next
+}
+
+type ScriptTaskState interface {
+ TaskState
+
+ ScriptType() string
+
+ ScriptContent() string
+}
+
+type ScriptTaskStateImpl struct {
+ *AbstractTaskState
+ scriptType string
+ scriptContent string
+}
+
+func NewScriptTaskStateImpl() *ScriptTaskStateImpl {
+ return &ScriptTaskStateImpl{
+ AbstractTaskState: NewAbstractTaskState(),
+ scriptType: constant.DefaultScriptType,
+ }
+}
+
+func (s *ScriptTaskStateImpl) SetScriptType(scriptType string) {
+ s.scriptType = scriptType
+}
+
+func (s *ScriptTaskStateImpl) SetScriptContent(scriptContent string) {
+ s.scriptContent = scriptContent
+}
+
+func (s *ScriptTaskStateImpl) ScriptType() string {
+ return s.scriptType
+}
+
+func (s *ScriptTaskStateImpl) ScriptContent() string {
+ return s.scriptContent
}
diff --git a/testdata/saga/statelang/simple_statelang_with_choice.json
b/testdata/saga/statelang/simple_statelang_with_choice.json
new file mode 100644
index 00000000..4be7a48f
--- /dev/null
+++ b/testdata/saga/statelang/simple_statelang_with_choice.json
@@ -0,0 +1,38 @@
+{
+ "Name": "simpleChoiceTestStateMachine",
+ "Comment": "带条件分支的测试状态机定义",
+ "StartState": "FirstState",
+ "Version": "0.0.1",
+ "States": {
+ "FirstState": {
+ "Type": "ServiceTask",
+ "ServiceName": "demoService",
+ "ServiceMethod": "foo",
+ "Next": "ChoiceState"
+ },
+ "ChoiceState":{
+ "Type": "Choice",
+ "Choices":[
+ {
+ "Expression":"[a] == 1",
+ "Next":"SecondState"
+ },
+ {
+ "Expression":"[a] == 2",
+ "Next":"ThirdState"
+ }
+ ],
+ "Default":"SecondState"
+ },
+ "SecondState": {
+ "Type": "ServiceTask",
+ "ServiceName": "demoService",
+ "ServiceMethod": "bar"
+ },
+ "ThirdState": {
+ "Type": "ServiceTask",
+ "ServiceName": "demoService",
+ "ServiceMethod": "foo"
+ }
+ }
+}
\ No newline at end of file
diff --git a/testdata/saga/statelang/simple_statemachine.json
b/testdata/saga/statelang/simple_statemachine.json
new file mode 100644
index 00000000..91ed5019
--- /dev/null
+++ b/testdata/saga/statelang/simple_statemachine.json
@@ -0,0 +1,138 @@
+{
+ "Name": "simpleTestStateMachine",
+ "Comment": "测试状态机定义",
+ "StartState": "FirstState",
+ "Version": "0.0.1",
+ "States": {
+ "FirstState": {
+ "Type": "ServiceTask",
+ "ServiceName": "is.seata.saga.DemoService",
+ "ServiceMethod": "foo",
+ "IsPersist": false,
+ "Next": "ScriptState"
+ },
+ "ScriptState": {
+ "Type": "ScriptTask",
+ "ScriptType": "groovy",
+ "ScriptContent": "return 'hello ' + inputA",
+ "Input": [
+ {
+ "inputA": "$.data1"
+ }
+ ],
+ "Output": {
+ "scriptStateResult": "$.#root"
+ },
+ "Next": "ChoiceState"
+ },
+ "ChoiceState": {
+ "Type": "Choice",
+ "Choices": [
+ {
+ "Expression": "foo == 1",
+ "Next": "FirstMatchState"
+ },
+ {
+ "Expression": "foo == 2",
+ "Next": "SecondMatchState"
+ }
+ ],
+ "Default": "FailState"
+ },
+ "FirstMatchState": {
+ "Type": "ServiceTask",
+ "ServiceName": "is.seata.saga.DemoService",
+ "ServiceMethod": "bar",
+ "CompensateState": "CompensateFirst",
+ "Status": {
+ "return.code == 'S'": "SU",
+ "return.code == 'F'": "FA",
+ "$exception{java.lang.Throwable}": "UN"
+ },
+ "Input": [
+ {
+ "inputA1": "$.data1",
+ "inputA2": {
+ "a": "$.data2.a"
+ }
+ },
+ {
+ "inputB": "$.header"
+ }
+ ],
+ "Output": {
+ "firstMatchStateResult": "$.#root"
+ },
+ "Retry": [
+ {
+ "Exceptions": ["java.lang.Exception"],
+ "IntervalSeconds": 2,
+ "MaxAttempts": 3,
+ "BackoffRate": 1.5
+ }
+ ],
+ "Catch": [
+ {
+ "Exceptions": [
+ "java.lang.Exception"
+ ],
+ "Next": "CompensationTrigger"
+ }
+ ],
+ "Next": "SuccessState"
+ },
+ "CompensateFirst": {
+ "Type": "ServiceTask",
+ "ServiceName": "is.seata.saga.DemoService",
+ "ServiceMethod": "compensateBar",
+ "IsForCompensation": true,
+ "IsForUpdate": true,
+ "Input": [
+ {
+ "input": "$.data"
+ }
+ ],
+ "Output": {
+ "firstMatchStateResult": "$.#root"
+ },
+ "Status": {
+ "return.code == 'S'": "SU",
+ "return.code == 'F'": "FA",
+ "$exception{java.lang.Throwable}": "UN"
+ }
+ },
+ "CompensationTrigger": {
+ "Type": "CompensationTrigger",
+ "Next": "CompensateEndState"
+ },
+ "CompensateEndState": {
+ "Type": "Fail",
+ "ErrorCode": "StateCompensated",
+ "Message": "State Compensated!"
+ },
+ "SecondMatchState": {
+ "Type": "SubStateMachine",
+ "StateMachineName": "simpleTestSubStateMachine",
+ "Input": [
+ {
+ "input": "$.data"
+ },
+ {
+ "header": "$.header"
+ }
+ ],
+ "Output": {
+ "firstMatchStateResult": "$.#root"
+ },
+ "Next": "SuccessState"
+ },
+ "FailState": {
+ "Type": "Fail",
+ "ErrorCode": "DefaultStateError",
+ "Message": "No Matches!"
+ },
+ "SuccessState": {
+ "Type": "Succeed"
+ }
+ }
+}
\ No newline at end of file
diff --git a/testdata/saga/statelang/state_machine_new_designer.json
b/testdata/saga/statelang/state_machine_new_designer.json
new file mode 100644
index 00000000..0fd815ec
--- /dev/null
+++ b/testdata/saga/statelang/state_machine_new_designer.json
@@ -0,0 +1,424 @@
+{
+ "Name": "StateMachineNewDesigner",
+ "Comment": "This state machine is modeled by designer tools.",
+ "Version": "0.0.1",
+ "style": {
+ "bounds": {
+ "x": 200,
+ "y": 200,
+ "width": 36,
+ "height": 36
+ }
+ },
+ "States": {
+ "ServiceTask-a9h2o51": {
+ "style": {
+ "bounds": {
+ "x": 300,
+ "y": 178,
+ "width": 100,
+ "height": 80
+ }
+ },
+ "Name": "ServiceTask-a9h2o51",
+ "IsForCompensation": false,
+ "Input": [
+ {}
+ ],
+ "Output": {},
+ "Status": {},
+ "Retry": [],
+ "ServiceName": "",
+ "ServiceMethod": "",
+ "Type": "ServiceTask",
+ "Next": "Choice-4ajl8nt",
+ "edge": {
+ "Choice-4ajl8nt": {
+ "style": {
+ "waypoints": [
+ {
+ "original": {
+ "x": 400,
+ "y": 218
+ },
+ "x": 400,
+ "y": 218
+ },
+ {
+ "x": 435,
+ "y": 218
+ },
+ {
+ "original": {
+ "x": 455,
+ "y": 218
+ },
+ "x": 455,
+ "y": 218
+ }
+ ],
+ "source": "ServiceTask-a9h2o51",
+ "target": "Choice-4ajl8nt"
+ },
+ "Type": "Transition"
+ }
+ },
+ "CompensateState": "CompensateFirstState"
+ },
+ "Choice-4ajl8nt": {
+ "style": {
+ "bounds": {
+ "x": 455,
+ "y": 193,
+ "width": 50,
+ "height": 50
+ }
+ },
+ "Name": "Choice-4ajl8nt",
+ "Type": "Choice",
+ "Choices": [
+ {
+ "Expression": "",
+ "Next": "SubStateMachine-cauj9uy"
+ },
+ {
+ "Expression": "",
+ "Next": "ServiceTask-vdij28l"
+ }
+ ],
+ "Default": "SubStateMachine-cauj9uy",
+ "edge": {
+ "SubStateMachine-cauj9uy": {
+ "style": {
+ "waypoints": [
+ {
+ "original": {
+ "x": 505,
+ "y": 218
+ },
+ "x": 505,
+ "y": 218
+ },
+ {
+ "x": 530,
+ "y": 218
+ },
+ {
+ "original": {
+ "x": 550,
+ "y": 218
+ },
+ "x": 550,
+ "y": 218
+ }
+ ],
+ "source": "Choice-4ajl8nt",
+ "target": "SubStateMachine-cauj9uy"
+ },
+ "Type": "ChoiceEntry"
+ },
+ "ServiceTask-vdij28l": {
+ "style": {
+ "waypoints": [
+ {
+ "original": {
+ "x": 480,
+ "y": 243
+ },
+ "x": 480,
+ "y": 243
+ },
+ {
+ "x": 600,
+ "y": 290
+ },
+ {
+ "original": {
+ "x": 600,
+ "y": 310
+ },
+ "x": 600,
+ "y": 310
+ }
+ ],
+ "source": "Choice-4ajl8nt",
+ "target": "ServiceTask-vdij28l"
+ },
+ "Type": "ChoiceEntry"
+ }
+ }
+ },
+ "CompensateFirstState": {
+ "style": {
+ "bounds": {
+ "x": 300,
+ "y": 310,
+ "width": 100,
+ "height": 80
+ }
+ },
+ "Name": "CompensateFirstState",
+ "IsForCompensation": true,
+ "Input": [
+ {}
+ ],
+ "Output": {},
+ "Status": {},
+ "Retry": [],
+ "ServiceName": "",
+ "ServiceMethod": "",
+ "Type": "ServiceTask"
+ },
+ "SubStateMachine-cauj9uy": {
+ "style": {
+ "bounds": {
+ "x": 550,
+ "y": 178,
+ "width": 100,
+ "height": 80
+ }
+ },
+ "Name": "SubStateMachine-cauj9uy",
+ "IsForCompensation": false,
+ "Input": [
+ {}
+ ],
+ "Output": {},
+ "Status": {},
+ "Retry": [],
+ "StateMachineName": "",
+ "Type": "SubStateMachine",
+ "Next": "Succeed-5x3z98u",
+ "edge": {
+ "Succeed-5x3z98u": {
+ "style": {
+ "waypoints": [
+ {
+ "original": {
+ "x": 650,
+ "y": 218
+ },
+ "x": 650,
+ "y": 218
+ },
+ {
+ "x": 702,
+ "y": 218
+ },
+ {
+ "original": {
+ "x": 722,
+ "y": 218
+ },
+ "x": 722,
+ "y": 218
+ }
+ ],
+ "source": "SubStateMachine-cauj9uy",
+ "target": "Succeed-5x3z98u"
+ },
+ "Type": "Transition"
+ }
+ }
+ },
+ "ServiceTask-vdij28l": {
+ "style": {
+ "bounds": {
+ "x": 550,
+ "y": 310,
+ "width": 100,
+ "height": 80
+ }
+ },
+ "Name": "ServiceTask-vdij28l",
+ "IsForCompensation": false,
+ "Input": [
+ {}
+ ],
+ "Output": {},
+ "Status": {},
+ "Retry": [],
+ "ServiceName": "",
+ "ServiceMethod": "",
+ "Catch": [
+ {
+ "Exceptions": [],
+ "Next": "CompensationTrigger-uldp2ou"
+ }
+ ],
+ "Type": "ServiceTask",
+ "catch": {
+ "style": {
+ "bounds": {
+ "x": 632,
+ "y": 372,
+ "width": 36,
+ "height": 36
+ }
+ },
+ "edge": {
+ "CompensationTrigger-uldp2ou": {
+ "style": {
+ "waypoints": [
+ {
+ "original": {
+ "x": 668,
+ "y": 390
+ },
+ "x": 668,
+ "y": 390
+ },
+ {
+ "x": 702,
+ "y": 390
+ },
+ {
+ "original": {
+ "x": 722,
+ "y": 390
+ },
+ "x": 722,
+ "y": 390
+ }
+ ],
+ "source": "ServiceTask-vdij28l",
+ "target": "CompensationTrigger-uldp2ou"
+ },
+ "Type": "ExceptionMatch"
+ }
+ }
+ },
+ "Next": "Succeed-5x3z98u",
+ "edge": {
+ "Succeed-5x3z98u": {
+ "style": {
+ "waypoints": [
+ {
+ "original": {
+ "x": 600,
+ "y": 310
+ },
+ "x": 600,
+ "y": 310
+ },
+ {
+ "x": 740,
+ "y": 256
+ },
+ {
+ "original": {
+ "x": 740,
+ "y": 236
+ },
+ "x": 740,
+ "y": 236
+ }
+ ],
+ "source": "ServiceTask-vdij28l",
+ "target": "Succeed-5x3z98u"
+ },
+ "Type": "Transition"
+ }
+ }
+ },
+ "Succeed-5x3z98u": {
+ "style": {
+ "bounds": {
+ "x": 722,
+ "y": 200,
+ "width": 36,
+ "height": 36
+ }
+ },
+ "Name": "Succeed-5x3z98u",
+ "Type": "Succeed"
+ },
+ "CompensationTrigger-uldp2ou": {
+ "style": {
+ "bounds": {
+ "x": 722,
+ "y": 372,
+ "width": 36,
+ "height": 36
+ }
+ },
+ "Name": "CompensationTrigger-uldp2ou",
+ "Type": "CompensationTrigger",
+ "Next": "Fail-9roxcv5",
+ "edge": {
+ "Fail-9roxcv5": {
+ "style": {
+ "waypoints": [
+ {
+ "original": {
+ "x": 758,
+ "y": 390
+ },
+ "x": 758,
+ "y": 390
+ },
+ {
+ "x": 792,
+ "y": 390
+ },
+ {
+ "original": {
+ "x": 812,
+ "y": 390
+ },
+ "x": 812,
+ "y": 390
+ }
+ ],
+ "source": "CompensationTrigger-uldp2ou",
+ "target": "Fail-9roxcv5"
+ },
+ "Type": "Transition"
+ }
+ }
+ },
+ "Fail-9roxcv5": {
+ "style": {
+ "bounds": {
+ "x": 812,
+ "y": 372,
+ "width": 36,
+ "height": 36
+ }
+ },
+ "Name": "Fail-9roxcv5",
+ "ErrorCode": "",
+ "Message": "",
+ "Type": "Fail"
+ }
+ },
+ "StartState": "ServiceTask-a9h2o51",
+ "edge": {
+ "style": {
+ "waypoints": [
+ {
+ "original": {
+ "x": 236,
+ "y": 218
+ },
+ "x": 236,
+ "y": 218
+ },
+ {
+ "x": 280,
+ "y": 218
+ },
+ {
+ "original": {
+ "x": 300,
+ "y": 218
+ },
+ "x": 300,
+ "y": 218
+ }
+ ],
+ "target": "ServiceTask-a9h2o51"
+ },
+ "Type": "Transition"
+ }
+}
\ No newline at end of file
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]