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