Skip to content

Kaligo/idempotency

Folders and files

NameName
Last commit message
Last commit date
Jan 7, 2025
Feb 4, 2025
Feb 4, 2025
Nov 24, 2024
Nov 24, 2024
Nov 24, 2024
Feb 4, 2025
Jan 14, 2025
Feb 4, 2025
Nov 13, 2024
Jan 15, 2025
Jan 7, 2025
Jan 14, 2025

Repository files navigation

Idempotency

Installation

Add this line to your Gemfile:

gem 'idempotency'

Configuration

Idempotency.configure do |config|
  # Required configurations
  config.redis_pool = ConnectionPool.new(size: 5) { Redis.new }
  config.logger = Logger.new # Use Rails.logger or Hanami.logger based on your framework

  # Optional configurations

  # Handles concurrent request locks. If a request with the same idempotency key is made before the first one finishes,
  # it will be blocked with a 409 status until the lock expires. Ensure this value is greater than the maximum response time.
  config.default_lock_expiry = 60

  # Match this config to your application's error format
  config.response_body.concurrent_error = {
    errors: [{ message: 'Concurrent requests occurred' }]
  }

  config.idempotent_methods = %w[POST PUT PATCH]
  config.idempotent_statuses = (200..299).to_a + (400..499).to_a

  # Metrics configuration
  config.metrics.statsd_client = statsd_client # Your StatsD client instance
  config.metrics.namespace = 'my_service_name' # Optional namespace for metrics

  # Custom instrumentation listeners (optional)
  config.instrumentation_listeners = [my_custom_listener] # Array of custom listeners
end

Usage

Rails

Add this to your controller:

require 'idempotency/rails'

class UserController < ApplicationController
  include Idempotency::Rails

  around_action :use_cache, except: %i[create]

  # Configure lock_duration for specific actions
  around_action :idempotency_cache, only: %i[update]

  private

  def idempotency_cache
    use_cache(lock_duration: 360) do # Lock for 6 minutes
      yield
    end
  end
end

Hanami

Add this to your controller:

require 'idempotency/hanami'

class Api::Controllers::Users::Create
  include Hanami::Action
  include Idempotency::Hanami

  around do |params, block|
    use_cache(request_ids, lock_duration: 360) do
      block.call
    end
  end
end

Manual

For custom implementations or if not using Rails or Hanami:

status, headers, body = Idempotency.use_cache(request, request_identifiers, lock_duration: 60) do
  yield
end

# Render your response

Testing

For those using mock_redis gem, some methods that idempotency gem uses are not implemented (e.g. eval, evalsha), and this could cause test cases to fail. To get around this, the gem has a monkeypatch over mock_redis gem to override the missing methods. To use it, simply add following lines to your spec_helper.rb:

RSpec.configure do |config|
  config.include Idempotency::Testing::Helpers
end

Instrumentation

The gem supports instrumentation through StatsD out of the box. When you configure a StatsD client in the configuration, the StatsdListener will be automatically set up. It tracks the following metrics:

  • idempotency_cache_hit_count - Incremented when a cached response is found
  • idempotency_cache_miss_count - Incremented when no cached response exists
  • idempotency_lock_conflict_count - Incremented when concurrent requests conflict
  • idempotency_cache_duration_seconds - Histogram of operation duration

Each metric includes tags:

  • action - Either the specified action name or "{HTTP_METHOD}:{PATH}"
  • namespace - Your configured namespace (if provided)
  • metric - The metric name (for duration histogram only)

To enable StatsD instrumentation, simply configure the metrics settings:

Idempotency.configure do |config|
  config.metrics.statsd_client = Datadog::Statsd.new
  config.metrics.namespace = 'my_service_name'
end