idempotency 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +6 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +5 -1
- data/README.md +38 -2
- data/idempotency.gemspec +2 -0
- data/lib/idempotency/version.rb +1 -1
- data/lib/idempotency.rb +41 -22
- metadata +16 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b90e384bed1ce0ef7d2e3aeb46f4e2f7d6f822e584d805234762e42d05500ef0
|
|
4
|
+
data.tar.gz: 61d0164bd2b498d160b486082298382d6d3ceef04aca2eca7b61c620ae9e0e6c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e3e0d18d788ef5a769450e741fecc05a496cecc829034047d3595b8b667b14bee0cac210c94d815058f95382996075c4e6b95d2f5c8856edcb3748ddf97f715f
|
|
7
|
+
data.tar.gz: ee15500014c64c54c7bb6746c6d4c4ee28bf4a6d69d9a47682514fc4d2bc6e48275a7c759907eb625a6a7f0b36baf99e59b5991a1442fdde2ddec4ad1cd6216f
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
## [Change Log]
|
|
2
2
|
|
|
3
|
+
## [0.3.0] - 2025-11-14
|
|
4
|
+
|
|
5
|
+
- Add AppSignal integration for transaction tracking in trace stacks
|
|
6
|
+
- Add Sentry integration for transaction tracking in trace stacks
|
|
7
|
+
- Add observability configuration options (appsignal_enabled, sentry_enabled)
|
|
8
|
+
|
|
3
9
|
## [0.2.0] - 2025-07-28
|
|
4
10
|
|
|
5
11
|
- Enforce explicit monkey-patch requirement
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
idempotency (0.
|
|
4
|
+
idempotency (0.3.0)
|
|
5
|
+
appsignal (>= 1.3.0)
|
|
5
6
|
base64
|
|
6
7
|
dry-configurable
|
|
7
8
|
dry-monitor
|
|
@@ -11,6 +12,8 @@ PATH
|
|
|
11
12
|
GEM
|
|
12
13
|
remote: https://rubygems.org/
|
|
13
14
|
specs:
|
|
15
|
+
appsignal (3.13.1)
|
|
16
|
+
rack
|
|
14
17
|
ast (2.4.2)
|
|
15
18
|
base64 (0.2.0)
|
|
16
19
|
byebug (11.1.3)
|
|
@@ -97,6 +100,7 @@ PLATFORMS
|
|
|
97
100
|
ruby
|
|
98
101
|
|
|
99
102
|
DEPENDENCIES
|
|
103
|
+
appsignal (>= 1.3.0)
|
|
100
104
|
connection_pool
|
|
101
105
|
dry-monitor
|
|
102
106
|
hanami-controller (~> 1.3)
|
data/README.md
CHANGED
|
@@ -34,6 +34,11 @@ Idempotency.configure do |config|
|
|
|
34
34
|
config.metrics.statsd_client = statsd_client # Your StatsD client instance
|
|
35
35
|
config.metrics.namespace = 'my_service_name' # Optional namespace for metrics
|
|
36
36
|
|
|
37
|
+
# APM/Observability configuration (optional) - adds method to trace stacks
|
|
38
|
+
# You can enable one or both observability tools simultaneously
|
|
39
|
+
config.observability.appsignal_enabled = true # Enable AppSignal transaction tracking
|
|
40
|
+
config.observability.sentry_enabled = true # Enable Sentry transaction tracking
|
|
41
|
+
|
|
37
42
|
# Custom instrumentation listeners (optional)
|
|
38
43
|
config.instrumentation_listeners = [my_custom_listener] # Array of custom listeners
|
|
39
44
|
end
|
|
@@ -110,7 +115,11 @@ end
|
|
|
110
115
|
|
|
111
116
|
### Instrumentation
|
|
112
117
|
|
|
113
|
-
The gem supports instrumentation through
|
|
118
|
+
The gem supports instrumentation through multiple observability platforms:
|
|
119
|
+
|
|
120
|
+
#### StatsD
|
|
121
|
+
|
|
122
|
+
When you configure a StatsD client in the configuration, the StatsdListener will be automatically set up. It tracks the following metrics:
|
|
114
123
|
|
|
115
124
|
- `idempotency_cache_hit_count` - Incremented when a cached response is found
|
|
116
125
|
- `idempotency_cache_miss_count` - Incremented when no cached response exists
|
|
@@ -122,7 +131,7 @@ Each metric includes tags:
|
|
|
122
131
|
- `namespace` - Your configured namespace (if provided)
|
|
123
132
|
- `metric` - The metric name (for duration histogram only)
|
|
124
133
|
|
|
125
|
-
To enable StatsD instrumentation
|
|
134
|
+
To enable StatsD instrumentation:
|
|
126
135
|
|
|
127
136
|
```ruby
|
|
128
137
|
Idempotency.configure do |config|
|
|
@@ -130,3 +139,30 @@ Idempotency.configure do |config|
|
|
|
130
139
|
config.metrics.namespace = 'my_service_name'
|
|
131
140
|
end
|
|
132
141
|
```
|
|
142
|
+
|
|
143
|
+
#### AppSignal
|
|
144
|
+
|
|
145
|
+
The gem can add the `use_cache` method to AppSignal transaction traces when enabled. This allows you to see the idempotency check as part of your request traces and helps identify performance bottlenecks.
|
|
146
|
+
|
|
147
|
+
To enable AppSignal transaction tracking:
|
|
148
|
+
|
|
149
|
+
```ruby
|
|
150
|
+
Idempotency.configure do |config|
|
|
151
|
+
config.observability.appsignal_enabled = true
|
|
152
|
+
end
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Note: The AppSignal gem must be installed and configured in your application.
|
|
156
|
+
|
|
157
|
+
#### Using Both AppSignal and Sentry
|
|
158
|
+
|
|
159
|
+
You can enable both observability tools simultaneously. When both are enabled, the `use_cache` method will be instrumented in both APM systems with nested transactions:
|
|
160
|
+
|
|
161
|
+
```ruby
|
|
162
|
+
Idempotency.configure do |config|
|
|
163
|
+
config.observability.appsignal_enabled = true
|
|
164
|
+
config.observability.sentry_enabled = true
|
|
165
|
+
end
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
This allows you to see the idempotency check in both your AppSignal and Sentry dashboards, providing comprehensive observability across your monitoring stack.
|
data/idempotency.gemspec
CHANGED
data/lib/idempotency/version.rb
CHANGED
data/lib/idempotency.rb
CHANGED
|
@@ -8,7 +8,7 @@ require_relative 'idempotency/constants'
|
|
|
8
8
|
require_relative 'idempotency/instrumentation/statsd_listener'
|
|
9
9
|
require 'dry-monitor'
|
|
10
10
|
|
|
11
|
-
class Idempotency
|
|
11
|
+
class Idempotency # rubocop:disable Metrics/ClassLength
|
|
12
12
|
extend Dry::Configurable
|
|
13
13
|
@monitor = Monitor.new
|
|
14
14
|
|
|
@@ -28,6 +28,10 @@ class Idempotency
|
|
|
28
28
|
setting :statsd_client
|
|
29
29
|
end
|
|
30
30
|
|
|
31
|
+
setting :observability do
|
|
32
|
+
setting :appsignal_enabled, default: false
|
|
33
|
+
end
|
|
34
|
+
|
|
31
35
|
setting :default_lock_expiry, default: 300 # 5 minutes
|
|
32
36
|
setting :idempotent_methods, default: %w[POST PUT PATCH DELETE]
|
|
33
37
|
setting :idempotent_statuses, default: (200..299).to_a + (400..499).to_a
|
|
@@ -60,41 +64,46 @@ class Idempotency
|
|
|
60
64
|
new.use_cache(request, request_identifiers, lock_duration:, action:, &blk)
|
|
61
65
|
end
|
|
62
66
|
|
|
63
|
-
|
|
67
|
+
# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
|
|
68
|
+
def use_cache(request, request_identifiers, lock_duration: nil, action: nil)
|
|
64
69
|
duration_start = Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
|
70
|
+
action_name = action || "#{request.request_method}:#{request.path}"
|
|
65
71
|
|
|
66
|
-
|
|
72
|
+
with_apm_instrumentation('idempotency.use_cache', action_name) do
|
|
73
|
+
return yield unless cache_request?(request)
|
|
67
74
|
|
|
68
|
-
|
|
69
|
-
|
|
75
|
+
request_headers = request.env
|
|
76
|
+
idempotency_key = unquote(request_headers[Constants::RACK_HEADER_KEY] || SecureRandom.hex)
|
|
70
77
|
|
|
71
|
-
|
|
78
|
+
fingerprint = calculate_fingerprint(request, idempotency_key, request_identifiers)
|
|
72
79
|
|
|
73
|
-
|
|
80
|
+
cached_response = cache.get(fingerprint)
|
|
74
81
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
82
|
+
if (cached_status, cached_headers, cached_body = cached_response)
|
|
83
|
+
cached_headers.merge!(Constants::HEADER_KEY => idempotency_key)
|
|
84
|
+
instrument(Events::CACHE_HIT, request:, action:, duration: calculate_duration(duration_start))
|
|
78
85
|
|
|
79
|
-
|
|
80
|
-
|
|
86
|
+
return [cached_status, cached_headers, cached_body]
|
|
87
|
+
end
|
|
81
88
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
89
|
+
lock_duration ||= config.default_lock_expiry
|
|
90
|
+
response_status, response_headers, response_body = cache.with_lock(fingerprint, lock_duration) do
|
|
91
|
+
yield
|
|
92
|
+
end
|
|
86
93
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
94
|
+
if cache_response?(response_status)
|
|
95
|
+
cache.set(fingerprint, response_status, response_headers, response_body)
|
|
96
|
+
response_headers.merge!({ Constants::HEADER_KEY => idempotency_key })
|
|
97
|
+
end
|
|
91
98
|
|
|
92
|
-
|
|
93
|
-
|
|
99
|
+
instrument(Events::CACHE_MISS, request:, action:, duration: calculate_duration(duration_start))
|
|
100
|
+
[response_status, response_headers, response_body]
|
|
101
|
+
end
|
|
94
102
|
rescue Idempotency::Cache::LockConflict
|
|
95
103
|
instrument(Events::LOCK_CONFLICT, request:, action:, duration: calculate_duration(duration_start))
|
|
96
104
|
[409, {}, config.response_body.concurrent_error]
|
|
97
105
|
end
|
|
106
|
+
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity
|
|
98
107
|
|
|
99
108
|
private
|
|
100
109
|
|
|
@@ -137,4 +146,14 @@ class Idempotency
|
|
|
137
146
|
str
|
|
138
147
|
end
|
|
139
148
|
end
|
|
149
|
+
|
|
150
|
+
def with_apm_instrumentation(name, action, &)
|
|
151
|
+
if config.observability.appsignal_enabled
|
|
152
|
+
Appsignal.instrument(name, action) do
|
|
153
|
+
yield
|
|
154
|
+
end
|
|
155
|
+
else
|
|
156
|
+
yield
|
|
157
|
+
end
|
|
158
|
+
end
|
|
140
159
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: idempotency
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Vu Hoang
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2025-
|
|
11
|
+
date: 2025-11-20 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: base64
|
|
@@ -80,6 +80,20 @@ dependencies:
|
|
|
80
80
|
- - ">="
|
|
81
81
|
- !ruby/object:Gem::Version
|
|
82
82
|
version: '0'
|
|
83
|
+
- !ruby/object:Gem::Dependency
|
|
84
|
+
name: appsignal
|
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
|
86
|
+
requirements:
|
|
87
|
+
- - ">="
|
|
88
|
+
- !ruby/object:Gem::Version
|
|
89
|
+
version: 1.3.0
|
|
90
|
+
type: :runtime
|
|
91
|
+
prerelease: false
|
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
93
|
+
requirements:
|
|
94
|
+
- - ">="
|
|
95
|
+
- !ruby/object:Gem::Version
|
|
96
|
+
version: 1.3.0
|
|
83
97
|
description: Caching requests for idempotency purpose
|
|
84
98
|
email: vu.hoang@ascenda.com
|
|
85
99
|
executables: []
|