Skip to content

Traditional State Machine Order Fulfilment

yogeshnachnani edited this page Mar 23, 2016 · 5 revisions

Consider a typical order fulfilment process:

We now show how the above state machine can be modelled using Flux primitives:

Current State Task Event raised
Order Created Decide if it's COD/online payment Payment Pending
OR Order Confirmed
Order Packed Pack order & proceed to shipment Package Ready
Order Shipped Route order to final destination.
Proceed for final mile delivery
Ready For Delivery
OR Payment Pending

Or, alternatively, this is how we can represent the above table in code

final StateDefinition orderCreated = new StateDefinition(
    "order created", /* Name of the state */
    Collections.<EventDefinition>emptySet(), /* No Dependencies */
    new Task() { /* Task to be executed */
        public Pair<Event, FluxError> execute(Event... events) {
        /* if order is COD */
        return new Pair<Event, FluxError>(new Event("Order Confirmed"),null);
        /* if order is for online payment */
        return new Pair<Event, FluxError>(new Event("Payment Pending"),null);
    }
});

final StateDefinition orderPacked = new StateDefinition(
    "order packed" /* Name of the state */,
    /* Depends on the "Order Confirmed" event. The event can be raised by Order Created OR after Payment Received */
    Collections.singleton(new EventDefinition("Order Confirmed")) ,
    new Task() {
        public Pair<Event, FluxError> execute(Event... events) {
            /* Work related to packing order */
            return new Pair<Event, FluxError>(new Event("Package Ready"),null);
        }
    }
);

final StateDefinition orderShipped = new StateDefinition(
    "order shipped"/* Name of the state */,
    Collections.singleton(new EventDefinition("Package Ready")) /* Depends on the "Package Ready" event */,
    new Task() {
        public Pair<Event, FluxError> execute(Event... events) {
            /* if order is COD */
              return new Pair<Event, FluxError>(new Event("Payment Pending",null));
            /* if order is Already paid for */
              return new Pair<Event, FluxError>(new Event("Ready for Delivery",null));  
        }
    }
);

final StateMachineDefinition fulfilment = new StateMachineDefinition(
    "Simple Order Fulfilment", "Fulfilment", /* Description & Name */
    Arrays.asList(orderCreated, orderShipped, orderPacked),
    "order created" /* Start State */);

We can begin the execution of the state machine using WorkflowExecutionController

workflowExecutionController.init(fulfilment);

Once the state machine is initialised, Flux would begin processing the state(s) that do not have any dependencies.

For the example given above, this would be the "order created" state.


The above code illustrates how Flux would execute a state machine. We have made it a lot simpler for developers to write the same State Machine as something very similar to regular Java code.

Firstly, we can annotate any public method with the @Workflow annotation to indicate that it defines the state transitions

public class SimpleOrderFulfilmentWorkflow {
    @Workflow(version = 1)
    public void start(OrderData orderData) {

        final Promise<CreatedOrder> createdOrder = orderManager.create(orderData);
        /* Now, we are in the Order Created state of the state machine */
        
        Promise<ConfirmedOrder> confirmedOrder = processPaymentIfRequired(createdOrder);
        /* With a confirmed order, we can now proceed for packing */

        /* Warehouse service can pack a confirmed order 
           Notice how we are staying true to the state transition diagram above by explicitly defining confirmedOrder as a dependency before beginning order packing
        */

        final Promise<PackedOrder> readyPackage = warehouseService.packOrder(confirmedOrder);
  
        /* Shipment service can ship a ready Package */
        final Promise<OrderDeliveryInformation> orderDeliveryInformation = shipmentService.ship(readyPackage);
    }
}

This looks like vanilla java code, except for the presence of the Promise<V> variables.

Also, each of the methods above - orderManager.create(), processPaymentIfRequired(), warehouseService.packOrder() and shipmentService.ship() are annotated with Flux's @Task annotation.

What this means is that when the start() method [annotated with @Workflow] is invoked, it basically "submits" the execution flow to the Flux engine.

The Flux engine, then, orchestrates the the state transitions by asynchronously invoking the actual code. Any dependency between two invocations is specified using Promise<V> variables. Promise<V> variables can be thought of carrying the promise of the corresponding event being raised. Once a method executes successfully, the returned value is received as an Event<> by the Flux engine. Once the event is actually raised, the method (or, more formally, the task associated with a particular state) can be entered. By specifying a signature like warehouseService.packOrder(Promise<ConfirmedOrder>), it is understood that the packOrder() method can be invoked only once the Promise of a confirmedOrder is fulfilled.

In the above example, the promise of the ConfirmedOrder is fulfilled by the processPaymentIfRequired() method

@Task(version = 1, timeout = 1000l)
public Promise<ConfirmedOrder> processPaymentIfRequired(Promise<CreatedOrder> createdOrderPromise) {
        /* Given that we are in the "Order Created" state of the state machine, we decide on how to proceed */
        final CreatedOrder createdOrder = createdOrderPromise.get();
        if (createdOrder.isCod()){
            /* The call to paymentService.processOnlinePayment() is equivalent to raising the "Payment Pending" event. */
            return orderManager.confirmOrder(createdOrderPromise,paymentService.processOnlinePayment(createdOrderPromise));
        } else {
            return orderManager.confirmOrder(createdOrderPromise);
        }
    }

The complete set of examples on how to define State Machines or Workflows is located in the examples module of the flux code base.

Clone this wiki locally