| @@ -0,0 +1,31 @@ | |||
| # seata saga | |||
| 未来计划有三种使用方式 | |||
| - 基于状态机引擎的 json | |||
| link: statemachine_engine#Start | |||
| - stream builder | |||
| stateMachine.serviceTask().build().Start | |||
| - 二阶段方式saga,类似tcc使用 | |||
| 上面1、2是以来[statemachine](statemachine),状态机引擎实现的,3相对比较独立。 | |||
| 状态机的实现在:saga-statemachine包中 | |||
| 其中[statelang](statemachine%2Fstatelang)是状态机语言的解析,目前实现的是json解析方式,状态机语言可以参考: | |||
| https://seata.io/docs/user/mode/saga | |||
| 状态机json执行的入口类是:[statemachine_engine.go](statemachine%2Fengine%2Fstatemachine_engine.go) | |||
| 下面简单说下engine中各个包的作用: | |||
| events:saga的是基于事件处理的,其中是event、eventBus的实现 | |||
| expr:表达式声明、解析、执行 | |||
| invoker:声明了serviceInvoker、scriptInvoker等接口、task调用管理、执行都在这个包中,例如httpInvoker | |||
| process_ctrl:状态机处理流程:上下文、执行、事件流转 | |||
| sequence:分布式id | |||
| store:状态机存储接口、实现 | |||
| status_decision:状态机状态决策 | |||
| @@ -0,0 +1,20 @@ | |||
| package engine | |||
| 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" | |||
| ) | |||
| @@ -0,0 +1,4 @@ | |||
| package events | |||
| type Event interface { | |||
| } | |||
| @@ -0,0 +1,49 @@ | |||
| package events | |||
| import ( | |||
| "context" | |||
| ) | |||
| type EventBus interface { | |||
| Offer(ctx context.Context, event Event) (bool, error) | |||
| RegisterEventConsumer(consumer EventConsumer) | |||
| } | |||
| type BaseEventBus struct { | |||
| eventConsumerList []EventConsumer | |||
| } | |||
| func (b *BaseEventBus) RegisterEventConsumer(consumer EventConsumer) { | |||
| if b.eventConsumerList == nil { | |||
| b.eventConsumerList = make([]EventConsumer, 0) | |||
| } | |||
| b.eventConsumerList = append(b.eventConsumerList, consumer) | |||
| } | |||
| func (b *BaseEventBus) GetEventConsumerList(event Event) []EventConsumer { | |||
| var acceptedConsumerList = make([]EventConsumer, 0) | |||
| for i := range b.eventConsumerList { | |||
| eventConsumer := b.eventConsumerList[i] | |||
| if eventConsumer.Accept(event) { | |||
| acceptedConsumerList = append(acceptedConsumerList, eventConsumer) | |||
| } | |||
| } | |||
| return acceptedConsumerList | |||
| } | |||
| type DirectEventBus struct { | |||
| BaseEventBus | |||
| } | |||
| func (d DirectEventBus) Offer(ctx context.Context, event Event) (bool, error) { | |||
| eventConsumerList := d.GetEventConsumerList(event) | |||
| if len(eventConsumerList) == 0 { | |||
| //TODO logger | |||
| return false, nil | |||
| } | |||
| //processContext, _ := event.(process_ctrl.ProcessContext) | |||
| return true, nil | |||
| } | |||
| @@ -0,0 +1,29 @@ | |||
| package events | |||
| import ( | |||
| "context" | |||
| "github.com/seata/seata-go/pkg/saga/statemachine/engine/process_ctrl" | |||
| ) | |||
| type EventConsumer interface { | |||
| Accept(event Event) bool | |||
| Process(ctx context.Context, event Event) error | |||
| } | |||
| type ProcessCtrlEventConsumer struct { | |||
| } | |||
| func (p ProcessCtrlEventConsumer) Accept(event Event) bool { | |||
| if event == nil { | |||
| return false | |||
| } | |||
| _, ok := event.(process_ctrl.ProcessContext) | |||
| return ok | |||
| } | |||
| func (p ProcessCtrlEventConsumer) Process(ctx context.Context, event Event) error { | |||
| //TODO implement me | |||
| panic("implement me") | |||
| } | |||
| @@ -0,0 +1,19 @@ | |||
| package events | |||
| import "context" | |||
| type EventPublisher interface { | |||
| PushEvent(ctx context.Context, event Event) (bool, error) | |||
| } | |||
| type ProcessCtrlEventPublisher struct { | |||
| eventBus EventBus | |||
| } | |||
| func NewProcessCtrlEventPublisher(eventBus EventBus) *ProcessCtrlEventPublisher { | |||
| return &ProcessCtrlEventPublisher{eventBus: eventBus} | |||
| } | |||
| func (p ProcessCtrlEventPublisher) PushEvent(ctx context.Context, event Event) (bool, error) { | |||
| return p.eventBus.Offer(ctx, event) | |||
| } | |||
| @@ -0,0 +1,10 @@ | |||
| package expr | |||
| type ExpressionResolver interface { | |||
| } | |||
| type Expression interface { | |||
| } | |||
| type ExpressionFactoryManager struct { | |||
| } | |||
| @@ -0,0 +1,14 @@ | |||
| package invoker | |||
| type ScriptInvokerManager interface { | |||
| } | |||
| type ScriptInvoker interface { | |||
| } | |||
| type ServiceInvokerManager interface { | |||
| } | |||
| type ServiceInvoker interface { | |||
| Invoke() | |||
| } | |||
| @@ -0,0 +1,92 @@ | |||
| package process_ctrl | |||
| import ( | |||
| "context" | |||
| "github.com/pkg/errors" | |||
| "github.com/seata/seata-go/pkg/saga/statemachine/engine" | |||
| "sync" | |||
| ) | |||
| type BusinessProcessor interface { | |||
| Process(ctx context.Context, processContext ProcessContext) error | |||
| Route(ctx context.Context, processContext ProcessContext) error | |||
| } | |||
| type DefaultBusinessProcessor struct { | |||
| processHandlers map[string]ProcessHandler | |||
| routerHandlers map[string]RouterHandler | |||
| mu sync.RWMutex | |||
| } | |||
| func (d *DefaultBusinessProcessor) RegistryProcessHandler(processType ProcessType, processHandler ProcessHandler) { | |||
| d.mu.Lock() | |||
| defer d.mu.Unlock() | |||
| d.processHandlers[string(processType)] = processHandler | |||
| } | |||
| func (d *DefaultBusinessProcessor) RegistryRouterHandler(processType ProcessType, routerHandler RouterHandler) { | |||
| d.mu.Lock() | |||
| defer d.mu.Unlock() | |||
| d.routerHandlers[string(processType)] = routerHandler | |||
| } | |||
| func (d *DefaultBusinessProcessor) Process(ctx context.Context, processContext ProcessContext) error { | |||
| processType := d.matchProcessType(processContext) | |||
| processHandler, err := d.getProcessHandler(processType) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| return processHandler.Process(ctx, processContext) | |||
| } | |||
| func (d *DefaultBusinessProcessor) Route(ctx context.Context, processContext ProcessContext) error { | |||
| processType := d.matchProcessType(processContext) | |||
| routerHandler, err := d.getRouterHandler(processType) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| return routerHandler.Route(ctx, processContext) | |||
| } | |||
| func (d *DefaultBusinessProcessor) getProcessHandler(processType ProcessType) (ProcessHandler, error) { | |||
| d.mu.RLock() | |||
| defer d.mu.RUnlock() | |||
| processHandler, ok := d.processHandlers[string(processType)] | |||
| if !ok { | |||
| return nil, errors.New("Cannot find process handler by type " + string(processType)) | |||
| } | |||
| return processHandler, nil | |||
| } | |||
| func (d *DefaultBusinessProcessor) getRouterHandler(processType ProcessType) (RouterHandler, error) { | |||
| d.mu.RLock() | |||
| defer d.mu.RUnlock() | |||
| routerHandler, ok := d.routerHandlers[string(processType)] | |||
| if !ok { | |||
| return nil, errors.New("Cannot find router handler by type " + string(processType)) | |||
| } | |||
| return routerHandler, nil | |||
| } | |||
| func (d *DefaultBusinessProcessor) matchProcessType(processContext ProcessContext) ProcessType { | |||
| ok := processContext.HasVariable(engine.VarNameProcessType) | |||
| if ok { | |||
| return processContext.GetVariable(engine.VarNameProcessType).(ProcessType) | |||
| } | |||
| return StateLang | |||
| } | |||
| type ProcessHandler interface { | |||
| Process(ctx context.Context, processContext ProcessContext) error | |||
| } | |||
| type RouterHandler interface { | |||
| Route(ctx context.Context, processContext ProcessContext) error | |||
| } | |||
| @@ -0,0 +1,25 @@ | |||
| package instruction | |||
| import ( | |||
| "github.com/seata/seata-go/pkg/saga/statemachine/engine/process_ctrl" | |||
| "github.com/seata/seata-go/pkg/saga/statemachine/statelang" | |||
| ) | |||
| type Instruction interface { | |||
| } | |||
| type StateInstruction struct { | |||
| StateName string | |||
| StateMachineName string | |||
| TenantId string | |||
| End bool | |||
| } | |||
| func (s StateInstruction) GetState(context process_ctrl.ProcessContext) (statelang.State, error) { | |||
| //TODO implement me | |||
| panic("implement me") | |||
| } | |||
| func NewStateInstruction(stateMachineName string, tenantId string) *StateInstruction { | |||
| return &StateInstruction{StateMachineName: stateMachineName, TenantId: tenantId} | |||
| } | |||
| @@ -0,0 +1,284 @@ | |||
| package process_ctrl | |||
| import ( | |||
| "github.com/seata/seata-go/pkg/saga/statemachine/engine" | |||
| "github.com/seata/seata-go/pkg/saga/statemachine/engine/process_ctrl/instruction" | |||
| "github.com/seata/seata-go/pkg/saga/statemachine/statelang" | |||
| "sync" | |||
| ) | |||
| type ProcessContext interface { | |||
| GetVariable(name string) interface{} | |||
| SetVariable(name string, value interface{}) | |||
| GetVariables() map[string]interface{} | |||
| SetVariables(variables map[string]interface{}) | |||
| RemoveVariable(name string) interface{} | |||
| HasVariable(name string) bool | |||
| GetInstruction() instruction.Instruction | |||
| SetInstruction(instruction instruction.Instruction) | |||
| } | |||
| type HierarchicalProcessContext interface { | |||
| ProcessContext | |||
| GetVariableLocally(name string) interface{} | |||
| SetVariableLocally(name string, value interface{}) | |||
| GetVariablesLocally() map[string]interface{} | |||
| SetVariablesLocally(variables map[string]interface{}) | |||
| RemoveVariableLocally(name string) interface{} | |||
| HasVariableLocally(name string) bool | |||
| ClearLocally() | |||
| } | |||
| type ProcessContextImpl struct { | |||
| parent ProcessContext | |||
| mu sync.RWMutex | |||
| mp map[string]interface{} | |||
| instruction instruction.Instruction | |||
| } | |||
| func (p *ProcessContextImpl) GetVariable(name string) interface{} { | |||
| p.mu.RLock() | |||
| defer p.mu.RUnlock() | |||
| value, ok := p.mp[name] | |||
| if ok { | |||
| return value | |||
| } | |||
| if p.parent != nil { | |||
| return p.parent.GetVariable(name) | |||
| } | |||
| return nil | |||
| } | |||
| func (p *ProcessContextImpl) SetVariable(name string, value interface{}) { | |||
| p.mu.Lock() | |||
| defer p.mu.Unlock() | |||
| _, ok := p.mp[name] | |||
| if ok { | |||
| p.mp[name] = value | |||
| } else { | |||
| if p.parent != nil { | |||
| p.parent.SetVariable(name, value) | |||
| } else { | |||
| p.mp[name] = value | |||
| } | |||
| } | |||
| } | |||
| func (p *ProcessContextImpl) GetVariables() map[string]interface{} { | |||
| p.mu.RLock() | |||
| defer p.mu.RUnlock() | |||
| newVariablesMap := make(map[string]interface{}) | |||
| if p.parent != nil { | |||
| variables := p.parent.GetVariables() | |||
| for k, v := range variables { | |||
| newVariablesMap[k] = v | |||
| } | |||
| } | |||
| for k, v := range p.mp { | |||
| newVariablesMap[k] = v | |||
| } | |||
| return newVariablesMap | |||
| } | |||
| func (p *ProcessContextImpl) SetVariables(variables map[string]interface{}) { | |||
| for k, v := range variables { | |||
| p.SetVariable(k, v) | |||
| } | |||
| } | |||
| func (p *ProcessContextImpl) RemoveVariable(name string) interface{} { | |||
| p.mu.Lock() | |||
| defer p.mu.Unlock() | |||
| value, ok := p.mp[name] | |||
| if ok { | |||
| delete(p.mp, name) | |||
| return value | |||
| } | |||
| if p.parent != nil { | |||
| return p.parent.RemoveVariable(name) | |||
| } | |||
| return nil | |||
| } | |||
| func (p *ProcessContextImpl) HasVariable(name string) bool { | |||
| p.mu.RLock() | |||
| defer p.mu.RUnlock() | |||
| _, ok := p.mp[name] | |||
| if ok { | |||
| return true | |||
| } | |||
| if p.parent != nil { | |||
| return p.parent.HasVariable(name) | |||
| } | |||
| return false | |||
| } | |||
| func (p *ProcessContextImpl) GetInstruction() instruction.Instruction { | |||
| return p.instruction | |||
| } | |||
| func (p *ProcessContextImpl) SetInstruction(instruction instruction.Instruction) { | |||
| p.instruction = instruction | |||
| } | |||
| func (p *ProcessContextImpl) GetVariableLocally(name string) interface{} { | |||
| p.mu.RLock() | |||
| defer p.mu.RUnlock() | |||
| value, _ := p.mp[name] | |||
| return value | |||
| } | |||
| func (p *ProcessContextImpl) SetVariableLocally(name string, value interface{}) { | |||
| p.mu.Lock() | |||
| defer p.mu.Unlock() | |||
| p.mp[name] = value | |||
| } | |||
| func (p *ProcessContextImpl) GetVariablesLocally() map[string]interface{} { | |||
| p.mu.RLock() | |||
| defer p.mu.RUnlock() | |||
| newVariablesMap := make(map[string]interface{}, len(p.mp)) | |||
| for k, v := range p.mp { | |||
| newVariablesMap[k] = v | |||
| } | |||
| return newVariablesMap | |||
| } | |||
| func (p *ProcessContextImpl) SetVariablesLocally(variables map[string]interface{}) { | |||
| for k, v := range variables { | |||
| p.SetVariableLocally(k, v) | |||
| } | |||
| } | |||
| func (p *ProcessContextImpl) RemoveVariableLocally(name string) interface{} { | |||
| p.mu.Lock() | |||
| defer p.mu.Unlock() | |||
| value, _ := p.mp[name] | |||
| delete(p.mp, name) | |||
| return value | |||
| } | |||
| func (p *ProcessContextImpl) HasVariableLocally(name string) bool { | |||
| p.mu.RLock() | |||
| defer p.mu.RUnlock() | |||
| _, ok := p.mp[name] | |||
| return ok | |||
| } | |||
| func (p *ProcessContextImpl) ClearLocally() { | |||
| p.mu.Lock() | |||
| defer p.mu.Unlock() | |||
| p.mp = map[string]interface{}{} | |||
| } | |||
| // ProcessContextBuilder process_ctrl builder | |||
| type ProcessContextBuilder struct { | |||
| processContext ProcessContext | |||
| } | |||
| func NewProcessContextBuilder() *ProcessContextBuilder { | |||
| processContextImpl := &ProcessContextImpl{} | |||
| return &ProcessContextBuilder{processContextImpl} | |||
| } | |||
| func (p *ProcessContextBuilder) WithProcessType(processType ProcessType) *ProcessContextBuilder { | |||
| p.processContext.SetVariable(engine.VarNameProcessType, processType) | |||
| return p | |||
| } | |||
| func (p *ProcessContextBuilder) WithOperationName(operationName string) *ProcessContextBuilder { | |||
| p.processContext.SetVariable(engine.VarNameOperationName, operationName) | |||
| return p | |||
| } | |||
| func (p *ProcessContextBuilder) WithAsyncCallback(callBack engine.CallBack) *ProcessContextBuilder { | |||
| if callBack != nil { | |||
| p.processContext.SetVariable(engine.VarNameAsyncCallback, callBack) | |||
| } | |||
| return p | |||
| } | |||
| func (p *ProcessContextBuilder) WithInstruction(instruction instruction.Instruction) *ProcessContextBuilder { | |||
| if instruction != nil { | |||
| p.processContext.SetInstruction(instruction) | |||
| } | |||
| return p | |||
| } | |||
| func (p *ProcessContextBuilder) WithStateMachineInstance(stateMachineInstance statelang.StateMachineInstance) *ProcessContextBuilder { | |||
| if stateMachineInstance != nil { | |||
| p.processContext.SetVariable(engine.VarNameStateMachineInst, stateMachineInstance) | |||
| p.processContext.SetVariable(engine.VarNameStateMachine, stateMachineInstance.StateMachine()) | |||
| } | |||
| return p | |||
| } | |||
| func (p *ProcessContextBuilder) WithStateMachineEngine(stateMachineEngine engine.StateMachineEngine) *ProcessContextBuilder { | |||
| if stateMachineEngine != nil { | |||
| p.processContext.SetVariable(engine.VarNameStateMachineEngine, stateMachineEngine) | |||
| } | |||
| return p | |||
| } | |||
| func (p *ProcessContextBuilder) WithStateMachineConfig(stateMachineConfig engine.StateMachineConfig) *ProcessContextBuilder { | |||
| if stateMachineConfig != nil { | |||
| p.processContext.SetVariable(engine.VarNameStateMachineConfig, stateMachineConfig) | |||
| } | |||
| return p | |||
| } | |||
| func (p *ProcessContextBuilder) WithStateMachineContextVariables(contextMap map[string]interface{}) *ProcessContextBuilder { | |||
| if contextMap != nil { | |||
| p.processContext.SetVariable(engine.VarNameStateMachineContext, contextMap) | |||
| } | |||
| return p | |||
| } | |||
| func (p *ProcessContextBuilder) WithIsAsyncExecution(async bool) *ProcessContextBuilder { | |||
| p.processContext.SetVariable(engine.VarNameIsAsyncExecution, async) | |||
| return p | |||
| } | |||
| func (p *ProcessContextBuilder) Build() ProcessContext { | |||
| return p.processContext | |||
| } | |||
| @@ -0,0 +1,7 @@ | |||
| package process_ctrl | |||
| type ProcessType string | |||
| const ( | |||
| StateLang ProcessType = "STATE_LANG" // SEATA State Language | |||
| ) | |||
| @@ -0,0 +1,139 @@ | |||
| package process_ctrl | |||
| import ( | |||
| "context" | |||
| "github.com/pkg/errors" | |||
| "github.com/seata/seata-go/pkg/saga/statemachine/engine/process_ctrl/instruction" | |||
| "sync" | |||
| ) | |||
| type StateHandler interface { | |||
| State() string | |||
| ProcessHandler | |||
| } | |||
| type StateRouter interface { | |||
| State() string | |||
| RouterHandler | |||
| } | |||
| type InterceptAbleStateHandler interface { | |||
| StateHandler | |||
| StateHandlerInterceptorList() []StateHandlerInterceptor | |||
| RegistryStateHandlerInterceptor(stateHandlerInterceptor StateHandlerInterceptor) | |||
| } | |||
| type StateHandlerInterceptor interface { | |||
| PreProcess(ctx context.Context, processContext ProcessContext) error | |||
| PostProcess(ctx context.Context, processContext ProcessContext) error | |||
| } | |||
| type StateMachineProcessHandler struct { | |||
| mp map[string]StateHandler | |||
| mu sync.RWMutex | |||
| } | |||
| func NewStateMachineProcessHandler() *StateMachineProcessHandler { | |||
| return &StateMachineProcessHandler{ | |||
| mp: make(map[string]StateHandler), | |||
| } | |||
| } | |||
| func (s *StateMachineProcessHandler) Process(ctx context.Context, processContext ProcessContext) error { | |||
| stateInstruction, _ := processContext.GetInstruction().(instruction.StateInstruction) | |||
| state, err := stateInstruction.GetState(processContext) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| stateType := state.Type() | |||
| stateHandler := s.GetStateHandler(stateType) | |||
| if stateHandler == nil { | |||
| return errors.New("Not support [" + stateType + "] state handler") | |||
| } | |||
| interceptAbleStateHandler, ok := stateHandler.(InterceptAbleStateHandler) | |||
| var stateHandlerInterceptorList []StateHandlerInterceptor | |||
| if ok { | |||
| stateHandlerInterceptorList = interceptAbleStateHandler.StateHandlerInterceptorList() | |||
| } | |||
| if stateHandlerInterceptorList != nil && len(stateHandlerInterceptorList) > 0 { | |||
| for _, stateHandlerInterceptor := range stateHandlerInterceptorList { | |||
| err = stateHandlerInterceptor.PreProcess(ctx, processContext) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| } | |||
| } | |||
| err = stateHandler.Process(ctx, processContext) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| if stateHandlerInterceptorList != nil && len(stateHandlerInterceptorList) > 0 { | |||
| for _, stateHandlerInterceptor := range stateHandlerInterceptorList { | |||
| err = stateHandlerInterceptor.PostProcess(ctx, processContext) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| } | |||
| } | |||
| return nil | |||
| } | |||
| func (s *StateMachineProcessHandler) GetStateHandler(stateType string) StateHandler { | |||
| s.mu.RLock() | |||
| defer s.mu.RUnlock() | |||
| return s.mp[stateType] | |||
| } | |||
| func (s *StateMachineProcessHandler) RegistryStateHandler(stateType string, stateHandler StateHandler) { | |||
| s.mu.Lock() | |||
| defer s.mu.Unlock() | |||
| if s.mp == nil { | |||
| s.mp = make(map[string]StateHandler) | |||
| } | |||
| s.mp[stateType] = stateHandler | |||
| } | |||
| type StateMachineRouterHandler struct { | |||
| mu sync.RWMutex | |||
| mp map[string]StateRouter | |||
| } | |||
| func (s *StateMachineRouterHandler) Route(ctx context.Context, processContext ProcessContext) error { | |||
| stateInstruction, _ := processContext.GetInstruction().(instruction.StateInstruction) | |||
| state, err := stateInstruction.GetState(processContext) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| stateType := state.Type() | |||
| stateRouter := s.GetStateRouter(stateType) | |||
| if stateRouter == nil { | |||
| return errors.New("Not support [" + stateType + "] state router") | |||
| } | |||
| return stateRouter.Route(ctx, processContext) | |||
| } | |||
| func (s *StateMachineRouterHandler) GetStateRouter(stateType string) StateRouter { | |||
| s.mu.RLock() | |||
| defer s.mu.RUnlock() | |||
| return s.mp[stateType] | |||
| } | |||
| func (s *StateMachineRouterHandler) RegistryStateRouter(stateType string, stateRouter StateRouter) { | |||
| s.mu.Lock() | |||
| defer s.mu.Unlock() | |||
| if s.mp == nil { | |||
| s.mp = make(map[string]StateRouter) | |||
| } | |||
| s.mp[stateType] = stateRouter | |||
| } | |||
| @@ -0,0 +1,124 @@ | |||
| package engine | |||
| import ( | |||
| "context" | |||
| "github.com/pkg/errors" | |||
| "github.com/seata/seata-go/pkg/saga/statemachine/engine/events" | |||
| "github.com/seata/seata-go/pkg/saga/statemachine/engine/process_ctrl" | |||
| "github.com/seata/seata-go/pkg/saga/statemachine/engine/process_ctrl/instruction" | |||
| "github.com/seata/seata-go/pkg/saga/statemachine/statelang" | |||
| "time" | |||
| ) | |||
| type ProcessCtrlStateMachineEngine struct { | |||
| StateMachineConfig StateMachineConfig | |||
| } | |||
| func (p ProcessCtrlStateMachineEngine) Start(ctx context.Context, stateMachineName string, tenantId string, startParams map[string]interface{}) (statelang.StateMachineInstance, error) { | |||
| return p.startInternal(ctx, stateMachineName, tenantId, "", startParams, false, nil) | |||
| } | |||
| func (p ProcessCtrlStateMachineEngine) startInternal(ctx context.Context, stateMachineName string, tenantId string, businessKey string, startParams map[string]interface{}, async bool, callback CallBack) (statelang.StateMachineInstance, error) { | |||
| if tenantId == "" { | |||
| tenantId = p.StateMachineConfig.DefaultTenantId() | |||
| } | |||
| stateMachineInstance, err := p.createMachineInstance(stateMachineName, tenantId, businessKey, startParams) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| // Build the process_ctrl context. | |||
| processContextBuilder := process_ctrl.NewProcessContextBuilder(). | |||
| WithProcessType(process_ctrl.StateLang). | |||
| WithOperationName(OperationNameStart). | |||
| WithAsyncCallback(callback). | |||
| WithInstruction(instruction.NewStateInstruction(stateMachineName, tenantId)). | |||
| WithStateMachineInstance(stateMachineInstance). | |||
| WithStateMachineConfig(p.StateMachineConfig). | |||
| WithStateMachineEngine(p). | |||
| WithIsAsyncExecution(async) | |||
| contextMap := p.copyMap(startParams) | |||
| stateMachineInstance.SetContext(contextMap) | |||
| processContext := processContextBuilder.WithStateMachineContextVariables(contextMap).Build() | |||
| if stateMachineInstance.StateMachine().IsPersist() && p.StateMachineConfig.StateLogStore() != nil { | |||
| err := p.StateMachineConfig.StateLogStore().RecordStateMachineStarted(ctx, stateMachineInstance, processContext) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| } | |||
| if stateMachineInstance.ID() == "" { | |||
| stateMachineInstance.SetID(p.StateMachineConfig.SeqGenerator().GenerateId(SeqEntityStateMachineInst, "")) | |||
| } | |||
| var eventPublisher events.EventPublisher | |||
| if async { | |||
| eventPublisher = p.StateMachineConfig.AsyncEventPublisher() | |||
| } else { | |||
| eventPublisher = p.StateMachineConfig.EventPublisher() | |||
| } | |||
| _, err = eventPublisher.PushEvent(ctx, processContext) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| return stateMachineInstance, nil | |||
| } | |||
| // copyMap not deep copy, so best practice: Don’t pass by reference | |||
| func (p ProcessCtrlStateMachineEngine) copyMap(startParams map[string]interface{}) map[string]interface{} { | |||
| copyMap := make(map[string]interface{}, len(startParams)) | |||
| for k, v := range startParams { | |||
| copyMap[k] = v | |||
| } | |||
| return copyMap | |||
| } | |||
| func (p ProcessCtrlStateMachineEngine) createMachineInstance(stateMachineName string, tenantId string, businessKey string, startParams map[string]interface{}) (statelang.StateMachineInstance, error) { | |||
| stateMachine, err := p.StateMachineConfig.StateMachineRepository().GetLastVersionStateMachine(stateMachineName, tenantId) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| if stateMachine == nil { | |||
| return nil, errors.New("StateMachine [" + stateMachineName + "] is not exists") | |||
| } | |||
| stateMachineInstance := statelang.NewStateMachineInstanceImpl() | |||
| stateMachineInstance.SetStateMachine(stateMachine) | |||
| stateMachineInstance.SetTenantID(tenantId) | |||
| stateMachineInstance.SetBusinessKey(businessKey) | |||
| stateMachineInstance.SetStartParams(startParams) | |||
| if startParams != nil { | |||
| if businessKey != "" { | |||
| startParams[VarNameBusinesskey] = businessKey | |||
| } | |||
| if startParams[VarNameParentId] != nil { | |||
| parentId, ok := startParams[VarNameParentId].(string) | |||
| if !ok { | |||
| } | |||
| stateMachineInstance.SetParentID(parentId) | |||
| delete(startParams, VarNameParentId) | |||
| } | |||
| } | |||
| stateMachineInstance.SetStatus(statelang.RU) | |||
| stateMachineInstance.SetRunning(true) | |||
| now := time.Now() | |||
| stateMachineInstance.SetStartedTime(now) | |||
| stateMachineInstance.SetUpdatedTime(now) | |||
| return stateMachineInstance, nil | |||
| } | |||
| func NewProcessCtrlStateMachineEngine(stateMachineConfig StateMachineConfig) *ProcessCtrlStateMachineEngine { | |||
| return &ProcessCtrlStateMachineEngine{StateMachineConfig: stateMachineConfig} | |||
| } | |||
| @@ -0,0 +1,5 @@ | |||
| package sequence | |||
| type SeqGenerator interface { | |||
| GenerateId(entity string, ruleName string) string | |||
| } | |||
| @@ -0,0 +1,14 @@ | |||
| package sequence | |||
| import "github.com/google/uuid" | |||
| type UUIDSeqGenerator struct { | |||
| } | |||
| func NewUUIDSeqGenerator() *UUIDSeqGenerator { | |||
| return &UUIDSeqGenerator{} | |||
| } | |||
| func (U UUIDSeqGenerator) GenerateId(entity string, ruleName string) string { | |||
| return uuid.New().String() | |||
| } | |||
| @@ -0,0 +1,44 @@ | |||
| package engine | |||
| import ( | |||
| "github.com/seata/seata-go/pkg/saga/statemachine/engine/events" | |||
| "github.com/seata/seata-go/pkg/saga/statemachine/engine/expr" | |||
| "github.com/seata/seata-go/pkg/saga/statemachine/engine/invoker" | |||
| "github.com/seata/seata-go/pkg/saga/statemachine/engine/sequence" | |||
| "github.com/seata/seata-go/pkg/saga/statemachine/engine/status_decision" | |||
| "github.com/seata/seata-go/pkg/saga/statemachine/engine/store" | |||
| ) | |||
| type StateMachineConfig interface { | |||
| StateLogRepository() store.StateLogRepository | |||
| StateMachineRepository() store.StateMachineRepository | |||
| StateLogStore() store.StateLogStore | |||
| StateLangStore() store.StateLangStore | |||
| ExpressionFactoryManager() expr.ExpressionFactoryManager | |||
| ExpressionResolver() expr.ExpressionResolver | |||
| SeqGenerator() sequence.SeqGenerator | |||
| StatusDecisionStrategy() status_decision.StatusDecisionStrategy | |||
| EventPublisher() events.EventPublisher | |||
| AsyncEventPublisher() events.EventPublisher | |||
| ServiceInvokerManager() invoker.ServiceInvokerManager | |||
| ScriptInvokerManager() invoker.ScriptInvokerManager | |||
| CharSet() string | |||
| DefaultTenantId() string | |||
| TransOperationTimeout() int | |||
| ServiceInvokeTimeout() int | |||
| } | |||
| @@ -0,0 +1,16 @@ | |||
| package engine | |||
| import ( | |||
| "context" | |||
| "github.com/seata/seata-go/pkg/saga/statemachine/engine/process_ctrl" | |||
| "github.com/seata/seata-go/pkg/saga/statemachine/statelang" | |||
| ) | |||
| type StateMachineEngine interface { | |||
| Start(ctx context.Context, stateMachineName string, tenantId string, startParams map[string]interface{}) (statelang.StateMachineInstance, error) | |||
| } | |||
| type CallBack interface { | |||
| OnFinished(ctx context.Context, context process_ctrl.ProcessContext, stateMachineInstance statelang.StateMachineInstance) | |||
| OnError(ctx context.Context, context process_ctrl.ProcessContext, stateMachineInstance statelang.StateMachineInstance, err error) | |||
| } | |||
| @@ -0,0 +1,4 @@ | |||
| package status_decision | |||
| type StatusDecisionStrategy interface { | |||
| } | |||
| @@ -0,0 +1,60 @@ | |||
| package store | |||
| import ( | |||
| "context" | |||
| "github.com/seata/seata-go/pkg/saga/statemachine/engine/process_ctrl" | |||
| "github.com/seata/seata-go/pkg/saga/statemachine/statelang" | |||
| "io" | |||
| ) | |||
| type StateLogRepository interface { | |||
| GetStateMachineInstance(stateMachineInstanceId string) (statelang.StateInstance, error) | |||
| GetStateMachineInstanceByBusinessKey(businessKey string, tenantId string) (statelang.StateInstance, error) | |||
| GetStateMachineInstanceByParentId(parentId string) ([]statelang.StateMachineInstance, error) | |||
| GetStateInstance(stateInstanceId string, stateMachineInstanceId string) (statelang.StateInstance, error) | |||
| GetStateInstanceListByMachineInstanceId(stateMachineInstanceId string) ([]statelang.StateInstance, error) | |||
| } | |||
| type StateLogStore interface { | |||
| RecordStateMachineStarted(ctx context.Context, machineInstance statelang.StateMachineInstance, context process_ctrl.ProcessContext) error | |||
| RecordStateMachineFinished(ctx context.Context, machineInstance statelang.StateMachineInstance, context process_ctrl.ProcessContext) error | |||
| RecordStateMachineRestarted(ctx context.Context, machineInstance statelang.StateMachineInstance, context process_ctrl.ProcessContext) error | |||
| RecordStateStarted(ctx context.Context, stateInstance statelang.StateInstance, context process_ctrl.ProcessContext) error | |||
| RecordStateFinished(ctx context.Context, stateInstance statelang.StateInstance, context process_ctrl.ProcessContext) error | |||
| GetStateMachineInstance(stateMachineInstanceId string) (statelang.StateInstance, error) | |||
| GetStateMachineInstanceByBusinessKey(businessKey string, tenantId string) (statelang.StateInstance, error) | |||
| GetStateMachineInstanceByParentId(parentId string) ([]statelang.StateMachineInstance, error) | |||
| GetStateInstance(stateInstanceId string, stateMachineInstanceId string) (statelang.StateInstance, error) | |||
| GetStateInstanceListByMachineInstanceId(stateMachineInstanceId string) ([]statelang.StateInstance, error) | |||
| } | |||
| type StateMachineRepository interface { | |||
| GetStateMachineById(stateMachineId string) (statelang.StateMachine, error) | |||
| GetLastVersionStateMachine(stateMachineName string, tenantId string) (statelang.StateMachine, error) | |||
| RegistryStateMachine(statelang.StateMachine) error | |||
| RegistryStateMachineByReader(reader io.Reader) error | |||
| } | |||
| type StateLangStore interface { | |||
| GetStateMachineById(stateMachineId string) (statelang.StateMachine, error) | |||
| GetLastVersionStateMachine(stateMachineName string, tenantId string) (statelang.StateMachine, error) | |||
| StoreStateMachine(stateMachine statelang.StateMachine) error | |||
| } | |||
| @@ -0,0 +1,72 @@ | |||
| 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/statelang" | |||
| "github.com/seata/seata-go/pkg/saga/statemachine/statelang/state" | |||
| ) | |||
| type ChoiceStateParser struct { | |||
| BaseStateParser | |||
| } | |||
| func NewChoiceStateParser() *ChoiceStateParser { | |||
| return &ChoiceStateParser{} | |||
| } | |||
| func (c ChoiceStateParser) StateType() string { | |||
| return engine.StateTypeChoice | |||
| } | |||
| func (c ChoiceStateParser) Parse(stateName string, stateMap map[string]interface{}) (statelang.State, error) { | |||
| choiceState := state.NewChoiceStateImpl() | |||
| choiceState.SetName(stateName) | |||
| //parse Type | |||
| typeName, err := c.GetString(stateName, stateMap, "Type") | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| choiceState.SetType(typeName) | |||
| //parse Default | |||
| defaultChoice, err := c.GetString(stateName, stateMap, "Default") | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| choiceState.SetDefault(defaultChoice) | |||
| //parse Choices | |||
| slice, err := c.GetSlice(stateName, stateMap, "Choices") | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| var choices []state.Choice | |||
| for i := range slice { | |||
| choiceValMap, ok := slice[i].(map[string]interface{}) | |||
| if !ok { | |||
| return nil, errors.New(fmt.Sprintf("State [%s] Choices element required struct", stateName)) | |||
| } | |||
| choice := state.NewChoiceImpl() | |||
| expression, err := c.GetString(stateName, choiceValMap, "Expression") | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| choice.SetExpression(expression) | |||
| next, err := c.GetString(stateName, choiceValMap, "Next") | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| choice.SetNext(next) | |||
| choices = append(choices, choice) | |||
| } | |||
| choiceState.SetChoices(choices) | |||
| return choiceState, nil | |||
| } | |||
| @@ -0,0 +1,100 @@ | |||
| package parser | |||
| import ( | |||
| "encoding/json" | |||
| "github.com/pkg/errors" | |||
| "github.com/seata/seata-go/pkg/saga/statemachine/statelang" | |||
| ) | |||
| type JSONStateMachineParser struct { | |||
| } | |||
| func NewJSONStateMachineParser() *JSONStateMachineParser { | |||
| return &JSONStateMachineParser{} | |||
| } | |||
| func (stateMachineParser JSONStateMachineParser) GetType() string { | |||
| return "JSON" | |||
| } | |||
| func (stateMachineParser JSONStateMachineParser) Parse(content string) (statelang.StateMachine, error) { | |||
| var stateMachineJsonObject StateMachineJsonObject | |||
| err := json.Unmarshal([]byte(content), &stateMachineJsonObject) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| stateMachine := statelang.NewStateMachineImpl() | |||
| stateMachine.SetName(stateMachineJsonObject.Name) | |||
| stateMachine.SetComment(stateMachineJsonObject.Comment) | |||
| stateMachine.SetVersion(stateMachineJsonObject.Version) | |||
| stateMachine.SetStartState(stateMachineJsonObject.StartState) | |||
| stateMachine.SetPersist(stateMachineJsonObject.Persist) | |||
| if stateMachineJsonObject.Type != "" { | |||
| stateMachine.SetType(stateMachineJsonObject.Type) | |||
| } | |||
| if stateMachineJsonObject.RecoverStrategy != "" { | |||
| recoverStrategy, ok := statelang.ValueOfRecoverStrategy(stateMachineJsonObject.RecoverStrategy) | |||
| if !ok { | |||
| return nil, errors.New("Not support " + stateMachineJsonObject.RecoverStrategy) | |||
| } | |||
| stateMachine.SetRecoverStrategy(recoverStrategy) | |||
| } | |||
| stateParserFactory := NewDefaultStateParserFactory() | |||
| stateParserFactory.InitDefaultStateParser() | |||
| for stateName, v := range stateMachineJsonObject.States { | |||
| stateMap, ok := v.(map[string]interface{}) | |||
| if !ok { | |||
| return nil, errors.New("State [" + stateName + "] scheme illegal, required map") | |||
| } | |||
| stateType, ok := stateMap["Type"].(string) | |||
| if !ok { | |||
| return nil, errors.New("State [" + stateName + "] Type illegal, required string") | |||
| } | |||
| //stateMap | |||
| stateParser := stateParserFactory.GetStateParser(stateType) | |||
| if stateParser == nil { | |||
| return nil, errors.New("State Type [" + stateType + "] is not support") | |||
| } | |||
| _, stateExist := stateMachine.States()[stateName] | |||
| if stateExist { | |||
| return nil, errors.New("State [name:" + stateName + "] already exists") | |||
| } | |||
| state, err := stateParser.Parse(stateName, stateMap) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| state.SetStateMachine(stateMachine) | |||
| stateMachine.States()[stateName] = state | |||
| } | |||
| //TODO setCompensateState | |||
| //for stateName, state := range stateMachine.GetStates() { | |||
| // | |||
| //} | |||
| // | |||
| return stateMachine, nil | |||
| } | |||
| type StateMachineJsonObject struct { | |||
| Name string `json:"Name"` | |||
| Comment string `json:"Comment"` | |||
| Version string `json:"Version"` | |||
| StartState string `json:"StartState"` | |||
| RecoverStrategy string `json:"RecoverStrategy"` | |||
| Persist bool `json:"IsPersist"` | |||
| RetryPersistModeUpdate bool `json:"IsRetryPersistModeUpdate"` | |||
| CompensatePersistModeUpdate bool `json:"IsCompensatePersistModeUpdate"` | |||
| Type string `json:"Type"` | |||
| States map[string]interface{} `json:"States"` | |||
| } | |||
| @@ -0,0 +1,13 @@ | |||
| package parser | |||
| import ( | |||
| "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) | |||
| if err != nil { | |||
| t.Error("parse fail: " + err.Error()) | |||
| } | |||
| } | |||
| @@ -0,0 +1,104 @@ | |||
| package parser | |||
| import ( | |||
| "github.com/pkg/errors" | |||
| "github.com/seata/seata-go/pkg/saga/statemachine/statelang" | |||
| "sync" | |||
| ) | |||
| type StateMachineParser interface { | |||
| GetType() string | |||
| Parse(content string) (statelang.StateMachine, error) | |||
| } | |||
| type StateParser interface { | |||
| StateType() string | |||
| Parse(stateName string, stateMap map[string]interface{}) (statelang.State, error) | |||
| } | |||
| type BaseStateParser struct { | |||
| } | |||
| func (b BaseStateParser) ParseBaseAttributes(stateName string, state statelang.State, stateMap map[string]interface{}) error { | |||
| state.SetName(stateName) | |||
| comment, err := b.GetString(stateName, stateMap, "Comment") | |||
| if err != nil { | |||
| return err | |||
| } | |||
| state.SetComment(comment) | |||
| next, err := b.GetString(stateName, stateMap, "Next") | |||
| if err != nil { | |||
| return err | |||
| } | |||
| state.SetNext(next) | |||
| return nil | |||
| } | |||
| func (b BaseStateParser) GetString(stateName string, stateMap map[string]interface{}, key string) (string, error) { | |||
| value := stateMap[key] | |||
| if value == nil { | |||
| var result string | |||
| return result, errors.New("State [" + stateName + "] " + key + " not exist") | |||
| } | |||
| valueAsString, ok := value.(string) | |||
| if !ok { | |||
| var s string | |||
| return s, 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") | |||
| } | |||
| valueAsSlice, ok := value.([]interface{}) | |||
| if !ok { | |||
| var result []interface{} | |||
| return result, errors.New("State [" + stateName + "] " + key + " illegal, required slice") | |||
| } | |||
| return valueAsSlice, nil | |||
| } | |||
| type StateParserFactory interface { | |||
| RegistryStateParser(stateType string, stateParser StateParser) | |||
| GetStateParser(stateType string) StateParser | |||
| } | |||
| type DefaultStateParserFactory struct { | |||
| stateParserMap map[string]StateParser | |||
| mutex sync.Mutex | |||
| } | |||
| func NewDefaultStateParserFactory() *DefaultStateParserFactory { | |||
| var stateParserMap map[string]StateParser = make(map[string]StateParser) | |||
| return &DefaultStateParserFactory{ | |||
| stateParserMap: stateParserMap, | |||
| } | |||
| } | |||
| // InitDefaultStateParser init StateParser by default | |||
| func (d *DefaultStateParserFactory) InitDefaultStateParser() { | |||
| choiceStateParser := NewChoiceStateParser() | |||
| d.RegistryStateParser(choiceStateParser.StateType(), choiceStateParser) | |||
| } | |||
| func (d *DefaultStateParserFactory) RegistryStateParser(stateType string, stateParser StateParser) { | |||
| d.mutex.Lock() | |||
| defer d.mutex.Unlock() | |||
| d.stateParserMap[stateType] = stateParser | |||
| } | |||
| func (d *DefaultStateParserFactory) GetStateParser(stateType string) StateParser { | |||
| return d.stateParserMap[stateType] | |||
| } | |||
| @@ -0,0 +1,71 @@ | |||
| package statelang | |||
| type State interface { | |||
| Name() string | |||
| SetName(name string) | |||
| Comment() string | |||
| SetComment(comment string) | |||
| Type() string | |||
| SetType(typeName string) | |||
| Next() string | |||
| SetNext(next string) | |||
| StateMachine() StateMachine | |||
| SetStateMachine(machine StateMachine) | |||
| } | |||
| type BaseState struct { | |||
| name string `alias:"Name"` | |||
| comment string `alias:"Comment"` | |||
| typeName string `alias:"Type"` | |||
| next string `alias:"Next"` | |||
| stateMachine StateMachine | |||
| } | |||
| func (b *BaseState) Name() string { | |||
| return b.name | |||
| } | |||
| func (b *BaseState) SetName(name string) { | |||
| b.name = name | |||
| } | |||
| func (b *BaseState) Comment() string { | |||
| return b.comment | |||
| } | |||
| func (b *BaseState) SetComment(comment string) { | |||
| b.comment = comment | |||
| } | |||
| func (b *BaseState) Type() string { | |||
| return b.typeName | |||
| } | |||
| func (b *BaseState) SetType(typeName string) { | |||
| b.typeName = typeName | |||
| } | |||
| func (b *BaseState) Next() string { | |||
| return b.next | |||
| } | |||
| func (b *BaseState) SetNext(next string) { | |||
| b.next = next | |||
| } | |||
| func (b *BaseState) StateMachine() StateMachine { | |||
| return b.stateMachine | |||
| } | |||
| func (b *BaseState) SetStateMachine(machine StateMachine) { | |||
| b.stateMachine = machine | |||
| } | |||
| @@ -0,0 +1,74 @@ | |||
| package state | |||
| import "github.com/seata/seata-go/pkg/saga/statemachine/statelang" | |||
| type ChoiceState interface { | |||
| statelang.State | |||
| Choices() []Choice | |||
| Default() string | |||
| } | |||
| type Choice interface { | |||
| Expression() string | |||
| SetExpression(expression string) | |||
| Next() string | |||
| SetNext(next string) | |||
| } | |||
| type ChoiceStateImpl struct { | |||
| statelang.BaseState | |||
| defaultChoice string `alias:"Default"` | |||
| choices []Choice `alias:"Choices"` | |||
| } | |||
| func NewChoiceStateImpl() *ChoiceStateImpl { | |||
| return &ChoiceStateImpl{ | |||
| choices: make([]Choice, 0), | |||
| } | |||
| } | |||
| func (choiceState *ChoiceStateImpl) Default() string { | |||
| return choiceState.defaultChoice | |||
| } | |||
| func (choiceState *ChoiceStateImpl) Choices() []Choice { | |||
| return choiceState.choices | |||
| } | |||
| func (choiceState *ChoiceStateImpl) SetDefault(defaultChoice string) { | |||
| choiceState.defaultChoice = defaultChoice | |||
| } | |||
| func (choiceState *ChoiceStateImpl) SetChoices(choices []Choice) { | |||
| choiceState.choices = choices | |||
| } | |||
| type ChoiceImpl struct { | |||
| expression string | |||
| next string | |||
| } | |||
| func NewChoiceImpl() *ChoiceImpl { | |||
| return &ChoiceImpl{} | |||
| } | |||
| func (c *ChoiceImpl) Expression() string { | |||
| return c.expression | |||
| } | |||
| func (c *ChoiceImpl) SetExpression(expression string) { | |||
| c.expression = expression | |||
| } | |||
| func (c *ChoiceImpl) Next() string { | |||
| return c.next | |||
| } | |||
| func (c *ChoiceImpl) SetNext(next string) { | |||
| c.next = next | |||
| } | |||
| @@ -0,0 +1,30 @@ | |||
| package state | |||
| import ( | |||
| "github.com/seata/seata-go/pkg/saga/statemachine/statelang" | |||
| ) | |||
| type TaskState interface { | |||
| statelang.State | |||
| CompensateState() string | |||
| Status() map[string]string | |||
| Retry() []Retry | |||
| } | |||
| type Retry interface { | |||
| ErrorTypeNames() []string | |||
| IntervalSecond() float64 | |||
| MaxAttempt() int | |||
| BackoffRate() float64 | |||
| } | |||
| type ServiceTaskState interface { | |||
| TaskState | |||
| //TODO add serviceTask | |||
| } | |||
| @@ -0,0 +1,330 @@ | |||
| package statelang | |||
| import "time" | |||
| type StateInstance interface { | |||
| ID() string | |||
| SetID(id string) | |||
| Name() string | |||
| SetName(name string) | |||
| Type() string | |||
| SetType(typeName string) | |||
| ServiceName() string | |||
| SetServiceName(serviceName string) | |||
| ServiceMethod() string | |||
| SetServiceMethod(serviceMethod string) | |||
| ServiceType() string | |||
| SetServiceType(serviceType string) | |||
| BusinessKey() string | |||
| SetBusinessKey(businessKey string) | |||
| StartedTime() time.Time | |||
| SetStartedTime(startedTime time.Time) | |||
| UpdatedTime() time.Time | |||
| SetUpdatedTime(updateTime time.Time) | |||
| EndTime() time.Time | |||
| SetEndTime(endTime time.Time) | |||
| IsForUpdate() bool | |||
| SetForUpdate(forUpdate bool) | |||
| Error() error | |||
| SetError(err error) | |||
| InputParams() interface{} | |||
| SetInputParams(inputParams interface{}) | |||
| OutputParams() interface{} | |||
| SetOutputParams(outputParams interface{}) | |||
| Status() ExecutionStatus | |||
| SetStatus(status ExecutionStatus) | |||
| StateIDCompensatedFor() string | |||
| SetStateIDCompensatedFor(stateIdCompensatedFor string) | |||
| StateIDRetriedFor() string | |||
| SetStateIDRetriedFor(stateIdRetriedFor string) | |||
| CompensationState() StateInstance | |||
| SetCompensationState(compensationState StateInstance) | |||
| StateMachineInstance() StateMachineInstance | |||
| SetStateMachineInstance(stateMachineInstance StateMachineInstance) | |||
| IsIgnoreStatus() bool | |||
| SetIgnoreStatus(ignoreStatus bool) | |||
| IsForCompensation() bool | |||
| SerializedInputParams() interface{} | |||
| SetSerializedInputParams(serializedInputParams interface{}) | |||
| SerializedOutputParams() interface{} | |||
| SetSerializedOutputParams(serializedOutputParams interface{}) | |||
| SerializedError() interface{} | |||
| SetSerializedError(serializedErr interface{}) | |||
| CompensationStatus() ExecutionStatus | |||
| } | |||
| type StateInstanceImpl struct { | |||
| id string | |||
| machineInstanceId string | |||
| name string | |||
| typeName string | |||
| serviceName string | |||
| serviceMethod string | |||
| serviceType string | |||
| businessKey string | |||
| startedTime time.Time | |||
| updatedTime time.Time | |||
| endTime time.Time | |||
| isForUpdate bool | |||
| err error | |||
| serializedErr interface{} | |||
| inputParams interface{} | |||
| serializedInputParams interface{} | |||
| outputParams interface{} | |||
| serializedOutputParams interface{} | |||
| status ExecutionStatus | |||
| stateIdCompensatedFor string | |||
| stateIdRetriedFor string | |||
| compensationState StateInstance | |||
| stateMachineInstance StateMachineInstance | |||
| ignoreStatus bool | |||
| } | |||
| func NewStateInstanceImpl() *StateInstanceImpl { | |||
| return &StateInstanceImpl{} | |||
| } | |||
| func (s *StateInstanceImpl) ID() string { | |||
| return s.id | |||
| } | |||
| func (s *StateInstanceImpl) SetID(id string) { | |||
| s.id = id | |||
| } | |||
| func (s *StateInstanceImpl) Name() string { | |||
| return s.name | |||
| } | |||
| func (s *StateInstanceImpl) SetName(name string) { | |||
| s.name = name | |||
| } | |||
| func (s *StateInstanceImpl) Type() string { | |||
| return s.typeName | |||
| } | |||
| func (s *StateInstanceImpl) SetType(typeName string) { | |||
| s.typeName = typeName | |||
| } | |||
| func (s *StateInstanceImpl) ServiceName() string { | |||
| return s.serviceName | |||
| } | |||
| func (s *StateInstanceImpl) SetServiceName(serviceName string) { | |||
| s.serviceName = serviceName | |||
| } | |||
| func (s *StateInstanceImpl) ServiceMethod() string { | |||
| return s.serviceMethod | |||
| } | |||
| func (s *StateInstanceImpl) SetServiceMethod(serviceMethod string) { | |||
| s.serviceMethod = serviceMethod | |||
| } | |||
| func (s *StateInstanceImpl) ServiceType() string { | |||
| return s.serviceType | |||
| } | |||
| func (s *StateInstanceImpl) SetServiceType(serviceType string) { | |||
| s.serviceType = serviceType | |||
| } | |||
| func (s *StateInstanceImpl) BusinessKey() string { | |||
| return s.businessKey | |||
| } | |||
| func (s *StateInstanceImpl) SetBusinessKey(businessKey string) { | |||
| s.businessKey = businessKey | |||
| } | |||
| func (s *StateInstanceImpl) StartedTime() time.Time { | |||
| return s.startedTime | |||
| } | |||
| func (s *StateInstanceImpl) SetStartedTime(startedTime time.Time) { | |||
| s.startedTime = startedTime | |||
| } | |||
| func (s *StateInstanceImpl) UpdatedTime() time.Time { | |||
| return s.updatedTime | |||
| } | |||
| func (s *StateInstanceImpl) SetUpdatedTime(updatedTime time.Time) { | |||
| s.updatedTime = updatedTime | |||
| } | |||
| func (s *StateInstanceImpl) EndTime() time.Time { | |||
| return s.endTime | |||
| } | |||
| func (s *StateInstanceImpl) SetEndTime(endTime time.Time) { | |||
| s.endTime = endTime | |||
| } | |||
| func (s *StateInstanceImpl) IsForUpdate() bool { | |||
| return s.isForUpdate | |||
| } | |||
| func (s *StateInstanceImpl) SetForUpdate(forUpdate bool) { | |||
| s.isForUpdate = forUpdate | |||
| } | |||
| func (s *StateInstanceImpl) Error() error { | |||
| return s.err | |||
| } | |||
| func (s *StateInstanceImpl) SetError(err error) { | |||
| s.err = err | |||
| } | |||
| func (s *StateInstanceImpl) InputParams() interface{} { | |||
| return s.inputParams | |||
| } | |||
| func (s *StateInstanceImpl) SetInputParams(inputParams interface{}) { | |||
| s.inputParams = inputParams | |||
| } | |||
| func (s *StateInstanceImpl) OutputParams() interface{} { | |||
| return s.outputParams | |||
| } | |||
| func (s *StateInstanceImpl) SetOutputParams(outputParams interface{}) { | |||
| s.outputParams = outputParams | |||
| } | |||
| func (s *StateInstanceImpl) Status() ExecutionStatus { | |||
| return s.status | |||
| } | |||
| func (s *StateInstanceImpl) SetStatus(status ExecutionStatus) { | |||
| s.status = status | |||
| } | |||
| func (s *StateInstanceImpl) StateIDCompensatedFor() string { | |||
| return s.stateIdCompensatedFor | |||
| } | |||
| func (s *StateInstanceImpl) SetStateIDCompensatedFor(stateIdCompensatedFor string) { | |||
| s.stateIdCompensatedFor = stateIdCompensatedFor | |||
| } | |||
| func (s *StateInstanceImpl) StateIDRetriedFor() string { | |||
| return s.stateIdRetriedFor | |||
| } | |||
| func (s *StateInstanceImpl) SetStateIDRetriedFor(stateIdRetriedFor string) { | |||
| s.stateIdRetriedFor = stateIdRetriedFor | |||
| } | |||
| func (s *StateInstanceImpl) CompensationState() StateInstance { | |||
| return s.compensationState | |||
| } | |||
| func (s *StateInstanceImpl) SetCompensationState(compensationState StateInstance) { | |||
| s.compensationState = compensationState | |||
| } | |||
| func (s *StateInstanceImpl) StateMachineInstance() StateMachineInstance { | |||
| return s.stateMachineInstance | |||
| } | |||
| func (s *StateInstanceImpl) SetStateMachineInstance(stateMachineInstance StateMachineInstance) { | |||
| s.stateMachineInstance = stateMachineInstance | |||
| } | |||
| func (s *StateInstanceImpl) IsIgnoreStatus() bool { | |||
| return s.ignoreStatus | |||
| } | |||
| func (s *StateInstanceImpl) SetIgnoreStatus(ignoreStatus bool) { | |||
| s.ignoreStatus = ignoreStatus | |||
| } | |||
| func (s *StateInstanceImpl) IsForCompensation() bool { | |||
| return s.stateIdCompensatedFor == "" | |||
| } | |||
| func (s *StateInstanceImpl) SerializedInputParams() interface{} { | |||
| return s.serializedInputParams | |||
| } | |||
| func (s *StateInstanceImpl) SetSerializedInputParams(serializedInputParams interface{}) { | |||
| s.serializedInputParams = serializedInputParams | |||
| } | |||
| func (s *StateInstanceImpl) SerializedOutputParams() interface{} { | |||
| return s.serializedOutputParams | |||
| } | |||
| func (s *StateInstanceImpl) SetSerializedOutputParams(serializedOutputParams interface{}) { | |||
| s.serializedOutputParams = serializedOutputParams | |||
| } | |||
| func (s *StateInstanceImpl) SerializedError() interface{} { | |||
| return s.serializedErr | |||
| } | |||
| func (s *StateInstanceImpl) SetSerializedError(serializedErr interface{}) { | |||
| s.serializedErr = serializedErr | |||
| } | |||
| func (s *StateInstanceImpl) CompensationStatus() ExecutionStatus { | |||
| if s.compensationState != nil { | |||
| return s.compensationState.Status() | |||
| } | |||
| //return nil ExecutionStatus | |||
| var status ExecutionStatus | |||
| return status | |||
| } | |||
| @@ -0,0 +1,261 @@ | |||
| package statelang | |||
| import ( | |||
| "time" | |||
| ) | |||
| type StateMachineStatus string | |||
| const ( | |||
| Active StateMachineStatus = "Active" | |||
| Inactive StateMachineStatus = "Inactive" | |||
| ) | |||
| // RecoverStrategy : Recover Strategy | |||
| type RecoverStrategy string | |||
| const ( | |||
| //Compensate stateMachine | |||
| Compensate RecoverStrategy = "Compensate" | |||
| // Forward stateMachine | |||
| Forward RecoverStrategy = "Forward" | |||
| ) | |||
| func ValueOfRecoverStrategy(recoverStrategy string) (RecoverStrategy, bool) { | |||
| switch recoverStrategy { | |||
| case "Compensate": | |||
| return Compensate, true | |||
| case "Forward": | |||
| return Forward, true | |||
| default: | |||
| var recoverStrategy RecoverStrategy | |||
| return recoverStrategy, false | |||
| } | |||
| } | |||
| type StateMachine interface { | |||
| ID() string | |||
| SetID(id string) | |||
| Name() string | |||
| SetName(name string) | |||
| Comment() string | |||
| SetComment(comment string) | |||
| StartState() string | |||
| SetStartState(startState string) | |||
| Version() string | |||
| SetVersion(version string) | |||
| States() map[string]State | |||
| State(stateName string) State | |||
| TenantId() string | |||
| SetTenantId(tenantId string) | |||
| AppName() string | |||
| SetAppName(appName string) | |||
| Type() string | |||
| SetType(typeName string) | |||
| Status() StateMachineStatus | |||
| SetStatus(status StateMachineStatus) | |||
| RecoverStrategy() RecoverStrategy | |||
| SetRecoverStrategy(recoverStrategy RecoverStrategy) | |||
| IsPersist() bool | |||
| SetPersist(persist bool) | |||
| IsRetryPersistModeUpdate() bool | |||
| SetRetryPersistModeUpdate(retryPersistModeUpdate bool) | |||
| IsCompensatePersistModeUpdate() bool | |||
| SetCompensatePersistModeUpdate(compensatePersistModeUpdate bool) | |||
| Content() string | |||
| SetContent(content string) | |||
| CreateTime() time.Time | |||
| SetCreateTime(createTime time.Time) | |||
| } | |||
| type StateMachineImpl struct { | |||
| id string | |||
| tenantId string | |||
| appName string | |||
| name string | |||
| comment string | |||
| version string | |||
| startState string | |||
| status StateMachineStatus | |||
| recoverStrategy RecoverStrategy | |||
| persist bool | |||
| retryPersistModeUpdate bool | |||
| compensatePersistModeUpdate bool | |||
| typeName string | |||
| content string | |||
| createTime time.Time | |||
| states map[string]State | |||
| } | |||
| func NewStateMachineImpl() *StateMachineImpl { | |||
| stateMap := make(map[string]State) | |||
| return &StateMachineImpl{ | |||
| appName: "SEATA", | |||
| status: Active, | |||
| typeName: "STATE_LANG", | |||
| states: stateMap, | |||
| } | |||
| } | |||
| func (s *StateMachineImpl) ID() string { | |||
| return s.id | |||
| } | |||
| func (s *StateMachineImpl) SetID(id string) { | |||
| s.id = id | |||
| } | |||
| func (s *StateMachineImpl) Name() string { | |||
| return s.name | |||
| } | |||
| func (s *StateMachineImpl) SetName(name string) { | |||
| s.name = name | |||
| } | |||
| func (s *StateMachineImpl) SetComment(comment string) { | |||
| s.comment = comment | |||
| } | |||
| func (s *StateMachineImpl) Comment() string { | |||
| return s.comment | |||
| } | |||
| func (s *StateMachineImpl) StartState() string { | |||
| return s.startState | |||
| } | |||
| func (s *StateMachineImpl) SetStartState(startState string) { | |||
| s.startState = startState | |||
| } | |||
| func (s *StateMachineImpl) Version() string { | |||
| return s.version | |||
| } | |||
| func (s *StateMachineImpl) SetVersion(version string) { | |||
| s.version = version | |||
| } | |||
| func (s *StateMachineImpl) States() map[string]State { | |||
| return s.states | |||
| } | |||
| func (s *StateMachineImpl) State(stateName string) State { | |||
| if s.states == nil { | |||
| return nil | |||
| } | |||
| return s.states[stateName] | |||
| } | |||
| func (s *StateMachineImpl) TenantId() string { | |||
| return s.tenantId | |||
| } | |||
| func (s *StateMachineImpl) SetTenantId(tenantId string) { | |||
| s.tenantId = tenantId | |||
| } | |||
| func (s *StateMachineImpl) AppName() string { | |||
| return s.appName | |||
| } | |||
| func (s *StateMachineImpl) SetAppName(appName string) { | |||
| s.appName = appName | |||
| } | |||
| func (s *StateMachineImpl) Type() string { | |||
| return s.typeName | |||
| } | |||
| func (s *StateMachineImpl) SetType(typeName string) { | |||
| s.typeName = typeName | |||
| } | |||
| func (s *StateMachineImpl) Status() StateMachineStatus { | |||
| return s.status | |||
| } | |||
| func (s *StateMachineImpl) SetStatus(status StateMachineStatus) { | |||
| s.status = status | |||
| } | |||
| func (s *StateMachineImpl) RecoverStrategy() RecoverStrategy { | |||
| return s.recoverStrategy | |||
| } | |||
| func (s *StateMachineImpl) SetRecoverStrategy(recoverStrategy RecoverStrategy) { | |||
| s.recoverStrategy = recoverStrategy | |||
| } | |||
| func (s *StateMachineImpl) IsPersist() bool { | |||
| return s.persist | |||
| } | |||
| func (s *StateMachineImpl) SetPersist(persist bool) { | |||
| s.persist = persist | |||
| } | |||
| func (s *StateMachineImpl) IsRetryPersistModeUpdate() bool { | |||
| return s.retryPersistModeUpdate | |||
| } | |||
| func (s *StateMachineImpl) SetRetryPersistModeUpdate(retryPersistModeUpdate bool) { | |||
| s.retryPersistModeUpdate = retryPersistModeUpdate | |||
| } | |||
| func (s *StateMachineImpl) IsCompensatePersistModeUpdate() bool { | |||
| return s.compensatePersistModeUpdate | |||
| } | |||
| func (s *StateMachineImpl) SetCompensatePersistModeUpdate(compensatePersistModeUpdate bool) { | |||
| s.compensatePersistModeUpdate = compensatePersistModeUpdate | |||
| } | |||
| func (s *StateMachineImpl) Content() string { | |||
| return s.content | |||
| } | |||
| func (s *StateMachineImpl) SetContent(content string) { | |||
| s.content = content | |||
| } | |||
| func (s *StateMachineImpl) CreateTime() time.Time { | |||
| return s.createTime | |||
| } | |||
| func (s *StateMachineImpl) SetCreateTime(createTime time.Time) { | |||
| s.createTime = createTime | |||
| } | |||
| @@ -0,0 +1,316 @@ | |||
| package statelang | |||
| import ( | |||
| "sync" | |||
| "time" | |||
| ) | |||
| type ExecutionStatus string | |||
| const ( | |||
| // RU Running | |||
| RU ExecutionStatus = "RU" | |||
| // SU Succeed | |||
| SU ExecutionStatus = "SU" | |||
| // FA Failed | |||
| FA ExecutionStatus = "FA" | |||
| // UN Unknown | |||
| UN ExecutionStatus = "UN" | |||
| // SK Skipped | |||
| SK ExecutionStatus = "SK" | |||
| ) | |||
| type StateMachineInstance interface { | |||
| ID() string | |||
| SetID(id string) | |||
| MachineID() string | |||
| SetMachineID(machineID string) | |||
| TenantID() string | |||
| SetTenantID(tenantID string) | |||
| ParentID() string | |||
| SetParentID(parentID string) | |||
| StartedTime() time.Time | |||
| SetStartedTime(startedTime time.Time) | |||
| EndTime() time.Time | |||
| SetEndTime(endTime time.Time) | |||
| StateList() []StateInstance | |||
| State(stateId string) StateInstance | |||
| PutState(stateId string, stateInstance StateInstance) | |||
| Status() ExecutionStatus | |||
| SetStatus(status ExecutionStatus) | |||
| CompensationStatus() ExecutionStatus | |||
| SetCompensationStatus(compensationStatus ExecutionStatus) | |||
| IsRunning() bool | |||
| SetRunning(isRunning bool) | |||
| UpdatedTime() time.Time | |||
| SetUpdatedTime(updatedTime time.Time) | |||
| BusinessKey() string | |||
| SetBusinessKey(businessKey string) | |||
| Error() error | |||
| SetError(err error) | |||
| StartParams() map[string]interface{} | |||
| SetStartParams(startParams map[string]interface{}) | |||
| EndParams() map[string]interface{} | |||
| SetEndParams(endParams map[string]interface{}) | |||
| PutContext(key string, value interface{}) | |||
| SetContext(context map[string]interface{}) | |||
| StateMachine() StateMachine | |||
| SetStateMachine(stateMachine StateMachine) | |||
| SerializedStartParams() interface{} | |||
| SetSerializedStartParams(serializedStartParams interface{}) | |||
| SerializedEndParams() interface{} | |||
| SetSerializedEndParams(serializedEndParams interface{}) | |||
| SerializedError() interface{} | |||
| SetSerializedError(serializedError interface{}) | |||
| } | |||
| type StateMachineInstanceImpl struct { | |||
| id string | |||
| machineId string | |||
| tenantId string | |||
| parentId string | |||
| businessKey string | |||
| startParams map[string]interface{} | |||
| serializedStartParams interface{} | |||
| startedTime time.Time | |||
| endTime time.Time | |||
| updatedTime time.Time | |||
| err error | |||
| serializedError interface{} | |||
| endParams map[string]interface{} | |||
| serializedEndParams interface{} | |||
| status ExecutionStatus | |||
| compensationStatus ExecutionStatus | |||
| isRunning bool | |||
| context map[string]interface{} | |||
| stateMachine StateMachine | |||
| stateList []StateInstance | |||
| stateMap map[string]StateInstance | |||
| contextMutex sync.RWMutex // Mutex to protect concurrent access to context | |||
| stateMutex sync.RWMutex // Mutex to protect concurrent access to stateList and stateMap | |||
| } | |||
| func NewStateMachineInstanceImpl() *StateMachineInstanceImpl { | |||
| return &StateMachineInstanceImpl{ | |||
| startParams: make(map[string]interface{}), | |||
| endParams: make(map[string]interface{}), | |||
| stateList: make([]StateInstance, 0), | |||
| stateMap: make(map[string]StateInstance)} | |||
| } | |||
| func (s *StateMachineInstanceImpl) ID() string { | |||
| return s.id | |||
| } | |||
| func (s *StateMachineInstanceImpl) SetID(id string) { | |||
| s.id = id | |||
| } | |||
| func (s *StateMachineInstanceImpl) MachineID() string { | |||
| return s.machineId | |||
| } | |||
| func (s *StateMachineInstanceImpl) SetMachineID(machineID string) { | |||
| s.machineId = machineID | |||
| } | |||
| func (s *StateMachineInstanceImpl) TenantID() string { | |||
| return s.tenantId | |||
| } | |||
| func (s *StateMachineInstanceImpl) SetTenantID(tenantID string) { | |||
| s.tenantId = tenantID | |||
| } | |||
| func (s *StateMachineInstanceImpl) ParentID() string { | |||
| return s.parentId | |||
| } | |||
| func (s *StateMachineInstanceImpl) SetParentID(parentID string) { | |||
| s.parentId = parentID | |||
| } | |||
| func (s *StateMachineInstanceImpl) StartedTime() time.Time { | |||
| return s.startedTime | |||
| } | |||
| func (s *StateMachineInstanceImpl) SetStartedTime(startedTime time.Time) { | |||
| s.startedTime = startedTime | |||
| } | |||
| func (s *StateMachineInstanceImpl) EndTime() time.Time { | |||
| return s.endTime | |||
| } | |||
| func (s *StateMachineInstanceImpl) SetEndTime(endTime time.Time) { | |||
| s.endTime = endTime | |||
| } | |||
| func (s *StateMachineInstanceImpl) StateList() []StateInstance { | |||
| return s.stateList | |||
| } | |||
| func (s *StateMachineInstanceImpl) State(stateId string) StateInstance { | |||
| s.stateMutex.RLock() | |||
| defer s.stateMutex.RUnlock() | |||
| return s.stateMap[stateId] | |||
| } | |||
| func (s *StateMachineInstanceImpl) PutState(stateId string, stateInstance StateInstance) { | |||
| s.stateMutex.Lock() | |||
| defer s.stateMutex.Unlock() | |||
| stateInstance.SetStateMachineInstance(s) | |||
| s.stateMap[stateId] = stateInstance | |||
| s.stateList = append(s.stateList, stateInstance) | |||
| } | |||
| func (s *StateMachineInstanceImpl) Status() ExecutionStatus { | |||
| return s.status | |||
| } | |||
| func (s *StateMachineInstanceImpl) SetStatus(status ExecutionStatus) { | |||
| s.status = status | |||
| } | |||
| func (s *StateMachineInstanceImpl) CompensationStatus() ExecutionStatus { | |||
| return s.compensationStatus | |||
| } | |||
| func (s *StateMachineInstanceImpl) SetCompensationStatus(compensationStatus ExecutionStatus) { | |||
| s.compensationStatus = compensationStatus | |||
| } | |||
| func (s *StateMachineInstanceImpl) IsRunning() bool { | |||
| return s.isRunning | |||
| } | |||
| func (s *StateMachineInstanceImpl) SetRunning(isRunning bool) { | |||
| s.isRunning = isRunning | |||
| } | |||
| func (s *StateMachineInstanceImpl) UpdatedTime() time.Time { | |||
| return s.updatedTime | |||
| } | |||
| func (s *StateMachineInstanceImpl) SetUpdatedTime(updatedTime time.Time) { | |||
| s.updatedTime = updatedTime | |||
| } | |||
| func (s *StateMachineInstanceImpl) BusinessKey() string { | |||
| return s.businessKey | |||
| } | |||
| func (s *StateMachineInstanceImpl) SetBusinessKey(businessKey string) { | |||
| s.businessKey = businessKey | |||
| } | |||
| func (s *StateMachineInstanceImpl) Error() error { | |||
| return s.err | |||
| } | |||
| func (s *StateMachineInstanceImpl) SetError(err error) { | |||
| s.err = err | |||
| } | |||
| func (s *StateMachineInstanceImpl) StartParams() map[string]interface{} { | |||
| return s.startParams | |||
| } | |||
| func (s *StateMachineInstanceImpl) SetStartParams(startParams map[string]interface{}) { | |||
| s.startParams = startParams | |||
| } | |||
| func (s *StateMachineInstanceImpl) EndParams() map[string]interface{} { | |||
| return s.endParams | |||
| } | |||
| func (s *StateMachineInstanceImpl) SetEndParams(endParams map[string]interface{}) { | |||
| s.endParams = endParams | |||
| } | |||
| func (s *StateMachineInstanceImpl) PutContext(key string, value interface{}) { | |||
| s.contextMutex.Lock() | |||
| defer s.contextMutex.Unlock() | |||
| s.context[key] = value | |||
| } | |||
| func (s *StateMachineInstanceImpl) SetContext(context map[string]interface{}) { | |||
| s.context = context | |||
| } | |||
| func (s *StateMachineInstanceImpl) StateMachine() StateMachine { | |||
| return s.stateMachine | |||
| } | |||
| func (s *StateMachineInstanceImpl) SetStateMachine(stateMachine StateMachine) { | |||
| s.stateMachine = stateMachine | |||
| s.machineId = stateMachine.ID() | |||
| } | |||
| func (s *StateMachineInstanceImpl) SerializedStartParams() interface{} { | |||
| return s.serializedStartParams | |||
| } | |||
| func (s *StateMachineInstanceImpl) SetSerializedStartParams(serializedStartParams interface{}) { | |||
| s.serializedStartParams = serializedStartParams | |||
| } | |||
| func (s *StateMachineInstanceImpl) SerializedEndParams() interface{} { | |||
| return s.endParams | |||
| } | |||
| func (s *StateMachineInstanceImpl) SetSerializedEndParams(serializedEndParams interface{}) { | |||
| s.serializedEndParams = serializedEndParams | |||
| } | |||
| func (s *StateMachineInstanceImpl) SerializedError() interface{} { | |||
| return s.serializedError | |||
| } | |||
| func (s *StateMachineInstanceImpl) SetSerializedError(serializedError interface{}) { | |||
| s.serializedError = serializedError | |||
| } | |||
| @@ -0,0 +1,147 @@ | |||
| package reflectx | |||
| import ( | |||
| "fmt" | |||
| "github.com/pkg/errors" | |||
| "reflect" | |||
| "unicode" | |||
| ) | |||
| // MapToStruct some state can use this util to parse | |||
| // TODO 性能测试,性能差的话,直接去解析,不使用反射 | |||
| func MapToStruct(stateName string, obj interface{}, stateMap map[string]interface{}) error { | |||
| objVal := reflect.ValueOf(obj) | |||
| if objVal.Kind() != reflect.Pointer { | |||
| return errors.New(fmt.Sprintf("State [%s] value required a pointer", stateName)) | |||
| } | |||
| structValue := objVal.Elem() | |||
| if structValue.Kind() != reflect.Struct { | |||
| return errors.New(fmt.Sprintf("State [%s] value elem required a struct", stateName)) | |||
| } | |||
| structType := structValue.Type() | |||
| for key, value := range stateMap { | |||
| //Get field, get alias first | |||
| field, found := getField(structType, key) | |||
| if !found { | |||
| continue | |||
| } | |||
| fieldVal := structValue.FieldByName(field.Name) | |||
| if !fieldVal.IsValid() { | |||
| return errors.New(fmt.Sprintf("State [%s] not support [%s] filed", stateName, key)) | |||
| } | |||
| //Get setMethod | |||
| var setMethod reflect.Value | |||
| if !fieldVal.CanSet() { | |||
| setMethod = getFiledSetMethod(field.Name, objVal) | |||
| if !setMethod.IsValid() { | |||
| fieldAliasName := field.Tag.Get("alias") | |||
| setMethod = getFiledSetMethod(fieldAliasName, objVal) | |||
| } | |||
| if !setMethod.IsValid() { | |||
| return errors.New(fmt.Sprintf("State [%s] [%s] field not support setMethod", stateName, key)) | |||
| } | |||
| setMethodType := setMethod.Type() | |||
| if !(setMethodType.NumIn() == 1 && setMethodType.In(0) == fieldVal.Type()) { | |||
| return errors.New(fmt.Sprintf("State [%s] [%s] field setMethod illegal", stateName, key)) | |||
| } | |||
| } | |||
| val := reflect.ValueOf(value) | |||
| if fieldVal.Kind() == reflect.Struct { | |||
| //map[string]interface{} | |||
| if val.Kind() != reflect.Map { | |||
| return errors.New(fmt.Sprintf("State [%s] [%s] field type required map", stateName, key)) | |||
| } | |||
| err := MapToStruct(stateName, fieldVal.Addr().Interface(), value.(map[string]interface{})) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| } else if fieldVal.Kind() == reflect.Slice { | |||
| if val.Kind() != reflect.Slice { | |||
| return errors.New(fmt.Sprintf("State [%s] [%s] field type required slice", stateName, key)) | |||
| } | |||
| sliceType := fieldVal.Type().Elem() | |||
| newSlice := reflect.MakeSlice(fieldVal.Type(), 0, val.Len()) | |||
| for i := 0; i < val.Len(); i++ { | |||
| newElem := reflect.New(sliceType.Elem()) | |||
| elemMap := val.Index(i).Interface().(map[string]interface{}) | |||
| err := MapToStruct(stateName, newElem.Interface(), elemMap) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| reflect.Append(newSlice, newElem.Elem()) | |||
| } | |||
| setFiled(fieldVal, setMethod, newSlice) | |||
| } else if fieldVal.Kind() == reflect.Map { | |||
| if val.Kind() != reflect.Map { | |||
| return errors.New(fmt.Sprintf("State [%s] [%s] field type required map", stateName, key)) | |||
| } | |||
| mapType := field.Type | |||
| newMap := reflect.MakeMap(mapType) | |||
| for _, key := range val.MapKeys() { | |||
| newVal := reflect.New(mapType.Elem().Elem()) | |||
| elemMap := val.MapIndex(key).Interface().(map[string]interface{}) | |||
| err := MapToStruct(stateName, newVal.Interface(), elemMap) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| newMap.SetMapIndex(key, newVal.Elem()) | |||
| } | |||
| setFiled(fieldVal, setMethod, newMap) | |||
| } else { | |||
| setFiled(fieldVal, setMethod, val) | |||
| } | |||
| } | |||
| return nil | |||
| } | |||
| func getField(t reflect.Type, name string) (reflect.StructField, bool) { | |||
| for i := 0; i < t.NumField(); i++ { | |||
| field := t.Field(i) | |||
| tag, hasAliasTag := field.Tag.Lookup("alias") | |||
| if (hasAliasTag && tag == name) || (!hasAliasTag && field.Name == name) { | |||
| return field, true | |||
| } | |||
| if field.Anonymous { | |||
| embeddedField, ok := getField(field.Type, name) | |||
| if ok { | |||
| return embeddedField, true | |||
| } | |||
| } | |||
| } | |||
| return reflect.StructField{}, false | |||
| } | |||
| func getFiledSetMethod(name string, structValue reflect.Value) reflect.Value { | |||
| fieldNameSlice := []rune(name) | |||
| fieldNameSlice[0] = unicode.ToUpper(fieldNameSlice[0]) | |||
| setMethodName := "Set" + string(fieldNameSlice) | |||
| setMethod := structValue.MethodByName(setMethodName) | |||
| return setMethod | |||
| } | |||
| func setFiled(fieldVal reflect.Value, setMethod reflect.Value, val reflect.Value) { | |||
| if !fieldVal.CanSet() { | |||
| setMethod.Call([]reflect.Value{ | |||
| val, | |||
| }) | |||
| } else { | |||
| fieldVal.Set(val) | |||
| } | |||
| } | |||