AizuDemy

Tutorial Temporal Engine & Go: Build Microservice Resilient di VPS

Tutorial Temporal Engine & Go: Build Microservice Resilient di VPS
IKLAN
IDCloudHost
๐ŸŽง
Dengarkan Artikel Ini
Suara AI Otomatis โ€ข 6 mnt baca baca
โšก TL;DR

Poin Kunci Artikel Ini:

  • Event History detail per alur eksekusi (Input, Output, State Change).
  • Stack Trace real-time workflow berjalan/terhenti.
  • Tragedi Transaksi Terdistribusi: Saat Service RontokUser tekan tombol bayar di aplikasi.
๐Ÿ“‹ Daftar Isi Materi Tutup โ–ด

Tragedi Transaksi Terdistribusi: Saat Service Rontok

User tekan tombol bayar di aplikasi. Service Payment potong saldo. Tiba-tiba jaringan ke Service Inventory drop. Stok gagal terpotong, tapi saldo user sudah berkurang.

Hasilnya database inkonsisten. Tim CS dibanjiri komplain, akhir pekan habis hanya untuk perbaiki data manual di DB. Masalah klasik microservices: transaksi tersebar di banyak service terpisah, fitur ACID bawaan database tidak berlaku lagi. Retries manual via HTTP call atau message broker justru menambah rumit kode dan memicu edge case baru.

Durable Execution via Temporal Engine

Temporal selesaikan masalah ini lewat konsep Durable Execution. Temporal menyimpan seluruh status eksekusi kode Go. Jika server mati, jaringan drop, atau worker restart, Temporal melanjutkan eksekusi dari titik terakhir sebelum failure.

Komponen Utama

  • Workflow: Fungsi Go penentu logika bisnis utama, wajib deterministik.
  • Activity: Fungsi Go eksekusi operasi berisiko seperti panggilan API, query database, atau kirim email.
  • Worker: Process Go eksekusi fungsi Workflow dan Activity via polling ke Temporal Server.
  • Temporal Server: Orchestrator pengelola status workflow, event history, dan antrean tugas.

Setup Temporal Server & Workflow Pertama di Go

Jalankan Temporal Server di lokal atau VPS dev pakai Docker Compose.

1. Konfigurasi Docker Compose

Buat file docker-compose.yml untuk menjalankan Temporal server, database PostgreSQL, dan Temporal UI:

version: '3.5'
services:
  postgresql:
    image: postgres:15-alpine
    environment:
      POSTGRES_USER: temporal
      POSTGRES_PASSWORD: temporal
      POSTGRES_DB: temporal
    ports:
      - "5432:5432"

  temporal:
    image: temporalio/auto-setup:1.24.2
    environment:
      - DB=postgres
      - DB_PORT=5432
      - POSTGRES_USER=temporal
      - POSTGRES_PWD=temporal
      - POSTGRES_SEEDS=postgresql
      - DYNAMIC_CONFIG_FILE_PATH=config/dynamicconfig/development-sql.yaml
    ports:
      - "7233:7233"
    depends_on:
      - postgresql

  temporal-ui:
    image: temporalio/ui:2.28.0
    environment:
      - TEMPORAL_ADDRESS=temporal:7233
      - TEMPORAL_AUTH_ENABLED=false
    ports:
      - "8088:8088"
    depends_on:
      - temporal

Jalankan container dengan command:

docker compose up -d

2. Inisialisasi Project Go

Buat modul Go baru dan install SDK resmi Temporal:

go mod init temporal-go-demo
go get go.temporal.io/sdk@v1.26.0

3. Koding Activity dan Workflow

Buat file activities.go untuk mendefinisikan transaksi lokal:

package main

import (
	"context"
	"fmt"
)

type PaymentRequest struct {
	OrderID string
	Amount  float64
}

func DeductPaymentActivity(ctx context.Context, req PaymentRequest) error {
	fmt.Printf("Memotong saldo untuk Order ID: %s sebesar %.2f\n", req.OrderID, req.Amount)
	// Simulasi logika pemotongan saldo DB / Payment Gateway
	return nil
}

func ReserveInventoryActivity(ctx context.Context, orderID string) error {
	fmt.Printf("Memotong stok barang untuk Order ID: %s\n", orderID)
	// Simulasi error untuk pengujian
	// return errors.New("stok barang habis")
	return nil
}

func RefundPaymentActivity(ctx context.Context, req PaymentRequest) error {
	fmt.Printf("Mengkompensasi: Mengembalikan saldo untuk Order ID: %s\n", req.OrderID)
	return nil
}

Buat file workflow.go pengatur alur transaksi:

package main

import (
	"time"

	"go.temporal.io/sdk/workflow"
)

func OrderCheckoutWorkflow(ctx workflow.Context, req PaymentRequest) error {
	ao := workflow.ActivityOptions{
		StartToCloseTimeout: 10 * time.Second,
	}
	ctx = workflow.WithActivityOptions(ctx, ao)

	err := workflow.ExecuteActivity(ctx, DeductPaymentActivity, req).Get(ctx, nil)
	if err != nil {
		return err
	}

	err = workflow.ExecuteActivity(ctx, ReserveInventoryActivity, req.OrderID).Get(ctx, nil)
	if err != nil {
		return err
	}

	return nil
}

4. Jalankan Worker Process

Buat file main.go untuk menghubungkan worker ke Temporal server:

package main

