workflow_rb 0.1.0 → 0.1.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 378c7aa6543eccd7a1706b1b4248d045fb68d705
4
- data.tar.gz: 2520ebcac7a53516bb207ef4365e17b346528e18
3
+ metadata.gz: db6ec565f41bd3aa0fb7185eb61d1bd564507b23
4
+ data.tar.gz: 8819c1ff1c35e6805d9087ade26b5b6551cd5027
5
5
  SHA512:
6
- metadata.gz: 1949842e74f1007bf3b422f586e7dc2c118ae96702949a019ad677766c6536c5ce547ad4566adc76ba3a11ac11f9e3b2bc520d03ecf72667ee1e6e2fd9a5d504
7
- data.tar.gz: cafba682c685829de508f3bb0c9ccf6b159f0ad4613b00b218df7c6da1d1b051eedf563b48e8c4d0962547eb5416ac1652f9568cb19704090dea361a75f4d2fc
6
+ metadata.gz: 047385eea283a299a6fd81b661207a412bb7dc1072487d6b679d673cb5f5df2f25711aefb7fc1a103897ee55ba5d3bbe0c0eb24e5f1ccb34b66ed182dff52b3b
7
+ data.tar.gz: 1673696f3d5fcb15ef57c32995854aefe70fdd25b023599f041674ebda50ae97ee2a5b8b0f6c510d59cbf6a18ccad222a6ca42f51a57a202e92ce28465185a77
data/.gitignore CHANGED
@@ -8,3 +8,4 @@
8
8
  /spec/reports/
9
9
  /tmp/
10
10
  /.idea
