simpler_workflow 0.1.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.
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +180 -0
- data/Rakefile +2 -0
- data/lib/simpler_workflow.rb +23 -0
- data/lib/simpler_workflow/activity.rb +125 -0
- data/lib/simpler_workflow/domain.rb +97 -0
- data/lib/simpler_workflow/version.rb +3 -0
- data/lib/simpler_workflow/workflow.rb +150 -0
- data/lib/simpler_workflow/workflow_collection.rb +16 -0
- data/simpler_workflow.gemspec +21 -0
- metadata +120 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Snugg Home LLC
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,180 @@
|
|
1
|
+
# SimplerWorkflow
|
2
|
+
|
3
|
+
A wrapper around Amazon's Simple Workflow Service meant to simplify declaring and using activities and workflow. Provides some sane defaults
|
4
|
+
and work around some idiosyncracies of the platform.
|
5
|
+
|
6
|
+
## Installation
|
7
|
+
|
8
|
+
Add this line to your application's Gemfile:
|
9
|
+
|
10
|
+
gem 'simpler_workflow'
|
11
|
+
|
12
|
+
And then execute:
|
13
|
+
|
14
|
+
$ bundle
|
15
|
+
|
16
|
+
Or install it yourself as:
|
17
|
+
|
18
|
+
$ gem install simpler_workflow
|
19
|
+
|
20
|
+
## Usage
|
21
|
+
|
22
|
+
### Configuring AWS Gem
|
23
|
+
|
24
|
+
We are using the aws-sdk gem to communicate with Amazon's service. You can configure the service by putting an ```aws.yml``` file
|
25
|
+
in your config directory. The file should contain the following information:
|
26
|
+
|
27
|
+
```yaml
|
28
|
+
development:
|
29
|
+
access_key_id: <Amazon Acess Key ID>
|
30
|
+
secret_access_key: <Amazon's secret access key>
|
31
|
+
```
|
32
|
+
|
33
|
+
This will authenticate your application or script against AWS and give you access to your SWF domain, workflows and activity.
|
34
|
+
|
35
|
+
### Access a domain
|
36
|
+
|
37
|
+
You will need to get a handle on a SWF domain before you can do anything else. Domains are accessed using the ```SimplerWorkflow::Domain```
|
38
|
+
class. This declares a domain that does not retain workflow execution data:
|
39
|
+
|
40
|
+
```ruby
|
41
|
+
domain = SimplerWorkflow::Domain["my-domain"]
|
42
|
+
```
|
43
|
+
An other option is to use the ```domains``` method to get a handle on a domain and pass a block. This allows you to write the following code:
|
44
|
+
|
45
|
+
```ruby
|
46
|
+
domain = SimplerWorkflow::Domain.domains("my_domain") do
|
47
|
+
# Register activities
|
48
|
+
register_activity(:an_activity, '1.0.0') do
|
49
|
+
# See details below...
|
50
|
+
end
|
51
|
+
# Register workflow(s)
|
52
|
+
register_workflow(:a_workflow, '1.1.0') do
|
53
|
+
# See details below...
|
54
|
+
end
|
55
|
+
end
|
56
|
+
```
|
57
|
+
|
58
|
+
You can also get a handle on a domain that retains information about workflow execution for 10 days with the following code:
|
59
|
+
|
60
|
+
```ruby
|
61
|
+
domain = SimplerWorkflow::Domain.new("my-domain", 10)
|
62
|
+
```
|
63
|
+
|
64
|
+
Domains are scoped by AWS accounts. The name of the domain must be unique within the account. You do not need to create the domain on AWS
|
65
|
+
since it is created the first time it is accessed.
|
66
|
+
|
67
|
+
### Creating an activity
|
68
|
+
|
69
|
+
Activities perform the work attached to the workflow and report back to SWF when the activity completes or it fails.
|
70
|
+
|
71
|
+
```SimplerWorkflow``` makes it easier to register an activity with your domain.
|
72
|
+
|
73
|
+
Activities must provide the following:
|
74
|
+
* A name
|
75
|
+
* A version
|
76
|
+
* Some code to run when it is invoked
|
77
|
+
|
78
|
+
You can also optionaly declare when what to do when the activity fails or succeeds.
|
79
|
+
|
80
|
+
```ruby
|
81
|
+
my_activity = domain.register_activity :my_activity, "1.0.0" do
|
82
|
+
perform_activity do |task|
|
83
|
+
input = task.input
|
84
|
+
puts task
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
my_activity.start_activity_loop
|
89
|
+
```
|
90
|
+
|
91
|
+
The activity manages a loop that waits for messages from SWF.
|
92
|
+
|
93
|
+
Activities are passed a task parameter. This parameter is provided to the activity by SWF and provides a lot of information about the task
|
94
|
+
at end. One item passed is the input attribute.
|
95
|
+
|
96
|
+
The block attached to the perform_activity method is called when the activity is invoked. This block contains the actions that an activity
|
97
|
+
will perform. The ```SimplerWorkflow::Activity``` class will automatically report that the activity completed successfully when the
|
98
|
+
block returns unless a response has been provided in the block. It will automatically report that an activity failed when an unhandled
|
99
|
+
exception is thrown within the block.
|
100
|
+
|
101
|
+
The activity can influence what happens when the activity succeeds or fail. You can specify the activity's failure response through the
|
102
|
+
```SimplerWorkflow::Activity#on_fail``` method. By default, the activity will ask the workflow to abort itself on failure. You can also
|
103
|
+
ask the workflow to repeat the activity by passing ```:retry``` to the method:
|
104
|
+
|
105
|
+
```ruby
|
106
|
+
my_activity = domain.register_activity :my_activity, "1.0.0" do
|
107
|
+
on_fail :retry
|
108
|
+
|
109
|
+
perform_activity do |task|
|
110
|
+
# ...
|
111
|
+
end
|
112
|
+
end
|
113
|
+
```
|
114
|
+
|
115
|
+
The activity can also tell a workflow what activity to trigger next on the workflow. This only works when using the default decision
|
116
|
+
loop (described later). This is done by declaring what is the next activity should be:
|
117
|
+
|
118
|
+
```ruby
|
119
|
+
my_activity = domain.register_activity :my_activity, "1.0.0" do
|
120
|
+
on_success :my_next_activity, "1.0.0"
|
121
|
+
|
122
|
+
perform_activity do |task|
|
123
|
+
# ...
|
124
|
+
end
|
125
|
+
end
|
126
|
+
```
|
127
|
+
|
128
|
+
### Workflow and Decision Loops
|
129
|
+
|
130
|
+
The next key concept in ```SimplerWorkflow``` is the workflow. The workflow decides what activities to invoke, what to do when
|
131
|
+
they complete and what to do when they fail. The ```SimplerWorkflow::Workflow``` object manages the decision loop.
|
132
|
+
|
133
|
+
By default, the workflow is setup to allow for a linear set of activities until the list runs out. This is convenient for simple
|
134
|
+
workflows. There are also hooks to override what happens with each decision point to accomodate more complex workflows.
|
135
|
+
|
136
|
+
Workflows are declared and registered through the ```SimplerWorkflow::Domain#register_workflow``` method. This will register a
|
137
|
+
workflow and configure it to start a linear workflow with version 1.0.0 of the :my_activity activity:
|
138
|
+
|
139
|
+
```ruby
|
140
|
+
my_workflow = domain.register_workflow :my_workflow, '1.0.0' do
|
141
|
+
initial_activity :my_activity, '1.0.0'
|
142
|
+
end
|
143
|
+
```
|
144
|
+
|
145
|
+
The next step is to start the decision loop:
|
146
|
+
|
147
|
+
```ruby
|
148
|
+
my_workflow.decision_loop
|
149
|
+
```
|
150
|
+
|
151
|
+
#### Customizing the workflow
|
152
|
+
|
153
|
+
There are hooks for different section of the decision loop. You can specify what happens when the workflow is started with the ```on_start_execution```
|
154
|
+
method:
|
155
|
+
|
156
|
+
```ruby
|
157
|
+
my_workflow = domain.register_workflow :my_workflow, '1.0.0' do
|
158
|
+
on_start_execution do |task, event|
|
159
|
+
puts "Mary had a little lamb"
|
160
|
+
task.schedule_activity_task my_activity.to_activity_type, :input => { :my_param => 'value'}
|
161
|
+
end
|
162
|
+
end
|
163
|
+
```
|
164
|
+
|
165
|
+
The task and event parameters are received from SWF. Unfortunately, you must still work within the constraints of the AWS SWF SDK. The ```SimplerWorkflow::Activity#to_activity_type``` generates the proper incantation used by SWF to identify and locate an activity.
|
166
|
+
|
167
|
+
You can also define similar hooks for events using the following methods:
|
168
|
+
|
169
|
+
* ```on_activity_completed``` is called when an activity completes and SWF reports back to the decision loop.
|
170
|
+
* ```on_activity_failed``` is called when an activity reports a failure to SWF.
|
171
|
+
|
172
|
+
## Contributing
|
173
|
+
|
174
|
+
We welcome all kinds of contributions. This include code, fixes, issues, documentation, tests... Here's how you can contribute:
|
175
|
+
|
176
|
+
1. Fork it
|
177
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
178
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
179
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
180
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'aws-sdk'
|
2
|
+
|
3
|
+
module SimplerWorkflow
|
4
|
+
def SimplerWorkflow.domain(domain_name)
|
5
|
+
@domains ||= {}
|
6
|
+
@domains[domain_name.to_sym] ||= Domain.new(domain_name)
|
7
|
+
end
|
8
|
+
|
9
|
+
def SimplerWorkflow.swf
|
10
|
+
@swf ||= ::AWS::SimpleWorkflow.new
|
11
|
+
end
|
12
|
+
|
13
|
+
autoload :Version, 'simpler_workflow/version'
|
14
|
+
autoload :Domain, 'simpler_workflow/domain'
|
15
|
+
autoload :Workflow, 'simpler_workflow/workflow'
|
16
|
+
autoload :Activity, 'simpler_workflow/activity'
|
17
|
+
end
|
18
|
+
|
19
|
+
class Map
|
20
|
+
def Map.from_json(json)
|
21
|
+
from_hash(JSON.parse(json))
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
module SimplerWorkflow
|
2
|
+
class Activity
|
3
|
+
attr_reader :domain, :name, :version, :options, :next_activity
|
4
|
+
|
5
|
+
def initialize(domain, name, version, options = {})
|
6
|
+
Activity.activities[[name, version]] ||= begin
|
7
|
+
default_options = {
|
8
|
+
:default_task_list => name,
|
9
|
+
:default_task_start_to_close_timeout => 1 * 60 * 60,
|
10
|
+
:default_task_schedule_to_start_timeout => 20,
|
11
|
+
:default_task_schedule_to_close_timeout => 1 * 60 * 60,
|
12
|
+
:default_task_heartbeat_timeout => :none
|
13
|
+
}
|
14
|
+
@options = default_options.merge(options)
|
15
|
+
@domain = domain
|
16
|
+
@name = name
|
17
|
+
@version = version
|
18
|
+
@failure_policy = :abort
|
19
|
+
self
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def method_missing(meth_name, *args)
|
24
|
+
if @options.has_key?(meth_name.to_sym)
|
25
|
+
@options[meth_name.to_sym] = args[0]
|
26
|
+
else
|
27
|
+
super
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def on_success(activity, version = nil)
|
32
|
+
case activity
|
33
|
+
when Hash
|
34
|
+
activity.symbolize_keys!
|
35
|
+
name = activity[:name].to_sym
|
36
|
+
version = activity[:version]
|
37
|
+
when String
|
38
|
+
name = activity.to_sym
|
39
|
+
when Symbol
|
40
|
+
name = activity
|
41
|
+
end
|
42
|
+
@next_activity = { :name => name, :version => version }
|
43
|
+
end
|
44
|
+
|
45
|
+
def on_fail(failure_policy)
|
46
|
+
@failure_policy = failure_policy.to_sym
|
47
|
+
end
|
48
|
+
|
49
|
+
def failure_policy
|
50
|
+
@failure_policy || :abort
|
51
|
+
end
|
52
|
+
|
53
|
+
def perform_activity(&block)
|
54
|
+
@perform_task = block
|
55
|
+
end
|
56
|
+
|
57
|
+
def perform_task(task)
|
58
|
+
logger.info("Performing task #{name}")
|
59
|
+
@perform_task.call(task)
|
60
|
+
rescue => e
|
61
|
+
logger.error e.message
|
62
|
+
logger.error e.backtrace.join("\n")
|
63
|
+
task.fail! :reason => e.message, :details => {:failure_policy => failure_policy}.to_json
|
64
|
+
end
|
65
|
+
|
66
|
+
def to_activity_type
|
67
|
+
domain.activity_types[name.to_s, version]
|
68
|
+
end
|
69
|
+
|
70
|
+
def start_activity_loop
|
71
|
+
Thread.new do
|
72
|
+
loop do
|
73
|
+
begin
|
74
|
+
logger.info("Starting activity_loop for #{name}")
|
75
|
+
domain.activity_tasks.poll(name.to_s) do |task|
|
76
|
+
logger.info("Received task...")
|
77
|
+
perform_task(task)
|
78
|
+
unless task.responded?
|
79
|
+
if next_activity
|
80
|
+
result = {:next_activity => next_activity}.to_json
|
81
|
+
task.complete!(:result => result)
|
82
|
+
else
|
83
|
+
task.complete!
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
rescue Timeout::Error => e
|
88
|
+
rescue => e
|
89
|
+
logger.error(e.message)
|
90
|
+
logger.error(e.backtrace.join("\n"))
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def self.[](name, version = nil)
|
97
|
+
case name
|
98
|
+
when String
|
99
|
+
name = name.to_sym
|
100
|
+
when Hash
|
101
|
+
name.symbolize_keys!
|
102
|
+
version = name[:version]
|
103
|
+
name = name[:name]
|
104
|
+
end
|
105
|
+
activities[[name, version]]
|
106
|
+
end
|
107
|
+
|
108
|
+
def self.register(name, version, activity)
|
109
|
+
activities[[name, version]] = activity
|
110
|
+
end
|
111
|
+
|
112
|
+
protected
|
113
|
+
def self.activities
|
114
|
+
@activities ||= {}
|
115
|
+
end
|
116
|
+
|
117
|
+
def self.swf
|
118
|
+
SimplerWorkflow.swf
|
119
|
+
end
|
120
|
+
|
121
|
+
def logger
|
122
|
+
$logger || Rails.logger
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
module SimplerWorkflow
|
2
|
+
class Domain
|
3
|
+
def initialize(domain_name, retention = 2, &block)
|
4
|
+
domain_name = domain_name.to_s
|
5
|
+
@domain = swf.domains[domain_name]
|
6
|
+
unless swf.domains.include?(@domain)
|
7
|
+
@domain = swf.domains.create(domain_name, retention)
|
8
|
+
end
|
9
|
+
|
10
|
+
self.instance_eval(&block) if block
|
11
|
+
|
12
|
+
self
|
13
|
+
end
|
14
|
+
|
15
|
+
def Domain.domains(domain_name, &block)
|
16
|
+
@domains ||= {}
|
17
|
+
@domains[domain_name] ||= Domain.new(domain_name)
|
18
|
+
@domains[domain_name].instance_eval(&block) if block
|
19
|
+
@domains[domain_name]
|
20
|
+
end
|
21
|
+
|
22
|
+
def Domain.[](domain_name)
|
23
|
+
Domain.domains(domain_name)
|
24
|
+
end
|
25
|
+
|
26
|
+
def register_workflow(name, version, &block)
|
27
|
+
unless workflow = Workflow[name, version]
|
28
|
+
workflow = Workflow.new(self, name, version)
|
29
|
+
end
|
30
|
+
|
31
|
+
workflow.instance_eval(&block) if block
|
32
|
+
|
33
|
+
begin
|
34
|
+
self.domain.workflow_types.register(name.to_s, version, workflow.options)
|
35
|
+
rescue ::AWS::SimpleWorkflow::Errors::TypeAlreadyExistsFault => e
|
36
|
+
# Instance already registered...
|
37
|
+
end
|
38
|
+
workflow
|
39
|
+
end
|
40
|
+
|
41
|
+
def workflows
|
42
|
+
Workflow
|
43
|
+
end
|
44
|
+
|
45
|
+
def start_workflow(name, version, input)
|
46
|
+
logger.info("Starting workflow[#{name},#{version}]")
|
47
|
+
workflow = Workflow[name, version] || Workflow.new(self, name, version)
|
48
|
+
workflow.start_workflow(input)
|
49
|
+
end
|
50
|
+
|
51
|
+
def activities
|
52
|
+
Activity
|
53
|
+
end
|
54
|
+
|
55
|
+
def activity_types
|
56
|
+
domain.activity_types
|
57
|
+
end
|
58
|
+
|
59
|
+
def register_activity(name, version, &block)
|
60
|
+
unless activity = Activity[name, version]
|
61
|
+
logger.info("Registering Activity[#{name},#{version}]")
|
62
|
+
activity = Activity.new(self, name, version)
|
63
|
+
end
|
64
|
+
|
65
|
+
activity.instance_eval(&block) if block
|
66
|
+
|
67
|
+
begin
|
68
|
+
self.domain.activity_types.register(name.to_s, version, activity.options)
|
69
|
+
rescue ::AWS::SimpleWorkflow::Errors::TypeAlreadyExistsFault
|
70
|
+
# Nothing to do, should probably log something here...
|
71
|
+
end
|
72
|
+
|
73
|
+
activity
|
74
|
+
end
|
75
|
+
|
76
|
+
def method_missing(meth_name, *args)
|
77
|
+
if domain.respond_to?(meth_name.to_sym)
|
78
|
+
domain.send(meth_name.to_sym, *args)
|
79
|
+
else
|
80
|
+
super
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
protected
|
85
|
+
def swf
|
86
|
+
SimplerWorkflow.swf
|
87
|
+
end
|
88
|
+
|
89
|
+
def domain
|
90
|
+
@domain
|
91
|
+
end
|
92
|
+
|
93
|
+
def logger
|
94
|
+
$logger || Rails.logger
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,150 @@
|
|
1
|
+
module SimplerWorkflow
|
2
|
+
class Workflow
|
3
|
+
attr_reader :task_list, :domain, :name, :version, :options, :initial_activity_type
|
4
|
+
|
5
|
+
def initialize(domain, name, version, options = {})
|
6
|
+
Workflow.workflows[[name, version]] ||= begin
|
7
|
+
default_options = {
|
8
|
+
:default_task_list => name,
|
9
|
+
:default_task_start_to_close_timeout => 1 * 24 * 60 * 60,
|
10
|
+
:default_execution_start_to_close_timeout => 1 * 24 * 60 * 60,
|
11
|
+
:default_child_policy => :terminate
|
12
|
+
}
|
13
|
+
@options = default_options.merge(options)
|
14
|
+
@domain = domain
|
15
|
+
@name = name
|
16
|
+
@version = version
|
17
|
+
self
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def initial_activity(name, version = nil)
|
22
|
+
if activity = Activity[name.to_sym, version]
|
23
|
+
@initial_activity_type = activity.to_activity_type
|
24
|
+
elsif activity = domain.activity_types[name.to_s, version]
|
25
|
+
@initial_activity_type = activity
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def decision_loop
|
30
|
+
logger.info("Starting decision loop for #{name.to_s}, #{version} listening to #{task_list}")
|
31
|
+
domain.decision_tasks.poll(task_list) do |decision_task|
|
32
|
+
logger.info("Received decision task")
|
33
|
+
decision_task.new_events.each do |event|
|
34
|
+
logger.info("Processing #{event.event_type}")
|
35
|
+
case event.event_type
|
36
|
+
when 'WorkflowExecutionStarted'
|
37
|
+
start_execution(decision_task, event)
|
38
|
+
when 'ActivityTaskCompleted'
|
39
|
+
activity_completed(decision_task, event)
|
40
|
+
when 'ActivityTaskFailed'
|
41
|
+
activity_failed(decision_task, event)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
rescue Timeout::Error => e
|
46
|
+
retry
|
47
|
+
end
|
48
|
+
|
49
|
+
def task_list
|
50
|
+
@options[:default_task_list][:name].to_s
|
51
|
+
end
|
52
|
+
|
53
|
+
def start_execution(decision_task, event)
|
54
|
+
logger.info "Starting the execution of the job."
|
55
|
+
if @on_start_execution && @on_start_execution.respond_to?(:call)
|
56
|
+
@on_start_execution.call(decision_task, event)
|
57
|
+
else
|
58
|
+
decision_task.schedule_activity_task initial_activity_type, :input => event.attributes.input
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def activity_completed(decision_task, event)
|
63
|
+
if @on_activity_completed && @on_activity_completed.respond_to?(:call)
|
64
|
+
@on_activity_completed.call(decision_task, event)
|
65
|
+
else
|
66
|
+
if event.attributes.keys.include?(:result)
|
67
|
+
result = Map.from_json(event.attributes.result)
|
68
|
+
next_activity = result[:next_activity]
|
69
|
+
activity_type = domain.activity_types[next_activity[:name], next_activity[:version]]
|
70
|
+
decision_task.schedule_activity_task activity_type, :input => scheduled_event(decision_task, event).attributes.input
|
71
|
+
else
|
72
|
+
logger.info("Workflow #{name}, #{version} completed")
|
73
|
+
decision_task.complete_workflow_execution :result => 'success'
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def activity_failed(decision_task, event)
|
79
|
+
logger.info("Activity failed.")
|
80
|
+
if @on_activity_failed && @on_activity_failed.respond_to?(:call)
|
81
|
+
@on_activity_failed.call(decision_task, event)
|
82
|
+
else
|
83
|
+
if event.attributes.keys.include?(:details)
|
84
|
+
details = Map.from_json(event.attributes.details)
|
85
|
+
case details.failure_policy.to_sym
|
86
|
+
when :abort
|
87
|
+
decision_task.cancel_workflow_execution
|
88
|
+
when :retry
|
89
|
+
logger.info("Retrying activity #{last_activity(decision_task, event).name} #{last_activity(decision_task, event).version}")
|
90
|
+
decision_task.schedule_activity_task last_activity(decision_task, event), :input => last_input(decision_task, event)
|
91
|
+
end
|
92
|
+
else
|
93
|
+
decision_task.cancel_workflow_execution
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def start_workflow(input)
|
99
|
+
domain.workflow_types[name.to_s, version].start_execution(:input => input)
|
100
|
+
end
|
101
|
+
|
102
|
+
def on_start_execution(&block)
|
103
|
+
@on_start_execution = block
|
104
|
+
end
|
105
|
+
|
106
|
+
def on_activity_completed(&block)
|
107
|
+
@on_activity_completed = block
|
108
|
+
end
|
109
|
+
|
110
|
+
def on_activity_failed(&block)
|
111
|
+
@on_activity_failed = block
|
112
|
+
end
|
113
|
+
|
114
|
+
def self.[](name, version)
|
115
|
+
workflows[[name, version]]
|
116
|
+
end
|
117
|
+
|
118
|
+
def self.register(name, version, workflow)
|
119
|
+
workflows[[name, version]] = workflow
|
120
|
+
end
|
121
|
+
|
122
|
+
def method_missing(meth_name, *args)
|
123
|
+
if @options.has_key?(meth_name.to_sym)
|
124
|
+
@options[meth_name.to_sym] = args[0]
|
125
|
+
else
|
126
|
+
super
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
protected
|
131
|
+
def self.workflows
|
132
|
+
@workflows ||= {}
|
133
|
+
end
|
134
|
+
|
135
|
+
def self.swf
|
136
|
+
SimplerWorkflow.swf
|
137
|
+
end
|
138
|
+
|
139
|
+
def scheduled_event(decision_task, event)
|
140
|
+
decision_task.events.to_a[event.attributes.scheduled_event_id - 1]
|
141
|
+
end
|
142
|
+
def last_activity(decision_task, event)
|
143
|
+
scheduled_event(decision_task, event).attributes.activity_type
|
144
|
+
end
|
145
|
+
|
146
|
+
def last_input(decision_task, event)
|
147
|
+
scheduled_event(decision_task, event).attributes.input
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module SimplerWorkflow
|
2
|
+
class WorkflowCollection
|
3
|
+
def [](name, version)
|
4
|
+
registry[[name,version]]
|
5
|
+
end
|
6
|
+
|
7
|
+
def []=(name, version, value)
|
8
|
+
registry[[name, version]] = value
|
9
|
+
end
|
10
|
+
|
11
|
+
protected
|
12
|
+
def registry
|
13
|
+
@registry ||= {}
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/simpler_workflow/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["Frederic Jean"]
|
6
|
+
gem.email = ["fred@snugghome.com"]
|
7
|
+
gem.description = %q{A wrapper around Amazon's Simple Workflow Service}
|
8
|
+
gem.summary = %q{A wrapper and DSL around Amazon's Simple Workflow Service with the goal of making it almost pleasant to define workflows.}
|
9
|
+
gem.homepage = "https://github.com/SnuggHome/simpler_workflow"
|
10
|
+
|
11
|
+
gem.files = `git ls-files`.split($\)
|
12
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
13
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
14
|
+
gem.name = "simpler_workflow"
|
15
|
+
gem.require_paths = ["lib"]
|
16
|
+
gem.version = SimplerWorkflow::VERSION
|
17
|
+
|
18
|
+
gem.add_dependency 'aws-sdk', '~> 1.4.0'
|
19
|
+
gem.add_dependency 'map'
|
20
|
+
gem.add_development_dependency 'rspec'
|
21
|
+
end
|
metadata
ADDED
@@ -0,0 +1,120 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: simpler_workflow
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 27
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 0
|
10
|
+
version: 0.1.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Frederic Jean
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2012-04-24 00:00:00 Z
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: aws-sdk
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ~>
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
hash: 7
|
29
|
+
segments:
|
30
|
+
- 1
|
31
|
+
- 4
|
32
|
+
- 0
|
33
|
+
version: 1.4.0
|
34
|
+
type: :runtime
|
35
|
+
version_requirements: *id001
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: map
|
38
|
+
prerelease: false
|
39
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
40
|
+
none: false
|
41
|
+
requirements:
|
42
|
+
- - ">="
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
hash: 3
|
45
|
+
segments:
|
46
|
+
- 0
|
47
|
+
version: "0"
|
48
|
+
type: :runtime
|
49
|
+
version_requirements: *id002
|
50
|
+
- !ruby/object:Gem::Dependency
|
51
|
+
name: rspec
|
52
|
+
prerelease: false
|
53
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
54
|
+
none: false
|
55
|
+
requirements:
|
56
|
+
- - ">="
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
hash: 3
|
59
|
+
segments:
|
60
|
+
- 0
|
61
|
+
version: "0"
|
62
|
+
type: :development
|
63
|
+
version_requirements: *id003
|
64
|
+
description: A wrapper around Amazon's Simple Workflow Service
|
65
|
+
email:
|
66
|
+
- fred@snugghome.com
|
67
|
+
executables: []
|
68
|
+
|
69
|
+
extensions: []
|
70
|
+
|
71
|
+
extra_rdoc_files: []
|
72
|
+
|
73
|
+
files:
|
74
|
+
- .gitignore
|
75
|
+
- Gemfile
|
76
|
+
- LICENSE
|
77
|
+
- README.md
|
78
|
+
- Rakefile
|
79
|
+
- lib/simpler_workflow.rb
|
80
|
+
- lib/simpler_workflow/activity.rb
|
81
|
+
- lib/simpler_workflow/domain.rb
|
82
|
+
- lib/simpler_workflow/version.rb
|
83
|
+
- lib/simpler_workflow/workflow.rb
|
84
|
+
- lib/simpler_workflow/workflow_collection.rb
|
85
|
+
- simpler_workflow.gemspec
|
86
|
+
homepage: https://github.com/SnuggHome/simpler_workflow
|
87
|
+
licenses: []
|
88
|
+
|
89
|
+
post_install_message:
|
90
|
+
rdoc_options: []
|
91
|
+
|
92
|
+
require_paths:
|
93
|
+
- lib
|
94
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
95
|
+
none: false
|
96
|
+
requirements:
|
97
|
+
- - ">="
|
98
|
+
- !ruby/object:Gem::Version
|
99
|
+
hash: 3
|
100
|
+
segments:
|
101
|
+
- 0
|
102
|
+
version: "0"
|
103
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
104
|
+
none: false
|
105
|
+
requirements:
|
106
|
+
- - ">="
|
107
|
+
- !ruby/object:Gem::Version
|
108
|
+
hash: 3
|
109
|
+
segments:
|
110
|
+
- 0
|
111
|
+
version: "0"
|
112
|
+
requirements: []
|
113
|
+
|
114
|
+
rubyforge_project:
|
115
|
+
rubygems_version: 1.8.20
|
116
|
+
signing_key:
|
117
|
+
specification_version: 3
|
118
|
+
summary: A wrapper and DSL around Amazon's Simple Workflow Service with the goal of making it almost pleasant to define workflows.
|
119
|
+
test_files: []
|
120
|
+
|