Skip to content

iWF Design

Quanzheng Long edited this page Apr 3, 2024 · 4 revisions

High-level design

An iWF application is composed of several iWF workflow workers. These workers host REST APIs as "worker APIs" for server to callback. The Worker APIs are exposed by the SDKs. E.g. the Java SDK.

An application also perform actions on workflow executions, such as starting, stopping, signaling, and retrieving results by calling iWF service APIs as "service APIs". The service APIs are provided by the "API service" in iWF server.

The API between client/worker and server is defined in this OpenAPI yaml.

Internally, this API service communicates with the Cadence/Temporal service as its backend. The iWF server runs the Cadence/Temporal workers as "worker service". The worker service hosts an interpreter workflow. This workflow implements all the core features as described above, and also things like "Auto ContinueAsNew" to let you use iWF without any scaling limitation.

Screenshot 2023-07-17 at 10 22 10 AM

Users define their workflow code with a new SDK “iWF SDK” and the code is running in workers that talk to the iWF interpreter engine.

The user workflow code defines a list of WorkflowState and kicks off a workflow execution.

At any workflow state, the interpreter will call back the user workflow code to invoke some APIs (waitUntil or execute). Calling the waitUntil API will return some command requests. When the command requests are finished, the interpreter will then call user workflow code to invoke the “execute” API to return a decision. The decision will decide how to complete or transitioning to other workflow states.

At any API, workflow code can mutate the data/search attributes or publish to internal channels.

Screenshot 2023-07-17 at 10 26 32 AM

Interpreter workflow pseudo code

Below is the signature of the interpreter workflow in Java. Notes Config will store configuration of the execution so that the workflow knows the end point of iwf user worker to callback user workflow code(“waitUntil” and “execute”). Input/output of this workflow are all binaries as the workflow doesn’t need to deserialize them. Similar as the activity input/output Here uses Java to demonstrate the signature because Java is more declarative. But we will use Golang to implement the workflow because the Cadence/Temporal Golang SDK is more powerful than Java SDK. Workflow thread in Golang SDK is based on goroutines which are lightweight and efficient(memory/CPU)

public interface InterpreterWorkflow{
   @WorkflowMethod
   void start(Config config, State startState, []byte startInput);
   @QueryMethod
   byte[] query(String key)    
   @SignalMethod
   void signal(String name, byte[] value)
}

Below is the implementation of the start method in “Java like” pseudo code by omitting lots of details.

public void start( State startState, byte[] startInput){
	currentStates = new Queue( new StateExecution(startState, startInput))
	while (currentStates.isNotEmpty()){
		stateExecution = currentStates.pop()
		Async.procedure( ()->{
			decision = executeState( stateExecution )
			if(decision.hasCompletedState){
				return decision.completedResult
			}else{
		 	currentStates.pushAll(decision.allNextStateExecutions)
			}
		})
     }
}

private void executeState(StateExecution currentState){
	commandRequest = executeActivity( currentState.waitUntil, input)
	commandResults = new Array()
	foreach activityCommand = commandRequest.longRunActivityCommands {
		commandResults.add ( scheduleActivity( activityCommand ) ) 
	}
	foreach timerCommand = commandRequest.timerCommands {
		commandResults.add ( scheduleTimer( timerCommand ) ) 
	}
	foreach signalCommand = commandRequest.signalCommands {
		commandResults.add ( waitFor( signalCommand ) ) 
	}
	Workflow.await( commandRequest.isReady(commandResults))
	return executeActivity( currentState.execute, input)      
}

private boolean timerReady;

private void scheduleTimer( TimerCommand timerCommand){
    timerReady = false
    Workflow.sleep( timerCommand.fireTimestamp - workflow.now() )
    timerReady = true
} 

private boolean isTimerReady(){
   return timerReady
}

Note, this is the pseudo code to outline the high-level idea. The actual implementation is probably 100x more complicated.