burstflow 0.2.0 → 0.2.1

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: 435e3f0b3dba1c654d192d3f5f4cb1687bfe55e6c60f03e6fcb4643946603f0a
4
- data.tar.gz: e5390d5f58fb4a2e3503516f0c0744bce0cac68411edf56477e82d84a9f9ed87
3
+ metadata.gz: 4d68ef2fa6c3af3c066b65ae6213d3ac96216ea1eb66b849bb979759ed25c67b
4
+ data.tar.gz: b7868f4aeca3863996bba8f256fa72b79f244375bc6c82c301386ca88b7f66f8
5
5
  SHA512:
6
- metadata.gz: fcb37c6b28837184136c25333cfaa3bd403bb953b9aa8dc8fe5971d499013650664444b5415aa50e2530218a88d07107e5032f90cc3ef3cd5114bd8593752669
7
- data.tar.gz: ae739a9ab5a48b10512c3461e4720e54e564722192fca3aaf36123a48ed3a1c36de28483d19fd975659b2586637120be3dd32068f62e5bb2a294a1f6c8177e33
6
+ metadata.gz: 59e06e384d30bec993c880bce050f41dbc017cf1b0814855dca2d538ea270a79d9a9cb6a8f4601b47848f810cc768508e4a0c7aa5eade69717c194dc52b07526
7
+ data.tar.gz: dfba34ed82a10b598dfac4539be6f0b8d6880061eb61ac85780c9fcd84f5f498e72ce9228c3bb088d83bba32bcda75d4238b03e070ea64eb3b9d334a88718636
data/Gemfile CHANGED
@@ -7,8 +7,8 @@ end
7
7
 
8
8
  group :test do
9
9
  gem 'activesupport'
10
+ gem 'awesome_print'
10
11
  gem 'database_cleaner'
11
12
  gem 'otr-activerecord'
12
13
  gem 'pg', '~> 0.21.0'
13
- gem 'awesome_print'
14
14
  end
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- burstflow (0.2.0)
4
+ burstflow (0.2.1)
5
5
  activejob
6
6
  activerecord
7
7
 
data/README.md CHANGED
@@ -2,25 +2,33 @@
2
2
 
3
3
  <!-- ## [![](http://i.imgur.com/ya8Wnyl.png)](https://chaps.io) proudly made by [Chaps](https://chaps.io) -->
4
4
 
