{{announcement.body}}
{{announcement.title}}

State Transitions With Spring Integration

DZone 's Guide to

State Transitions With Spring Integration

In this article, look at how to implement a non-blocking state machine with the Spring Integration framework.

· Integration Zone ·
Free Resource

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:

Java


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:

Java


Order Processing Components

The order processing components are shown in the following diagram...


Order processing components in a 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:

Java


A schema.sql file is used to create the table:

Java


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.

XML

 

OrderStateTransitionsMgrBlocking.java  — a facade class that handles all state transitions that need to be synchronous.

Java


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.

Java


Note that non-blocking is enabled by setting timeout=0.

OrderProcessor.java:

Java

 

PaymentProcessor.java:

Java

 

PostEventHandler.java:

Java

 

OrderController.java  - generates pre-events for the state machine.

Java

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. 

Topics:
java ,non-blocking ,spring integration ,state machines ,state transitions

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}