import (
	"log"

	"go.temporal.io/sdk/client"
	"go.temporal.io/sdk/worker"
)

func main() {
	c, err := client.Dial(client.Options{
		HostPort: "localhost:7233",
	})
	if err != nil {
		log.Fatalln("Gagal terhubung ke Temporal Server:", err)
	}
	defer c.Close()

	w := worker.New(c, "checkout-task-queue", worker.Options{})

	w.RegisterWorkflow(OrderCheckoutWorkflow)
	w.RegisterActivity(DeductPaymentActivity)
	w.RegisterActivity(ReserveInventoryActivity)
	w.RegisterActivity(RefundPaymentActivity)

	err = w.Run(worker.InterruptCh())
	if err != nil {
		log.Fatalln("Worker gagal berjalan:", err)
	}
}

Handling Retry Policy & Saga Pattern di Go

Dua hal wajib dikelola benar pada transaksi terdistribusi: retry otomatis saat transient error dan kompensasi pembatalan otomatis saat permanent error.

Konfigurasi Retry Policy

Temporal menyediakan fitur retry otomatis tanpa perlu loop manual. Set temporal.RetryPolicy di activity options:

retryPolicy := &temporal.RetryPolicy{
	InitialInterval:    1 * time.Second,
	BackoffCoefficient: 2.0,
	MaximumInterval:    10 * time.Second,
	MaximumAttempts:    5,
}

ao := workflow.ActivityOptions{
	StartToCloseTimeout: 10 * time.Second,
	RetryPolicy:         retryPolicy,
}
ctx = workflow.WithActivityOptions(ctx, ao)

Saga Pattern & Compensations Stack

Saga Pattern mengatasi kegagalan dengan merekam transaksi kompensasi dan mengeksekusinya secara urut terbalik (LIFO) jika ada step gagal. Implementasi di Temporal Go:

func OrderCheckoutSagaWorkflow(ctx workflow.Context, req PaymentRequest) (err error) {
	ao := workflow.ActivityOptions{
		StartToCloseTimeout: 10 * time.Second,
	}
	ctx = workflow.WithActivityOptions(ctx, ao)

	var compensations []func(workflow.Context)

	defer func() {
		if err != nil {
			// Disconnected context agar kompensasi jalan walau parent workflow batal
			dctx, _ := workflow.NewDisconnectedContext(ctx)
			for i := len(compensations) - 1; i >= 0; i-- {
				compensations[i](dctx)
			}
		}
	}()

	// Step 1: Potong Saldo
	err = workflow.ExecuteActivity(ctx, DeductPaymentActivity, req).Get(ctx, nil)
	if err != nil {
		return err
	}

	// Register kompensasi jika step lanjut gagal
	compensations = append(compensations, func(c workflow.Context) {
		_ = workflow.ExecuteActivity(c, RefundPaymentActivity, req).Get(c, nil)
	})

	// Step 2: Potong Stok Barang
	err = workflow.ExecuteActivity(ctx, ReserveInventoryActivity, req.OrderID).Get(ctx, nil)
	if err != nil {
		return err // Trigger defer untuk eksekusi kompensasi
	}

	return nil
}

Deployment VPS & Monitoring

Menjalankan Temporal Worker di VPS Ubuntu butuh konfigurasi tepat agar proses tetap jalan di background dan otomatis restart saat reboot.

1. Compiling & Deployment via Systemd

Build binary aplikasi Go:

CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o checkout-worker ./...

Upload binary checkout-worker ke VPS (contoh: /opt/app/checkout-worker). Buat file systemd di /etc/systemd/system/temporal-worker.service:

[Unit]
Description=Temporal Go Worker Service
After=network.target

[Service]
Type=simple
User=ubuntu
WorkingDirectory=/opt/app
ExecStart=/opt/app/checkout-worker
Restart=always
RestartSec=5s
Environment=TEMPORAL_HOSTPORT=localhost:7233

[Install]
WantedBy=multi-user.target

Aktifkan service:

sudo systemctl daemon-reload
sudo systemctl enable temporal-worker
sudo systemctl start temporal-worker

2. Amankan Dashboard Temporal UI

Temporal UI default berjalan di port 8088. Jangan buka port publik tanpa proteksi. Gunakan Nginx reverse proxy + HTTP Basic Authentication:

server {
    listen 80;
    server_name temporal-ui.domainanda.com;

    location / {
        auth_basic "Restricted Access";
        auth_basic_user_file /etc/nginx/.htpasswd;
        proxy_pass http://127.0.0.1:8088;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

3. Monitoring Workflow via UI

Dashboard Temporal UI (port 8088) menyediakan fitur pemantauan lengkap:

  • Event History detail per alur eksekusi (Input, Output, State Change).
  • Stack Trace real-time workflow berjalan/terhenti.
  • Tombol Terminate, Cancel, dan Reset untuk replay eksekusi workflow ke titik sebelum error tanpa deploy kode baru.

Ringkasan

Temporal Engine menghilangkan kerumitan distributed transaction. Failure handling diubah jadi logika pemrograman deterministik tanpa perlu membuat custom state machine rawan bug.

Pilih satu endpoint backend berisiko failure jaringan, refactor ke Temporal Workflow sekarang.

A
Aizu Dev

Tim penulis AizuDemy yang menyajikan tutorial teknologi, cloud, dan pengembangan perangkat lunak dalam Bahasa Indonesia.

๐Ÿ“– Artikel Terkait