Browse Source

feature: add serverice task parse in statelang (#650)

pull/668/head
Jingliu Xiong GitHub 1 year ago
parent
commit
9c4c0f44a0
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
19 changed files with 1909 additions and 42 deletions
  1. +28
    -0
      pkg/saga/statemachine/constant/constant.go
  2. +0
    -20
      pkg/saga/statemachine/constant/contant.go
  3. +6
    -4
      pkg/saga/statemachine/statelang/parser/choice_state_json_parser.go
  4. +31
    -0
      pkg/saga/statemachine/statelang/parser/compensation_trigger_state_parser.go
  5. +66
    -0
      pkg/saga/statemachine/statelang/parser/end_state_parser.go
  6. +36
    -6
      pkg/saga/statemachine/statelang/parser/statemachine_json_parser.go
  7. +34
    -2
      pkg/saga/statemachine/statelang/parser/statemachine_json_parser_test.go
  8. +127
    -6
      pkg/saga/statemachine/statelang/parser/statemachine_parser.go
  9. +84
    -0
      pkg/saga/statemachine/statelang/parser/sub_state_machine_parser.go
  10. +330
    -0
      pkg/saga/statemachine/statelang/parser/task_state_json_parser.go
  11. +4
    -0
      pkg/saga/statemachine/statelang/state.go
  12. +3
    -2
      pkg/saga/statemachine/statelang/state/choice_state.go
  13. +22
    -0
      pkg/saga/statemachine/statelang/state/compensation_trigger_state.go
  14. +64
    -0
      pkg/saga/statemachine/statelang/state/end_state.go
  15. +69
    -0
      pkg/saga/statemachine/statelang/state/sub_state_machine.go
  16. +405
    -2
      pkg/saga/statemachine/statelang/state/task_state.go
  17. +38
    -0
      testdata/saga/statelang/simple_statelang_with_choice.json
  18. +138
    -0
      testdata/saga/statelang/simple_statemachine.json
  19. +424
    -0
      testdata/saga/statelang/state_machine_new_designer.json

+ 28
- 0
pkg/saga/statemachine/constant/constant.go View File

@@ -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"
)

+ 0
- 20
pkg/saga/statemachine/constant/contant.go View File

@@ -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"
)

+ 6
- 4
pkg/saga/statemachine/statelang/parser/choice_state_json_parser.go View File

@@ -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) {


+ 31
- 0
pkg/saga/statemachine/statelang/parser/compensation_trigger_state_parser.go View File

@@ -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
}

+ 66
- 0
pkg/saga/statemachine/statelang/parser/end_state_parser.go View File

@@ -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
}

+ 36
- 6
pkg/saga/statemachine/statelang/parser/statemachine_json_parser.go View File

@@ -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"`


+ 34
- 2
pkg/saga/statemachine/statelang/parser/statemachine_json_parser_test.go View File

@@ -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\":\"ThirdState\"\n }\n ],\n \"Default\":\"Fail\"\n }\n }\n}"
_, 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())
}


+ 127
- 6
pkg/saga/statemachine/statelang/parser/statemachine_parser.go View File

@@ -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) {


+ 84
- 0
pkg/saga/statemachine/statelang/parser/sub_state_machine_parser.go View File

@@ -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
}

+ 330
- 0
pkg/saga/statemachine/statelang/parser/task_state_json_parser.go View File

@@ -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
}

+ 4
- 0
pkg/saga/statemachine/statelang/state.go View File

@@ -30,6 +30,10 @@ type BaseState struct {
stateMachine StateMachine
}

func NewBaseState() *BaseState {
return &BaseState{}
}

func (b *BaseState) Name() string {
return b.name
}


+ 3
- 2
pkg/saga/statemachine/statelang/state/choice_state.go View File

@@ -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),
}
}



+ 22
- 0
pkg/saga/statemachine/statelang/state/compensation_trigger_state.go View File

@@ -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
}

+ 64
- 0
pkg/saga/statemachine/statelang/state/end_state.go View File

@@ -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
}

+ 69
- 0
pkg/saga/statemachine/statelang/state/sub_state_machine.go View File

@@ -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
}

+ 405
- 2
pkg/saga/statemachine/statelang/state/task_state.go View File

@@ -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
}

+ 38
- 0
testdata/saga/statelang/simple_statelang_with_choice.json View File

@@ -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"
}
}
}

+ 138
- 0
testdata/saga/statelang/simple_statemachine.json View File

@@ -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"
}
}
}

+ 424
- 0
testdata/saga/statelang/state_machine_new_designer.json View File

@@ -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"
}
}

Loading…
Cancel
Save