Skip to content
Β 
Β 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

79 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Loyalty Points Program - Temporal Workflow Demo

License Build Status

Demonstration of Temporal patterns through a customer loyalty program

This project illustrates the implementation of core Temporal patterns (Entity Workflow, Update Methods, Queries, Saga) in a realistic business context: a loyalty points system with tier management (Basic β†’ Gold β†’ Platinum), point accumulation/redemption, and grace period upon program departure.

Customer Page

🎯 Why This Project?

Temporal excels at managing long-running business workflows, and this project demonstrates:

  • Entity Workflow Pattern: One workflow per customer, living for years
  • Update Methods: State modification with transactional guarantees
  • Queries: Synchronous workflow state reads
  • Saga Pattern: Automatic compensation on failure (point spending)
  • Resilience: Chaos injection to test robustness
  • Multi-language: Go, Java, Python, TypeScript in a single system

πŸ—οΈ Architecture

graph TD
    Gateway["Gateway<br/>(Caddy :8080)"]

    Dashboard["Dashboard<br/>(Vue/Nuxt)"]
    ChaosService["Chaos Service<br/>(Java/Spring)"]
    ChaosEngine["Chaos Engine<br/>(Toxiproxy)"]

    CustomerService["Customer Service<br/>(Go)"]
    LoyaltyService["Loyalty Service<br/>(Java/Temporal)"]
    EmailService["Email Service<br/>(Python/FastAPI)"]
    RedemptionService["Redemption Service<br/>(TypeScript/Node.js)"]

    Redis[("Redis")]
    Temporal[("Temporal Server")]

    Gateway -->|"/*"| Dashboard
    Gateway -->|"/api/chaos/*"| ChaosService
    Gateway -->|"/api/*"| ChaosEngine

    ChaosService --> ChaosEngine

    ChaosEngine -->|"proxies"| CustomerService
    ChaosEngine -->|"proxies"| LoyaltyService
    ChaosEngine -->|"proxies"| EmailService
    ChaosEngine -->|"proxies"| RedemptionService

    CustomerService --> Redis

    LoyaltyService -->|"orchestrates"| Temporal
    LoyaltyService -.->|"activity: sendEmail"| EmailService
    LoyaltyService -.->|"activity: processRedemption"| RedemptionService
Loading

Components

Service Technology Role
Dashboard Vue.js + Nuxt (static) User interface
Gateway Caddy Unified reverse proxy
Loyalty Service Java + Spring Boot + Temporal SDK Loyalty workflows (core logic)
Customer Service Go + Redis Customer CRUD
Email Service Python + FastAPI Notifications (mock)
Redemption Service TypeScript + Node.js Redemption processing (mock)
Chaos Service Java + Spring Boot Chaos control API
Chaos Helper Go HTTP error generator
Chaos Engine Toxiproxy Failure injection
Temporal Server Temporal Workflow orchestration

πŸš€ Launch the Demo (Production)

# Start all containerized services
docker compose up -d

# Check status
docker compose ps

# Access the application
open http://localhost:8080

# Access Temporal UI
open http://localhost:8233

Available features:

  • Create customers and enroll them in the program
  • Earn points (automatic tier upgrade at 500 and 1000 points)
  • Spend points (with Saga compensation on failure)
  • Leave and rejoin the program (30-day grace period)
  • Inject chaos (ON/OFF/SLOW/ERROR) to test resilience

πŸ› οΈ Development Workflow

Development Mode with Hot Reload

# 1. Start infrastructure (in Docker)
docker compose -f compose-dev.yaml up -d

# 2. Separate terminal: Dashboard with hot reload
cd dashboard
npm install
npm run dev

# 3. Separate terminal: Loyalty Service with hot reload
cd loyalty
mvn spring-boot:run

Unified access: Always via http://localhost:8080 (gateway proxies to host)

Useful Commands

# Rebuild a specific image
docker compose build loyalty-service

