Temporal: Getting started with Java and Python
Introduction
In this blogpost I will demonstrate how temporal can be used to implement Workers based execution model accross different services.
Temporal provides durable execution. It maintains the state of functions, so that we can keep track of the function execution.
Functions can be registered as Workflow if they are more deterministic, or else if they have non-deterministic functionalities like http calls, they can be registered as Activity
In this example we will create a Workflow in Java which will be picked by a Python worker.
Installation
Easiest way to install Temproal is via docker-compose
Follow the below steps:
-
Clone the repo: https://github.com/temporalio/docker-compose.git
-
cd temporal/docker-compose -
docker compose up -dThe defaultdocker-compose.ymlfile usespostgres, you can use other docker compose files for using different databases that are supported eq. mysql
This will start the Temporal Cluster and some other dependencies like database and elastic-search. The cluster will listen on port 7233
It will also start the UI which can be accessed on port 8080.
Implementation
This implementation will require the following components
- Python Worker which will execute the Activity
- Java Workflow which will call for the execution of the python activity
- Workflow Starter for the Java Workeflow and another Worker in Java which will execute the workflow code.
Writing a Java Workflow
Include the temporal dependency.
If you are using maven, add below dependency to your pom.xml
<dependency>
<groupId>io.temporal</groupId>
<artifactId>temporal-sdk</artifactId>
<version>1.19.1</version>
</dependency>
Create a Workflow Interface SimpleWorkflow.java
@WorkflowInterface
public interface SimpleWorkflow {
@WorkflowMethod
String simpleActivity(String value);
}
Implement the WorkflowInterface in SimpleWorkflowImpl.java
First create an AcitvityStub.
The temporal Worker will communicate with the cluster using a task-queue and both the worker and workflow should be resgistered on the same task-queue.
ActivityOptions options =
ActivityOptions.newBuilder()
.setTaskQueue("simple-task-queue")
.build();
private final ActivityStub externalActivity = Workflow.newUntypedActivityStub(options);
Since we will create our Worker in Python, we register the Activity using newUntypedActivityStub
Next, lets implement the WorkflowMethod and call for the execution of python activity
@Override
public String SimpleActivity(String name) {
String result =
externalActivity.execute("PythonActivity", String.class, name);
return result;
}
We will name our activity in python code as PythonActivity
Writing the Workerflow Starter and Java Worker code
We will write workerflow starter and Java worker code in the same file WorkflowStarter.java
package simpleWorkflow
import io.temporal.client.WorkflowClient;
import io.temporal.client.WorkflowOptions;
import io.temporal.client.WorkflowStub;
import io.temporal.serviceclient.WorkflowServiceStubs;
import io.temporal.worker.Worker;
import io.temporal.worker.WorkerFactory;
public class WorkflowStarter {
public static final String TASK_QUEUE = "java-task-queue";
public static void main(String[] args) throws Exception {
WorkflowServiceStubs service = WorkflowServiceStubs.newLocalServiceStubs();
WorkflowClient client = WorkflowClient.newInstance(service);
WorkflowOptions options = WorkflowOptions.newBuilder()
.setWorkflowId("simple-workflow")
.setTaskQueue(TASK_QUEUE)
.build();
WorkerFactory factory = WorkerFactory.newInstance(client);
// create a new worker
Worker worker = factory.newWorker(TASK_QUEUE);
// register the java workflow
worker.registerWorkflowImplementationTypes(SimpleWorkflowImpl.class);
// start the worker
factory.start();
System.out.println("Started the Worker, starting workflow...");
SimpleWorkflow workflow = client.newWorkflowStub(SimpleWorkflow.class, options);
// execute the workflow
String result = workflow.pythonActivity(args[0]);
String workflowId = WorkflowStub.fromTyped(workflow).getExecution().getWorkflowId();
factory.shutdown();
}
}
In the above code,
- we registered the Workerflow on
java-task-queuewith locally running Temporal instance. - Started a Java worker listening on
java-task-queue - Started the Workflow. The Java worker should pick the task, execute the workflow which will in turn call the python activity.
Writing the Python Worker
Finally we need the Python worker to execute the python activity.
We will define the activity and write the worker in the same file python_worker.py
@activity.defn(name="PythonActivity")
async def simple_activity(name: str) -> str:
print(f"Hello {name}")
return "Success"
async def main():
temporal_client = await client.Client.connect("localhost:7233")
python_worker = worker.Worker(
client=temporal_client,
task_queue="simple-task-queue",
activities=[simple_activity],
)
await python_worker.run()
if __name__ == "__main__":
asyncio.run(main())
In the above code,
- we registered the worker on the locally running Temporal instance and listening on queue
simple-task--queue - We defined the activity and registered the acitvity with the worker.
Execution
In the final step we will execute the two programs,
WorkflowStarter.javapython_worker.py
In different terminals execute the programs.
First start the python worker
python python_worker.py
Next execute the java program
mvn compile exec:java -Dexec.mainClass="simpleWorkflow.WorkflowStarter" -Dexec.ar
gs="Sohit"
The execution can be monitored on localhost:8080
Conclusion
Temporal helps execute the code using workers easily. We can have multiple workers listening on the same taskQueue and temporal will take care of the distribution of the tasks.
Complete code can be found at: https://github.com/Sohit1212/temporal-polyglot