Temporal ile Dayanıklı İş Akışları: Java 25 + Spring Rehberi
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
| Kavram | Açıklama |
|---|---|
| Workflow | İş mantığının tanımlandığı, deterministik kod |
| Activity | Yan etkili işlemler (DB, HTTP, dosya vb.) |
| Worker | Workflow ve Activity kodunu çalıştıran süreç |
| Task Queue | Worker’ların dinlediği iş kuyruğu |
| Temporal Server | Durumu 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:
- Süreç ortasında sunucu çökerse durum kaybolur.
- Retry/backoff mantığını her yerde elle yazmanız gerekir.
- Günler/haftalar süren süreçlerde (örn. onay bekleyen kredi başvurusu) durumu bir veritabanında saklamak ve senkronize etmek karmaşıklaşır.
- Dağıtık işlemlerde (saga pattern) telafi (compensation) mantığı elle yönetilmesi gereken kırılgan bir hal alır.
Temporal bu problemleri şu şekilde çözer:
- Durability: Workflow durumu otomatik olarak kalıcıdır, kod çökse bile kaybolmaz.
- Otomatik retry: Activity’ler için retry policy tanımlarsınız, Temporal gerisini halleder.
- Kod olarak orkestrasyon: Karmaşık iş akışlarını if/else, döngü, try/catch ile normal Java koduymuş gibi yazarsınız.
- Görünürlük: Temporal Web UI üzerinden her workflow’un tam geçmişini (hangi adım ne zaman çalıştı, hangi hata alındı) görebilirsiniz.
- Ölçeklenebilirlik: Binlerce/milyonlarca eşzamanlı workflow instance’ı yönetebilir.
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ış
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-starterkullanı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
- Workflow kodu deterministik olmalıdır: içinde
System.currentTimeMillis(),Random, doğrudan HTTP çağrısı gibi yan etkiler kullanılmamalıdır - bunun yerine Activity kullanın. - Uzun süren bekleyişler için
Thread.sleep()değil,Workflow.sleep()veyaWorkflow.await()kullanın. - Activity’lere idempotent davranacak şekilde tasarlayın; retry’lerde aynı işlemin tekrar tekrar çalışabileceğini unutmayın.
- Workflow ID’lerini iş mantığınıza uygun ve benzersiz seçin (örn.
order-12345); bu doğal bir idempotency sağlar. - Versiyonlama (
Workflow.getVersion) kullanarak, üretimde çalışan workflow’ları etkilemeden kod değişikliği yapın. - Retry yapılabilir hatalarla kalıcı hataları ayırt etmek için
ApplicationFailurekullanın - asla başarılı olmayacak hatalarda Temporal’ın retry hakkını boşa harcamasına izin vermeyin. - Sonsuz döngüde çalışan workflow’larda event history’nin sınırsız büyümesini önlemek için periyodik olarak
continueAsNewçağırın. TestWorkflowEnvironmentile birim testleri yazın - bu,Workflow.sleep()ile günler/haftalar süren beklemeleri milisaniyeler içinde simüle etmenizi sağlar; zaman aşımı ve hatırlatma mantığını test etmek için gereklidir.- Önemli iş alanlarına (müşteri ID, sipariş tutarı, bölge vb.) Search Attributes ekleyerek Temporal Web UI üzerinden workflow’ları etkin şekilde sorgulayın ve izleyin.
8. Kaynaklar
- Temporal Resmi Dokümantasyon: https://docs.temporal.io
- Temporal Java SDK: https://github.com/temporalio/sdk-java
- Temporal Spring Boot Starter: https://github.com/temporalio/sdk-java/tree/master/temporal-spring-boot-autoconfigure
- Temporal Samples (Java): https://github.com/temporalio/samples-java