# View service logs
docker compose logs -f loyalty-service

# Restart a service
docker compose restart customer-service

# Stop everything
docker compose down

πŸ“ Temporal Patterns Implemented

1. Entity Workflow Pattern

Each customer has their own long-running workflow:

  • Workflow ID: loyalty-customer-{id}
  • Lifetime: Potentially years
  • Persistent state: Points, tier, transactions, grace period

2. Update Methods (Signals)

Synchronous workflow state modification:

  • earnPoints(purchaseId, amount) - Earn points (idempotent)
  • spendPoints(redemptionId, points, reason) - Spend points (saga)
  • leaveProgram(reason) - Leave program (starts grace period)
  • rejoinProgram() - Rejoin program (within 30 days)

3. Queries

Synchronous reads without state modification:

  • getBalance() - Current points and tier
  • getCustomerStatus() - Full status (Active/Left/Expired)
  • getTransactions() - Transaction history

4. Saga Pattern

Automatic compensation on failure during point spending:

  1. Deduct points from workflow
  2. Call redemption service
  3. If failure β†’ Automatically credit points back (rollback)
  4. Send email notification of result

5. Chaos Engineering

Controlled failure injection via Toxiproxy:

  • ON: Service operational
  • OFF: Service unreachable (timeout)
  • SLOW: 30-second latency injection
  • ERROR: HTTP 400 responses
  • KILL: Abrupt process termination (auto-restart in 10-20s)

πŸ”„ Saga Pattern in Detail

The Saga pattern ensures data consistency when orchestrating distributed transactions across multiple services. This project implements the Saga pattern for the spend points operation, which involves external service calls that may fail.

Compensation Mechanism

When a customer spends points, the system must:

  1. Deduct points from the workflow state
  2. Process the redemption via an external service
  3. Handle failures gracefully by compensating (rolling back) the deduction

This prevents inconsistent states where points are deducted but the redemption fails.

Sequence Diagram

sequenceDiagram
    participant User as Dashboard
    participant API as Loyalty API
    participant WF as Loyalty Workflow
    participant TS as Temporal Server
    participant RS as Redemption Service
    participant ES as Email Service

    Note over User,ES: Success Scenario
    User->>API: POST /spend (points: 100)
    API->>TS: Send Update Method
    TS->>WF: spendPoints(redemptionId, 100)
    WF->>WF: Reserve points (-100)
    WF->>RS: Activity: processRedemption(100)
    RS-->>WF: Success (value: $100)
    WF->>WF: Confirm spend (update state)
    WF->>ES: Activity: sendEmail(SUCCESS)
    ES-->>WF: Email sent
    WF-->>API: Update complete
    API-->>User: 200 OK

    Note over User,ES: Failure Scenario with Compensation
    User->>API: POST /spend (points: 200)
    API->>TS: Send Update Method
    TS->>WF: spendPoints(redemptionId, 200)
    WF->>WF: Reserve points (-200)
    WF->>RS: Activity: processRedemption(200)
    RS-->>WF: Error (insufficient funds)
    WF->>WF: Compensate: Restore points (+200)
    WF->>ES: Activity: sendEmail(FAILED)
    ES-->>WF: Email sent
    WF-->>API: Update complete (compensated)
    API-->>User: 200 OK (with compensation message)
Loading

Implementation Details

The implementation uses Temporal's native Saga class to manage compensation automatically:

Temporal UI

Step 1: Initialize Saga

final var sagaOptions = new Saga.Options.Builder()
    .setParallelCompensation(false)  // Sequential compensation
    .setContinueWithError(false)     // Stop on first error
    .build();
final var saga = new Saga(sagaOptions);

Step 2: Reserve Points & Register Compensation

// Deduct points optimistically (before external call)
final var transaction = state.reservePoints(redemptionId, points, reason);

// Register compensation BEFORE calling external service
// This ensures we have a rollback plan ready
saga.addCompensation(state::compensateSpend, points, transaction);

