Introduction to Temporal

Temporal is a powerful tool for building fault-tolerant and resilient distributed systems. It allows developers to orchestrate complex business processes using workflows and activities, ensuring that tasks are executed reliably even in the face of failures. In this article, we will explore how to create a distributed task system using Go and the Temporal Go SDK.

Setting Up the Environment

Before diving into the code, you need to set up your local development environment for developing Temporal applications using Go.

  1. Install Go: Ensure you have Go installed on your machine. You can download it from the official Go website.
  2. Install Temporal CLI: The Temporal CLI is necessary for managing and running Temporal servers. You can install it using the following command:
    curl -sf https://install.temporal.io/docker | sh
    
  3. Start Temporal Server: Start the Temporal server using Docker:
    docker run -p 7233:7233 temporalio/admin-tools
    
  4. Install Temporal Go SDK: You need to install the Temporal Go SDK to interact with the Temporal server. You can do this by running:
    go get -u go.temporal.io/sdk@latest
    

Creating a Temporal Application

A Temporal application consists of four main components: Workflows, Activities, Workers, and an initiator.

1. Workflow

Workflows define the overall flow of the application and represent the orchestration aspect of the business logic. Here is an example of a simple Workflow in Go:

package app

import (
	"context"
	"fmt"
	"go.temporal.io/sdk/workflow"
)

func GreetingWorkflow(ctx workflow.Context, name string) (string, error) {
	var greeting string
	err := workflow.ExecuteActivity(ctx, "ComposeGreeting", name).Get(ctx, &greeting)
	if err != nil {
		return "", err
	}
	return greeting, nil
}

2. Activity

Activities are functions called during Workflow execution and represent the execution aspect of your business logic. Here is an example of an Activity:

package app

import (
	"context"
	"fmt"
)

func ComposeGreeting(ctx context.Context, name string) (string, error) {
	greeting := fmt.Sprintf("Hello %s!", name)
	return greeting, nil
}

3. Worker

Workers host the Activity and Workflow code and execute the code piece by piece. Here is how you can set up a Worker:

package main

import (
	"context"
	"fmt"
	"go.temporal.io/sdk/client"
	"go.temporal.io/sdk/worker"
	"log"
)

func main() {
	// Create a client
	c, err := client.NewClient(client.Options{
		HostPort: "localhost:7233",
	})
	if err != nil {
		log.Fatal(err)
	}
	defer c.Close()

	// Create a worker
	w := worker.New(c, "your-task-queue", worker.Options{})

	// Register the Workflow and Activity
	w.RegisterWorkflow(GreetingWorkflow)
	w.RegisterActivity(ComposeGreeting)

	// Start the worker
	err = w.Start()
	if err != nil {
		log.Fatal(err)
	}
}

4. Initiator

To start a Workflow, you need to send a message to the Temporal server to tell it to track the state of the Workflow. Here is an example of how to initiate a Workflow:

package main

import (
	"context"
	"fmt"
	"go.temporal.io/sdk/client"
	"log"
)

func main() {
	// Create a client
	c, err := client.NewClient(client.Options{
		HostPort: "localhost:7233",
	})
	if err != nil {
		log.Fatal(err)
	}
	defer c.Close()

	// Start the Workflow
	workflowOptions := client.StartWorkflowOptions{
		ID:        "your-workflow-id",
		TaskQueue: "your-task-queue",
	}
	we, err := c.ExecuteWorkflow(context.Background(), workflowOptions, GreetingWorkflow, "John")
	if err != nil {
		log.Fatal(err)
	}

	// Get the result of the Workflow
	var result string
	err = we.Get(context.Background(), &result)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(result)
}

Testing Your Workflow

Testing is crucial to ensure that your Workflow and Activities work correctly. Here is an example of how you can write a unit test for your Workflow:

package app

import (
	"context"
	"testing"
	"time"

	"go.temporal.io/sdk/testsuite"
)

func TestGreetingWorkflow(t *testing.T) {
	testSuite := testsuite.WorkflowTestSuite{}
	env := testSuite.NewTestWorkflowEnvironment()
	env.RegisterWorkflow(GreetingWorkflow)
	env.RegisterActivity(ComposeGreeting)

	workflowRun, err := env.ExecuteWorkflow(context.Background(), GreetingWorkflow, "John")
	if err != nil {
		t.Fatal(err)
	}

	var result string
	err = workflowRun.Get(context.Background(), &result)
	if err != nil {
		t.Fatal(err)
	}

	if result != "Hello John!" {
		t.Errorf("Expected 'Hello John!', but got '%s'", result)
	}
}

Running Your Application

To run your application, you need to start the Worker and then initiate the Workflow. Here are the steps:

  1. Start the Worker: Run the Worker code to start the Worker process.
  2. Initiate the Workflow: Run the initiator code to start the Workflow.

Conclusion

Temporal provides a robust framework for building distributed task systems that are fault-tolerant and resilient. By following the steps outlined in this article, you can create a basic Temporal application using Go, which includes Workflows, Activities, Workers, and an initiator. This setup ensures that your business processes are executed reliably and can recover from failures.

Further Reading

  • Temporal Documentation: For more detailed information on Temporal and its components, refer to the official Temporal documentation.
  • Temporal Go SDK: Explore the Temporal Go SDK for more examples and advanced features.
  • Real-World Applications: Learn how companies are using Temporal in real-world applications to manage complex workflows.

By mastering Temporal and integrating it into your Go applications, you can significantly improve the reliability and scalability of your distributed systems.