Nedir Bu Temporal.IO?

Alameddin Çelik
8 min readJan 1, 2022

--

Öncelikle merhabalar

Sizlere uzun süredir kullanmakta olduğum ve kullanırken keyif aldığım açık kaynak orkestrasyon platformu olan Temporal.io’dan bahsedeceğim.

Bunun yanında artık yazılarımı hem türkçe hem ingilizce yazmaya çalışacağım. İngilizce olarak ele aldığım yazıya aşağıdan ulaşabilirsiniz.

Yazı 3 ana başlıkta incelenecektir, Bunlar sırasıyla;

  • Tanımak: Temporal.io Nedir?, Nasıl Çalışır?
  • Anlamak: Hangi şirketler kullanıyor?, Ne zaman kullandım?
  • Öğrenmek: Golang ile basit bir workflow geliştirelim.
https://www.temporal.io

A) Tanımak

Temporal.io, mikro servisleri koordine edebilen, kod yazılarak geliştirdiğimiz ve modüler mimarisi ile parçalarını tekrar kullanabildiğimiz bir iş akışı motorudur.

Bazen sosyal ağlar gibi büyük projeler geliştirmek isteriz ama genellikle tüm akışı planlamanın planlayıp, izleyebilmenin kolay bir yolunu bulamıyoruz. Tam bu sırada temporal.io işimizi çözebilecek bir dost gibi çıkagelir.

Öncelikle Diagram ile genel yapısını görelim

Temporal.io’nun çalışması için iki parça zorunludur. İlk’i worker’ları koordine eden temporal-service ve ikincisi worker’dır.

Devam ederken, iki farklı tanım bilmeliyiz. Birincisi workflow(iş akışı), bunu zaten biliyoruz ama yine de kısaca anlatmalıyım. Workflow sayesinde planladığımız tüm akışı bilgisayara anlatabiliyoruz. Diğeriyse activity(aktivite), aktivite ise iş akışının bir adımıdır. Diğer aktivitelerden bağımsız olan ve bir ufak eylem planı olarak tanımlayabiliriz.

Temporal service Verileri tutar ve ihtiyaç duyduğunu kullanır. Temporal-service’i 4 alt servis ile tanımlayabiliriz.

Interior design of Temporal Service
  • Macthing Service: Gönderme için Görev Kuyruklarını barındırır
  • Worker Service: Dahili arka plan iş akışları için çalışır
  • History Service: Verileri tutar (değişebilir durum, kuyruklar ve zamanlayıcılar)
  • Frontend Service: Hız sınırlama, yönlendirme, yetkilendirme için çalışır

daha fazla bilgi için aşağıdaki linke tıklayabilirsiniz.

Worker’a dönecek olursak, iş akışlarının tüm aktivitelerini çalıştırabilir, herhangi bir akışla ilgili bilgiye sahip olmayan sadece temporal-service’in yap dediğini yapan servislerimizdir.

Golang, java, php veya typescript ile worker’ı geliştirebilirsiniz. Benim tercihim golang olacak bu yazıdaki örnekte ama eğer Java dilinde örnekler istiyorsanız, aşağıdaki satırda benzer örnekleri paylaşacağım

Başka bir dil kullanmak istiyorsanız resmi belgeleri okuyabilirsiniz.

Unutmadan, temporal.io’nun aşağıdaki gibi bir UI paketi var, bu sayede iş akışlarını kolayca takip edebiliyoruz. UI paketi için port açmak istemiyorsanız admin tool kullanabilirsiniz. UI paketinde gördüğümüz verilerin dışında bir çok eylemi de admin tool aracılı ile yapabiliyoruz

Temporal Web UI

Admin tool ile ilgili detaylı bilgiye aşağıdaki linkten ulaşabilirsiniz.

Temporal CLI

B) Anlamak

Geldik en önemli sorulardan biri olan “kim kullanıyor?” ‘a

Birincisi Uber;

Uber, kendisi için bir iş akışı motoru geliştiriyordu. Her müşterisinin tüm seyahatleri için çok fazla adıma sahipti ve müşterilerinin bir sürücü aramasından aylık faturalandırmaya kadar devam eden iş akışını planlayabilmesi gerekiyordu. her akış çok uzun ve karmaşıklığından dolayı bu iş akış motoru esnek olmalı ve mümkün olduğunca kolay geliştirilebilir olmalıydı. Geliştiricileri bu sorun için “Cadence” geliştirmeye başladılar.

Şimdi “bunu neden anlattı” dediğinizi duyar gibiyim. Anlatmamın asıl sebenine dönecek olursak Temporal.io Cadence’in kodları üzerinden geliştirilmiştir, yani kısaca yaklaşık olarak bu senaryoyu kaldırabilecek kapasitedir.

Cadence hakkında detaylı bilgi içinde aşağıdaki linke gidebilirsiniz

https://cadenceworkflow.io/

İkinci Ünlümüz, Netflix:

Aşağıda paylaştığım video size detaylı bilgi verecektir

Son olarak Stripe adındaki finans platformunu söylemek isterim çünkü finans uygulamaları ile başa çıkabilecek bir iş akış motoru bizim her işimizi çözebilecek potansiyele sahiptir.

Daha fazla detay ve kullanım alanı için google üzerinden araştırma yapabilirsiniz. Ben yazının daha da fazla uzamaması için şimdilik burada kesip, Kişisel deneyimlerime dönmek istiyorum.