Step 3: Call External Service

try {
    // Attempt to process redemption via Temporal Activity
    activities.processRedemption(redemptionId, customerId, points, reason);

    // Success: Confirm the transaction
    state.confirmSpend(redemptionId, transaction);

} catch (ActivityFailure e) {
    // Failure: Execute ALL registered compensations in LIFO order
    saga.compensate();

    // Extract error code for reporting
    var errorCode = LoyaltyErrorCode.UNKNOWN_ERROR;
    if (e.getCause() instanceof ApplicationFailure appFailure) {
        errorCode = LoyaltyErrorCode.valueOf(appFailure.getType());
    }
    return SpendResult.failure(state.getCurrentPoints(), currentLevel, errorCode);
}

Key Benefits

  1. Automatic Rollback: Points are restored if redemption fails
  2. Guaranteed Execution: Temporal ensures compensation runs even after process crashes
  3. Audit Trail: All compensation events are recorded in transaction history
  4. Idempotency: Using redemptionId prevents duplicate processing
  5. User Notification: Email activities inform users of both success and failure

Error Handling

The saga handles multiple failure scenarios:

  • Service Unavailable: Temporal retries the activity automatically
  • Business Rejection: Immediate compensation without retry
  • Timeout: Retry with backoff, then compensate if exhausted
  • Network Error: Retry with exponential backoff

All failures trigger the compensation logic, ensuring the workflow state remains consistent.

πŸ”§ Chaos Engineering System

This project includes a comprehensive chaos engineering system to demonstrate Temporal's resilience patterns under various failure conditions. The system allows controlled injection of failures to test how the workflow handles real-world distributed system challenges.

Chaos Controls Panel

Architecture

The chaos system consists of three components working together:

graph LR
    Gateway["Gateway"] -->|"/api/*"| Toxiproxy
    Dashboard -->|"Control API"| ChaosService["Chaos Service<br/>(Spring Boot)"]
    ChaosService -->|"Configure"| Toxiproxy["Toxiproxy<br/>(Shopify)"]

    Toxiproxy -->|"proxy"| Customer["Customer Service"]
    Toxiproxy -->|"proxy"| Email["Email Service"]
    Toxiproxy -->|"proxy"| Redemption["Redemption Service"]
    Toxiproxy -.->|"redirect (ERROR)"| Helper["Chaos Helper<br/>(Go)"]
Loading
Component Role Technology
Toxiproxy Network proxy that injects failures Shopify Toxiproxy
Chaos Service REST API to control Toxiproxy Java/Spring Boot
Chaos Helper Mock service returning HTTP 400 errors Go

Failure Modes

Each service can be set to one of four states to simulate different failure scenarios:

State Implementation HTTP Response Temporal Behavior
ON No failures, normal operation 200 OK Activities succeed
OFF Connection reset (TCP reset) Connection failed Activities retry with exponential backoff
SLOW 30-second latency injection Timeout Activities timeout, retry until exhausted
ERROR Redirect to mock service 400 Bad Request Activities fail immediately (non-retryable)

Implementation Details:

  • ON: No Toxiproxy toxics applied, traffic flows normally
  • OFF: reset_peer toxic immediately closes TCP connections
  • SLOW: latency toxic adds 30,000ms delay to all requests
  • ERROR: Upstream redirected to Chaos Helper returning HTTP 400 + 2s delay

Testing Temporal Resilience

The chaos system demonstrates key Temporal capabilities:

1. Automatic Activity Retries (OFF State)

When a service is OFF, Temporal automatically retries failed activities:

T+0s  : Activity starts β†’ Connection refused
T+1s  : Retry #1 β†’ Connection refused
T+3s  : Retry #2 β†’ Connection refused
T+7s  : Retry #3 β†’ Connection refused
T+15s : Retry #4 β†’ Connection refused
...   : (Turn service ON)
T+31s : Retry #5 β†’ Success!

