Temporal ile Dayanıklı İş Akışları: Java 25 + Spring Rehberi

25 Haziran 2026 · netologist · 10 dakika, 2113 kelime ·

1. Temporal Nedir?

Temporal, dağıtık sistemlerde uzun süren, güvenilir ve durum yönetimi gerektiren iş akışlarını (workflow) kod olarak yazmanızı sağlayan açık kaynaklı bir iş akışı orkestrasyon platformudur. Sunucu çökse, ağ kesilse, servis yeniden başlasa bile, Temporal iş akışınızın kaldığı yerden devam etmesini garanti eder. Bunu, her adımı ve olayı bir “event history” olarak kalıcı depoda saklayarak yapar; bir worker öldüğünde başka bir worker aynı geçmişi “replay” ederek kaldığı yerden devam eder.

Temel Bileşenler

KavramAçıklama
Workflowİş mantığının tanımlandığı, deterministik kod
ActivityYan etkili işlemler (DB, HTTP, dosya vb.)
WorkerWorkflow ve Activity kodunu çalıştıran süreç
Task QueueWorker’ların dinlediği iş kuyruğu
Temporal ServerDurumu ve event history’yi tutan sunucu
SignalÇalışan workflow’a dışarıdan mesaj gönderme
QueryÇalışan workflow’un anlık durumunu okuma

2. Neden Temporal Kullanılır?

Geleneksel yaklaşımlarda (cron job, mesaj kuyruğu + state machine, manuel retry mantığı) şu problemler ortaya çıkar:

Temporal bu problemleri şu şekilde çözer:


3. Gerçek Hayat Kullanım Senaryoları

3.1 E-Ticaret Sipariş Süreci (Saga Pattern)

Bir sipariş; stok rezervasyonu, ödeme alma, kargo oluşturma gibi birden fazla mikroservisi kapsar. Ödeme başarısız olursa stok rezervasyonu geri alınmalıdır (compensation). Temporal, bu saga akışını tek bir workflow içinde, adım adım ve hataya dayanıklı şekilde yönetmenizi sağlar.

3.2 Ödeme İşleme ve Otomatik Yeniden Deneme

Ödeme sağlayıcısı (Stripe, iyzico vb.) geçici olarak yanıt vermeyebilir. Temporal, exponential backoff ile otomatik retry yapar; belirli hata tiplerinde (kart reddi gibi) ise retry yapmadan hemen başarısız olur.

3.3 İnsan Onayı Gerektiren Süreçler

Kredi başvurusu, masraf onayı, KYC (kimlik doğrulama) gibi günler sürebilen süreçlerde workflow, bir insan onayı (signal) gelene kadar “uyur” ve kaynak tüketmez; onay geldiğinde kaldığı yerden devam eder.

3.4 Zamanlanmış Görevler

Günlük rapor oluşturma, abonelik yenileme, periyodik veri senkronizasyonu gibi işler için Temporal’ın yerleşik cron ve schedule desteği, klasik cron job’lardan daha güvenilirdir çünkü her çalıştırmanın geçmişi ve hata durumu izlenebilir.

3.5 ETL / Veri İşleme Hatları

Büyük veri setlerini adım adım çekme, dönüştürme, yükleme işlemlerinde her adım bir Activity olarak modellenir; bir adım başarısız olursa sadece o adım yeniden denenir, tüm pipeline’ı baştan başlatmaya gerek kalmaz.

3.6 Mikroservis Orkestrasyonu

Onlarca mikroservisin katıldığı karmaşık iş süreçlerinde (örn. yeni kullanıcı onboarding’i: hesap oluşturma → e-posta doğrulama → ödeme yöntemi ekleme → hoş geldin kampanyası) Temporal, merkezi ve izlenebilir bir orkestratör görevi görür.

3.7 Bildirim ve Hatırlatma Sistemleri

