This is an automated email from the ASF dual-hosted git repository.
luky116 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 e70cabe8 [Refactor] Migrate StateMachineObject to client/config and
unify config parsing with koanf (#785)
e70cabe8 is described below
commit e70cabe8c71017585e6668cabead3658eb67430e
Author: flypiggy <[email protected]>
AuthorDate: Sat Mar 15 19:22:20 2025 +0800
[Refactor] Migrate StateMachineObject to client/config and unify config
parsing with koanf (#785)
* optimize saga config reuse
* upd-test
* Modularize SagaConfig into independent configuration structure
---
pkg/client/config.go | 4 ++
pkg/saga/config.go | 34 +++++++++++++
.../statelang/parser/statemachine_config_parser.go | 55 +++++++++++----------
.../parser/statemachine_config_parser_test.go | 57 +++++++++++-----------
.../statelang/parser/statemachine_json_parser.go | 5 +-
.../parser/statemachine_json_parser_test.go | 30 ++++++++++--
pkg/saga/statemachine/statemachine.go | 47 ++++++++++++++++++
7 files changed, 172 insertions(+), 60 deletions(-)
diff --git a/pkg/client/config.go b/pkg/client/config.go
index bd84852d..c4fce0f2 100644
--- a/pkg/client/config.go
+++ b/pkg/client/config.go
@@ -20,6 +20,7 @@ package client
import (
"flag"
"fmt"
+ "github.com/seata/seata-go/pkg/saga"
"io/ioutil"
"os"
"path/filepath"
@@ -84,6 +85,8 @@ type Config struct {
TransportConfig remoteConfig.TransportConfig `yaml:"transport"
json:"transport" koanf:"transport"`
ServiceConfig discovery.ServiceConfig `yaml:"service"
json:"service" koanf:"service"`
RegistryConfig discovery.RegistryConfig `yaml:"registry"
json:"registry" koanf:"registry"`
+
+ SagaConfig saga.Config `yaml:"saga" json:"saga" koanf:"saga"`
}
func (c *Config) RegisterFlags(f *flag.FlagSet) {
@@ -102,6 +105,7 @@ func (c *Config) RegisterFlags(f *flag.FlagSet) {
c.TransportConfig.RegisterFlagsWithPrefix("transport", f)
c.RegistryConfig.RegisterFlagsWithPrefix("registry", f)
c.ServiceConfig.RegisterFlagsWithPrefix("service", f)
+ c.SagaConfig.RegisterFlagsWithPrefix("saga", f)
}
type loaderConf struct {
diff --git a/pkg/saga/config.go b/pkg/saga/config.go
new file mode 100644
index 00000000..dd72bc6b
--- /dev/null
+++ b/pkg/saga/config.go
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package saga
+
+import (
+ "flag"
+
+ "github.com/seata/seata-go/pkg/saga/statemachine"
+)
+
+type Config struct {
+ StateMachine *statemachine.StateMachineObject `yaml:"state-machine"
json:"state-machine" koanf:"state-machine"`
+}
+
+func (cfg *Config) RegisterFlagsWithPrefix(prefix string, f *flag.FlagSet) {
+ if cfg.StateMachine != nil {
+
cfg.StateMachine.RegisterFlagsWithPrefix(prefix+".state-machine", f)
+ }
+}
diff --git
a/pkg/saga/statemachine/statelang/parser/statemachine_config_parser.go
b/pkg/saga/statemachine/statelang/parser/statemachine_config_parser.go
index 63276e2e..a33bbdec 100644
--- a/pkg/saga/statemachine/statelang/parser/statemachine_config_parser.go
+++ b/pkg/saga/statemachine/statelang/parser/statemachine_config_parser.go
@@ -19,16 +19,20 @@ package parser
import (
"bytes"
- "encoding/json"
"fmt"
- "gopkg.in/yaml.v3"
+ "github.com/seata/seata-go/pkg/saga/statemachine"
"io"
"os"
+
+ "github.com/knadh/koanf"
+ "github.com/knadh/koanf/parsers/json"
+ "github.com/knadh/koanf/parsers/yaml"
+ "github.com/knadh/koanf/providers/rawbytes"
)
// ConfigParser is a general configuration parser interface, used to agree on
the implementation of different types of parsers
type ConfigParser interface {
- Parse(configContent []byte) (*StateMachineObject, error)
+ Parse(configContent []byte) (*statemachine.StateMachineObject, error)
}
type JSONConfigParser struct{}
@@ -37,16 +41,21 @@ func NewJSONConfigParser() *JSONConfigParser {
return &JSONConfigParser{}
}
-func (p *JSONConfigParser) Parse(configContent []byte) (*StateMachineObject,
error) {
+func (p *JSONConfigParser) Parse(configContent []byte)
(*statemachine.StateMachineObject, error) {
if configContent == nil || len(configContent) == 0 {
return nil, fmt.Errorf("empty JSON config content")
}
- var stateMachineObject StateMachineObject
- if err := json.Unmarshal(configContent, &stateMachineObject); err !=
nil {
+ k := koanf.New(".")
+ if err := k.Load(rawbytes.Provider(configContent), json.Parser()); err
!= nil {
return nil, fmt.Errorf("failed to parse JSON config content:
%w", err)
}
+ var stateMachineObject statemachine.StateMachineObject
+ if err := k.Unmarshal("", &stateMachineObject); err != nil {
+ return nil, fmt.Errorf("failed to unmarshal JSON config to
struct: %w", err)
+ }
+
return &stateMachineObject, nil
}
@@ -56,16 +65,21 @@ func NewYAMLConfigParser() *YAMLConfigParser {
return &YAMLConfigParser{}
}
-func (p *YAMLConfigParser) Parse(configContent []byte) (*StateMachineObject,
error) {
+func (p *YAMLConfigParser) Parse(configContent []byte)
(*statemachine.StateMachineObject, error) {
if configContent == nil || len(configContent) == 0 {
return nil, fmt.Errorf("empty YAML config content")
}
- var stateMachineObject StateMachineObject
- if err := yaml.Unmarshal(configContent, &stateMachineObject); err !=
nil {
+ k := koanf.New(".")
+ if err := k.Load(rawbytes.Provider(configContent), yaml.Parser()); err
!= nil {
return nil, fmt.Errorf("failed to parse YAML config content:
%w", err)
}
+ var stateMachineObject statemachine.StateMachineObject
+ if err := k.Unmarshal("", &stateMachineObject); err != nil {
+ return nil, fmt.Errorf("failed to unmarshal YAML config to
struct: %w", err)
+ }
+
return &stateMachineObject, nil
}
@@ -102,18 +116,20 @@ func (p *StateMachineConfigParser)
ReadConfigFile(configFilePath string) ([]byte
}
func (p *StateMachineConfigParser) getParser(content []byte) (ConfigParser,
error) {
- var obj interface{}
- if err := json.Unmarshal(content, &obj); err == nil {
+ k := koanf.New(".")
+ if err := k.Load(rawbytes.Provider(content), json.Parser()); err == nil
{
return NewJSONConfigParser(), nil
}
- if err := yaml.Unmarshal(content, &obj); err == nil {
+
+ k = koanf.New(".")
+ if err := k.Load(rawbytes.Provider(content), yaml.Parser()); err == nil
{
return NewYAMLConfigParser(), nil
}
return nil, fmt.Errorf("unsupported config file format")
}
-func (p *StateMachineConfigParser) Parse(content []byte) (*StateMachineObject,
error) {
+func (p *StateMachineConfigParser) Parse(content []byte)
(*statemachine.StateMachineObject, error) {
parser, err := p.getParser(content)
if err != nil {
return nil, err
@@ -121,16 +137,3 @@ func (p *StateMachineConfigParser) Parse(content []byte)
(*StateMachineObject, e
return parser.Parse(content)
}
-
-type StateMachineObject struct {
- Name string `json:"Name"
yaml:"Name"`
- Comment string `json:"Comment"
yaml:"Comment"`
- Version string `json:"Version"
yaml:"Version"`
- StartState string `json:"StartState"
yaml:"StartState"`
- RecoverStrategy string
`json:"RecoverStrategy" yaml:"RecoverStrategy"`
- Persist bool `json:"IsPersist"
yaml:"IsPersist"`
- RetryPersistModeUpdate bool
`json:"IsRetryPersistModeUpdate" yaml:"IsRetryPersistModeUpdate"`
- CompensatePersistModeUpdate bool
`json:"IsCompensatePersistModeUpdate" yaml:"IsCompensatePersistModeUpdate"`
- Type string `json:"Type"
yaml:"Type"`
- States map[string]interface{} `json:"States"
yaml:"States"`
-}
diff --git
a/pkg/saga/statemachine/statelang/parser/statemachine_config_parser_test.go
b/pkg/saga/statemachine/statelang/parser/statemachine_config_parser_test.go
index d901296b..e634fc2f 100644
--- a/pkg/saga/statemachine/statelang/parser/statemachine_config_parser_test.go
+++ b/pkg/saga/statemachine/statelang/parser/statemachine_config_parser_test.go
@@ -18,6 +18,7 @@
package parser
import (
+ "github.com/seata/seata-go/pkg/saga/statemachine"
"github.com/stretchr/testify/assert"
"testing"
)
@@ -26,39 +27,39 @@ func TestStateMachineConfigParser_Parse(t *testing.T) {
parser := NewStateMachineConfigParser()
tests := []struct {
- name string
- configFilePath string
- expectedStateMachineObject *StateMachineObject
+ name string
+ configFilePath string
+ expectedObject *statemachine.StateMachineObject
}{
{
- name: "JSON Simple 1",
- configFilePath:
"../../../../../testdata/saga/statelang/simple_statelang_with_choice.json",
- expectedStateMachineObject:
GetStateMachineObject1("json"),
+ name: "JSON Simple 1",
+ configFilePath:
"../../../../../testdata/saga/statelang/simple_statelang_with_choice.json",
+ expectedObject: GetStateMachineObject1("json"),
},
{
- name: "JSON Simple 2",
- configFilePath:
"../../../../../testdata/saga/statelang/simple_statemachine.json",
- expectedStateMachineObject:
GetStateMachineObject2("json"),
+ name: "JSON Simple 2",
+ configFilePath:
"../../../../../testdata/saga/statelang/simple_statemachine.json",
+ expectedObject: GetStateMachineObject2("json"),
},
{
- name: "JSON Simple 3",
- configFilePath:
"../../../../../testdata/saga/statelang/state_machine_new_designer.json",
- expectedStateMachineObject:
GetStateMachineObject3("json"),
+ name: "JSON Simple 3",
+ configFilePath:
"../../../../../testdata/saga/statelang/state_machine_new_designer.json",
+ expectedObject: GetStateMachineObject3("json"),
},
{
- name: "YAML Simple 1",
- configFilePath:
"../../../../../testdata/saga/statelang/simple_statelang_with_choice.yaml",
- expectedStateMachineObject:
GetStateMachineObject1("yaml"),
+ name: "YAML Simple 1",
+ configFilePath:
"../../../../../testdata/saga/statelang/simple_statelang_with_choice.yaml",
+ expectedObject: GetStateMachineObject1("yaml"),
},
{
- name: "YAML Simple 2",
- configFilePath:
"../../../../../testdata/saga/statelang/simple_statemachine.yaml",
- expectedStateMachineObject:
GetStateMachineObject2("yaml"),
+ name: "YAML Simple 2",
+ configFilePath:
"../../../../../testdata/saga/statelang/simple_statemachine.yaml",
+ expectedObject: GetStateMachineObject2("yaml"),
},
{
- name: "YAML Simple 3",
- configFilePath:
"../../../../../testdata/saga/statelang/state_machine_new_designer.yaml",
- expectedStateMachineObject:
GetStateMachineObject3("yaml"),
+ name: "YAML Simple 3",
+ configFilePath:
"../../../../../testdata/saga/statelang/state_machine_new_designer.yaml",
+ expectedObject: GetStateMachineObject3("yaml"),
},
}
@@ -72,18 +73,18 @@ func TestStateMachineConfigParser_Parse(t *testing.T) {
if err != nil {
t.Error("parse fail: " + err.Error())
}
- assert.Equal(t, tt.expectedStateMachineObject, object)
+ assert.Equal(t, tt.expectedObject, object)
})
}
}
-func GetStateMachineObject1(format string) *StateMachineObject {
+func GetStateMachineObject1(format string) *statemachine.StateMachineObject {
switch format {
case "json":
case "yaml":
}
- return &StateMachineObject{
+ return &statemachine.StateMachineObject{
Name: "simpleChoiceTestStateMachine",
Comment: "带条件分支的测试状态机定义",
StartState: "FirstState",
@@ -123,7 +124,7 @@ func GetStateMachineObject1(format string)
*StateMachineObject {
}
}
-func GetStateMachineObject2(format string) *StateMachineObject {
+func GetStateMachineObject2(format string) *statemachine.StateMachineObject {
var retryMap map[string]interface{}
switch format {
@@ -147,7 +148,7 @@ func GetStateMachineObject2(format string)
*StateMachineObject {
}
}
- return &StateMachineObject{
+ return &statemachine.StateMachineObject{
Name: "simpleTestStateMachine",
Comment: "测试状态机定义",
StartState: "FirstState",
@@ -282,7 +283,7 @@ func GetStateMachineObject2(format string)
*StateMachineObject {
}
}
-func GetStateMachineObject3(format string) *StateMachineObject {
+func GetStateMachineObject3(format string) *statemachine.StateMachineObject {
var (
boundsMap1 map[string]interface{}
boundsMap2 map[string]interface{}
@@ -685,7 +686,7 @@ func GetStateMachineObject3(format string)
*StateMachineObject {
}
}
- return &StateMachineObject{
+ return &statemachine.StateMachineObject{
Name: "StateMachineNewDesigner",
Comment: "This state machine is modeled by
designer tools.",
Version: "0.0.1",
diff --git a/pkg/saga/statemachine/statelang/parser/statemachine_json_parser.go
b/pkg/saga/statemachine/statelang/parser/statemachine_json_parser.go
index c3003ddf..37ceaaec 100644
--- a/pkg/saga/statemachine/statelang/parser/statemachine_json_parser.go
+++ b/pkg/saga/statemachine/statelang/parser/statemachine_json_parser.go
@@ -106,14 +106,13 @@ func (stateMachineParser JSONStateMachineParser)
Parse(content string) (statelan
}
func (stateMachineParser JSONStateMachineParser) setForCompensation(stateValue
statelang.State, stateMachine *statelang.StateMachineImpl) {
- switch stateValue.Type() {
- case stateValue.Type():
+ if stateValue.Type() == constant.StateTypeServiceTask {
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)
+ compStateImpl, ok :=
compState.(*state.ServiceTaskStateImpl)
if ok {
compStateImpl.SetForCompensation(true)
}
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 2cc82808..21508755 100644
--- a/pkg/saga/statemachine/statelang/parser/statemachine_json_parser_test.go
+++ b/pkg/saga/statemachine/statelang/parser/statemachine_json_parser_test.go
@@ -18,9 +18,18 @@
package parser
import (
+ "os"
"testing"
)
+func readFileContent(filePath string) (string, error) {
+ content, err := os.ReadFile(filePath)
+ if err != nil {
+ return "", err
+ }
+ return string(content), nil
+}
+
func TestParseChoice(t *testing.T) {
parser := NewJSONStateMachineParser()
@@ -40,7 +49,12 @@ func TestParseChoice(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- _, err := parser.Parse(tt.configFilePath)
+ content, err := readFileContent(tt.configFilePath)
+ if err != nil {
+ t.Error("read file fail: " + err.Error())
+ return
+ }
+ _, err = parser.Parse(content)
if err != nil {
t.Error("parse fail: " + err.Error())
}
@@ -67,7 +81,12 @@ func TestParseServiceTaskForSimpleStateMachine(t *testing.T)
{
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- _, err := parser.Parse(tt.configFilePath)
+ content, err := readFileContent(tt.configFilePath)
+ if err != nil {
+ t.Error("read file fail: " + err.Error())
+ return
+ }
+ _, err = parser.Parse(content)
if err != nil {
t.Error("parse fail: " + err.Error())
}
@@ -94,7 +113,12 @@ func TestParseServiceTaskForNewDesigner(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- _, err := parser.Parse(tt.configFilePath)
+ content, err := readFileContent(tt.configFilePath)
+ if err != nil {
+ t.Error("read file fail: " + err.Error())
+ return
+ }
+ _, err = parser.Parse(content)
if err != nil {
t.Error("parse fail: " + err.Error())
}
diff --git a/pkg/saga/statemachine/statemachine.go
b/pkg/saga/statemachine/statemachine.go
new file mode 100644
index 00000000..087f2123
--- /dev/null
+++ b/pkg/saga/statemachine/statemachine.go
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package statemachine
+
+import (
+ "flag"
+)
+
+type StateMachineObject struct {
+ Name string `json:"Name"
yaml:"Name"`
+ Comment string `json:"Comment"
yaml:"Comment"`
+ Version string `json:"Version"
yaml:"Version"`
+ StartState string `json:"StartState"
yaml:"StartState"`
+ RecoverStrategy string
`json:"RecoverStrategy" yaml:"RecoverStrategy"`
+ Persist bool `json:"IsPersist"
yaml:"IsPersist"`
+ RetryPersistModeUpdate bool
`json:"IsRetryPersistModeUpdate" yaml:"IsRetryPersistModeUpdate"`
+ CompensatePersistModeUpdate bool
`json:"IsCompensatePersistModeUpdate" yaml:"IsCompensatePersistModeUpdate"`
+ Type string `json:"Type"
yaml:"Type"`
+ States map[string]interface{} `json:"States"
yaml:"States"`
+}
+
+func (smo *StateMachineObject) RegisterFlagsWithPrefix(prefix string, f
*flag.FlagSet) {
+ f.StringVar(&smo.Name, prefix+".name", "", "State machine name.")
+ f.StringVar(&smo.Comment, prefix+".comment", "", "State machine
comment.")
+ f.StringVar(&smo.Version, prefix+".version", "1.0", "State machine
version.")
+ f.StringVar(&smo.StartState, prefix+".start-state", "", "State machine
start state.")
+ f.StringVar(&smo.RecoverStrategy, prefix+".recover-strategy", "",
"State machine recovery strategy.")
+ f.BoolVar(&smo.Persist, prefix+".persist", false, "Whether to persist
state machine.")
+ f.BoolVar(&smo.RetryPersistModeUpdate,
prefix+".retry-persist-mode-update", false, "Whether to use update mode for
retry persistence.")
+ f.BoolVar(&smo.CompensatePersistModeUpdate,
prefix+".compensate-persist-mode-update", false, "Whether to use update mode
for compensate persistence.")
+ f.StringVar(&smo.Type, prefix+".type", "", "State machine type.")
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]