About Temporal SDKs
Temporal SDKs (software development kits) are an open source collection of tools, libraries, and APIs that enable Temporal Application development.
They offer a Temporal Client to interact with the Temporal Service, APIs to develop your Temporal Application, and APIs to run horizontally scalable Workers.
SDKs are more than just a development tool, however. The SDK APIs enable developers to write code in a particular pattern that mirrors real world processes. The SDK's internal implementation, working in collaboration with the Temporal Service, steps through that code, guaranteeing execution progression during application runtime.
Temporal Applications
A Temporal Application is the code you write, comprised of Workflow Definitions, Activity Definitions, code used to configure Temporal Clients, and code used to configure and start Workers. Developers create Temporal Applications using an official Temporal SDK.
Consider that the Workflow Definition code can be executed repeatedly. The Temporal Platform can concurrently support millions to billions of Workflow Executions, each of which representing an invoked Workflow Definition.
Additionally, a Temporal Workflow Execution is both resumable and recoverable, and it can react to external events.
- Resumable: The ability of a process to resume execution after suspending on an awaitable.
- Recoverable: The ability of a process to resume execution after suspending due to a failure.
- Reactive: The ability of a process to respond to external events.
Hence, a Temporal Application can run for seconds or years in the presence of arbitrary load and failures.
Official SDKs
What are the officially supported SDKs?
Each Temporal SDK targets a specific programming language.
- Go SDK feature guides
- Java SDK feature guides
- PHP SDK feature guides
- Python SDK feature guides
- TypeScript SDK feature guides
- .NET SDK feature guides
Despite supporting multiple languages, and supporting many features, Temporal SDKs aim to make developers feel at home in their language.
Third-party SDKs
The following third-party SDKs exist but are not supported in Temporal's documentation:
Why use a Temporal SDK?
Temporal SDKs empowers developers to concentrate on creating dependable and scalable business logic, alleviating the need to build home grown supervisor systems to ensure reliability and fault-tolerance. This is possible because the Temporal SDK provides a unified library that abstracts the intricacies of how Temporal handles distributed systems.
Development pattern
By abstracting complexities and streamlining boilerplate code, developers can craft straightforward code that directly aligns with their business logic, enhancing code readability and bolstering developer productivity.
Consider a bank loan application. Developers can design the business logic of a bank loan using the Temporal SDK. The Workflow defines the overarching business logic, encompassing tasks such as validating applicant information, credit checks, loan approval, and applicant notifications, as Activities.
The following is pseudocode. For tested samples see your language SDK's developer's guide.
func LoanApplicationWorkflow {
sdk.ExecuteActivity(CreditCheck)
sdk.ExecuteActivity(AutomatedApproval)
sdk.ExecuteActivity(NotifyApplicant)
// ...
}
For instance, Temporal SDKs have built-in support for handling failures, timeouts, and retries. In the event of an Activity failure, the SDK automatically initiates retries according to configurable policies established by the developer within the SDK. This streamlined process simplifies the integration of fault-tolerance mechanisms into applications.
The following is pseudocode. For tested samples see your language SDK's developer's guide.
func LoanApplicationWorkflow {
options = {
MaxAttempts: 3,
StartToCloseTimeout: 30min,
HeartbeatTimeout: 10min,
}
sdk.ExecuteActivity(CreditCheck, options)
sdk.ExecuteActivity(AutomatedApproval)
sdk.ExecuteActivity(NotifyApplicant)
// ...
}
Replays
Another quality of the SDKs lies in their ability to replay Workflow Executions, a complex operation that contributes significantly to the Platform's promised reliability.
The SDKs Replay code execution to continue from the last step
We will delve into this idea more later, but for now, it signifies that the SDKs can automatically continue a process from the point of interruption, should a failure occur. This capability stems from the SDK's ability to persist each step the program takes.
Temporal SDKs major components
What are the major components of Temporal SDKs?
Temporal SDKs offer developers the following:
- A Temporal Client to communicate with a Temporal Service
- APIs to develop application code (Workflows & Activities)
- APIs to configure and run Workers
Temporal SDK components create a runtime across your environment and a Temporal Service
Let's break down each one.
Temporal Client
A Temporal Client acts as the bridge for communication between your applications and the Temporal Service. The Client performs key functions that facilitate the execution of, management of, and communication with Workflows.
The most common operations that a Temporal Client enables you to perform are the following:
- Get the result of Workflow Execution.
- List Workflow Executions.
- Query a Workflow Execution.
- Signal a Workflow Execution.
- Start a Workflow Execution.
The following code is an example using the Go SDK. It showcases how to initialize a Temporal Client, create a connection to a local Temporal Service, and start a Workflow Execution:
The following code is for example purposes only. For tested code samples and best practices, use your preferred language SDK's developer's guide.
package main
import (
"context"
"go.temporal.io/sdk/client"
)
func main() {
// Temporal Client setup code
c, err := client.NewClient(client.Options{})
if err != nil {
log.Fatalln("Unable to create client", err)
}
defer c.Close()
// Prepare Workflow option and parameters
workflowOptions := client.StartWorkflowOptions{
ID: "loan-application-1",
TaskQueue: "loan-application-task-queue",
}
applicantDetails := ApplicantDetails{
// ...
}
// Start the Workflow
workflowRun, err := c.ExecuteWorkflow(context.Background(), workflowOptions, "loan-application-workflow", applicantDetails)
if err != nil {
// ...
}
// ...
}
Developers can then use the Client as the main entry point for interacting with the application through Temporal.
Using that Client, developers may for example start or Signal Workflows, Query a Workflow's state, etc.
We can see in the example above how the developer has used ExecuteWorkflow
API to start a Workflow.
APIs to Develop Workflows
Workflows are defined as code: either a function or an object method, depending on the language.
For example, the following is a valid Temporal Workflow in Go:
The following code is for example purposes only. For tested code samples and best practices, use your preferred language SDK's developer's guide.
func LoanApplication(ctx context.Context) (error) {
// ...
return nil
}
The Workflow code uses Temporal SDK APIs to orchestrate the steps of the application.
The following code is for example purposes only. For tested code samples and best practices, use your preferred language SDK's developer's guide.
func LoanApplication(ctx workflow.Context, input *LoanApplicationWorkflowInput) (*LoanApplicationWorkflowResult, error) {
// ...
var result activities.CreditCheckResult
f := workflow.ExecuteActivity(ctx, a.CreditCheck, CreditCheckInput(*input))
err := f.Get(ctx, &result)
// ...
// Return the results
return &loanApplicationResults, nil
}
A Workflow executes Activities (other functions that interact with external systems), handles and sends messages (Queries, Signals, Updates), and interacts with other Workflows.
This Workflow code, while executing, can be paused, resumed, and migrated across physical machines without losing state.
When a Workflow calls the API to execute an Activity, the Worker sends a Command back to the Temporal Service. The Temporal Service creates Activity Tasks in response which the same or a different Worker can then pick up and begin executing. In this way, the Worker and Temporal Service work together to incrementally execute Workflow code in a reliable way. We discuss this more in detail in The SDK and Temporal Service relationship section.
The SDK APIs also enable developers to write code that more genuinely maps to their process. This is because without a specialized SDK, developers might have to write a lot of boilerplate code. This can lead to code that's hard to maintain, difficult to understand, or that doesn't directly correspond to the underlying business process.
For example, the bank loan application Workflow might actually look like this:
The following code is for example purposes only. For tested code samples and best practices, use your preferred language SDK's developer's guide.
// LoanApplicationWorkflow is the workflow definition.
func LoanApplicationWorkflow(ctx workflow.Context, applicantName string, loanAmount int) (string, error) {
// Step 1: Notify the applicant that the application process has started
err := workflow.ExecuteActivity(ctx, NotifyApplicantActivity, applicantName, "Application process started").Get(ctx, nil)
if err != nil {
return "", err
}
// Step 2: Perform a credit check
var creditCheckResult string
err = workflow.ExecuteActivity(ctx, LoanCreditCheckActivity, loanAmount).Get(ctx, &creditCheckResult)
if err != nil {
return "", err
}
// Step 3: Perform an automatic approval check
var approvalCheckResult string
err = workflow.ExecuteActivity(ctx, AutomaticApprovalCheckActivity, creditCheckResult).Get(ctx, &approvalCheckResult)
if err != nil {
return "", err
}
// Step 4: Notify the applicant of the decision
var notificationResult string
err = workflow.ExecuteActivity(ctx, NotifyApplicantActivity, applicantName, approvalCheckResult).Get(ctx, ¬ificationResult)
if err != nil {
return "", err
}
return notificationResult, nil
}
The level of abstraction that APIs offer enables the developer to focus on business logic without having to worry about the intricacies of distributed computing such as retries, or having to explicitly maintain a state machine and the intermediate state for each step of the process.
Additionally, the state of the Workflow is automatically persisted so if a failure does occur, it resumes right where it left off.
APIs to create and manage Worker Processes
Workers are responsible for executing Workflow and Activity code (application code). The SDK provides APIs for configuring and starting Workers, enabling developers to control how the code is executed. Workers are horizontally scalable, often run with systems like Kubernetes, and configured according to the application's needs.
Here is an example of how you could initialize a Worker using the Go SDK.
The following code is for example purposes only. For tested code samples and best practices, use your preferred language SDK's developer's guide.
func main() {
// Create the client object just once per process
c, err := client.NewClient(client.Options{})
if err != nil {
log.Fatalln("Unable to create Temporal client", err)
}
defer c.Close()
// Create the Worker instance
w := worker.New(c, "loan-application-task-queue", worker.Options{})
// Register the workflow and activity with the worker
w.RegisterWorkflow(LoanApplicationWorkflow)
w.RegisterActivity(LoanCreditCheck)
// Start listening to the Task Queue
err = w.Run(worker.InterruptCh())
if err != nil {
log.Fatalln("Unable to start Worker", err)
}
}
The Worker polls on the specified Task Queue, processing those Tasks, and reporting the results back to the Temporal Service. They execute both the Workflows and Activities, and the SDK ensures that they perform these tasks efficiently and reliably.
APIs to customize Activity Execution behavior
Activities in Temporal are individual units of work that often represent non-deterministic parts of the code logic, such as querying a database or calling an external service. The SDK provides APIs to customize the behavior of an Activity Execution.
By default, if an Activity attempts to communicate with another system and encounters a transient failure like a network issue, Temporal ensures the Activity is tried again automatically.
However, Temporal enables developers to control a variety of timeouts, a Retry Policy, Heartbeat monitoring, and asynchronous completion.
The following code is an example of a custom set of Activity Execution options that affect the timeout and retry behavior of the execution, should the Activity encounter a failure.
The following code is for example purposes only. For tested code samples and best practices, use your preferred language SDK's developer's guide.
// LoanApplicationWorkflow is the Workflow Definition.
func LoanApplicationWorkflow(ctx workflow.Context, applicantName string, loanAmount int) (string, error) {
// ...
var creditCheckResult string
// set a Retry Policy
ao := workflow.ActivityOptions{
ScheduleToCloseTimeout: time.Hour,
HeartbeatTimeout: time.Minute,
RetryPolicy: &temporal.RetryPolicy{
InitialInterval: time.Second,
BackoffCoefficient: 2,
MaximumInterval: time.Minute,
MaximumAttempts: 5,
},
}
ctx = workflow.WithActivityOptions(ctx, ao)
err = workflow.ExecuteActivity(ctx, LoanCreditCheckActivity, loanAmount).Get(ctx, &creditCheckResult)
if err != nil {
return "", err
}
// ...
return notificationResult, nil
}
// LoanCreditCheckActivity is an Activity function that performs a credit check.
func LoanCreditCheckActivity(ctx context.Context, loanAmount int) (string, error) {
// ... your logic here ...
return "Credit check passed", nil
}
The SDK and Temporal Service relationship
How do the Temporal SDKs work with the Temporal Service?
The Temporal Service functions more as a choreographer than a conductor. Rather than directly assigning tasks to Workers, the Temporal Service arranges the Tasks into a Task Queue while Workers poll the Task Queue. Developers may create a fleet of Workers and tune them so that a Task is picked up as soon as it is available. If a Worker goes down, Tasks can wait until the next Worker is available.
A Workflow might request to execute an Activity, start a Timer, or start a Child Workflow, each of which translates into a Command, dispatched to the Temporal Service. In addition to acting on these Commands, the Temporal Service documents that interaction by appending their corresponding Events into to the Workflow Execution's Event History.
Take for instance the call to execute an Activity. When a Workflow invokes it, the Worker doesn't immediately execute that Activity code. Instead, it generates a ScheduleActivityTask Command, dispatching it to the Cluster. In response, the Cluster queues up a new Activity Task. Only when a Worker finds itself free, it collects the task and begins executing the Activity code.
The Temporal Service persists Workflow Execution Event History, so that if there is a failure, the SDK Worker is able to Replay the execution and resume where it left off.
This is where the deterministic constraints of the Workflow code comes into play, requiring the use of Activities to create side effects and interact with the outside world.
Let's look at an example Workflow with a single Activity.
func LoanApplication(ctx workflow.Context, input *LoanApplicationWorkflowInput) (*LoanApplicationWorkflowResult, error) {
ctx = workflow.WithActivityOptions(ctx, workflow.ActivityOptions{
StartToCloseTimeout: time.Minute,
})
var result activities.NotifyApplicantActivityResult
f := workflow.ExecuteActivity(ctx, a.NotifyApplicantActivity, NotifyApplicantActivityInput(*input))
err := f.Get(ctx, &result)
// Return the results
return &l.LoanApplicationState, nil
}
type Activities struct {}
func (a *Activities) NotifyApplicantActivity(ctx context.Context, input *NotifyApplicantActivityInput) (*NotifyApplicantActivityResult, error) {
var result NotifyApplicantActivityResult
// Call the thirdparty API and handle the result
return &result, err
}
The Activity above is performing a single call to an external API. Since the call can fail due to transient issues, we define it outside of the Workflow and provide it with retry options.
When you create a new Worker process, the Worker creates a long-lasting connection to the Temporal Service, polling a Task Queue for Tasks that related to the code it is capable of executing.
A Worker long polls for Tasks
Although the Worker is now running, unless a Workflow is explicitly started, the Task Queue doesn't have any Tasks on it and so, no code executes. We can use a Temporal Client (available in Temporal SDKs and the Temporal CLI) to start a new Workflow.
Start a Workflow using a Temporal Client
Starting a Workflow Execution creates a new Event, WorkflowExecutionStarted, and adds it to the Workflow Execution's Event History.
The Temporal Service then schedules a Workflow Task by adding it to the Task Queue. When the Worker has capacity, it picks up this Task, and begin executing code.
Each step of the Task (e.g. Scheduled, Started, and Completed), gets recorded into the Event History.
- Scheduled means that the Temporal Service has added a Task to the Task Queue.
- Started means that the Worker has dequeued the Task.
- Completed means that the Worker finished executing the Task by responding to the Temporal Service.
When the call to invoke the Activity is evaluated, the Worker suspends executing the code and sends a Command to the Temporal Service to schedule an Activity Task.
Worker suspends code execution and sends a Command to the Temporal Service
When the Worker process can perform more work, it picks up the Activity Task and begins executing the Activity code, which includes the call to the external API.
If the Activity fails, say the API goes down, Temporal will automatically retry the Activity with one second between intervals, as the configurations have defined, an infinite amount of times until the Activity succeeds or is canceled.
In the case where the calls succeeds, and the code completes, the Worker tells the Temporal Service the Activity Task completed.
The Worker reports that the Activity Execution completed
Included is any data that was returned from the Activity (results of the API call), which is then persisted in the Workflow Execution Event History, and is now accessible to the Workflow code.
The Temporal Service creates a new Workflow Task which the Worker picks up.
The Worker picks up the new Task
This is when the SDK Worker Replays the Workflow code, uses the Event History as guidance on what to expect. If the Replay encounters an Event that doesn't match up with what is expected from the code, a non-determinism error gets thrown.
If there is alignment, the Worker continues evaluating code.
Assuming the Activity Execution is successful, the Workflow now has the result of the Activity and the Worker is able to finish evaluating and executing the Workflow code, responding to the Temporal Service when complete.
The result of the Workflow can now be retrieved using a Temporal Client.
The Temporal Client can now access the result of the Workflow
And that’s how a Temporal Worker and Temporal Service work together.