What is Temporal.io?

Alameddin Çelik
10 min readJan 1, 2022

--

Hi everybody
First of all, this is my first english article so If I will make a mistake, I’m sorry at now :)

Note: If you want to read this article in Turkish, you can click on the link below

Let’s get start ;)

Today, I would like to tell you about temporal.io in details.
Before that, I would like to divide it into sections as follows to write an intelligible article.

  1. Introduction : What is temporal.io? How is It works?
  2. Understanding: Which companies use it?, When did I used?
  3. Learning: How can we develop workflow with it?
https://www.temporal.io

1. Introduction

Temporal.io is a workflow engine that able to coordinate microservices and can be developed by writing code and we can use its parts over and over again with a modular architecture.

Sometimes, We want to develop big projects like a social network or a platform and We have to handle a lof of complex case but We usually can’t find easily way to plan all cases and track all flows. Just at this time, temporal.io comes like a hero and help us:)

If I want to tell that how is it works? I can show a diagram to you like below:

Temporal.io requires two parts to work, first one temporal-service, other part is worker.

By the way we should know two different definetion. First one is workflow, we know it clearly, I should still tell shortly. we tell all steps of stories(or cases) to computer with this. Other one is the activity, activity is a step of the workflow. They must independent with other activities and we can define as they are micro-action packages.

Temporal-service is cordinate workers, It keeps data and uses it needed. We should see four sub-service to created temporal-service , It’s like below:

Interior design of Temporal Service
  1. Macthing Service: It hosts Task Queues for dispatching
  2. Worker Service: It works for internal background workflows
  3. History Service: It maintains data (mutable state, queues, and timers)
  4. Frontend Service: It works for rate limiting, routing, authorizing

If you want more information about these, You can click the link below,

Second part is worker, worker can run all events of workflows, It hasn’t a memory to keep workflow status or happened previous events. They only do that what is temporal-service want it.

We can develop workers with golang, java, php or typescript. I prefer to use golang on this article. If you want to example java codes, you should go to documentation, I will share below

If you want to use another language, you can read the official documentation. It has really good documentation.

Before I forget, temporal.io have an UI package like below, so We can track workflows easily with this. If you don’t want to open a port for UI package, you can use admin tools. We can see everything that when we used UI and we can run a lot of actions with admin tool.

Temporal Web UI

You can access to temporal CLI package with this link.

Temporal CLI

2. Understanding

We came to the most important question :) Who is use it?

First one Uber;

Uber has created a workflow engine for itself. It has a lot of steps for all travels of every customers. We can imagable workflow that It continues from customers search a driver to mounthly billing. each flow was very long and complex and this system should be flexible and can be developed as easily as possible. Its developers has started to develop “Cadence” for this problem.

I can hear you that you say “what is Cadence, why did you tell this” . Please keep calm :) because I want to say “temporal.io is forked from Cadence”. In short, we can do a lot of things that we can do with Cadence

By the way, If you want to know more information about Cadence, you can enter below

https://cadenceworkflow.io/

Second one Netflix:

I think you should watch the following video so I don’t need tell more thing about it :)

Last one of my say, Stripe:

Stripe is very importand for our article because If a workflow engine can handle an economical portal, It can usually everything.

You can find more example project with temporal.io when search on google. but I think, I should not continue to tell example for not to bore you

I would like to share my experience under this subject. This article can not be effective and helpful enough, if I don’t tell my experience and even if i give a lot of examples

Let’s continue, I could catch to opportunity of use temporal.io about 1.5 years ago. at that time, I was working at Turkish Radio and Television Corparation(TRT) and we wanted to develop a media asset management system. You know that management systems are very complex. media management is one of the most challenging projects in the management systems, as it greatly required both people-based operations and service-based operations. for example transcoding for all supported codecs and resolutions, keyframes extracting, face detection, convertation speech to text and hierarchical approvals etc. Maybe all of these are simple cases when using alone but when we wanted to use together. It will act as a hero.

