👛 E-Wallet Service

A complete e-wallet implementation with deposit, withdraw, and transfer operations.

WalletService.java
@Service
public class WalletService {
    private final OrbitLedger ledger;

    public WalletService(WalletRepository walletRepo) {
        this.ledger = OrbitLedger.builder()
            .bufferSize(8192)
            .releaseType(ReleaseType.HYBRID)
            .releaseThreshold(100)
            .releaseInterval(Duration.ofSeconds(10))
            .balanceLoader(id -> walletRepo.getBalance(id))
            .evictionPolicy(EvictionPolicy.AFTER_RELEASE)
            .onRelease(result -> {
                walletRepo.updateBalance(result.key(), result.runningBalance());
            })
            .build();
        ledger.start();
    }

    public void deposit(String walletId, long amount) {
        ledger.credit(walletId, amount);
    }

    public void withdraw(String walletId, long amount) {
        ledger.debit(walletId, amount);
    }

    public void transfer(String from, String to, long amount) {
        ledger.debit(from, amount);
        ledger.credit(to, amount);
    }
}

📊 Example Database Result

After calling deposit("wallet-001", 100), withdraw("wallet-001", 30), deposit("wallet-001", 50):

wallet_balance
wallet_id balance updated_at
wallet-001 120 2024-01-15 10:30:00
✅ 1 UPDATE (running balance)
wallet_history
wallet_id amount balance type created_at
wallet-001 +100 100 CREDIT 10:30:00.001
wallet-001 -30 70 DEBIT 10:30:00.002
wallet-001 +50 120 CREDIT 10:30:00.003
✅ 1 INSERT (batch history)
💡 Result: 3 requests → Only 2 DB calls (1 UPDATE + 1 batch INSERT)

🌱 Spring Boot Integration

Configure Orbit Ledger as a Spring Bean with automatic lifecycle management.

LedgerConfig.java
@Configuration
public class LedgerConfig {

    @Bean(destroyMethod = "shutdown")
    public OrbitLedger ledgerEngine(JdbcTemplate jdbc) {
        OrbitLedger Orbit = OrbitLedger.builder()
            .bufferSize(8192)
            .releaseType(ReleaseType.HYBRID)
            .releaseThreshold(500)
            .releaseInterval(Duration.ofSeconds(30))
            .balanceLoader(id -> jdbc.queryForObject(
                "SELECT balance FROM accounts WHERE id = ?",
                Long.class, id))
            .evictionPolicy(EvictionPolicy.AFTER_RELEASE)
            .onRelease(result -> {
                jdbc.update("UPDATE accounts SET balance = ? WHERE id = ?",
                    result.runningBalance(), result.key());
            })
            .build();
        Orbit.start();
        return Orbit;
    }
}
💡 Spring Lifecycle

Using destroyMethod = "shutdown" ensures the ledger releases all pending events before the application shuts down.

⚡ High-Throughput Batch Processing

Optimized configuration for processing millions of transactions.

BatchProcessor.java
OrbitLedger ledger = OrbitLedger.builder()
    .bufferSize(65536)           // Large buffer for burst
    .threadCount(8)              // Match CPU cores
    .releaseType(ReleaseType.COUNT)
    .releaseThreshold(10_000)   // Large batches
    .evictionPolicy(EvictionPolicy.NONE)
    .onRelease(result -> {
        CompletableFuture.runAsync(() -> db.batchInsert(result.events()));
    })
    .build();

// Generate 1 million transactions
IntStream.range(0, 1_000_000).parallel().forEach(i -> {
    String account = "ACC-" + (i % 10_000);
    ledger.credit(account, 100);
});
// Throughput: ~4.5M ops/sec
⚠️ Thread Safety Note

This example uses delta-only pattern (no runningBalance()) with EvictionPolicy.NONE, which is safe for async writes.

If using balance tracking with AFTER_RELEASE eviction + balanceLoader, keep onRelease synchronous to avoid race conditions where balanceLoader reads stale data before the async write completes.

🌐 REST API Controller

Expose ledger operations via REST endpoints.

LedgerController.java
@RestController
@RequestMapping("/api/ledger")
public class LedgerController {
    private final OrbitLedger ledger;

    @PostMapping("/{accountId}/credit")
    public ResponseEntity<Void> credit(
            @PathVariable String accountId,
            @RequestParam long amount) {
        ledger.credit(accountId, amount);
        return ResponseEntity.accepted().build();
    }

    @GetMapping("/{accountId}/balance")
    public ResponseEntity<Long> getBalance(@PathVariable String accountId) {
        OrbitRelease release = ledger.release(accountId);
        return ResponseEntity.ok(release.runningBalance());
    }
}
⚠️ HTTP 202 Accepted

Credit and debit operations return 202 Accepted because they are non-blocking. Use GET /balance to force synchronization.