11
+ /workflow_rb-0.1.0.gem
data/README.md CHANGED
@@ -1,8 +1,6 @@
1
1
  # WorkflowRb
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/workflow_rb`. To experiment with that code, run `bin/console` for an interactive prompt.
4
-
5
- TODO: Delete this and the text above, and describe your gem
3
+ WorkflowRb is a light weight workflow engine for Ruby. It supports pluggable persistence and concurrency providers to allow for multi-node clusters.
6
4
 
7
5
  ## Installation
8
6
 
@@ -20,17 +18,264 @@ Or install it yourself as:
20
18
 
21
19
  $ gem install workflow_rb
22
20
 
23
- ## Usage
21
+ ## Basic Concepts
22
+
23
+ ### Steps
24
+
25
+ A workflow consists of a series of connected steps. Each step produces an outcome value and subsequent steps are triggered by subscribing to a particular outcome of a preceeding step. The default outcome of *nil* can be used for a basic linear workflow.
26
+ Steps are usually defined by inheriting from the StepBody abstract class and implementing the *run* method. They can also be created inline while defining the workflow structure.
27
+
28
+ First we define some steps
29
+
30
+ ```ruby
31
+ class HelloWorld < WorkflowRb::StepBody
32
+ def run(context)
33
+ puts 'Hello world'
34
+ WorkflowRb::ExecutionResult.NextStep
35
+ end
36
+ end
37
+ ```
38
+
39
+ Then we define the workflow structure by composing a chain of steps.
40
+
41
+ ```ruby
42
+ class HelloWorld_Workflow
43
+ ID = 'hello world'
44
+ VERSION = 1
45
+ DATA_CLASS = nil
46
+
47
+ def build(builder)
48
+ builder
49
+ .start_with(HelloWorld)
50
+ .then(GoodbyeWorld)
51
+ end
52
+ end
53
+
54
+ ```
55
+ The ID and VERSION constants are used to identify the workflow definition.
56
+
57
+ You can also define your steps inline
58
+
59
+ ```ruby
60
+ class HelloWorld_Workflow
61
+ ID = 'hello world'
62
+ VERSION = 1
63
+ DATA_CLASS = nil
64
+
65
+ def build(builder)
66
+ builder
67
+ .start_step do |context|
68
+ puts 'Hello world'
69
+ WorkflowRb::ExecutionResult.NextStep
70
+ end
71
+ .then_step do |context|
72
+ puts 'Goodbye world'
73
+ WorkflowRb::ExecutionResult.NextStep
74
+ end
75
+ end
76
+ end
77
+
78
+ ```
79
+
80
+ Each running workflow is persisted to the chosen persistence provider between each step, where it can be picked up at a later point in time to continue execution. The outcome result of your step can instruct the workflow host to defer further execution of the workflow until a future point in time or in response to an external event.
81
+
82
+ The first time a particular step within the workflow is called, the persistence_data property on the context object is *nil*. The *ExecutionResult* produced by the *run* method can either cause the workflow to proceed to the next step by providing an outcome value, instruct the workflow to sleep for a defined period or simply not move the workflow forward. If no outcome value is produced, then the step becomes re-entrant by setting persistence_data, so the workflow host will call this step again in the future buy will populate the persistence_data with it's previous value.
83
+
84
+ For example, this step will initially run with *nil* persistence_data and put the workflow to sleep for 12 hours, while setting the persistence_data to *'something'*. 12 hours later, the step will be called again but context.persistence_data will now contain the object constructed in the previous iteration, and will now produce an outcome value of *nil*, causing the workflow to move forward.
85
+
86
+ ```C#
87
+ public class SleepStep : StepBody
88
+ {
89
+ public override ExecutionResult Run(IStepExecutionContext context)
90
+ {
91
+ if (context.PersistenceData == null)
92
+ return ExecutionResult.Sleep(Timespan.FromHours(12), new Object());
93
+ else
94
+ return ExecutionResult.Next();
95
+ }
96
+ }
97
+ ```
98
+
99
+ ### Passing data between steps
100
+
101
+ Each step is intented to be a blackbox, therefore they support inputs and outputs. These inputs and outputs can be mapped to a data class that defines the custom data relevant to each workflow instance.
102
+
103
+ The following sample shows how to define inputs and outputs on a step, it then shows how define a workflow with a typed class for internal data and how to map the inputs and outputs to properties on the custom data class.
104
+
105
+ ```C#
106
+ //Our workflow step with inputs and outputs
107
+ public class AddNumbers : StepBody
108
+ {
109
+ public int Input1 { get; set; }
110
+
111
+ public int Input2 { get; set; }
112
+
113
+ public int Output { get; set; }
114
+
115
+ public override ExecutionResult Run(IStepExecutionContext context)
116
+ {
117
+ Output = (Input1 + Input2);
118
+ return ExecutionResult.Next();
119
+ }
120
+ }
121
+
122
+ //Our class to define the internal data of our workflow
123
+ public class MyDataClass
124
+ {
125
+ public int Value1 { get; set; }
126
+ public int Value2 { get; set; }
127
+ public int Value3 { get; set; }
128
+ }
129
+
130
+ //Our workflow definition with strongly typed internal data and mapped inputs & outputs
131
+ public class PassingDataWorkflow : IWorkflow<MyDataClass>
132
+ {
133
+ public void Build(IWorkflowBuilder<MyDataClass> builder)
134
+ {
135
+ builder
136
+ .StartWith<AddNumbers>()
137
+ .Input(step => step.Input1, data => data.Value1)
138
+ .Input(step => step.Input2, data => data.Value2)
139
+ .Output(data => data.Value3, step => step.Output)
140
+ .Then<CustomMessage>()
141
+ .Input(step => step.Message, data => "The answer is " + data.Value3.ToString());
142
+ }
143
+ ...
144
+ }
145
+
146
+ ```
147
+
148
+ ### Multiple outcomes / forking
149
+
150
+ A workflow can take a different path depending on the outcomes of preceeding steps. The following example shows a process where first a random number of 0 or 1 is generated and is the outcome of the first step. Then, depending on the outcome value, the workflow will either fork to (TaskA + TaskB) or (TaskC + TaskD)
151
+
152
+ ```C#
153
+ public class MultipleOutcomeWorkflow : IWorkflow
154
+ {
155
+ public void Build(IWorkflowBuilder<object> builder)
156
+ {
157
+ builder
158
+ .StartWith<RandomOutput>(x => x.Name("Random Step"))
159
+ .When(0)
160
+ .Then<TaskA>()
161
+ .Then<TaskB>()
162
+ .End<RandomOutput>("Random Step")
163
+ .When(1)
164
+ .Then<TaskC>()
165
+ .Then<TaskD>()
166
+ .End<RandomOutput>("Random Step");
167
+ }
168
+ }
169
+ ```
170
+
171
+ ### Events
172
+
173
+ A workflow can also wait for an external event before proceeding. In the following example, the workflow will wait for an event called *"MyEvent"* with a key of *0*. Once an external source has fired this event, the workflow will wake up and continute processing, passing the data generated by the event onto the next step.
174
+
175
+ ```C#
176
+ public class EventSampleWorkflow : IWorkflow<MyDataClass>
177
+ {
178
+ public void Build(IWorkflowBuilder<MyDataClass> builder)
179
+ {
180
+ builder
181
+ .StartWith(context =>
182
+ {
183
+ Console.WriteLine("workflow started");
184
+ return ExecutionResult.Next();
185
+ })
186
+ .WaitFor("MyEvent", "0")
187
+ .Output(data => data.Value, step => step.EventData)
188
+ .Then<CustomMessage>()
189
+ .Input(step => step.Message, data => "The data from the event is " + data.Value);
190
+ }
191
+ }
192
+ ...
193
+ //External events are published via the host
194
+ //All workflows that have subscribed to MyEvent 0, will be passed "hello"
195
+ host.PublishEvent("MyEvent", "0", "hello");
196
+ ```
197
+
198
+ ### Host
199
+
200
+ The workflow host is the service responsible for executing workflows. It does this by polling the persistence provider for workflow instances that are ready to run, executes them and then passes them back to the persistence provider to by stored for the next time they are run. It is also responsible for publishing events to any workflows that may be waiting on one.
201
+
202
+ #### Setup
203
+
204
+ Use the *AddWorkflow* extension method for *IServiceCollection* to configure the workflow host upon startup of your application.
205
+ By default, it is configured with *MemoryPersistenceProvider* and *SingleNodeConcurrencyProvider* for testing purposes. You can also configure a DB persistence provider at this point.
206
+
207
+ ```C#
208
+ services.AddWorkflow();
209
+ ```
210
+
211
+ #### Usage
212
+
213
+ When your application starts, grab the workflow host from the built-in dependency injection framework *IServiceProvider*. Make sure you call *RegisterWorkflow*, so that the workflow host knows about all your workflows, and then call *Start()* to fire up the thread pool that executes workflows. Use the *StartWorkflow* method to initiate a new instance of a particular workflow.
214
+
215
+
216
+ ```C#
217
+ var host = serviceProvider.GetService<IWorkflowHost>();
218
+ host.RegisterWorkflow<HelloWorldWorkflow>();
219
+ host.Start();
220
+
221
+ host.StartWorkflow("HelloWorld", 1, null);
222
+
223
+ Console.ReadLine();
224
+ host.Stop();
225
+ ```
226
+
227
+
228
+ ### Persistence
229
+
230
+ Since workflows are typically long running processes, they will need to be persisted to storage between steps.
231
+ There are several persistence providers available as seperate Nuget packages.
232
+
233
+ * MemoryPersistenceProvider *(Default provider, for demo and testing purposes)*
234
+ * [MongoDB](src/providers/WorkflowCore.Persistence.MongoDB)
235
+ * [SQL Server](src/providers/WorkflowCore.Persistence.SqlServer)
236
+ * [PostgreSQL](src/providers/WorkflowCore.Persistence.PostgreSQL)
237
+ * [Sqlite](src/providers/WorkflowCore.Persistence.Sqlite)
238
+ * Redis *(coming soon...)*
239
+
240
+ ### Multi-node clusters
241
+
242
+ By default, the WorkflowHost service will run as a single node using the built-in queue and locking providers for a single node configuration. Should you wish to run a multi-node cluster, you will need to configure an external queueing mechanism and a distributed lock manager to co-ordinate the cluster. These are the providers that are currently available.
243
+
244
+ #### Queue Providers
245
+
246
+ * SingleNodeQueueProvider *(Default built-in provider)*
247
+ * [RabbitMQ](src/providers/WorkflowCore.QueueProviders.RabbitMQ)
248
+ * Apache ZooKeeper *(coming soon...)*
249
+ * 0MQ *(coming soon...)*
250
+
251
+ #### Distributed lock managers
252
+
253
+ * SingleNodeLockProvider *(Default built-in provider)*
254
+ * [Redis Redlock](src/providers/WorkflowCore.LockProviders.Redlock)
255
+ * Apache ZooKeeper *(coming soon...)*
256
+
257
+
258
+ ## Samples
259
+
260
+ [Hello World](src/samples/WorkflowCore.Sample01)
261
+
262
+ [Multiple outcomes](src/samples/WorkflowCore.Sample06)
263
+
264
+ [Passing Data](src/samples/WorkflowCore.Sample03)
265
+
266
+ [Events](src/samples/WorkflowCore.Sample04)
267
+
268
+ [Deferred execution & re-entrant steps](src/samples/WorkflowCore.Sample05)
269
+
270
+ [Looping](src/samples/WorkflowCore.Sample02)
24
271
 
25
- TODO: Write usage instructions here
26
272
 
27
- ## Development
273
+ ## Authors
28
274
 
29
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
275
+ * **Daniel Gerlag** - *Initial work*
30
276
 
31
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
277
+ ## License
32
278
 
33
- ## Contributing
279
+ This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details
34
280
 
35
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/workflow_rb.
36
281
 
@@ -4,7 +4,7 @@ module WorkflowRb
4
4
  attr_accessor :proceed
5
5
  attr_accessor :outcome_value
6
6
  attr_accessor :persistence_data
7
- attr_accessor :sleep_for
7
+ attr_accessor :sleep_until
8
8
 
9
9
  def self.NextStep
10
10
  result = ExecutionResult.new
@@ -20,6 +20,21 @@ module WorkflowRb
20
20
  result
21
21
  end
22
22
 
23
+ def self.Persist(data)
24
+ result = ExecutionResult.new
25
+ result.proceed = false
26
+ result.persistence_data = data
27
+ result
28
+ end
29
+
30
+ def self.Sleep(sleep_until, data)
31
+ result = ExecutionResult.new
32
+ result.proceed = false
33
+ result.persistence_data = data
34
+ result.sleep_until = sleep_until
35
+ result
36
+ end
37
+
23
38
  end
24
39
 
25
40
  end
@@ -1,3 +1,3 @@
1
1
  module WorkflowRb
2
- VERSION = "0.1.0"
2
+ VERSION = "0.1.1"
3
3
  end
data/workflow_rb.gemspec CHANGED
@@ -8,7 +8,7 @@ Gem::Specification.new do |spec|
8
8
  spec.version = WorkflowRb::VERSION
9
9
  spec.authors = ["Daniel Gerlag"]
10
10
  spec.email = ["daniel@gerlag.ca"]
11
-
11
+ spec.license = "MIT"
12
12
  spec.summary = %q{Lightweight workflow library}
13
13
  spec.homepage = "http://github.com/danielgerlag/workflow_rb"
14
14
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: workflow_rb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel Gerlag
@@ -91,7 +91,8 @@ files:
91
91
  - lib/workflow_rb/version.rb
92
92
  - workflow_rb.gemspec
93
93
  homepage: http://github.com/danielgerlag/workflow_rb
94
- licenses: []
94
+ licenses:
95
+ - MIT
95
96
  metadata: {}
96
97
  post_install_message:
97
98
  rdoc_options: []