State Transitions With Spring Integration
State Transitions With Spring Integration
In this article, look at how to implement a non-blocking state machine with the Spring Integration framework.
Apr. 07, 20 · Integration Zone ·
Comment (0)
Join the DZone community and get the full member experience.
In a previous article, I presented a framework for a simple state machine. In a follow-up article, I customized the framework for non-blocking processes using callback functions. In this article, I propose an approach to implementing a non-blocking state machine with the Spring Integration framework. The Spring Integration framework provides many communication mechanisms between applications and components. I will be using just the Message Channels for my example. I’ll also use Spring Boot to drive the state transitions.
Order Processing Example: State Transitions
I consider a simple order processing application where a customer creates an order and then pays for the order. The first step in implementing a state machine is to write the state transitions for the application. I will assume that the following are the allowable state transitions for the above order scenario.
Initial State | Pre-Event | Processor | Post-Event | Final State |
Default | create | orderProcessor() | orderCreated | PaymentPending |
PaymentPending | pay | paymentProcessor() | paymentError | PaymentErrorEmailSent |
PaymentErrorEmailSent | retryPay | paymentProcessor() | paymentSuccess | PaymenSuccessEmailSent |
PaymentPending | pay | paymentProcessor() | paymentSuccess | PaymenSuccessEmailSent |
I’ll further assume that the orderProcessor()
is a blocking process and the paymentProcessor()
is a non-blocking process.
Configuring States and Events
The next step is to configure the states and events identified above. I use Java enums to configure the states and events.
The states are configured like:
//ProcessState is a marker interface
public interface ProcessState {
}
public enum OrderState implements ProcessState {
DEFAULT,
PAYMENTPENDING,
PAYINPROGRESS,
PAYMENTERROREMAILSENT,
REPAYINPROGRESS,
public interface ProcessState {
0
public interface ProcessState {
1
Note that I have added two additional states – PAYINPROGRESS and RETRYPAYINPROGRESS corresponding to the two pre-events PAY and RETRYPAY. This will help us in validating the pre-events that might arrive before the long running non-blocking process completes.
The events are then configured like:
public interface ProcessState {
2
public interface ProcessState {
3
public interface ProcessState {
4
public interface ProcessState {
5
public interface ProcessState {
6
public interface ProcessState {
7
public interface ProcessState {
8
public interface ProcessState {
9
}
0
}
1
}
2
}
3
}
4
}
5
}
6
}
7
}
8
}
9
0
1
2
3
4
5
6
7
8
9
public enum OrderState implements ProcessState {
0
public enum OrderState implements ProcessState {
1
public enum OrderState implements ProcessState {
2
public enum OrderState implements ProcessState {
3
public enum OrderState implements ProcessState {
4
public enum OrderState implements ProcessState {
5
public enum OrderState implements ProcessState {
6
public enum OrderState implements ProcessState {
7
public enum OrderState implements ProcessState {
8
public enum OrderState implements ProcessState {
9
DEFAULT,
0
DEFAULT,
1
DEFAULT,
2
DEFAULT,
3
DEFAULT,
4
DEFAULT,
5
DEFAULT,
6
DEFAULT,
7
DEFAULT,
8
DEFAULT,
9
PAYMENTPENDING,
0
PAYMENTPENDING,
1
PAYMENTPENDING,
2
PAYMENTPENDING,
3
PAYMENTPENDING,
4
PAYMENTPENDING,
5
PAYMENTPENDING,
6
PAYMENTPENDING,
7
PAYMENTPENDING,
8
PAYMENTPENDING,
9
PAYINPROGRESS,
0
PAYINPROGRESS,
1
PAYINPROGRESS,
2
PAYINPROGRESS,
3
PAYINPROGRESS,
4
PAYINPROGRESS,
5
PAYINPROGRESS,
6
PAYINPROGRESS,
7
PAYINPROGRESS,
8
PAYINPROGRESS,
9
PAYMENTERROREMAILSENT,
0
PAYMENTERROREMAILSENT,
1
PAYMENTERROREMAILSENT,
2
PAYMENTERROREMAILSENT,
3
PAYMENTERROREMAILSENT,
4
PAYMENTERROREMAILSENT,
5
PAYMENTERROREMAILSENT,
6
PAYMENTERROREMAILSENT,
7
PAYMENTERROREMAILSENT,
8
PAYMENTERROREMAILSENT,
9
REPAYINPROGRESS,
0
REPAYINPROGRESS,
1
REPAYINPROGRESS,
2
REPAYINPROGRESS,
3
REPAYINPROGRESS,
4
REPAYINPROGRESS,
5
REPAYINPROGRESS,
6
REPAYINPROGRESS,
7
REPAYINPROGRESS,
8
REPAYINPROGRESS,
9
public interface ProcessState {
00
public interface ProcessState {
01
public interface ProcessState {
02
public interface ProcessState {
03
public interface ProcessState {
04
public interface ProcessState {
05
public interface ProcessState {
06
public interface ProcessState {
07
public interface ProcessState {
08
public interface ProcessState {
09
public interface ProcessState {
10
public interface ProcessState {
11
public interface ProcessState {
12
public interface ProcessState {
13
public interface ProcessState {
14
public interface ProcessState {
15
public interface ProcessState {
16
public interface ProcessState {
17
public interface ProcessState {
18
public interface ProcessState {
19
public interface ProcessState {
20
public interface ProcessState {
21
public interface ProcessState {
22
public interface ProcessState {
23
public interface ProcessState {
24
public interface ProcessState {
25
public interface ProcessState {
26
public interface ProcessState {
27
public interface ProcessState {
28
public interface ProcessState {
29
public interface ProcessState {
30
public interface ProcessState {
31
public interface ProcessState {
32
public interface ProcessState {
33
public interface ProcessState {
34
public interface ProcessState {
35
public interface ProcessState {
36
public interface ProcessState {
37
public interface ProcessState {
38
public interface ProcessState {
39
public interface ProcessState {
40
public interface ProcessState {
41
public interface ProcessState {
42
public interface ProcessState {
43
public interface ProcessState {
44
public interface ProcessState {
45
public interface ProcessState {
46
public interface ProcessState {
47
public interface ProcessState {
48
public interface ProcessState {
49
public interface ProcessState {
50
public interface ProcessState {
51
public interface ProcessState {
52
public interface ProcessState {
53
public interface ProcessState {
54
public interface ProcessState {
55
public interface ProcessState {
56
public interface ProcessState {
57
public interface ProcessState {
58
public interface ProcessState {
59
public interface ProcessState {
60
public interface ProcessState {
61
public interface ProcessState {
62
public interface ProcessState {
63
public interface ProcessState {
64
public interface ProcessState {
65
public interface ProcessState {
66
public interface ProcessState {
67
public interface ProcessState {
68
public interface ProcessState {
69
public interface ProcessState {
70
public interface ProcessState {
71
public interface ProcessState {
72
public interface ProcessState {
73
Order Processing Components
The order processing components are shown in the following diagram…
…where I have also shown the message channels used. The Spring Integration framework provides Messaging Gateway components. However, the custom facade components shown above offer more flexibility for the state machine implementation.
Persisting State
An in-memory H2 database is used to persist the order states with the following configuration:
public interface ProcessState {
74
public interface ProcessState {
75
public interface ProcessState {
76
public interface ProcessState {
77
public interface ProcessState {
78
public interface ProcessState {
79
public interface ProcessState {
80
public interface ProcessState {
81
public interface ProcessState {
82
public interface ProcessState {
83
public interface ProcessState {
84
public interface ProcessState {
85
public interface ProcessState {
86
public interface ProcessState {
87
public interface ProcessState {
88
public interface ProcessState {
89
public interface ProcessState {
90
public interface ProcessState {
91
public interface ProcessState {
92
public interface ProcessState {
93
public interface ProcessState {
94
public interface ProcessState {
95
public interface ProcessState {
96
public interface ProcessState {
97
public interface ProcessState {
98
public interface ProcessState {
99
}
00
}
01
}
02
}
03
}
04
}
05
}
06
}
07
}
08
}
09
}
10
}
11
}
12
A schema.sql file is used to create the table:
}
13
}
14
}
15
}
16
}
17
}
18
}
19
For brevity of discussion I am not tracking order state history.
Spring Integration Message Channels Configuration
The next step is to configure the message channels. I will be using the xml configuration option as suggested in the Spring Integration sample.
}
20
}
21
}
22
}
23
}
24
}
25
}
26
}
27
}
28
}
29
}
30
}
31
}
32
}
33
}
34
}
35
}
36
}
37
}
38
}
39
}
40
}
41
}
42
}
43
}
44
}
45
}
46
}
47
}
48
}
49
}
50
}
51
}
52
}
53
}
54
}
55
}
56
}
57
}
58
}
59
}
60
}
61
}
62
}
63
}
64
}
65
}
66
}
67
OrderStateTransitionsMgrBlocking.java
— a facade class that handles all state transitions that need to be synchronous.
}
68
}
69
}
70
}
71
}
72
}
73
}
74
}
75
}
76
}
77
}
78
}
79
}
80
}
81
}
82
}
83
}
84
}
85
}
86
}
87
}
88
}
89
}
90
}
91
}
92
}
93
}
94
}
95
}
96
}
97
}
98
}
99
00
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
Note that blocking is enabled in the above by setting the timeout < 0.
OrderStateTransitionsMgrNonBlocking.java
— a facade class that handles all state transitions that need to be non-blocking.
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
Note that non-blocking is enabled by setting timeout=0.
OrderProcessor.java
:
63
64
65
66
67
68
69
70
71
72
73
74
PaymentProcessor.java:
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
PostEventHandler.java:
97
98
99
public enum OrderState implements ProcessState {
00
public enum OrderState implements ProcessState {
01
public enum OrderState implements ProcessState {
02
public enum OrderState implements ProcessState {
03
public enum OrderState implements ProcessState {
04
public enum OrderState implements ProcessState {
05
public enum OrderState implements ProcessState {
06
public enum OrderState implements ProcessState {
07
public enum OrderState implements ProcessState {
08
public enum OrderState implements ProcessState {
09
public enum OrderState implements ProcessState {
10
public enum OrderState implements ProcessState {
11
public enum OrderState implements ProcessState {
12
OrderController.java
– generates pre-events for the state machine.
public enum OrderState implements ProcessState {
13
public enum OrderState implements ProcessState {
14
public enum OrderState implements ProcessState {
15
public enum OrderState implements ProcessState {
16
public enum OrderState implements ProcessState {
17
public enum OrderState implements ProcessState {
18
public enum OrderState implements ProcessState {
19
public enum OrderState implements ProcessState {
20
public enum OrderState implements ProcessState {
21
public enum OrderState implements ProcessState {
22
public enum OrderState implements ProcessState {
23
public enum OrderState implements ProcessState {
24
public enum OrderState implements ProcessState {
25
public enum OrderState implements ProcessState {
26
public enum OrderState implements ProcessState {
27
public enum OrderState implements ProcessState {
28
public enum OrderState implements ProcessState {
29
public enum OrderState implements ProcessState {
30
public enum OrderState implements ProcessState {
31
public enum OrderState implements ProcessState {
32
public enum OrderState implements ProcessState {
33
public enum OrderState implements ProcessState {
34
public enum OrderState implements ProcessState {
35
public enum OrderState implements ProcessState {
36
public enum OrderState implements ProcessState {
37
public enum OrderState implements ProcessState {
38
public enum OrderState implements ProcessState {
39
public enum OrderState implements ProcessState {
40
public enum OrderState implements ProcessState {
41
public enum OrderState implements ProcessState {
42
public enum OrderState implements ProcessState {
43
public enum OrderState implements ProcessState {
44
public enum OrderState implements ProcessState {
45
public enum OrderState implements ProcessState {
46
public enum OrderState implements ProcessState {
47
public enum OrderState implements ProcessState {
48
public enum OrderState implements ProcessState {
49
public enum OrderState implements ProcessState {
50
public enum OrderState implements ProcessState {
51
public enum OrderState implements ProcessState {
52
public enum OrderState implements ProcessState {
53
public enum OrderState implements ProcessState {
54
public enum OrderState implements ProcessState {
55
public enum OrderState implements ProcessState {
56
public enum OrderState implements ProcessState {
57
public enum OrderState implements ProcessState {
58
public enum OrderState implements ProcessState {
59
public enum OrderState implements ProcessState {
60
public enum OrderState implements ProcessState {
61
public enum OrderState implements ProcessState {
62
public enum OrderState implements ProcessState {
63
public enum OrderState implements ProcessState {
64
public enum OrderState implements ProcessState {
65
public enum OrderState implements ProcessState {
66
public enum OrderState implements ProcessState {
67
public enum OrderState implements ProcessState {
68
public enum OrderState implements ProcessState {
69
public enum OrderState implements ProcessState {
70
public enum OrderState implements ProcessState {
71
public enum OrderState implements ProcessState {
72
public enum OrderState implements ProcessState {
73
public enum OrderState implements ProcessState {
74
Full source for this sample application is available on GitHub.
Testing the State Machine
Due to the delayed responses from the non-blocking processes, the requests should be made with appropriate delays. Otherwise, either "Invalid state" or "The PAY/RETRYPAY event is in progress" responses are sent to the customer. The following scenarios can be tested:
Scenario #1: Create Order and make a valid payment(amount>0.00) – Customer receives success email.
Scenario #2: Create an order, make an invalid payment(amount=0.00) and followed by a valid payment without delay – Customer receives "The PAY event is in progress".
Scenario #3: Create an order, make an invalid payment and followed by a valid retry-payment without delay – Customer receives "Invalid state" response.
Scenario #4: Create an order, make an invalid payment and followed by a valid retry-payment with a delay(> 1s) – Customer receives payment success email.
I have included a JMeter test plan file so interested readers can run the above scenarios. The logs display the state transitions.
Conclusions
A simple example for a state machine with blocking and non-blocking processes is presented. It is shown that pre-defined state transitions can be maintained to produce robust applications even when non-blocking processes are introduced. The use of the state machine is found to produce clean and maintainable code. The Spring Integration framework is found to be minimally invasive and enables loose coupling of application components with simple configuration.
Like This Article? Read More From DZone
Comment (0)
Opinions expressed by DZone contributors are their own.
Integration Partner Resources
- {{ node.blurb }}
{{ editionName }}
{{ parent.title || parent.header.title}}
{{ parent.tldr }}
{{ parent.linkDescription }}
{{ message }}
{{ $dialog.title }}