Saga state machine configuration
Saga mode transactions are based on a state machine engine. The distributed transaction console provides a state designer for orchestrating state graphs and defining node details and property configurations.
The implementation works as follows:
You define the service invocation flow using a state graph, which generates a JSON state language definition file.
A node in the state graph can represent a service invocation. You can configure a compensation node for the node.
The state machine engine executes the JSON state definition file. If an exception occurs, the engine executes the compensation nodes for successfully completed nodes in reverse order to roll back the transaction.
NoteIf an exception occurs, you can also customize whether to compensate. This feature lets you implement service orchestration. It supports features such as single choice, concurrency, child flows, parameter transforms, parameter mapping, service execution status checks, and exception catching.
This topic describes the properties of state machines and states to help you design and orchestrate your business flows.
State machine properties
The properties of a state machine are as follows:
{
"Name":"reduceInventoryAndBalance",
"Comment":"reduce inventory then reduce balance in a transaction",
"StartState":"ReduceInventory",
"Version":"0.0.1",
"States":{}
}Property descriptions
Parameter | Description |
| The name of the state machine. This name must be unique. |
| The description of the state machine. |
| The version of the state machine. |
| The first state to run at startup. |
| A list of states, which is a map structure. The key is the name of the state and must be unique within the state machine. The value is a map structure that represents the property list of the state. |
State properties
ServiceTask
"States":{
...
"ReduceBalance":{
"Type":"ServiceTask",
"ServiceName":"balanceAction",
"ServiceMethod":"reduce",
"CompensateState":"CompensateReduceBalance",
"IsForUpdate":true,
"IsPersist":true,
"IsAsync":false,
"Input":[
"$.[businessKey]",
"$.[amount]",
{
"throwException":"$.[mockReduceBalanceFail]"
}
],
"Output":{
"compensateReduceBalanceResult":"$.#root"
},
"Status":{
"#root == true":"SU",
"#root == false":"FA",
"$Exception{java.lang.Throwable}":"UN"
},
"Retry":[
{
"Exceptions":["io.seata.saga.engine.mock.DemoException"],
"IntervalSeconds":1.5,
"MaxAttempts":3,
"BackoffRate":1.5
},
{
"IntervalSeconds":1,
"MaxAttempts":3,
"BackoffRate":1.5
}
],
"Catch":[
{
"Exceptions":[
"java.lang.Throwable"
],
"Next":"CompensationTrigger"
}
],
"Next":"Succeed"
}
...
}Property descriptions
Parameter | Description |
| The service name. This is usually the beanId of the service. |
| The name of the service method. |
| The compensation state for this state. |
| Indicates whether the service updates data. The default value is `false`. If `CompensateState` is configured, the default value is `true`. A service with a compensation service is always a data update service. |
| Specifies whether to store the execution log. The default value is `true`. For some query services, you can set this property to `false` to improve performance by not storing logs. This is safe because these services can be re-executed during exception recovery. |
| Invokes the service asynchronously. Asynchronous invocation ignores the return value of the service. Therefore, the user-defined service execution status mapping (the `status` property) is ignored, and the service call is considered successful by default. If the asynchronous call fails to submit, for example, because the thread pool is full, the service execution fails. |
| A list of input parameters for the service call, provided as an array that corresponds to the parameter list of the service method. |
| Assigns parameters from the service's return value to the state machine context. This is a map structure.
|
| Maps the service execution status. The framework defines three statuses: `SU` (success), `FA` (failure), and `UN` (unknown). You must map the service execution status to one of these three statuses to help the framework determine the consistency of the entire transaction. This property is a map structure.
|
| The routing to take after an exception is caught. |
| The retry policy to use after an exception is caught. This is an array, and you can configure multiple rules.
|
| The next state to execute after the service completes. |
If you do not configure `Status` to map the service execution status, the system automatically determines the status as follows:
If there is no exception, the execution is considered successful.
If there is an exception, the system checks if it is a network connectivity timeout. If it is, the status is considered `FA`.
For any other exception, if the service has `IsForUpdate=true`, the status is `UN`. Otherwise, the status is `FA`.
The framework determines the execution status of the entire state machine. A state machine has two statuses: `status` (forward execution status) and `compensateStatus` (compensation status).
If all services execute successfully (transaction commits), then `status`=SU and `compensateStatus`=null.
If a service fails, a data update service has succeeded, and no compensation has occurred (transaction fails to commit), then `status`=UN and `compensateStatus`=null.
If a service fails, no data update service has succeeded, and no compensation has occurred (transaction fails to commit), then `status`=FA and `compensateStatus`=null.
If compensation succeeds (transaction rolls back), then `status`=FA/UN and `compensateStatus`=SU.
If compensation occurs but some services fail to compensate (rollback fails), then `status`=FA/UN and `compensateStatus`=UN.
If a transaction fails to commit or roll back, the Seata Server continuously retries the operation.
Choice
"ChoiceState":{
"Type":"Choice",
"Choices":[
{
"Expression":"[reduceInventoryResult] == true",
"Next":"ReduceBalance"
}
],
"Default":"Fail"
}A `Choice` state provides single-choice routing.
`Choices`: A list of available branches. Only the first branch that meets its condition is executed.
`Expression`: A SpringEL expression.
`Next`: The next state to execute when the `Expression` is true.
Succeed
"Succeed":{
"Type":"Succeed"
}Reaching a Succeed state means the state machine terminated normally. Normal termination does not mean the transaction was successful. Success depends on whether all states completed successfully.
Fail
"Fail":{
"Type":"Fail",
"ErrorCode":"PURCHASE_FAILED",
"Message":"purchase failed"
}Reaching a Fail state means the state machine has terminated with an error. In this case, you can configure `ErrorCode` and `Message` to specify the error details, which are then returned to the caller.
CompensationTrigger
"CompensationTrigger":{
"Type":"CompensationTrigger",
"Next":"Fail"
}A `CompensationTrigger` state is used to trigger a compensation event and roll back the distributed transaction. `Next` specifies the state to route to after compensation is successful.
SubStateMachine
"CallSubStateMachine":{
"Type":"SubStateMachine",
"StateMachineName":"simpleCompensationStateMachine",
"CompensateState":"CompensateSubMachine",
"Input":[
{
"a":"$.1",
"barThrowException":"$.[barThrowException]",
"fooThrowException":"$.[fooThrowException]",
"compensateFooThrowException":"$.[compensateFooThrowException]"
}
],
"Output":{
"fooResult":"$.#root"
},
"Next":"Succeed"
}A `SubStateMachine` state calls a child state machine.
`StateMachineName`: The name of the child state machine to call.
`CompensateState`: The compensation state for the child state machine. This property is optional. If you do not configure it, the system automatically creates a compensation state. Compensating a child state machine involves calling its `compensate` method. Therefore, you do not need to implement a compensation service for the child state machine yourself. When you configure this property, you can use the `Input` property to pass custom variables. See `CompensateSubMachine` below.
CompensateSubMachine
"CompensateSubMachine":{
"Type":"CompensateSubMachine",
"Input":[
{
"compensateFooThrowException":"$.[compensateFooThrowException]"
}
]
}A CompensateSubMachine state is used to compensate a child state machine by calling its compensate method. You can use the Input property to pass custom variables and the Status property to determine whether the compensation was successful.
Defining complex parameter inputs
"FirstState":{
"Type":"ServiceTask",
"ServiceName":"demoService",
"ServiceMethod":"complexParameterMethod",
"Next":"ChoiceState",
"ParameterTypes":["java.lang.String","int","io.seata.saga.engine.mock.DemoService$People","[Lio.seata.saga.engine.mock.DemoService$People;","java.util.List","java.util.Map"],
"Input":[
"$.[people].name",
"$.[people].age",
{
"name":"$.[people].name",
"age":"$.[people].age",
"childrenArray":[
{
"name":"$.[people].name",
"age":"$.[people].age"
},
{
"name":"$.[people].name",
"age":"$.[people].age"
}
],
"childrenList":[
{
"name":"$.[people].name",
"age":"$.[people].age"
},
{
"name":"$.[people].name",
"age":"$.[people].age"
}
],
"childrenMap":{
"lilei":{
"name":"$.[people].name",
"age":"$.[people].age"
}
}
},
[
{
"name":"$.[people].name",
"age":"$.[people].age"
},
{
"name":"$.[people].name",
"age":"$.[people].age"
}
],
[
{
"@type":"io.seata.saga.engine.mock.DemoService$People",
"name":"$.[people].name",
"age":"$.[people].age"
}
],
{
"lilei":{
"@type":"io.seata.saga.engine.mock.DemoService$People",
"name":"$.[people].name",
"age":"$.[people].age"
}
}
],
"Output":{
"complexParameterMethodResult":"$.#root"
}
}The `complexParameterMethod` method shown above is defined as follows:
People complexParameterMethod(String name, int age,People people,People[] peopleArray,List<People> peopleList,Map<String,People> peopleMap)
class People{
private String name;
private int age;
private People[] childrenArray;
private List<People> childrenList;
private Map<String,People> childrenMap;
...
}When you start the state machine, pass the parameters as follows:
Map<String,Object> paramMap =new HashMap<>(1);
People people =new People();
people.setName("lilei");
people.setAge(18);
paramMap.put("people", people);
String stateMachineName ="simpleStateMachineWithComplexParams";
StateMachineInstance inst = stateMachineEngine.start(stateMachineName,null, paramMap);The `ParameterTypes` property is optional but required when the called method's parameter list includes collection types with generics, such as `Map` or `List`. This is necessary because Java compilation erases generic type information. You must also add an `@type` attribute to the corresponding JSON in the `Input` to declare the generic type, which is the element type of the collection.