acidic_job 0.1.4 → 0.1.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fb1de28508c8a662b772a9b9f8fb4046d8c2a64e242826ec92c1ecc820dbc18b
4
- data.tar.gz: a0db4307ec7d7c971d9a5e1ff30ccb2243afa9ea35a7f17fe91f8c81d775a52b
3
+ metadata.gz: 1c51b3ed20bebb6322ab050f122c83d863fe16436e302b61f91eea84fd9cc117
4
+ data.tar.gz: 7f65a1d6c99d23930c4730557388833b3ebd277117394b18d6665978bf35119d
5
5
  SHA512:
6
- metadata.gz: 0e39981533a09f2f197f8c047d057328be88c5a34666bd8520500a805bee7fb4d86ac74fdebe673cd67fe92c3faff072a3d62eef79e4e14b21ffdfb05b292e2b
7
- data.tar.gz: b2eb0c3c2fe92b27fc065fc0a450261743b3b6b397c2212e1b46ed4773074342c64c8433930b71f4125a71b9c692f5baa1d29b3ec9d9fc6ac13905c1fe4e2d2f
6
+ metadata.gz: 3a1910297ba003ca354ede4dd5312d12af4f6662d8cb0eca451f4f01e51ba54d80f3f27632785d2ba7c69d5504da042861d122c7a81356d19ca04046097928cd
7
+ data.tar.gz: 6d57ce3ec53d5ed22bfd91f284611c505400ad7f524f0fbfe0bec4fbf335e94fd4c91781248980342d127186c5973dfed9cd32011c2e6b2e1c6ea72531247f87
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- acidic_job (0.1.4)
4
+ acidic_job (0.1.5)
5
5
  activesupport
6
6
 
7
7
  GEM
data/README.md CHANGED
@@ -1,8 +1,25 @@
1
1
  # AcidicJob
2
2
 