Yaklaşık 1.5 yıl önce temporal.io kullanma fırsatını yakaladım. o zamanlar TRT’de çalışıyordum ve bir medya varlık yönetim sistemi (kısacası MAM) geliştirmek istiyorduk. Yönetim sistemlerinin çok karmaşık olduğunu biliyorsunuz. Medya yönetim sistemleri, hem insan bazlı operasyonların çok olması hem de servis bazlı gereksinimlerin gerektirdiğinden dolayı yönetim sistemlerindeki en zorlu projelerden biri olduğunu düşünmekteyim. örneğin, desteklenen tüm codec’lere ve çözünürlükler için kod dönüştürme, ana kare çıkarma, yüz algılama, konuşmayı metne dönüştürme ve hiyerarşik onayların takibi gibi bir çok olayın takip edilebilmesi ve olağan tüm senaryoların bir noktaya bağlanabilmesi gerekmektedir. Belki bunları tek başına kullanıldığımızda basit şekilde kullanabilirdik ancak biz tam bir şaheser oluşturma niyetindeydik.

En ideal yolu bulmak için biraz zaman harcamaya başlamıştık ki, temporal.io’yu orada keşfettik. Daha doğrusu geniş vizyona sahip yöneticimizi Yusuf bey sayesinde kullanma fırsatımız oldu.

Çok sayıda iş akışı oluşturduk. Yaklaşık 5 bin civarında iş akışı çalıştırmışızdır. Birkaç hafta önce oradan ayrıldım ve bu süreçte temporal.io hakkında olumsuz hiç bir şey görmedim. Bu arada bazen eski ekibimden arkadaşlarım ile görüşüyoruz. Muhabbet hep “bu temporal.io nasıl güzel şeydir” e geliyor :)

Yazıyı çok uzatmadan son kısma geçelim

C) Öğrenmek

Bence anlaşılır bir örnek ile başlamalıyız, Bazen derinden başlamak isteriz lakin nefes antrenmanı yapmadan derine gitmeye çalışmak öğreneceğimiz şeyden kormamıza neden olur ondan kolaydan başlayalım. ayrıca daha teknik bilgi isterseniz istediğiniz zaman yazabilirsiniz. Kısa süre içinde cevap vermeye gayret gösterceğim inşallah. (mail adresimi aşağıda yazdım.)

Örneklemeye başlayalım,

Bir kahve dükkanı için ödeme ve teslim iş akışı oluşturacağız, Bu nasıl olacak? İnsansız bir makine hayal edelim ve müşteriler oraya gidip ekrana dokunacak, bundan sonra iş akışımız başlayıp kahve hazırlanacak ve ödeme için beklenecek. Müşteri ödemeyi gerçekleştirince devam edip kahveyi servis edecek.

Hadi diagrama bakalım

Klasör yapımız şöyle olacak;

  • activity : Tüm aktiviteler orada tutulur.
  • client: İstemci, iş akışını başlatıcı veya işaretçi olarak tetikleyen bir Dinlenme API’sidir.
  • engine: temporal-service’i çalıştırmak için bir docker-compose.yml dosyasına sahiptir
  • signals: Tüm sinyalleri oraya yazmalıyız, örneğin ödeme sinyali
  • starters: starter’lar workflowları başlatır. bu arada önceki ekibimdeki tüm iş akışları için dinamik bir iş akışı başlatıcı kullanmıştık.
  • worker: Bunu biliyoruz, tüm faaliyetleri temporal-service‘in verdiği talimatlar ile yürütür.
  • workflows: bunlar bizim iş akışı şemalarımızdır. tüm adımları onlarda ayarlayabiliriz.

Öncelikle activitelerimizi oluşturalım

package activities

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
}

şimdi de sinyalimiz için recevier ve sender fonksiyonlarımızı oluşturalım

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
}

Artık workflow’umuzu oluşturabiliriz.

n, we should create our workflow

package workflowsimport (
"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
}

Worker’ımızı oluşturalım. Kesinlikle activity ve workflow kayıt adımını unutmamalıyız.

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)
}

}

Bu kısımda workflow group id olarak “worker-group-1” tanımlaması yaptık. bu çok önemli çünkü bir workflow çalıştıracağımız zaman bu gruba atama yapmazsak workflow’umuz çalışmayacaktır. Şimdi Starter’ı yazalım.

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)
}
}

Son olarak Rest API’yi tasarlayalım

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..."))
}

Docker-compose.yml ile temporal-service’i ayağa kaldırıyoruz.

Şimdide worker ve rest api’yi ayağa kaldıralım.

Worker Started
Client Started

Hadi ilk workflowumuzu tetikleyelim

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

Şimdide UI aracılığı ile detaylara bakalım

üzerine tıklarsak aşağıdaki gibi detayları görürüz.

şimdide ödemeyi gerçekleştirelim

daha fazla zaman harcamadan workflow’un geçmiş kayıtlarını izleyelim.

Bitirmeden önce son olarak birde worker’ın log kayıtlarını izleyip oradan da takip edebileceğimiz görelim

Yazıyı okuyup takip ettiğiniz için teşekkür ederim. Aşağıda github reposunu paylaşacağım. aklınıza takılan sorular olursa veya beyin fırtınası yapalım derseniz istediğiniz zaman yazabilirsiniz. Kişisel mail adresin alameddinc@gmail.com.

Github Reposityory Bağlantısı:

--

--