And, we spent a lot of time to find best way. In that time, We found temporal.io, (I don’t want to say lie, We didn’t find it, Our manager found and suggested us, He is a hero :))

We used a lot of workflows and run it about 5k workflows. I left there before a few weeks ago and I didn’t see any negative things about temporal.io. By the way I talk my previous team sometimes, when we talks, They always say “How can it be so successful ?” (Its a true story :))

without any further delay, I want to go to next section…

3. Learning

We should create an understandable example and I think we shouldn’t want to dive the deepest point as first example, also If you want to more technical information, you can write indepently of time. You can sure that I reply you as soon as possible

Let’s start to example,

We will create a pay and deliver workflow for a coffee shop. What is this? You can imagable an automachine without human and customers go there and touch to screen, after that our workflow starts and prepare the coffee, after that workflow waits for payment. When customer paid, It serves coffee to him or her…

First of all, we should see how is our workflow running with diagram.

We can see folders’ structure below:

  • activities : All activities are kept under there
  • client: Client is an Rest API to trigger workflow as starter or signaler
  • engine: It has a docker-compose.yml to run temporal-service
  • signals: We should write all signals in there, for example payment signal
  • starters: starters can start workflow. by the way we used an dynamic workflow starter for all workflows on my previous team.
  • worker: We know it, It run all activities when temporal-service instructs them
  • workflows: they are our workflow schemas. we can set all steps in there

First steps of develop workflow is creating activities that we use in workflow, you can read codes below

package activitiesreciver

import (
"context"
"log"
)

func PrepareCoffee(ctx context.Context) error {
log.Println("Coffee is preparing...")
return nil
}

func GiveCoffee(ctx context.Context, customerName string) error {
log.Printf("%s took coffee", customerName)
return nil
}

func WriteAsDept(ctx context.Context, customerName string) error {
log.Printf("%s borrowed a coffee", customerName)
return nil
}

and we create payment signal’s trigger methods as receiver and sender

package signals

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

const (
PAYMENT_SIGNAL = "payment_signal"
)

func SendPaymentSignal(workflowID string, paymentStatus bool) (err error) {
temporalClient, err := client.NewClient(client.Options{})
if err != nil {
log.Fatalln("Unable to create Temporal client", err)
return
}

err = temporalClient.SignalWorkflow(context.Background(), workflowID, "", PAYMENT_SIGNAL, paymentStatus)
if err != nil {
log.Fatalln("Error signaling client", err)
return
}

return nil
}

func ReciveSignal(ctx workflow.Context, signalName string) (paymentStatus bool){
workflow.GetSignalChannel(ctx, signalName).Receive(ctx, &paymentStatus)
return
}

After that, we should create our workflow

package workflows

import (
"github.com/alameddinc/temporal-workflow-golang-example/activities"
"github.com/alameddinc/temporal-workflow-golang-example/signals"
"go.temporal.io/sdk/workflow"
"log"
"time"
)

func CoffeeShopWorkflow(ctx workflow.Context, customerName string) error {
// We set our activity options with ActivtiyOptions
// if we want to use childworkflow and if we want to set custom settings for that
// we should use ChildWorkflowOptions like that.
ao := workflow.ActivityOptions{
StartToCloseTimeout: 50 * time.Second,
ScheduleToCloseTimeout: 100 * time.Second,
}
ctx = workflow.WithActivityOptions(ctx, ao)
// the workflow is preparing coffee with PrepareCoffee activity
workflow.ExecuteActivity(ctx, activities.PrepareCoffee, nil).Get(ctx, nil)
workflow.Sleep(ctx, 5 * time.Second)
log.Println("Coffee is ready to serve")
// Customer paid bill
if status := signals.ReciveSignal(ctx, signals.PAYMENT_SIGNAL); !status{
log.Println("Payment couldn't be completed! ")
// We sent customerName to WriteAsDept activity for It can write an dept to him
workflow.ExecuteActivity(ctx, activities.WriteAsDept, customerName).Get(ctx, nil)
}
// Customer took coffee
workflow.ExecuteActivity(ctx, activities.GiveCoffee, customerName).Get(ctx, nil)
return nil
}