3
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/acidic_job`. To experiment with that code, run `bin/console` for an interactive prompt.
3
+ ### Idempotent operations for Rails apps, built on top of ActiveJob.
4
4
 
5
- TODO: Delete this and the text above, and describe your gem
5
+ At the conceptual heart of basically any software are "operations"—the discrete actions the software performs. Rails provides a powerful abstraction layer for building operations in the form of `ActiveJob`. With `ActiveJob`, we can easily trigger from other Ruby code throughout our Rails application (controller actions, model methods, model callbacks, etc.); we can run operations both synchronously (blocking execution and then returning its response to the caller) and asychronously (non-blocking and the caller doesn't know its response); and we can also retry a specific operation if needed seemlessly.
6
+
7
+ However, in order to ensure that our operational jobs are _robust_, we need to ensure that they are properly [idempotent and transactional](https://github.com/mperham/sidekiq/wiki/Best-Practices#2-make-your-job-idempotent-and-transactional). As stated in the [GitLab Sidekiq Style Guide](https://docs.gitlab.com/ee/development/sidekiq_style_guide.html#idempotent-jobs):
8
+
9
+ >As a general rule, a worker can be considered idempotent if:
10
+ > * It can safely run multiple times with the same arguments.
11
+ > * Application side-effects are expected to happen only once (or side-effects of a second run do not have an effect).
12
+
13
+ This is, of course, far easier said than done. Thus, `AcidicJob`.
14
+
15
+ `AcidicJob` provides a framework to help you make your operational jobs atomic ⚛️, consistent 🤖, isolated 🕴🏼, and durable ⛰️. Its conceptual framework is directly inspired by a truly wonderful loosely collected series of articles written by Brandur Leach, which together lay out core techniques and principles required to make an HTTP API properly ACIDic:
16
+
17
+ 1. https://brandur.org/acid
18
+ 2. https://brandur.org/http-transactions
19
+ 3. https://brandur.org/job-drain
20
+ 4. https://brandur.org/idempotency-keys
21
+
22
+ `AcididJob` brings these techniques and principles into the world of a standard Rails application.
6
23
 
7
24
  ## Installation
8
25
 
@@ -20,9 +37,45 @@ Or install it yourself as:
20
37
 
21
38
  $ gem install acidic_job
22
39
 
40
+ Then, use the following command to copy over the AcidicJobKey migration.
41
+
42
+ ```
43
+ rails generate acidic_job:key
44
+ ```
45
+
23
46
  ## Usage
24
47
 
25
- TODO: Write usage instructions here
48
+ `AcidicJob` is a concern that you `include` into your operation jobs which provides two public methods to help you make your jobs idempotent and robust—`idempotently` and `step`. You can see them "in action" in the example job below:
49
+
50
+ ```ruby
51
+ class RideCreateJob < ActiveJob::Base
52
+ include AcidicJob
53
+
54
+ def perform(ride_params)
55
+ idempotently with: { user: current_user, params: ride_params, ride: nil } do
56
+ step :create_ride_and_audit_record
57
+ step :create_stripe_charge
58
+ step :send_receipt
59
+ end
60
+ end
61
+
62
+ def create_ride_and_audit_record
63
+ # ...
64
+ end
65
+
66
+ def create_stripe_charge
67
+ # ...
68
+ end
69
+
70
+ def send_receipt
71
+ # ...
72
+ end
73
+ end
74
+ ```
75
+
76
+ `idempotently` takes only the `with:` named parameter and a block where you define the steps of this operation. `step` simply takes the name of a method available in the job. That's all!
77
+
78
+ So, how does `AcidicJob` make this operation idempotent and robust then? In simplest form, `AcidicJob` creates an "idempotency key" record for each job run, where it stores information about that job run, like the parameters passed in and the step the job is on. It then wraps each of your step methods in a database transaction to ensure that each step in the operation is transactionally secure. Finally, it handles a variety of edge-cases and error conditions for you as well. But, basically, by explicitly breaking your operation into steps and storing a record of each job run and updating its current step as it runs, we level up the `ActiveJob` retry mechanism to ensure that we don't retry already finished steps if something goes wrong and the job has to retry. Then, by wrapping each step in a transaction, we ensure each individual step is ACIDic. Taken together, these two strategies help us to ensure that our operational jobs are both idempotent and ACIDic.
26
79
 
27
80
  ## Development
28
81
 
data/lib/acidic_job.rb CHANGED
@@ -37,18 +37,6 @@ module AcidicJob
37
37
  # retry_on ActiveRecord::SerializationFailure
38
38
  end
39
39
 
40
- class_methods do
41
- def required(*names)
42
- required_attributes.push(*names)
43
- end
44
-
45
- def required_attributes
46
- return @required_attributes if instance_variable_defined?(:@required_attributes)
47
-
48
- @required_attributes = []
49
- end
50
- end
51
-
52
40
  # Number of seconds passed which we consider a held idempotency key lock to be
53
41
  # defunct and eligible to be locked again by a different API call. We try to
54
42
  # unlock keys on our various failure conditions, but software is buggy, and
@@ -67,8 +55,6 @@ module AcidicJob
67
55
  # to the step methods the job will have written
68
56
  define_accessors_for_passed_arguments(with)
69
57
 
70
- validate_passed_arguments(with)
71
-
72
58
  # execute the block to gather the info on what phases are defined for this job
73
59
  defined_steps = yield
74
60
 
@@ -77,7 +63,7 @@ module AcidicJob
77
63
 
78
64
  # find or create an AcidicJobKey record to store all information about this job
79
65
  # side-effect: will set the @key instance variable
80
- ensure_idempotency_key_record(job_id, with[:params], defined_steps.first)
66
+ ensure_idempotency_key_record(job_id, with, defined_steps.first)
81
67
 
82
68
  # if the key record is already marked as finished, immediately return its result
83
69
  return @key.succeeded? if @key.finished?
@@ -135,7 +121,7 @@ module AcidicJob
135
121
  end
136
122
  end
137
123
 
138
- def ensure_idempotency_key_record(key_val, params, first_step)
124
+ def ensure_idempotency_key_record(key_val, job_args, first_step)
139
125
  # isolation_level = case ActiveRecord::Base.connection.adapter_name.downcase.to_sym
140
126
  # when :sqlite
141
127
  # :read_uncommitted
@@ -150,7 +136,7 @@ module AcidicJob
150
136
  if @key
151
137
  # Programs enqueuing multiple jobs with different parameters but the
152
138
  # same idempotency key is a bug.
153
- raise MismatchedIdempotencyKeyAndJobArguments if @key.job_args != params.as_json
139
+ raise MismatchedIdempotencyKeyAndJobArguments if @key.job_args != job_args.as_json
154
140
 
155
141
  # Only acquire a lock if the key is unlocked or its lock has expired
156
142
  # because the original job was long enough ago.
@@ -166,7 +152,7 @@ module AcidicJob
166
152
  last_run_at: Time.current,
167
153
  recovery_point: first_step,
168
154
  job_name: self.class.name,
169
- job_args: params.as_json
155
+ job_args: job_args.as_json
170
156
  )
171
157
  end
172
158
  end
@@ -183,17 +169,6 @@ module AcidicJob
183
169
  true
184
170
  end
185
171
 
186
- def validate_passed_arguments(attributes)
187
- missing_attributes = self.class.required_attributes.select do |required_attribute|
188
- attributes[required_attribute].nil?
189
- end
190
-
191
- return if missing_attributes.empty?
192
-
193
- raise MissingRequiredAttribute,
194
- "The following required job parameters are missing: #{missing_attributes.to_sentence}"
195
- end
196
-
197
172
  def define_atomic_phases(defined_steps)
198
173
  defined_steps << :FINISHED
199
174
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AcidicJob
4
- VERSION = "0.1.4"
4
+ VERSION = "0.1.5"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: acidic_job
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.4
4
+ version: 0.1.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - fractaledmind
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-07-08 00:00:00.000000000 Z
11
+ date: 2021-07-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport