Maintain Temporal Workflow Sequence

Hi Team,
I am totally new to temporal :slight_smile:

I am building the food delivery workflow using temporal. A typical sequence of workflow looks like this:
Order Food β†’ Restaurant accepts the order β†’ pick up the order β†’ order delivered.

As per my understanding, Signals are operated in multi-threaded env, so we can trigger any signal at any point in time. I need to know how we maintain the sequence in the workflow.

Like before the order has been picked up we can not send the signal of an order being delivered or before accepting the order we can deliver the order.

Please find the below code I have used. Please let me know your thoughts about this.

@WorkflowInterface
public interface WorkFlow {
	public static final String QUEUE_NAME = "Customer_Order";
	@WorkflowMethod
	void startApprovalWorkflow();
	@SignalMethod
	void signalOrderAccepted();
	@SignalMethod
	void signalOrderPickedUp();
	@SignalMethod
	void signalOrderDelivered();

}
public class WorkflowImpl implements WorkFlow {

	private final RetryOptions retryoptions = RetryOptions.newBuilder().setInitialInterval(Duration.ofSeconds(1))
			.setMaximumInterval(Duration.ofSeconds(100)).setBackoffCoefficient(2).setMaximumAttempts(50000).build();
	private final ActivityOptions options = ActivityOptions.newBuilder().setStartToCloseTimeout(Duration.ofSeconds(30))
			.setRetryOptions(retryoptions).build();

	private final Activity activity = Workflow.newActivityStub(Activity.class, options);

	public boolean isOrderConfirmed = false;

	public boolean isOrderPickedUp = false;

	public boolean isOrderDelivered = false;

	@Override
	public void startApprovalWorkflow() {
		
		activity.placeOrder();
		
		System.out.println("***** Waiting for Restaurant to confirm your order");
		Workflow.await(() -> isOrderConfirmed);
		
		System.out.println("***** Please wait till we assign a delivery executive");
		Workflow.await(() -> isOrderPickedUp);

		System.out.println("***** Delivery executive is on the way");
		Workflow.await(() -> isOrderDelivered);

	}

	@Override
	public void signalOrderAccepted() {
		activity.setOrderAccepted();
		this.isOrderConfirmed = true;
	}

	@Override
	public void signalOrderPickedUp() {
		activity.setOrderPickedUp();
		this.isOrderPickedUp = true;
	}

	@Override
	public void signalOrderDelivered() {
		activity.setOrderDelivered();
		this.isOrderDelivered = true;
	}

}
@ActivityInterface
public interface Activity {
	@ActivityMethod
	void placeOrder();
	@ActivityMethod
	void setOrderAccepted();
	@ActivityMethod
	void setOrderPickedUp();
	@ActivityMethod
	void setOrderDelivered();
}
public class ActivityImpl implements Activity {
	@Override
	public void placeOrder() {
		System.out.println("***** Order has been placed");
	}
	@Override
	public void setOrderAccepted() {
		System.out.println("***** Restaurant has accepted your order");
	}
	@Override
	public void setOrderPickedUp() {
		System.out.println("***** Order has been picked up");
	}
	@Override
	public void setOrderDelivered() {
		System.out.println("***** Order Delivered");
	}

}
public class OrderService {

	@Autowired
	WorkflowServiceStubs workflowServiceStubs;

	@Autowired
	WorkflowClient workflowClient;

	public void placeOrder(String workflowId) {
		WorkFlow workflow = createWorkFlowConnection(workflowId);
		WorkflowClient.start(workflow::startApprovalWorkflow);
	}

	public void makeOrderAccepted(String workflowId) {
		WorkFlow workflow = workflowClient.newWorkflowStub(WorkFlow.class, "Order_" + workflowId);
		workflow.signalOrderAccepted();
	}

	public void makeOrderPickedUp(String workflowId) {
		WorkFlow workflow = workflowClient.newWorkflowStub(WorkFlow.class, "Order_" + workflowId);
		workflow.signalOrderPickedUp();
	}

	public void makeOrderDelivered(String workflowId) {
		WorkFlow workflow = workflowClient.newWorkflowStub(WorkFlow.class, "Order_" + workflowId);
		workflow.signalOrderDelivered();
	}

	public WorkFlow createWorkFlowConnection(String id) {
		WorkflowOptions options = WorkflowOptions.newBuilder().setTaskQueue(WorkFlow.QUEUE_NAME)
				.setWorkflowId("Order_" + id).build();
		return workflowClient.newWorkflowStub(WorkFlow.class, options);
	}

}

Sorry I don’t fully understand your use case,
seems you are creating a single workflow execution that keeps track of multiple orders. I think that maybe would be better approach to create a workflow execution per order.
When you receive a new order request through your rest api you could start a workflow execution passing it the order payload.

Another question, is the workflow supposed to orchestrate the order steps or just react to external events (completion of certain milestone)? It seems as you might have another workflow or system that is actually orchestrating these steps?

Maybe you can define the order process steps in your workflow and have something like:

public void startApprovalWorkflow(Order order) {
    try {
       notifyRestaurant(order);
       Workflow.await(() -> restaurantConfirmedOrder); // or Workflow.await(duration, condition)
       notifyDriverToPickup(order);
       Workflow.await(() -> driverConfirmedPickup); // or Workflow.await(duration, condition)
      // ...
    } catch(ActivityFailure e) {
      // handle error according to your business logic
      // you can also catch ActivityFailure for each individual activity invocation if you want
     // and have compensation for each step or run the step again, up to you
    }
}

So your would execute activities in the order you define inside your workflow code, and your signal handlers would just handle the signals and set your workflow state for the await conditions to unblock.

Hi,
I am trying to build the food order and delivery app use case, where there is no other system that orchestrates the steps, I want this workflow itself to orchestrate the order steps/transition of order.

When I receive a new request payload to create an order via REST API, I am waiting for the restaurant to confirm the order (REST API call), once the order is confirmed next step is order pick up (REST API), and then delivery (RESP API call).

The issue I am facing here is, that after an order is placed, we should wait for the next subsequent signal i.e. restaurant confirmation whereas, my app is accepting the signal of order pick up or order delivery (REST API). But this is not a valid scenario before an order is accepted we can’t pick up or deliver it up. So I’m not sure how should the workflow accept the signals in sequence so there won’t be any overlap between them.

So, I was able to hit the signal via the REST API of order delivery just after placing the order and skipping the confirmation and pick-up steps. Though my workflow was still in the running phase as I didn’t provide order signals, once I provide accept and delivery signal workflow was closed but not in the proper state/transition that I was expecting.

Here is a screenshot for your reference.

Signals are asynchronous. So they cannot be rejected by the workflow. But they can be ignored if come out of order. Something like this:

	@Override
	public void signalOrderPickedUp() {
		if (!this.isOrderAccepted || this.isOrderPickedUp) {
			log.warn("signal in invalid state");
			return;
		}
		activity.setOrderPickedUp();
		this.isOrderPickedUp = true;
	}

We are currently working on a synchronous update feature that would support rejecting requests.

1 Like