5
- Burst is a parallel workflow runner using [ActiveRecord] and [ActiveJob](https://guides.rubyonrails.org/v4.2/active_job_basics.html) for scheduling and executing jobs.
5
+ [![Build Status](https://travis-ci.com/RnD-Soft/burstflow.svg?branch=master)](https://travis-ci.com/RnD-Soft/burstflow) [![Gem Version](https://badge.fury.io/rb/burstflow.svg)](https://badge.fury.io/rb/burstflow)
6
6
 
7
- This gem is higly inspired by [Gush](https://github.com/chaps-io/gush). But burst not tied to Reddis or Sidekiq.
7
+ Burstflow is a parallel workflow runner using [ActiveRecord] and [ActiveJob](https://guides.rubyonrails.org/v4.2/active_job_basics.html) for scheduling and executing jobs.
8
8
 
9
- The main feature of this runner is availablity to **suspend** job(and whole workflow) and **resume** it in future. For example if your job makes asynchronous request and will receive a response some time later. In this case, the job can send request and suspend until some external event resumes it eventually.
9
+ This gem is higly inspired by [Gush](https://github.com/chaps-io/gush). But not tied to Reddis or Sidekiq like Gush.
10
+
11
+ ActiveRecord used as persisted store during workflow execution.
12
+
13
+ Additional features:
14
+
15
+ * **susped** and **resume** job(and whole workflow). For example if your job makes asynchronous request and will receive a response some time later. In this case, the job can send request and suspend until some external event resumes it eventually.
16
+ * before/after callbacks on jobs level
17
+ * before/after callbacks on workflow level
18
+ * **Dynamic workflows**. Any job can produce another jobs while executing. This jobs has its parent as incomming jobs, and all parents outgoing jobs as own outgoings.
10
19
 
11
- Another difference from Gush is **Dynamic workflows**. Any job can produce another jobs while executing. This jobs has its parent as incomming jobs, and all parents outgoing jobs as own outgoings.
12
20
 
13
21
  ## Installation
14
22
 
15
23
  ### 1. Add `burst` to Gemfile
16
24
 
17
25
  ```ruby
18
- gem 'burst', '~> 0.1.0'
26
+ gem 'burstflow', '~> 0.2.0'
19
27
  ```
20
28
 
21
29
  ### 2. Run migrations
22
30
 
23
- Under development
31
+ ```rake burstflow:install:migrations```
24
32
 
25
33
  ## Example
26
34
 
@@ -29,7 +37,7 @@ Here is a complete example of a workflow you can create:
29
37
 
30
38
  ```ruby
31
39
  # app/workflows/sample_workflow.rb
32
- class SampleWorkflow < Burst::Workflow
40
+ class SampleWorkflow < Burstflow::Workflow
33
41
  configure do |url_to_fetch_from|
34
42
  run FetchJob1, params: { url: url_to_fetch_from }
35
43
  run FetchJob2, params: { some_flag: true, url: 'http://url.com' }
@@ -56,7 +64,7 @@ and this is how the graph will look like:
56
64
  Let's start with the simplest workflow possible, consisting of a single job:
57
65
 
58
66
  ```ruby
59
- class SimpleWorkflow < Burst::Workflow
67
+ class SimpleWorkflow < Burstflow::Workflow
60
68
  configure do
61
69
  run DownloadJob
62
70
  end
@@ -74,12 +82,12 @@ class SimpleWorkflow < Burst::Workflow
74
82
  end
75
83
  ```
76
84
 
77
- We just told Burst to execute `SaveJob` right after `DownloadJob` finishes **successfully**.
85
+ We just told Burstflow to execute `SaveJob` right after `DownloadJob` finishes **successfully**.
78
86
 
79
87
  But what if your job must have multiple dependencies? That's easy, just provide an array to the `after` attribute:
80
88
 
81
89
  ```ruby
82
- class SimpleWorkflow < Burst::Workflow
90
+ class SimpleWorkflow < Burstflow::Workflow
83
91
  configure do
84
92
  run FirstDownloadJob
85
93
  run SecondDownloadJob
@@ -98,7 +106,7 @@ With this simple syntax you can build any complex workflows you can imagine!
98
106
  `run` method also accepts `before:` attribute to define the opposite association. So we can write the same workflow as above, but like this:
99
107
 
100
108
  ```ruby
101
- class SimpleWorkflow < Burst::Workflow
109
+ class SimpleWorkflow < Burstflow::Workflow
102
110
  configure do
103
111
  run FirstDownloadJob, before: SaveJob
104
112
  run SecondDownloadJob, before: SaveJob
@@ -118,7 +126,7 @@ Workflows can accept any primitive arguments in their constructor, which then wi
118
126
  Let's assume we are writing a book publishing workflow which needs to know where the PDF of the book is and under what ISBN it will be released:
119
127
 
120
128
  ```ruby
121
- class PublishBookWorkflow < Burst::Workflow
129
+ class PublishBookWorkflow < Burstflow::Workflow
122
130
  configure do |url, isbn|
123
131
  run FetchBook, params: { url: url }
124
132
  run PublishBook, params: { book_isbn: isbn }, after: FetchBook
@@ -136,7 +144,7 @@ and that's basically it for defining workflows, see below on how to define jobs:
136
144
 
137
145
  ## Defining jobs
138
146
 
139
- The simplest job is a class inheriting from `Burst::Job` and responding to `perform` and `resume` method. Much like any other ActiveJob class.
147
+ The simplest job is a class inheriting from `Burstflow::Job` and responding to `perform` and `resume` method. Much like any other ActiveJob class.
140
148
 
141
149
  ```ruby
142
150
  class FetchBook < Burst::Job
@@ -189,14 +197,14 @@ flow = PublishBookWorkflow.build("http://url.com/book.pdf", "978-0470081204")
189
197
  flow.start!
190
198
  ```
191
199
 
192
- Now Burst will start processing jobs in the background using ActiveJob and your chosen backend.
200
+ Now Burstflow will start processing jobs in the background using ActiveJob and your chosen backend.
193
201
 
194
202
  ### 3. Monitor its progress:
195
203
 
196
204
  ```ruby
197
205
  flow.reload
198
206
  flow.status
199
- #=> :running|:finished|:failed
207
+ #=> :running|:finished|:failed|:suspended
200
208
  ```
201
209
 
202
210
  `reload` is needed to see the latest status, since workflows are updated asynchronously.
@@ -205,7 +213,7 @@ flow.status
205
213
 
206
214
  ### Pipelining
207
215
 
208
- Burst offers a useful tool to pass results of a job to its dependencies, so they can act differently.
216
+ Burstflow offers a useful tool to pass results of a job to its dependencies, so they can act differently.
209
217
 
210
218
  **Example:**
211
219
 
@@ -228,9 +236,9 @@ end
228
236
  Now, since `DownloadVideo` finished and its dependant job `EncodeVideo` started, we can access that payload inside it:
229
237
 
230
238
  ```ruby
231
- class EncodeVideo < Burst::Job
239
+ class EncodeVideo < Burstflow::Job
232
240
  def perform
233
- video_path = payloads.first[:output]
241
+ video_path = payloads.first[:value]
234
242
  end
235
243
  end
236
244
  ```
@@ -243,7 +251,7 @@ end
243
251
  {
244
252
  id: "DownloadVideo-41bfb730-b49f-42ac-a808-156327989294" # unique id of the ancestor job
245
253
  class: "DownloadVideo",
246
- output: "https://s3.amazonaws.com/somebucket/downloaded-file.mp4" #the payload returned by DownloadVideo job using `output()` method
254
+ value: "https://s3.amazonaws.com/somebucket/downloaded-file.mp4" #the payload returned by DownloadVideo job using `output()` method
247
255
  }
248
256
  ]
249
257
  ```
@@ -259,7 +267,7 @@ As an example, let's write a workflow which accepts an array of users and has to
259
267
 
260
268
  ```ruby
261
269
 
262
- class ParentJob < Burst::Job
270
+ class ParentJob < Burstflow::Job
263
271
  def perform
264
272
  configure do
265
273
  params[:user_ids].map do |user_id|
@@ -286,7 +294,7 @@ https://github.com/chaps-io/gush#contributors
286
294
 
287
295
  ## Contributing
288
296
 
289
- 1. Fork it ( https://github.com/RnD-Soft/burst/fork )
297
+ 1. Fork it ( https://github.com/RnD-Soft/burstflow/fork )
290
298
  2. Create your feature branch (`git checkout -b my-new-feature`)
291
299
  3. Commit your changes (`git commit -am 'Add some feature'`)
292
300
  4. Push to the branch (`git push origin my-new-feature`)
data/burstflow.gemspec CHANGED
@@ -1,7 +1,7 @@
1
- $:.push File.expand_path("lib", __dir__)
1
+ $:.push File.expand_path('lib', __dir__)
2
2
 
3
3
  # Maintain your gem's version:
4
- require "burstflow/version"
4
+ require 'burstflow/version'
5
5
 
6
6
  Gem::Specification.new do |spec|
7
7
  spec.name = 'burstflow'
@@ -19,9 +19,9 @@ Gem::Specification.new do |spec|
19
19
 
20
20
  spec.add_dependency 'activejob'
21
21
  spec.add_dependency 'activerecord'
22
-
22
+
23
23
  spec.add_development_dependency 'bundler'
24
+ spec.add_development_dependency 'generator_spec'
24
25
  spec.add_development_dependency 'rake'
25
26
  spec.add_development_dependency 'rspec'
26
- spec.add_development_dependency 'generator_spec'
27
27
  end
data/db/schema.rb CHANGED
@@ -10,20 +10,18 @@
10
10
  #
11
11
  # It's strongly recommended that you check this file into your version control system.
12
12
 
13
- ActiveRecord::Schema.define(version: 2018_01_01_000001) do
14
-
13
+ ActiveRecord::Schema.define(version: 20_180_101_000_001) do
15
14
  # These are extensions that must be enabled in order to support this database
16
- enable_extension "pgcrypto"
17
- enable_extension "plpgsql"
15
+ enable_extension 'pgcrypto'
16
+ enable_extension 'plpgsql'
18
17
 
19
- create_table "burstflow_workflows", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
20
- t.string "type"
21
- t.string "status"
22
- t.jsonb "flow", default: {}, null: false
23
- t.datetime "created_at", null: false
24
- t.datetime "updated_at", null: false
25
- t.index ["status"], name: "index_burstflow_workflows_on_status"
26
- t.index ["type"], name: "index_burstflow_workflows_on_type"
18
+ create_table 'burstflow_workflows', id: :uuid, default: -> { 'gen_random_uuid()' }, force: :cascade do |t|
19
+ t.string 'type'
20
+ t.string 'status'
21
+ t.jsonb 'flow', default: {}, null: false
22
+ t.datetime 'created_at', null: false
23
+ t.datetime 'updated_at', null: false
24
+ t.index ['status'], name: 'index_burstflow_workflows_on_status'
25
+ t.index ['type'], name: 'index_burstflow_workflows_on_type'
27
26
  end
28
-
29
27
  end
data/lib/burstflow/job.rb CHANGED
@@ -1,12 +1,13 @@
1
- require "active_support/rescuable"
2
- require "active_job/callbacks"
1
+ require 'active_support/rescuable'
2
+ require 'active_job/callbacks'
3
3
 
4
4
  class Burstflow::Job
5
- require "burstflow/job/exception"
6
- require "burstflow/job/model"
7
- require "burstflow/job/initialization"
8
- require "burstflow/job/state"
9
- require "burstflow/job/callbacks"
5
+
6
+ require 'burstflow/job/exception'
7
+ require 'burstflow/job/model'
8
+ require 'burstflow/job/initialization'
9
+ require 'burstflow/job/state'
10
+ require 'burstflow/job/callbacks'
10
11
 
11
12
  include Burstflow::Job::Model
12
13
  include Burstflow::Job::Initialization
@@ -30,28 +31,28 @@ class Burstflow::Job
30
31
  end
31
32
 
32
33
  # execute this code by ActiveJob. You may return Burstflow::Job::SUSPEND to suspend job, or call suspend method
33
- def perform
34
- end
34
+ def perform; end
35
35
 
36
36
  def perform_now
37
37
  run_callbacks :perform do
38
38
  perform
39
39
  end
40
- rescue => exception
40
+ rescue StandardError => exception
41
41
  rescue_with_handler(exception) || raise
42
42
  end
43
43
 
44
44
  # execute this code when resumes after suspending
45
45
  def resume(data)
46
- raise InternalError.new(self, "Can't perform resume: not resumed") if !resumed?
46
+ raise InternalError.new(self, "Can't perform resume: not resumed") unless resumed?
47
+
47
48
  set_output(data)
48
49
  end
49
50
 
50
- def resume_now data
51
+ def resume_now(data)
51
52
  run_callbacks :resume do
52
53
  resume(data)
53
54
  end
54
- rescue => exception
55
+ rescue StandardError => exception
55
56
  rescue_with_handler(exception) || raise
56
57
  end
57
58
 
@@ -62,13 +63,13 @@ class Burstflow::Job
62
63
 
63
64
  # mark execution as suspended
64
65
  def suspend
65
- raise InternalError.new(self, "Can't suspend: not running") if !running?
66
+ raise InternalError.new(self, "Can't suspend: not running") unless running?
67
+
66
68
  set_output(SUSPEND)
67
69
  end
68
70
 
69
- def configure *args, &block
71
+ def configure(*args, &block)
70
72
  workflow.with_lock do
71
-
72
73
  builder = Burstflow::Workflow::Builder.new(workflow, *args, &block)
73
74
  workflow.flow['jobs_config'] = builder.as_json
74
75
  workflow.save!
@@ -78,12 +79,12 @@ class Burstflow::Job
78
79
 
79
80
  def attributes
80
81
  {
81
- workflow_id: self.workflow_id,
82
- id: self.id,
83
- klass: self.klass,
82
+ workflow_id: workflow_id,
83
+ id: id,
84
+ klass: klass,
84
85
  params: params,
85
- incoming: self.incoming,
86
- outgoing: self.outgoing,
86
+ incoming: incoming,
87
+ outgoing: outgoing,
87
88
  output: output,
88
89
  started_at: started_at,
89
90
  enqueued_at: enqueued_at,
@@ -91,12 +92,8 @@ class Burstflow::Job
91
92
  failed_at: failed_at,
92
93
  suspended_at: suspended_at,
93
94
  resumed_at: resumed_at,
94
- failure: self.failure
95
+ failure: failure
95
96
  }
96
97
  end
97
98
 
98
-
99
-
100
-
101
-
102
99
  end
@@ -1,17 +1,15 @@
1
1
  module Burstflow::Job::Callbacks
2
+
2
3
  extend ActiveSupport::Concern
3
4
  include ActiveJob::Callbacks
4
5
 
5
6
  included do
6
-
7
7
  define_callbacks :suspend
8
8
  define_callbacks :resume
9
9
  define_callbacks :failure
10
-
11
10
  end
12
11
 
13
12
  class_methods do
14
-
15
13
  def before_suspend(*filters, &blk)
16
14
  set_callback(:suspend, :before, *filters, &blk)
17
15
  end
@@ -24,7 +22,6 @@ module Burstflow::Job::Callbacks
24
22
  set_callback(:suspend, :around, *filters, &blk)
25
23
  end
26
24
 
27
-
28
25
  def before_resume(*filters, &blk)
29
26
  set_callback(:resume, :before, *filters, &blk)
30
27
  end
@@ -37,19 +34,17 @@ module Burstflow::Job::Callbacks
37
34
  set_callback(:resume, :around, *filters, &blk)
38
35
  end
39
36
 
40
-
41
37
  def before_failure(*filters, &blk)
42
38
  set_callback(:failure, :before, *filters, &blk)
43
39
  end
44
-
40
+
45
41
  def after_failure(*filters, &blk)
46
42
  set_callback(:failure, :after, *filters, &blk)
47
43
  end
48
-
44
+
49
45
  def around_failure(*filters, &blk)
50
46
  set_callback(:failure, :around, *filters, &blk)
51
47
  end
52
-
53
48
  end
54
49
 
55
- end
50
+ end
@@ -1,8 +1,10 @@
1
1
  class Burstflow::Job::InternalError < ::RuntimeError
2
+
2
3
  attr_accessor :job
3
4
 
4
5
  def initialize(job, message)
5
6
  @job = job
6
7
  super(message)
7
8
  end
8
- end
9
+
10
+ end
@@ -1,8 +1,8 @@
1
1
  module Burstflow::Job::Initialization
2
- extend ActiveSupport::Concern
3
2
 
4
- included do
3
+ extend ActiveSupport::Concern
5
4
 
5
+ included do
6
6
  def initialize(workflow, job_config = {})
7
7
  @workflow = workflow
8
8
  assign_default_values(job_config)
@@ -21,15 +21,12 @@ module Burstflow::Job::Initialization
21
21
  def reload
22
22
  assign_default_values(@workflow.job_hash(self.id))
23
23
  end
24
-
25
24
  end
26
25
 
27
26
  class_methods do
28
-
29
27
  def from_hash(workflow, job_config)
30
28
  job_config[:klass].constantize.new(workflow, job_config)
31
29
  end
32
-
33
30
  end
34
31
 
35
- end
32
+ end
@@ -1,11 +1,12 @@
1
1
  module Burstflow::Job::State
2
- extend ActiveSupport::Concern
3
2
 
4
- included do
3
+ extend ActiveSupport::Concern
5
4
 
5
+ included do
6
6
  # mark job as enqueued when it is scheduled to queue
7
7
  def enqueue!
8
8
  raise Burstflow::Job::InternalError.new(self, "Can't enqueue: already enqueued") if enqueued?
9
+
9
10
  self.enqueued_at = current_timestamp
10
11
  self.started_at = nil
11
12
  self.finished_at = nil
@@ -17,22 +18,25 @@ module Burstflow::Job::State
17
18
  # mark job as started when it is start performing
18
19
  def start!
19
20
  raise Burstflow::Job::InternalError.new(self, "Can't start: already started") if started?
20
- raise Burstflow::Job::InternalError.new(self, "Can't start: not enqueued") if !enqueued?
21
+ raise Burstflow::Job::InternalError.new(self, "Can't start: not enqueued") unless enqueued?
22
+
21
23
  self.started_at = current_timestamp
22
24
  end
23
25
 
24
26
  # mark job as finished when it is finish performing
25
27
  def finish!
26
28
  raise Burstflow::Job::InternalError.new(self, "Can't finish: already finished") if finished?
27
- raise Burstflow::Job::InternalError.new(self, "Can't finish: not started") if !started?
29
+ raise Burstflow::Job::InternalError.new(self, "Can't finish: not started") unless started?
30
+
28
31
  self.finished_at = current_timestamp
29
32
  end
30
33
 
31
34
  # mark job as failed when it is failed
32
- def fail! msg_or_exception
33
- #raise Burstflow::Job::InternalError.new(self, "Can't fail: already failed") if failed?
34
- #raise Burstflow::Job::InternalError.new(self, Can't fail: already finished") if finished?
35
- raise Burstflow::Job::InternalError.new(self, "Can't fail: not started") if !started?
35
+ def fail!(msg_or_exception)
36
+ # raise Burstflow::Job::InternalError.new(self, "Can't fail: already failed") if failed?
37
+ # raise Burstflow::Job::InternalError.new(self, Can't fail: already finished") if finished?
38
+ raise Burstflow::Job::InternalError.new(self, "Can't fail: not started") unless started?
39
+
36
40
  self.finished_at = self.failed_at = current_timestamp
37
41
 
38
42
  context = {}
@@ -51,14 +55,16 @@ module Burstflow::Job::State
51
55
  # mark job as suspended
52
56
  def suspend!
53
57
  raise Burstflow::Job::InternalError.new(self, "Can't suspend: already suspended") if suspended?
54
- raise Burstflow::Job::InternalError.new(self, "Can't suspend: not runnig") if !running?
58
+ raise Burstflow::Job::InternalError.new(self, "Can't suspend: not runnig") unless running?
59
+
55
60
  self.suspended_at = current_timestamp
56
61
  end
57
62
 
58
63
  # mark job as resumed
59
64
  def resume!
60
65
  raise Burstflow::Job::InternalError.new(self, "Can't resume: already resumed") if resumed?
61
- raise Burstflow::Job::InternalError.new(self, "Can't resume: not suspended") if !suspended?
66
+ raise Burstflow::Job::InternalError.new(self, "Can't resume: not suspended") unless suspended?
67
+
62
68
  self.resumed_at = current_timestamp
63
69
  end
64
70
 
@@ -115,11 +121,9 @@ module Burstflow::Job::State
115
121
  def current_timestamp
116
122
  Time.now.to_i
117
123
  end
118
-
119
124
  end
120
125
 
121
126
  class_methods do
122
-
123
127
  end
124
128
 
125
- end
129
+ end