Coder Social home page Coder Social logo

idempotent-request's Introduction

Gem Version CI Status

Idempotent Request

Rack middleware ensuring at most once requests for mutating endpoints.

Installation

Add this line to your application's Gemfile:

gem 'idempotent-request'

And then execute:

$ bundle

Or install it yourself as:

$ gem install idempotent-request

How it works

  1. Front-end generates a unique key then a user goes to a specific route (for example, transfer page).
  2. When user clicks "Submit" button, the key is sent in the header idempotency-key and back-end stores server response into redis.
  3. All the consecutive requests with the key won't be executer by the server and the result of previous response (2) will be fetched from redis.
  4. Once the user leaves or refreshes the page, front-end should re-generate the key.

Configuration

# application.rb
config.middleware.use IdempotentRequest::Middleware,
  storage: IdempotentRequest::RedisStorage.new(::Redis.current, expire_time: 1.day),
  policy: YOUR_CLASS

To define a policy, whether a request should be idempotent, you have to provider a class with the following interface:

class Policy
  attr_reader :request

  def initialize(request)
    @request = request
  end

  def should?
    # request is Rack::Request class
  end
end

Example of integration for rails

# application.rb
config.middleware.use IdempotentRequest::Middleware,
  storage: IdempotentRequest::RedisStorage.new(::Redis.current, expire_time: 1.day),
  policy: IdempotentRequest::Policy

config.idempotent_routes = [
  { controller: :'v1/transfers', action: :create },
]
# lib/idempotent-request/policy.rb
module IdempotentRequest
  class Policy
    attr_reader :request

    def initialize(request)
      @request = request
    end

    def should?
      route = Rails.application.routes.recognize_path(request.path, method: request.request_method)
      Rails.application.config.idempotent_routes.any? do |idempotent_route|
        idempotent_route[:controller] == route[:controller].to_sym &&
          idempotent_route[:action] == route[:action].to_sym
      end
    end
  end
end

Use ActiveSupport::Notifications to read events

# config/initializers/idempotent_request.rb
ActiveSupport::Notifications.subscribe('idempotent.request') do |name, start, finish, request_id, payload|
  notification = payload[:request].env['idempotent.request']
  if notification['read']
    Rails.logger.info "IdempotentRequest: Hit cached response from key #{notification['key']}, response: #{notification['read']}"
  elsif notification['write']
    Rails.logger.info "IdempotentRequest: Write: key #{notification['key']}, status: #{notification['write'][0]}, headers: #{notification['write'][1]}, unlocked? #{notification['unlocked']}"
  elsif notification['concurrent_request_response']
    Rails.logger.warn "IdempotentRequest: Concurrent request detected with key #{notification['key']}"
  end
end

Custom options

# application.rb
config.middleware.use IdempotentRequest::Middleware,
  header_key: 'X-Qonto-Idempotency-Key', # by default Idempotency-key
  policy: IdempotentRequest::Policy,
  callback: IdempotentRequest::RailsCallback,
  storage: IdempotentRequest::RedisStorage.new(::Redis.current, expire_time: 1.day, namespace: 'idempotency_keys'),
  conflict_response_status: 409

Policy

Custom class to decide whether the request should be idempotent.

See Example of integration for rails

Storage

Where the response will be stored. Can be any class that implements the following interface:

def read(key)
  # read from a storage
end

def write(key, payload)
  # write to a storage
end

Callback

Get notified when a client sends a request with the same idempotency key:

class RailsCallback
  attr_reader :request

  def initialize(request)
    @request = request
  end

  def detected(key:)
    Rails.logger.warn "IdempotentRequest request detected, key: #{key}"
  end
end

Conflict response status

Define http status code that should be returned when a client sends concurrent requests with the same idempotency key.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/idempotent-request. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the Contributor Covenant code of conduct.

License

The gem is available as open source under the terms of the MIT License.

Code of Conduct

Everyone interacting in the Idempotent::Request project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.

Releasing

To publish a new version to rubygems, update the version in lib/version.rb, and merge.

idempotent-request's People

Contributors

adrdra avatar arikarim avatar dmytro-zakharov avatar douglaslise avatar gomayonqui avatar laurynas avatar maximed avatar nickgrim avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

idempotent-request's Issues

Disuse SETNX

We currently rely on SETNX here, followed by an EXPIRE on the key.

Redis no longer recommends using SETNX, going so far as to suggest it may deprecate it at any moment:

Note: Since the SET command options can replace SETNX, SETEX, PSETEX, it is possible that in future versions of Redis these three commands will be deprecated and finally removed.

Instead, it recommends the Redlock algorithm, meant to be fault-tolerant by being multi-master. However, I agree with this analysis of the algorithm. The problem of distributed locks is a well-known one, with Google’s Chubby being a notable landmark that enabled the search company to build larger and more powerful systems such as Bigtable, which incidentally started the NoSQL movement. The conclusion of that paper is similar to that of Martin Kleppmann: either we are ready to lose data and availability (which happens with single-node Redis, which has the benefit of being sub-millisecond operations) or we really want correctness and fault tolerance, in which case we should use etcd or a database system.

In our case, it can be OK to use a single-node Redis for speed and low latency, and rely on Amazon to move elastic IPs (<2min downtimes upon failover). We can then use a single-Redis-request with a SET … NX PX, as suggested here in the Redis documentation, instead of having two round trips to Redis as we currently do. If the SET yields a null reply, it was already locked in the past.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.