Retry Policy:

RetryOptions.newBuilder()
    .setInitialInterval(Duration.ofSeconds(1))
    .setBackoffCoefficient(2.0)
    .setMaximumAttempts(5)
    .setMaximumInterval(Duration.ofSeconds(30))
    .build();

ActivityOptions.newBuilder()
    .setStartToCloseTimeout(Duration.ofSeconds(10))
    .setRetryOptions(retryOptions)
    .build()

2. Saga Compensation (OFF/ERROR States)

When the Redemption Service fails during spend points:

// Service OFF β†’ Connection failure β†’ Retries exhausted β†’ Saga compensates
// Service ERROR β†’ HTTP 400 β†’ Immediate failure β†’ Saga compensates

Expected behavior:

  • Points deducted optimistically
  • Redemption activity fails
  • Compensation automatically restores points
  • Transaction marked as COMPENSATED in audit trail

3. Timeout Handling (SLOW State)

When latency exceeds activity timeout, Temporal retries until exhausted:

T+0s  : spendPoints() called
T+0s  : Reserve 100 points (workflow state)
T+0s  : processRedemption activity starts
T+10s : Activity timeout (30s latency > 10s timeout)
T+11s : Retry #1 starts
T+21s : Retry #1 timeout
T+23s : Retry #2 starts
...   : Retries continue with exponential backoff
T+63s : All retries exhausted (5 attempts)
T+63s : Compensation triggered β†’ Points restored

Activity timeout (10 seconds) is less than 30s latency injection, causing retries to exhaust and saga compensation to trigger.

4. Non-Retryable Errors (ERROR State)

HTTP 400 errors trigger immediate failure without retries:

catch (ActivityFailure e) {
    if (e.getCause() instanceof ApplicationFailure appFailure) {
        String errorCode = appFailure.getType();
        // errorCode = "REDEMPTION_REJECTED" or similar
    }
    saga.compensate(); // Immediate rollback
}

Demo Scenarios

Scenario 1: Email Service Resilience

Setup: Set Email Service to OFF

Test:

  1. Enroll customer (triggers welcome email)
  2. Email activity fails immediately
  3. Temporal retries automatically (5 attempts with exponential backoff)
  4. Set Email Service to ON
  5. Next retry succeeds

Result: Demonstrates Temporal's automatic retry mechanism

Scenario 2: Saga Compensation with Connection Failure

Setup: Set Redemption Service to OFF

Test:

  1. Customer has 500 points
  2. Spend 100 points
  3. Points deducted β†’ 400 points
  4. Redemption activity fails (retries exhausted)
  5. Compensation triggers β†’ 500 points restored

Result: Demonstrates saga compensation on network failure

Scenario 3: Saga Compensation with Business Error

Setup: Set Redemption Service to ERROR

Test:

  1. Customer has 500 points
  2. Spend 100 points
  3. Points deducted β†’ 400 points
  4. Redemption returns HTTP 400 (immediate failure, no retries)
  5. Compensation triggers immediately β†’ 500 points restored

Result: Demonstrates fast compensation on non-retryable errors

Scenario 4: Timeout and Retry Exhaustion

Setup: Set Redemption Service to SLOW

Test:

  1. Spend 100 points
  2. Activity starts processing
  3. Activity times out after 10 seconds (latency is 30s)
  4. Temporal retries automatically (5 attempts)
  5. All retries exhaust (each times out after 10s)
  6. Compensation triggers β†’ Points restored

Result: Demonstrates timeout handling and compensation when latency exceeds activity timeout

Chaos Control API

Control chaos states via REST API or Dashboard UI:

# Get all service states
curl http://localhost:8080/api/chaos/services

# Set service to OFF
curl -X POST http://localhost:8080/api/chaos/services/email/state \
  -H "Content-Type: application/json" \
  -d '{"state": "off"}'

# Reset all services to ON
curl -X POST http://localhost:8080/api/chaos/reset

