Use Temporal with Encore
Temporal is a workflow orchestration system for building highly reliable systems. Encore works great with Temporal, and this guide shows you how to integrate Temporal into your Encore application.
Set up Temporal clusters
You'll need at least two Temporal clusters: one for local development and one for cloud environments.
We recommend using Temporalite for local development, and Temporal Cloud for cloud environments.
Set up Temporal Workflow
Next it's time to create a Temporal Workflow. We'll base this on the Temporal Hello World example.
Create a new Encore service named greeting
:
greeting/greeting.gopackage greeting
import (
"context"
"fmt"
"go.temporal.io/sdk/client"
"go.temporal.io/sdk/worker"
"encore.dev"
)
// Use an environment-specific task queue so we can use the same
// Temporal Cluster for all cloud environments.
var (
envName = encore.Meta().Environment.Name
greetingTaskQueue = envName + "-greeting"
)
//encore:service
type Service struct {
client client.Client
worker worker.Worker
}
func initService() (*Service, error) {
c, err := client.Dial(client.Options{})
if err != nil {
return nil, fmt.Errorf("create temporal client: %v", err)
}
w := worker.New(c, greetingTaskQueue, worker.Options{})
err = w.Start()
if err != nil {
c.Close()
return nil, fmt.Errorf("start temporal worker: %v", err)
}
return &Service{client: c, worker: w}, nil
}
func (s *Service) Shutdown(force context.Context) {
s.client.Close()
s.worker.Stop()
}
Next it's time to define some workflows. These need to be in the same service,
so add a new workflow
package inside the greeting
service, containing
a workflow and activity definition in separate files:
greeting/workflow/workflow.gogreeting/workflow/activity.gopackage workflow
import (
"time"
"go.temporal.io/sdk/workflow"
)
func Greeting(ctx workflow.Context, name string) (string, error) {
options := workflow.ActivityOptions{
StartToCloseTimeout: time.Second * 5,
}
ctx = workflow.WithActivityOptions(ctx, options)
var result string
err := workflow.ExecuteActivity(ctx, ComposeGreeting, name).Get(ctx, &result)
return result, err
}
Then, go back to the greeting
service and register the workflow and activity:
greeting/greeting.go// Import the package at the top:
import "encore.app/greeting/workflow"
// Add these lines to `initService`, below the call to `worker.New`:
w.RegisterWorkflow(workflow.Greeting)
w.RegisterActivity(workflow.ComposeGreeting)
Now let's create an Encore API that triggers this workflow.
Add a new file greeting/greet.go
:
greeting/greet.gopackage greeting
import (
"context"
"encore.app/greeting/workflow"
"encore.dev/rlog"
"go.temporal.io/sdk/client"
)
type GreetResponse struct {
Greeting string
}
//encore:api public path=/greet/:name
func (s *Service) Greet(ctx context.Context, name string) (*GreetResponse, error) {
options := client.StartWorkflowOptions{
ID: "greeting-workflow",
TaskQueue: greetingTaskQueue,
}
we, err := s.client.ExecuteWorkflow(ctx, options, workflow.Greeting, name)
if err != nil {
return nil, err
}
rlog.Info("started workflow", "id", we.GetID(), "run_id", we.GetRunID())
// Get the results
var greeting string
err = we.Get(ctx, &greeting)
if err != nil {
return nil, err
}
return &GreetResponse{Greeting: greeting}, nil
}
Run it locally
Now we're ready to test it out. Start up temporalite
and your Encore application (in separate terminals):
$ temporalite start --namespace default $ encore run
Now try calling it, either from the Local Development Dashboard or using cURL:
$ curl 'http://localhost:4000/greeting/Temporal'
{"Greeting": "Hello Temporal!"}
If you see this, it works!
Run in the cloud
To run it in the cloud, you will need to use Temporal Cloud or your own, self-hosted Temporal cluster. The easiest way to automatically pick up the correct cluster address is to use Encore's config functionality.
Add two new files:
greeting/config.gogreeting/config.cuepackage greeting
import "encore.dev/config"
type Config struct {
TemporalServer string
}
var cfg = config.Load[*Config]()
Finally go back to greeting/greeting.go
and update the client.Dial
call to look like:
greeting/greeting.goclient.Dial(client.Options{HostPort: cfg.TemporalServer})
With that, Encore will automatically connect to the correct Temporal cluster, using a local cluster for local development and your cloud-hosted cluster for everything else.