Skip to main content

External workflows

A Workflow execution may interact with other workflow executions even if it's not a child.
The second workflow is External to the first one.
The first workflow may send signals or cancel the External workflow.

Defining child workflows​

Let's start with some basic imports that will be required for the whole demonstration:

import zio._
import zio.temporal._
import zio.temporal.workflow._
import zio.temporal.state._
import java.util.UUID

Then define workflow interfaces:

// First workflow
trait FoodDeliveryWorkflow {
def deliver(goods: List[String]): Unit

def startDelivery(address: String): Unit

def cancelDelivery(): Unit

object FoodDeliveryWorkflow {
// A helper function to get a stable workflowId
def makeId(orderId: String): String = orderId + "-delivery"

// The second workflow
trait FoodOrderWorkflow {
// Returns true when completed
def order(goods: List[String], deliveryAddress: String): Boolean

def confirmPayed(): Unit

def cancelOrder(): Unit

Let's imagine those workflows are started in parallel by some other workflow.
To avoid communicating through the parent workflow, they can simply interact with each other.
From the perspective of FoodOrderWorkflow, the FoodDeliveryWorkflow is External.

Let's first define FoodDeliveryWorkflow

// Implementation details don't matter in this example
class FoodDeliveryWorkflowImpl extends FoodDeliveryWorkflow {
private val logger = ZWorkflow.makeLogger

override def deliver(goods: List[String]): Unit = {"Delivering...")
// Do something ...

override def startDelivery(address: String): Unit = {"Delivery started. Address: $address")

override def cancelDelivery(): Unit = {"Delivery cancelled")

Then the FoodOrderWorkflow:

// The order has a state
sealed trait OrderState extends Product with Serializable
object OrderState {
case object Initial extends OrderState
case object Payed extends OrderState
case object Cancelled extends OrderState

class FoodOrderWorkflowImpl extends FoodOrderWorkflow {
private val logger = ZWorkflow.makeLogger

// The workflow state
private val state = ZWorkflowState.make[OrderState](OrderState.Initial)

override def order(goods: List[String], deliveryAddress: String): Boolean = {"Waiting until payment received or cancel or timeout...")
// Wait for condition to be met
val touched = ZWorkflow.awaitWhile(2.minutes)(state =:= OrderState.Initial)

// Create an external workflow stub
val deliveryWorkflow: ZExternalWorkflowStub.Of[FoodDeliveryWorkflow] =
// Must know the exact workflow ID

if (!touched || state =:= OrderState.Cancelled) {
// Sending signal
} else {
// Sending signal

override def confirmPayed(): Unit = {"The order was payed")
state := OrderState.Payed

override def cancelOrder(): Unit = {"The order was cancelled")
state := OrderState.Cancelled


Important notes:

  • To create an external workflow stub, you must use ZWorkflow.newExternalWorkflowStub[<WorkflowType>] method.
    • The workflow ID must be known to create the stub
  • Reminder: you must always wrap the external workflow signal invocation into ZExternalWorkflowStub.signal method.
    • deliveryWorkflow.cancelDelivery() invocation would be re-written into an untyped Temporal's signal invocation
    • A direct method invocation will throw an exception