Simple annotation based FSM java library.
This project aim is to provide a lightweight state machine library that allows to easily implement bean flows.
- Simple one level state machine
- Annotation based flow configuration:
- Guards: prevents an state transition to be executed
- On: executes an action when a transition is executed
- OnState: executes an action when a transition to an state is completed
- Thread safe
- State is stored inside your own bean and never inside the state machine
- When ever you trigger an event you provide the bean over which the FSM should operate.
Let's say you have to build a REST end point to handle orders. You will like the orders to go through different states, for example:
- Placed
- Payed
- Sent
The order should only be able to pass from one state to the next one and only if it satisfies some conditions:
- Placed: it will only be placed if the products and the customer are provided.
- At this point the price should be calculated depending on the products.
- Payed: it can only get payed if a valid payment recipe is provided.
- Sent: it can only be marked as sent if a TrackingInfo object is provided.
The bean supporting the information of an order could be something like this:
class Order {
@StateReference
OrderStatus status = OrderStatus.INITIAL;
Object[] products;
Float price;
Object customer;
Object track;
}
enum OrderStatus{
INITIAL,
PLACED,
PAYED,
SENT
}
This library provides a way of implementing a flow class to handle this flow in the following manner:
@Flow(OrderStatus.class)
@Transitions({
@Transition(event = "PLACE", from="INITIAL", to = "PLACED"),
@Transition(event = "PAY", from="PLACED", to = "PAYED"),
@Transition(event = "SEND", from="PAYED", to = "SENT")
})
public class OrderFlow extends AbstractBeanFlow<Order> {
@Guard("PLACE")
private boolean checkParams(Order order, @EventParam("products") Object[] products, @EventParam("customer") Object customer){
return products != null && customer != null;
}
@On("PLACE")
private void fillOrderData(Order order, @EventParam("products") Object[] products, @EventParam("customer") Object customer){
order.products = products;
order.customer = customer;
}
@OnState("PLACED")
private void calculatePrice(Order order){
order.price = 10f; //this should be calculated depending on the products
}
@Guard("PAY")
private boolean checkPayment(Order order, @EventParam("payment") String recipe){
return recipe != null;
}
@Guard("SEND")
private boolean checkPayment(Order order, @EventParam("tracking") Object track){
return track != null;
}
@On("SEND")
private void fillOrderTracking(Order order, @EventParam("tracking") Object track){
order.track = track;
}
}
Then by running the following you should be able to make an order pass through the bean flow:
Order o = new Order();
OrderFlow flow = new OrderFlow();
flow.raise(Event.build("PLACE").param("products", new Object[]{"pid"}).param("customer", "customer"), o);
flow.raise(Event.build("PAY").param("payment", "123"), o);
flow.raise(Event.build("SEND").param("trancking", "UPS-ABC"), o);
Of course you can persist the bean and recover it later on between each step.
To see this sample inside a persisted web application go to the restflow-spring-web-sample
Look at src/text/java/com/apresa/restflow/samples for more examples.
With gradle:
compile group: 'com.github.alfonso-presa.restflow', name: 'restflow', version: '0.1.0'
With maven:
<dependency>
<groupId>com.github.alfonso-presa.restflow</groupId>
<artifactId>restflow</artifactId>
<version>0.1.0</version>
</dependency>
You can also use snapshot builds by pointing to the sonatype snapshot repository