Add this line to your Gemfile:
gem 'idempotency'
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
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
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
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
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
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 foundidempotency_cache_miss_count
- Incremented when no cached response existsidempotency_lock_conflict_count
- Incremented when concurrent requests conflictidempotency_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