Nedir Bu Temporal.IO?
Ö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.
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.
- 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
Admin tool ile ilgili detaylı bilgiye aşağıdaki linkten ulaşabilirsiniz.
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
İ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.
Hadi ilk workflowumuzu tetikleyelim
Ş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ı: