Class CaffeineIdempotencyRepository

java.lang.Object
io.github.ravocode.avoonce.caffeine.CaffeineIdempotencyRepository
All Implemented Interfaces:
IdempotencyRepository

public class CaffeineIdempotencyRepository extends Object implements IdempotencyRepository
In-memory IdempotencyRepository backed by a Caffeine cache.

Suitable for single-node deployments or local testing. TTL-based expiry and conflict detection are handled entirely in-process via Caffeine's atomic Cache.asMap() compute operations.

  • Constructor Details

    • CaffeineIdempotencyRepository

      public CaffeineIdempotencyRepository(IdempotencyConfig config)
      Constructs a CaffeineIdempotencyRepository with the given configuration. A new Caffeine cache is created with TTL-based expiry matching config.getTtl().
      Parameters:
      config - the idempotency TTL and lock-timeout configuration.
  • Method Details

    • acquireOrGet

      public Optional<IdempotencyRecord> acquireOrGet(String idempotencyKey)
      Delegates to acquireOrGet(String, String) with a null hash, keeping all locking logic in one place.
      Specified by:
      acquireOrGet in interface IdempotencyRepository
      Parameters:
      idempotencyKey - The unique key for the request.
      Returns:
      An optional containing the existing record if it's completed, or an empty optional if a new lock was acquired.
    • acquireOrGet

      public Optional<IdempotencyRecord> acquireOrGet(String idempotencyKey, String requestHash)
      Atomically acquires a lock or returns an existing COMPLETED record.

      This override is required to ensure the requestHash is persisted inside the newly-created STARTED record. Without it, the SPI default would delegate to acquireOrGet(String), producing a record with a null requestHash and making the mismatch guard permanently ineffective.

      Specified by:
      acquireOrGet in interface IdempotencyRepository
      Parameters:
      idempotencyKey - The unique key for the request.
      requestHash - The hash of the request payload to ensure the key is not reused for a different payload.
      Returns:
      An optional containing the existing record if it's completed, or an empty optional if a new lock was acquired.
      Throws:
      IdempotencyConflictException - if a non-expired STARTED record already exists.
      IdempotencyMismatchException - if a COMPLETED record exists with a different hash.
    • saveSuccess

      public void saveSuccess(String idempotencyKey, IdempotencyResponse response)
      Transitions the record for the given key to IdempotencyStatus.COMPLETED and stores the response.
      Specified by:
      saveSuccess in interface IdempotencyRepository
      Parameters:
      idempotencyKey - the idempotency key of the completing request.
      response - the response to cache for future replay.
    • saveFailure

      public void saveFailure(String idempotencyKey, String errorMessage)
      Transitions the record for the given key to IdempotencyStatus.FAILED, allowing the client to safely retry the operation.
      Specified by:
      saveFailure in interface IdempotencyRepository
      Parameters:
      idempotencyKey - the idempotency key of the failed request.
      errorMessage - a description of the failure cause (not persisted in Caffeine).
    • get

      public Optional<IdempotencyRecord> get(String idempotencyKey)
      Retrieves the current IdempotencyRecord for the given key without modifying it.
      Specified by:
      get in interface IdempotencyRepository
      Parameters:
      idempotencyKey - the key to look up.
      Returns:
      an Optional containing the record, or empty if not found or expired.
    • delete

      public void delete(String idempotencyKey)
      Removes the idempotency record for the given key from the in-memory cache. Useful for administrative invalidation of a specific key.
      Specified by:
      delete in interface IdempotencyRepository
      Parameters:
      idempotencyKey - The unique key to remove.
    • evictExpired

      public int evictExpired()
      No-op for Caffeine: TTL-based expiry is handled automatically by the underlying cache.
      Specified by:
      evictExpired in interface IdempotencyRepository
      Returns:
      always 0