Class: Webhookdb::Idempotency

Inherits:
Object
  • Object
show all
Extended by:
MethodUtilities
Defined in:
lib/webhookdb/idempotency.rb

Overview

Support idempotent operations. This is very useful when 1) protecting the API against requests dispatched multiple times, as browsers are liable to do, and 2) designing parts of a system so they can be used idempotently, especially async jobs. This ensures an event can be republished if a job fails, but jobs that worked won’t be re-run.

In general, you do not use Idempotency instances directly; instead, you will use once_ever and every. For example, to only send a welcome email once:

Webhookdb::Idempotency.once_ever.under_key("welcome-email-#{customer.id}") { send_welcome_email(customer) }

Similarly, to prevent an action email from going out multiple times in a short period accidentally:

Webhookdb::Idempotency.every(1.hour).under_key("new-order-#{order.id}") { send_new_order_email(order) }

Note that idempotency cannot be executed while already in a transaction. If it were, the unique row would not be visible to other transactions. So the new row must be committed, then the idempotency evaluated (and the callback potentially run). To disable this check, set ‘Postgres.unsafe_skip_transaction_check’ to true, usually using the :no_transaction_check spec metadata.

Constant Summary collapse

NOOP =
:skipped

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from MethodUtilities

attr_predicate, attr_predicate_accessor, singleton_attr_accessor, singleton_attr_reader, singleton_attr_writer, singleton_method_alias, singleton_predicate_accessor, singleton_predicate_reader

Instance Attribute Details

#__everyObject

Returns the value of attribute __every.



48
49
50
# File 'lib/webhookdb/idempotency.rb', line 48

def __every
  @__every
end

#__once_everObject

Returns the value of attribute __once_ever.



48
49
50
# File 'lib/webhookdb/idempotency.rb', line 48

def __once_ever
  @__once_ever
end

Class Method Details

.every(interval) ⇒ Object



42
43
44
45
46
# File 'lib/webhookdb/idempotency.rb', line 42

def self.every(interval)
  idem = self.new
  idem.__every = interval
  return idem
end

.once_everObject



36
37
38
39
40
# File 'lib/webhookdb/idempotency.rb', line 36

def self.once_ever
  idem = self.new
  idem.__once_ever = true
  return idem
end

Instance Method Details

#executeObject



56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# File 'lib/webhookdb/idempotency.rb', line 56

def execute
  Webhookdb::Postgres.check_transaction(
    self.db,
    "Cannot use idempotency while already in a transaction, since side effects may not be idempotent",
  )

  self.class.dataset.insert_conflict.insert(key: self.key)
  self.db.transaction do
    idem = Webhookdb::Idempotency[key: self.key].lock!
    if idem.last_run.nil?
      result = yield()
      idem.update(last_run: Time.now)
      return result
    end
    return NOOP if self.__once_ever
    return NOOP if Time.now < (idem.last_run + self.__every)
    result = yield()
    idem.update(last_run: Time.now)
    return result
  end
end

#under_key(key, &block) ⇒ Object



50
51
52
53
54
# File 'lib/webhookdb/idempotency.rb', line 50

def under_key(key, &block)
  self.key = key
  return self.execute(&block) if block
  return self
end