# Emergency: Kill a service process
curl http://localhost:8080/api/chaos/services/email/kill

Key Insights

The chaos system validates that:

  1. Workflows are durable: State persists through service failures
  2. Activities retry automatically: Network failures don't require manual intervention
  3. Compensation executes reliably: Saga pattern maintains consistency even after retry exhaustion
  4. Timeouts protect workflows: Activities exceeding timeout trigger retries and eventual compensation
  5. Error codes matter: Retryable vs non-retryable errors trigger different behavior

This demonstrates Temporal's core value proposition: reliable execution in unreliable environments.

πŸ“Š Loyalty Tiers

Tier Points Required Description
Basic 0 Default tier upon enrollment
Gold 500 Mid-tier
Platinum 1000 Premium tier

Tier upgrades are automatic when accumulating points.

πŸ“ Project Structure

.
β”œβ”€β”€ dashboard/          # Vue.js/Nuxt frontend (static)
β”œβ”€β”€ loyalty/            # Java/Spring Boot + Temporal SDK service
β”œβ”€β”€ customer/           # Go + Redis service
β”œβ”€β”€ email/              # Python/FastAPI service (mock)
β”œβ”€β”€ redemption/         # TypeScript/Node.js service (mock)
β”œβ”€β”€ chaos/              # Chaos Service (Java) + Chaos Helper (Go)
β”œβ”€β”€ gateway/            # Caddy configuration
β”œβ”€β”€ specs/              # Complete documentation
β”‚   β”œβ”€β”€ index.md        # Overview
β”‚   β”œβ”€β”€ domain.md       # Domain model
β”‚   β”œβ”€β”€ workflow.md     # Workflow specification
β”‚   β”œβ”€β”€ architecture.md # Detailed architecture
β”‚   β”œβ”€β”€ api.md          # REST endpoints
β”‚   β”œβ”€β”€ chaos.md        # Chaos strategies
β”‚   └── testing.md      # Manual testing guide
β”œβ”€β”€ compose.yaml        # Production (all services containerized)
└── compose-dev.yaml    # Development (Dashboard + Loyalty on host)

πŸ“š Complete Documentation

Detailed documentation is located in the specs/ folder:

πŸŽ“ Temporal Concepts Demonstrated

This project is designed as a learning reference for:

  1. Long-running workflows: Complete lifecycle of a business entity
  2. Determinism: Guaranteed workflow replay
  3. Activities: Non-deterministic external calls (Redis, HTTP)
  4. Persistent state: State management without external database
  5. Idempotency: Deduplication via processedTransactionIds
  6. Timers: 30-day grace period with Workflow.await()
  7. Compensation: Automatic rollback with Saga pattern
  8. Resilience: Automatic recovery after injected failures

βš™οΈ Prerequisites

  • Docker + Docker Compose (for services)
  • Java 25+ + Maven (for Loyalty Service in dev)
  • Node.js 24+ + npm (for Dashboard in dev)

πŸ€– Built with Claude Code

This project was designed and implemented with assistance from Claude Code, Anthropic's CLI tool for software development. Claude Code contributed to:

  • Specification Design: Detailed technical specifications in the specs/ folder
  • Architecture Planning: Multi-service architecture with chaos engineering components
  • Implementation: Code generation across multiple languages (Java, Go, Python, TypeScript)
  • Documentation: Comprehensive README and inline code documentation
  • Testing Scenarios: Manual test scenarios and chaos engineering strategies

πŸ“„ License

This project is distributed under the Apache 2.0 license. See LICENSE for details.

🀝 Contributing

This project is an educational demonstration. Contributions are welcome for:

  • Adding automated tests
  • Improving documentation
  • Proposing new Temporal patterns
  • Fixing bugs

β†’ Quick start: docker compose up -d then open http://localhost:8080

About

A loyalty points program demo showcasing Temporal for durable workflows

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages