Executing Logic
Workflows4s supports two ways of embedding custom logic within a workflow:
- Deterministic (Pure) Operations - operations that always produce the same output for the same input, without any side effects
- Nondeterministic Operations - operations that may produce different outputs for the same input, typically involving side effects
Deterministic operations are executed with each workflow recovery, while nondeterministic ones are memoized through event persistance.
Deterministic (Pure) Operations
Without Error
val doThings: WIO[Any, Nothing, MyState] =
WIO.pure(MyState(1)).autoNamed()
val doThings2: WIO[MyState, Nothing, MyState] =
WIO.pure
.makeFrom[MyState]
.value(state => MyState(state.counter))
.autoNamed()
Rendering Outputs
- Flowchart
- BPMN
- Model
- Debug
{
"meta" : {
"name" : "Do Things",
"error" : null,
"description" : null
},
"_type" : "Pure"
}
[Pure](Do Things)
With Error
val doThingsWithError: WIO[Any, MyError, Nothing] =
WIO.pure.error(MyError()).autoNamed()
val doThingsWithError2: WIO[MyState, MyError, Nothing] =
WIO.pure
.makeFrom[MyState]
.error(_ => MyError())
.autoNamed()
Rendering Outputs
- Flowchart
- BPMN
- Model
- Debug
{
"meta" : {
"name" : "Do Things With Error",
"error" : {
"name" : "My Error"
},
"description" : null
},
"_type" : "Pure"
}
[Pure](Do Things With Error)
Unnamed pure elements are considered technical in nature and are not rendered in diagrams.
To make them visible, use .named("Name") or .autoNamed.
Nondeterministic Operations (Side Effects)
Nondeterministic operations involve side effects like API calls, database operations, or other external interactions. These operations need special handling because they shouldn't be re-executed during workflow recovery.
The effect type used in runIO is determined by the Effect type in your WorkflowContext. While the method is
named runIO, it works with any effect type — cats.effect.IO, Future, zio.Task, Try, etc.
Without Error
val doThings: WIO[MyState, Nothing, MyState] =
WIO
.runIO[MyState](state => IO(MyEvent()))
.handleEvent((state, event) => MyState(state.counter + 1))
.autoNamed()
Rendering Outputs
- Flowchart
- BPMN
- Model
- Debug
{
"meta" : {
"name" : "Do Things",
"error" : null,
"description" : null
},
"_type" : "RunIO"
}
[RunIO](Do Things)
With Error
val doThingsWithError: WIO[MyState, MyError, MyState] =
WIO
.runIO[MyState](state => IO(MyEvent()))
.handleEventWithError((state, event) =>
if true then MyState(state.counter + 1).asRight
else MyError().asLeft,
)
.autoNamed()
Rendering Outputs
- Flowchart
- BPMN
- Model
- Debug
{
"meta" : {
"name" : "Do Things With Error",
"error" : {
"name" : "My Error"
},
"description" : null
},
"_type" : "RunIO"
}
[RunIO](Do Things With Error)
Errors render in flowchart only when they are handled
With Description
You can add descriptions to RunIO operations to provide additional context in diagrams and documentation:
val doThingsWithDescription: WIO[MyState, Nothing, MyState] =
WIO
.runIO[MyState](state => IO(MyEvent()))
.handleEvent((state, event) => MyState(state.counter + 1))
.autoNamed(description = "This operation increments the counter by one")
Rendering Outputs
- Flowchart
- BPMN
- Model
- Debug
{
"meta" : {
"name" : "Do Things With Description",
"error" : null,
"description" : "This operation increments the counter by one"
},
"_type" : "RunIO"
}
[RunIO](Do Things With Description)
Drafting support
Executing logic comes with drafting support.
val basic = WIO.draft.step()
val withError = WIO.draft.step(error = "MyError")