We should definitely not forget to register the workflow and activities to the worker.

package main

package main

import (
"github.com/alameddinc/temporal-workflow-golang-example/activities"
"github.com/alameddinc/temporal-workflow-golang-example/workflows"
"go.temporal.io/sdk/client"
"go.temporal.io/sdk/worker"
"log"
)

func main() {
log.Println("Worker Starting...")
opt := client.Options{
HostPort:client.DefaultHostPort,
}
c, err := client.NewClient(opt)
if err != nil {
log.Fatalln("unable to create Temporal client", err)
}
defer c.Close()
w := worker.New(c, "worker-group-1", worker.Options{})
w.RegisterWorkflow(workflows.CoffeeShopWorkflow)
w.RegisterActivity(activities.PrepareCoffee)
w.RegisterActivity(activities.WriteAsDept)
w.RegisterActivity(activities.GiveCoffee)
if err := w.Run(worker.InterruptCh()); err != nil{
log.Fatalln(err)
}

}

In here, we defined `worker-group-1` to workflow group id. It’s very important because when we will start a workflow, we have to use it to assign to worker, after then we create a starter like below

package starters

import (
"context"
"github.com/alameddinc/temporal-workflow-golang-example/workflows"
"go.temporal.io/sdk/client"
)

func StartWorkflowFunc(workflowID string, customerName string){
c, err := client.NewClient(client.Options{})
if err != nil {
panic(err)
}
defer c.Close()
opt := client.StartWorkflowOptions{
ID: workflowID,
TaskQueue: "worker-group-1",
}
ctx := context.Background()
if _, err := c.ExecuteWorkflow(ctx, opt, workflows.CoffeeShopWorkflow, customerName); err != nil{
panic(err)
}
}

Lastly, we need to create trigger Rest API

package main

import (
"github.com/alameddinc/temporal-workflow-golang-example/signals"
"github.com/alameddinc/temporal-workflow-golang-example/starters"
"github.com/google/uuid"
"github.com/gorilla/mux"
"log"
"net/http"
)

func main() {
r := mux.NewRouter()
r.HandleFunc("/about", GetAbout)
r.HandleFunc("/start-workflow", StartWorkflow)
r.HandleFunc("/send-signal/{workflowID}", SendSignal)
log.Fatal(http.ListenAndServe(":3310", r))
}

func SendSignal(writer http.ResponseWriter, request *http.Request) {
vars := mux.Vars(request)
workflowId := vars["workflowID"]
if err := signals.SendPaymentSignal(workflowId, true); err != nil {
writer.Write([]byte(err.Error()))
return
}
writer.Write([]byte("Sent it"))
return
}

func StartWorkflow(writer http.ResponseWriter, request *http.Request) {
workflowUUID, err := uuid.NewUUID()
if err != nil {
writer.Write([]byte(err.Error()))
return
}
starters.StartWorkflowFunc(workflowUUID.String(), "alameddin")
writer.Write([]byte("OK"))
}

func GetAbout(writer http.ResponseWriter, request *http.Request) {
writer.Write([]byte("It's running..."))
}

Now, we can run docker-compose.yml to start Temporal-Service.

We need to run worker and our rest-api

Worker Started
Client Started

at this point, we can try to create a workflow via endpoint

by the way If you think why we used 3310 to port number, Its for respect to Nokia :)

Let’s go to Web UI and see started workflow’s details

We can see details with click on it

We will trigger payment endpoint with workflow id

without spend more time, I should show history on Web UI

we can see signal and activities.

Finally, I should show worker’s terminal logs, You can track your workflows with worker logs. by the way If you have multiple workers, do not forget workers work asynchron,

Thanks for everything :) I will share github link below of article. If you have any question or you want to do a mind storm, you can send an email to me, My personal email address is alameddinc@gmail.com

Alameddin Çelik

Github Repository

--

--