Skip to main content

Child workflows

A Child workflow is a Workflow Execution that is spawned from within another Workflow.
A Workflow Execution can be both a Parent and a Child Workflow Execution because any Workflow can spawn another 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 java.util.UUID

Then define workflow interfaces:

// Child Workflow interface
@workflowInterface
trait GreetingChild {
@workflowMethod
def composeGreeting(greeting: String, name: String): String

// Note the signal method
@signalMethod
def updateName(name: String): Unit
}

// Parent Workflow interface
@workflowInterface
trait GreetingWorkflow {
def getGreeting(name: String): String
}

Synchronous invocation​

The simplest case of using child workflows is invoking them synchronously using ZChildWorkflowStub.execute:

// Simple parent Workflow implementation
class GreetingWorkflowSimple extends GreetingWorkflow {
private val workflowId = ZWorkflow.info.workflowId

override def getGreeting(name: String): String = {
val child: ZChildWorkflowStub.Of[GreetingChild] =
ZWorkflow.newChildWorkflowStub[GreetingChild](
ZChildWorkflowOptions.withWorkflowId(s"$workflowId/child")
)

ZChildWorkflowStub.execute(
child.composeGreeting("Hello", name)
)
}
}

Important notes:

  • To create a child workflow stub, you must use ZWorkflow.newChildWorkflowStub[<ChildType>](options) method.
    • ZChildWorkflowOptions configures the child workflow run. The child Workflow ID is mandatory. You can also specify a new Task Queue for the child workflow, and other various options (such as timeouts)
  • Reminder: you must always wrap the child workflow invocation into ZChildWorkflowStub.execute method.
    • child.composeGreeting("Hello", name) invocation would be re-written into an untyped Temporal's workflow invocation
    • A direct method invocation will throw an exception

NOTE: Do not annotate workflow stubs with the workflow interface type. It must be ZWorkflowStub.Of[EchoWorkflow].
Otherwise, you'll get a compile-time error:

def doSomething(child: GreetingChild): String =
ZChildWorkflowStub.execute(child.composeGreeting("Hello", "World"))
// error: zio.temporal.workflow.ZChildWorkflowStub.execute must be used only with typed zio.temporal.workflow.ZChildWorkflowStub.Of[A],
// but repl.MdocSession.MdocApp.GreetingChild found. Perhaps you added an explicit type annotation?
// The actual type must be zio.temporal.workflow.ZChildWorkflowStub.Of[repl.MdocSession.MdocApp.GreetingChild]
// ZChildWorkflowStub.execute(child.composeGreeting("Hello", "World"))
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Asynchronous invocation​

It's also possible to spawn multiple child workflows and running them in parallel using ZChildWorkflowStub.executeAsync:

// Parent Workflow implementation
class GreetingWorkflowParallel extends GreetingWorkflow {
private val workflowId = ZWorkflow.info.workflowId

override def getGreeting(name: String): String = {

// Workflows are stateful, so a new stub must be created for each new child.
val child1 = ZWorkflow.newChildWorkflowStub[GreetingChild](
ZChildWorkflowOptions.withWorkflowId(s"$workflowId/child-1")
)
// The result of the async invocation
val greeting1: ZAsync[String] = ZChildWorkflowStub.executeAsync(
child1.composeGreeting("Hello", name)
)

// Both children will run concurrently.
val child2 = ZWorkflow.newChildWorkflowStub[GreetingChild](
ZChildWorkflowOptions.withWorkflowId(s"$workflowId/child-2")
)
val greeting2 = ZChildWorkflowStub.executeAsync(
child2.composeGreeting("Bye", name)
)

// You can combine ZAsync instances like ZIO
val result: ZAsync[String] = greeting1.zipWith(greeting2)(
(first, second) =>
s"First: $first , second: $second"
)

// Block until async computation completes
result.run.getOrThrow
}
}

Signals​

It's possible to send a Signal to a Child Workflow from the parent using ZChildWorkflowStub.signal:

// Parent Workflow implementation
class GreetingWorkflowSignaling extends GreetingWorkflow {
private val workflowId = ZWorkflow.info.workflowId

override def getGreeting(name: String): String = {
val child = ZWorkflow.newChildWorkflowStub[GreetingChild](
ZChildWorkflowOptions.withWorkflowId(s"$workflowId/child")
)

// Execute the child asynchronously
val greeting = ZChildWorkflowStub.executeAsync(
child.composeGreeting("Hello", name)
)

// Send the signal to the child
ZChildWorkflowStub.signal(
child.updateName("Temporal")
)

// Block until async computation completes
greeting.run.getOrThrow
}
}

NOTE Sending a Query to Child Workflows from within the parent Workflow code is not supported.
However, you can send a Query to Child Workflows from Activities using WorkflowClient.