“Kullanıcı 3 gün içinde işlemi tamamlamazsa hatırlatma e-postası gönder, 7 gün içinde tamamlamazsa hesabı pasif yap” gibi zaman tabanlı iş kuralları, Workflow.sleep() ile doğal şekilde ifade edilir.


4. Mimari Genel Bakış

flowchart LR A["Spring Boot
REST API Controller"] -->|"start / signal / query"| B["Temporal Server
(durum & geçmiş)"] B <-->|"poll tasks"| C["Worker (Spring)
Workflow + Activities"]

Spring Boot uygulaması WorkflowClient üzerinden Temporal Server’a bağlanır, workflow başlatır, signal/query gönderir. Ayrı (veya aynı) bir Worker süreci, ilgili Task Queue’yu dinleyerek workflow ve activity kodunu çalıştırır.


5. Kurulum

5.1 Maven Bağımlılıkları

Java 25 ve Spring Boot 3.x ile aşağıdaki bağımlılıkları ekleyin.

<properties>
    <java.version>25</java.version>
    <temporal.version>1.26.1</temporal.version>
</properties>

<dependencies>
    <dependency>
        <groupId>io.temporal</groupId>
        <artifactId>temporal-sdk</artifactId>
        <version>${temporal.version}</version>
    </dependency>
    <dependency>
        <groupId>io.temporal</groupId>
        <artifactId>temporal-spring-boot-starter</artifactId>
        <version>${temporal.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>

Not: Güncel sürüm numaralarını Maven Central üzerinden kontrol edin.

5.2 application.yml Konfigürasyonu

spring:
  temporal:
    connection:
      target: 127.0.0.1:7233
    namespace: default
    workers:
      - task-queue: order-processing-queue
    workers-auto-discovery:
      packages:
        - com.example.temporaldemo.workflows

Yerel geliştirme için Temporal Server’ı Temporal CLI (temporal server start-dev) ile veya resmi Temporal docker-compose dosyasıyla çalıştırabilirsiniz.


6. Kod Örnekleri

6.1 Sipariş Saga’sı: Workflow Arayüzü

package com.example.temporaldemo.workflows;

import io.temporal.workflow.WorkflowInterface;
import io.temporal.workflow.WorkflowMethod;
import io.temporal.workflow.SignalMethod;
import io.temporal.workflow.QueryMethod;

// Workflow arayüzü - iş akışının dış dünyaya sunduğu API
@WorkflowInterface
public interface OrderWorkflow {

    @WorkflowMethod
    OrderResult processOrder(OrderRequest request);

    // Kargo takip numarası geldiğinde workflow'a bildirim
    @SignalMethod
    void shipmentTrackingReceived(String trackingNumber);

    // Workflow'un anlık durumunu sorgulamak için
    @QueryMethod
    String getOrderStatus();
}

6.2 Workflow Implementasyonu (Saga + Compensation)

package com.example.temporaldemo.workflows;

import io.temporal.activity.ActivityOptions;
import io.temporal.common.RetryOptions;
import io.temporal.workflow.Workflow;

import java.time.Duration;

public class OrderWorkflowImpl implements OrderWorkflow {

    private String status = "STARTED";
    private String trackingNumber;

    // Retry politikası - geçici hatalarda otomatik yeniden dene
    private final RetryOptions retryOptions = RetryOptions.newBuilder()
            .setInitialInterval(Duration.ofSeconds(1))
            .setMaximumInterval(Duration.ofSeconds(30))
            .setBackoffCoefficient(2.0)
            .setMaximumAttempts(5)
            .build();

    private final ActivityOptions activityOptions = ActivityOptions.newBuilder()
            .setStartToCloseTimeout(Duration.ofSeconds(10))
            .setRetryOptions(retryOptions)
            .build();

    private final OrderActivities activities =
            Workflow.newActivityStub(OrderActivities.class, activityOptions);

    @Override
    public OrderResult processOrder(OrderRequest request) {
        status = "RESERVING_INVENTORY";
        activities.reserveInventory(request.getOrderId(), request.getItems());

        try {
            status = "CHARGING_PAYMENT";
            activities.chargePayment(request.getOrderId(), request.getAmount());
        } catch (Exception e) {
            // Ödeme başarısız oldu -> stok rezervasyonunu telafi et (compensation)
            status = "COMPENSATING";
            activities.releaseInventory(request.getOrderId(), request.getItems());
            status = "FAILED";
            return new OrderResult(request.getOrderId(), "FAILED", "Payment failed: " + e.getMessage());
        }

        status = "CREATING_SHIPMENT";
        activities.createShipment(request.getOrderId());

        // Kargo takip numarası signal ile gelene kadar bekle (gün/hafta sürebilir)
        status = "AWAITING_TRACKING";
        Workflow.await(() -> trackingNumber != null);

        status = "COMPLETED";
        return new OrderResult(request.getOrderId(), "COMPLETED", "Tracking: " + trackingNumber);
    }

    @Override
    public void shipmentTrackingReceived(String trackingNumber) {
        this.trackingNumber = trackingNumber;
    }

    @Override
    public String getOrderStatus() {
        return status;
    }
}

6.3 Activity Arayüzü ve Implementasyonu

package com.example.temporaldemo.workflows;

import io.temporal.activity.ActivityInterface;
import io.temporal.activity.ActivityMethod;
import java.util.List;

// Activity'ler yan etkili (DB, HTTP çağrısı vb.) işlemleri içerir
@ActivityInterface
public interface OrderActivities {

    @ActivityMethod
    void reserveInventory(String orderId, List<String> items);

    @ActivityMethod
    void releaseInventory(String orderId, List<String> items);

    @ActivityMethod
    void chargePayment(String orderId, double amount);

    @ActivityMethod
    void createShipment(String orderId);
}
package com.example.temporaldemo.workflows;

import org.springframework.stereotype.Component;
import java.util.List;

// Spring bean olarak tanımlanır, gerçek servis çağrılarını içerir
@Component
public class OrderActivitiesImpl implements OrderActivities {

    @Override
    public void reserveInventory(String orderId, List<String> items) {
        // Envanter servisine HTTP/gRPC çağrısı
        System.out.println("Reserving inventory for order " + orderId);
    }

    @Override
    public void releaseInventory(String orderId, List<String> items) {
        System.out.println("Releasing inventory for order " + orderId);
    }

    @Override
    public void chargePayment(String orderId, double amount) {
        // Ödeme servisine çağrı; hata fırlatırsa Temporal retry mantığını devreye sokar
        System.out.println("Charging payment for order " + orderId + ": " + amount);
    }

    @Override
    public void createShipment(String orderId) {
        System.out.println("Creating shipment for order " + orderId);
    }
}

6.4 Spring Boot Worker Konfigürasyonu

package com.example.temporaldemo.config;

import io.temporal.client.WorkflowClient;
import io.temporal.serviceclient.WorkflowServiceStubs;
import io.temporal.serviceclient.WorkflowServiceStubsOptions;
import io.temporal.worker.Worker;
import io.temporal.worker.WorkerFactory;
import com.example.temporaldemo.workflows.OrderActivitiesImpl;
import com.example.temporaldemo.workflows.OrderWorkflowImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import jakarta.annotation.PostConstruct;

@Configuration
public class TemporalConfig {

    public static final String TASK_QUEUE = "order-processing-queue";

    @Autowired
    private OrderActivitiesImpl orderActivities;

    @Bean
    public WorkflowServiceStubs workflowServiceStubs() {
        return WorkflowServiceStubs.newServiceStubs(
                WorkflowServiceStubsOptions.newBuilder()
                        .setTarget("127.0.0.1:7233")
                        .build());
    }

    @Bean
    public WorkflowClient workflowClient(WorkflowServiceStubs serviceStubs) {
        return WorkflowClient.newInstance(serviceStubs);
    }

    @Bean
    public WorkerFactory workerFactory(WorkflowClient client) {
        WorkerFactory factory = WorkerFactory.newInstance(client);
        Worker worker = factory.newWorker(TASK_QUEUE);

        // Workflow implementasyonunu kaydet
        worker.registerWorkflowImplementationTypes(OrderWorkflowImpl.class);

        // Activity bean'ini kaydet (Spring tarafından yönetilen bağımlılıklarla)
        worker.registerActivitiesImplementations(orderActivities);

        return factory;
    }

    @PostConstruct
    public void startWorkerFactory() {
        // Not: Bean sırasına bağlı olarak factory.start() ayrı bir @Bean metodunda
        // veya ApplicationRunner içinde çağrılabilir.
    }
}

Üretimde temporal-spring-boot-starter kullanırsanız worker kayıt ve başlatma işlemleri büyük ölçüde otomatik hale gelir; yukarıdaki örnek, mekanizmayı elle göstermek amacıyla verilmiştir.

6.5 REST Controller ile Workflow Başlatma

package com.example.temporaldemo.controller;

import com.example.temporaldemo.workflows.OrderRequest;
import com.example.temporaldemo.workflows.OrderWorkflow;
import io.temporal.client.WorkflowClient;
import io.temporal.client.WorkflowOptions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/orders")
public class OrderController {

    @Autowired
    private WorkflowClient workflowClient;

    private static final String TASK_QUEUE = "order-processing-queue";

    @PostMapping
    public String createOrder(@RequestBody OrderRequest request) {
        // Her sipariş için benzersiz bir workflow ID kullanılır (idempotency sağlar)
        WorkflowOptions options = WorkflowOptions.newBuilder()
                .setTaskQueue(TASK_QUEUE)
                .setWorkflowId("order-" + request.getOrderId())
                .build();

        OrderWorkflow workflow = workflowClient.newWorkflowStub(OrderWorkflow.class, options);

        // Asenkron başlat, hemen dön (workflow arka planda çalışmaya devam eder)
        WorkflowClient.start(workflow::processOrder, request);

        return "Order submitted: " + request.getOrderId();
    }

    @PostMapping("/{orderId}/tracking")
    public String submitTracking(@PathVariable String orderId, @RequestBody String trackingNumber) {
        OrderWorkflow workflow = workflowClient.newWorkflowStub(OrderWorkflow.class, "order-" + orderId);
        workflow.shipmentTrackingReceived(trackingNumber);
        return "Tracking number recorded";
    }

    @GetMapping("/{orderId}/status")
    public String getStatus(@PathVariable String orderId) {
        OrderWorkflow workflow = workflowClient.newWorkflowStub(OrderWorkflow.class, "order-" + orderId);
        return workflow.getOrderStatus();
    }
}

6.6 Zamanlanmış İş Akışı Örneği

Her gün belirli bir saatte çalışan bir rapor workflow’u.

package com.example.temporaldemo.workflows;

import io.temporal.client.WorkflowClient;
import io.temporal.client.schedules.*;
import java.time.Duration;

public class DailyReportScheduler {

    public void scheduleDailyReport(WorkflowClient client) {
        ScheduleClient scheduleClient = ScheduleClient.newInstance(client.getWorkflowServiceStubs());

        Schedule schedule = Schedule.newBuilder()
                .setAction(ScheduleActionStartWorkflow.newBuilder()
                        .setWorkflowType(ReportWorkflow.class)
                        .setArguments("daily-sales-report")
                        .setOptions(io.temporal.client.WorkflowOptions.newBuilder()
                                .setTaskQueue("report-queue")
                                .setWorkflowId("daily-report")
                                .build())
                        .build())
                // Her gün saat 06:00'da çalıştır
                .setSpec(ScheduleSpec.newBuilder()
                        .setCronExpressions(java.util.List.of("0 6 * * *"))
                        .build())
                .build();

        scheduleClient.createSchedule("daily-report-schedule", schedule, ScheduleOptions.newBuilder().build());
    }
}

6.7 Human-in-the-Loop: Onay Bekleyen Workflow

package com.example.temporaldemo.workflows;

import io.temporal.workflow.*;
import java.time.Duration;

@WorkflowInterface
public interface ExpenseApprovalWorkflow {
    @WorkflowMethod
    String submitExpense(ExpenseRequest request);

    @SignalMethod
    void approve(String approverId);

    @SignalMethod
    void reject(String approverId, String reason);
}

class ExpenseApprovalWorkflowImpl implements ExpenseApprovalWorkflow {

    private boolean approved = false;
    private boolean rejected = false;
    private String rejectionReason;

    @Override
    public String submitExpense(ExpenseRequest request) {
        // Yöneticiye bildirim gönder (Activity üzerinden)
        ExpenseActivities activities = Workflow.newActivityStub(
                ExpenseActivities.class,
                io.temporal.activity.ActivityOptions.newBuilder()
                        .setStartToCloseTimeout(Duration.ofSeconds(10))
                        .build());
        activities.notifyApprover(request);

        // Onay/red sinyali gelene kadar bekle, 3 gün içinde yanıt yoksa zaman aşımı
        boolean receivedInTime = Workflow.await(
                Duration.ofDays(3),
                () -> approved || rejected);

        if (!receivedInTime) {
            return "TIMED_OUT";
        }
        if (rejected) {
            return "REJECTED: " + rejectionReason;
        }
        return "APPROVED";
    }

    @Override
    public void approve(String approverId) {
        this.approved = true;
    }

    @Override
    public void reject(String approverId, String reason) {
        this.rejected = true;
        this.rejectionReason = reason;
    }
}

6.8 Activity’lerde Doğru Hata Yönetimi

Her hatanın yeniden denenmesi (retry) doğru değildir. Kart reddi veya geçersiz girdi gibi kalıcı hatalarda ApplicationFailure kullanarak hatayı “retry yapılamaz” olarak işaretleyin; böylece Temporal boşuna retry denemesi yapmaz, hemen başarısız olur.

package com.example.temporaldemo.workflows;

import io.temporal.failure.ApplicationFailure;

public class PaymentActivitiesImpl implements OrderActivities {

    @Override
    public void chargePayment(String orderId, double amount) {
        PaymentResult result = callPaymentGateway(orderId, amount);

        if (result.isDeclined()) {
            // Retry yapılamaz: reddedilen bir kartı tekrar denemek hiçbir zaman başarılı olmaz
            throw ApplicationFailure.newNonRetryableFailure(
                    "Card declined for order " + orderId,
                    "CardDeclined");
        }

        if (result.isGatewayTimeout()) {
            // Retry yapılabilir: geçici bir ağ/gateway sorunu, tekrar denemek güvenlidir
            throw ApplicationFailure.newFailure(
                    "Gateway timeout for order " + orderId,
                    "GatewayTimeout");
        }
    }

    private PaymentResult callPaymentGateway(String orderId, double amount) {
        // ödeme sağlayıcısına gerçek HTTP çağrısı
        return new PaymentResult();
    }
}

Belirli hata tiplerini doğrudan RetryOptions üzerinden retry dışı bırakabilirsiniz:

RetryOptions retryOptions = RetryOptions.newBuilder()
        .setInitialInterval(Duration.ofSeconds(1))
        .setMaximumAttempts(5)
        .setDoNotRetry(IllegalArgumentException.class.getName(), "CardDeclined")
        .build();

6.9 Sonu Olmayan / Çok Uzun Workflow’lar için Continue-As-New

Sonsuz döngüde çalışan workflow’lar (örn. her kullanıcı için “hep açık” bir durum makinesi veya sınırsız olay akışı işleyen bir workflow), event history’yi sıfırlamak için periyodik olarak continueAsNew çağırmalıdır. Aksi halde geçmiş sınırsız büyür ve replay işlemi yavaşlayıp maliyetli hale gelir.

package com.example.temporaldemo.workflows;

import io.temporal.workflow.Workflow;
import io.temporal.workflow.WorkflowMethod;
import io.temporal.workflow.WorkflowInterface;

@WorkflowInterface
public interface UserSessionWorkflow {
    @WorkflowMethod
    void run(UserSessionState state);
}

class UserSessionWorkflowImpl implements UserSessionWorkflow {

    private static final int MAX_EVENTS_PER_RUN = 10_000;

    @Override
    public void run(UserSessionState state) {
        int eventsProcessed = 0;

        while (eventsProcessed < MAX_EVENTS_PER_RUN) {
            // ... gelen sinyalleri/olayları işle, durumu güncelle ...
            eventsProcessed++;
        }

        // Mevcut durumu bir sonraki çalıştırmaya girdi olarak vererek geçmişi sıfırla
        Workflow.continueAsNew(state);
    }
}

6.10 Workflow Testleri

Temporal, TestWorkflowEnvironment ile bellek içinde çalışan bir Temporal sunucusu sağlar ve simüle edilmiş zamanı hızlandırmanıza olanak tanır (örn. Workflow.sleep(Duration.ofDays(3)) çağrısını milisaniyeler içinde ileri sarabilirsiniz).

package com.example.temporaldemo.workflows;

import io.temporal.testing.TestWorkflowEnvironment;
import io.temporal.testing.TestWorkflowExtension;
import io.temporal.worker.Worker;
import io.temporal.client.WorkflowClient;
import io.temporal.client.WorkflowOptions;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.*;

public class OrderWorkflowTest {

    @RegisterExtension
    public static final TestWorkflowExtension testWorkflowExtension =
            TestWorkflowExtension.newBuilder()
                    .setWorkflowTypes(OrderWorkflowImpl.class)
                    .setDoNotStart(true)
                    .build();

    @Test
    public void testSuccessfulOrder(TestWorkflowEnvironment testEnv, Worker worker, OrderWorkflow workflow) {
        // Gerçek activity yerine mock bir implementasyon kaydediyoruz
        OrderActivities mockActivities = mock(OrderActivities.class);
        worker.registerActivitiesImplementations(mockActivities);
        testEnv.start();

        OrderRequest request = new OrderRequest("order-1", java.util.List.of("item-1"), 100.0);
        OrderResult result = workflow.processOrder(request);

        verify(mockActivities).reserveInventory(eq("order-1"), any());
        verify(mockActivities).chargePayment(eq("order-1"), eq(100.0));
        assertEquals("COMPLETED", result.getStatus());
    }
}

6.11 Search Attributes ile Görünürlük (Observability)

Search Attributes, Temporal Web UI’da veya CLI üzerinden çalışan/tamamlanmış workflow’ları filtrelemenizi ve sorgulamanızı sağlar (örn. “X müşterisine ait başarısız tüm siparişleri göster”).

package com.example.temporaldemo.workflows;

import io.temporal.workflow.Workflow;
import io.temporal.common.SearchAttributeKey;

public class OrderWorkflowImpl implements OrderWorkflow {

    private static final SearchAttributeKey<String> CUSTOMER_ID_KEY =
            SearchAttributeKey.forKeyword("CustomerId");

    private static final SearchAttributeKey<Double> ORDER_AMOUNT_KEY =
            SearchAttributeKey.forDouble("OrderAmount");

    public OrderResult processOrder(OrderRequest request) {
        Workflow.upsertTypedSearchAttributes(
                CUSTOMER_ID_KEY.valueSet(request.getCustomerId()),
                ORDER_AMOUNT_KEY.valueSet(request.getAmount()));

        // ... workflow mantığının geri kalanı ...
        return null; // yer tutucu
    }
}

7. En İyi Uygulamalar


8. Kaynaklar