Skip to main content

Workflow state

Temporal allows your workflows to be stateful.
Basically, it means that you could store the workflow state inside a plain Scala variable in the workflow implementation.

Using workflow state​

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

import zio._
import zio.temporal._
import zio.temporal.worker._
import zio.temporal.state._
import zio.temporal.workflow._
import zio.temporal.activity._

import java.util.UUID

Consider following workflow and activity:

@activityInterface
trait PaymentActivity {

def debit(amount: BigDecimal, from: String): Unit

def credit(amount: BigDecimal, to: String): Unit
}

@workflowInterface
trait PaymentWorkflow {

@workflowMethod
def proceed(amount: BigDecimal, from: String, to: String): Unit
}

While implementing PaymentWorkflow, we may be interested in the current payment state: is it on debit or credit step? Let's model it using the following enum:

sealed trait PaymentState
object PaymentState {
case object Initial extends PaymentState
case object Debited extends PaymentState
case object Credited extends PaymentState
}

Then we could implement a stateful workflow as follows:

class PaymentWorkflowImpl extends PaymentWorkflow {
private val paymentActivity: ZActivityStub.Of[PaymentActivity] =
ZWorkflow.newActivityStub[PaymentActivity](
ZActivityOptions.withStartToCloseTimeout(10.seconds)
)

private val paymentState = ZWorkflowState.make[PaymentState](PaymentState.Initial)

override def proceed(amount: BigDecimal, from: String, to: String): Unit = {
ZActivityStub.execute(
paymentActivity.debit(amount, from)
)

paymentState := PaymentState.Debited

ZActivityStub.execute(
paymentActivity.credit(amount, to)
)

paymentState := PaymentState.Credited
}
}

While it's allowed to use plain Scala var, it's recommended to use ZWorkflowState instead.
First, it allows to deal with uninitialized state (aka null):

Uninitialized state​

val missingState = ZWorkflowState.empty[PaymentState]

Check whenever the state is initialized​

missingState.isEmpty
// res0: Boolean = true

Second, it provides a lot of handy methods for dealing with mutable state:

Set the state​

missingState := PaymentState.Initial
missingState.snapshot
// res2: PaymentState = Initial

Update the state on certain conditions​

def condition: Boolean = true
missingState.setToIf(condition)(PaymentState.Debited)
missingState.snapshot
// res4: PaymentState = Debited

Update using partial functions​

missingState.updateWhen {
case PaymentState.Debited => PaymentState.Credited
}
missingState.snapshot
// res6: PaymentState = Credited

Equality checks:​

missingState =:= PaymentState.Credited
// res7: Boolean = true

missingState =!= PaymentState.Debited
// res8: Boolean = true