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
Alameddin Çelik 184
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
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:
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
You can access to temporal CLI package with this link.
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 “Cadance” for this problem.
I can hear you that you say “what is Cadance, why did you tell this” . Please keep calm :) because I want to say “temporal.io is improved from Cadance”. In short, we can do everything that we can do with Cadence.
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…
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:
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 adlı müşteriye kahve teslim edildi.", 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
at this point, we can try to create a workflow via endpoint
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 [email protected]
Alameddin Çelik
Github Repository