light-service 0.8.4 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +3 -0
- data/README.md +25 -33
- data/RELEASES.md +3 -0
- data/lib/light-service.rb +7 -0
- data/lib/light-service/orchestrator.rb +21 -3
- data/lib/light-service/organizer.rb +15 -20
- data/lib/light-service/organizer/execute.rb +14 -0
- data/lib/light-service/organizer/iterate.rb +22 -0
- data/lib/light-service/organizer/reduce_if.rb +17 -0
- data/lib/light-service/organizer/reduce_until.rb +20 -0
- data/lib/light-service/organizer/scoped_reducable.rb +13 -0
- data/lib/light-service/organizer/verify_call_method_exists.rb +28 -0
- data/lib/light-service/organizer/with_callback.rb +26 -0
- data/lib/light-service/organizer/with_reducer.rb +14 -4
- data/lib/light-service/organizer/with_reducer_factory.rb +2 -0
- data/lib/light-service/version.rb +1 -1
- data/light-service.gemspec +1 -1
- data/resources/orchestrators_deprecated.svg +10 -0
- data/spec/acceptance/orchestrator/context_failure_and_skipping_spec.rb +6 -4
- data/spec/acceptance/orchestrator/execute_spec.rb +6 -4
- data/spec/acceptance/orchestrator/iterate_spec.rb +7 -5
- data/spec/acceptance/orchestrator/organizer_action_combination_spec.rb +13 -11
- data/spec/acceptance/orchestrator/reduce_if_spec.rb +7 -5
- data/spec/acceptance/orchestrator/reduce_until_spec.rb +6 -4
- data/spec/acceptance/orchestrator/with_callback_spec.rb +8 -6
- data/spec/acceptance/organizer/around_each_with_reduce_if_spec.rb +42 -0
- data/spec/acceptance/organizer/context_failure_and_skipping_spec.rb +65 -0
- data/spec/acceptance/organizer/execute_spec.rb +46 -0
- data/spec/acceptance/organizer/iterate_spec.rb +51 -0
- data/spec/acceptance/organizer/reduce_if_spec.rb +51 -0
- data/spec/acceptance/organizer/reduce_until_spec.rb +43 -0
- data/spec/acceptance/organizer/with_callback_spec.rb +169 -0
- data/spec/organizer/with_reducer_spec.rb +2 -7
- data/spec/spec_helper.rb +12 -0
- data/spec/support.rb +9 -0
- data/spec/test_doubles.rb +7 -3
- metadata +28 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 30ef43a9d6e26800b7599546ffea8b1cb15b1533
|
4
|
+
data.tar.gz: 28ee4863b5ea1a859c9e4ca288c20b23f99bc4e9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: dab3019cc90cecb1d97b2d36dcf5ad97dde57a22424eefbe91978d024205ea90215df4045594e8744fc9af5ae58ffd45f9c9727d921aee8af1bae50b0df754d3
|
7
|
+
data.tar.gz: 528fc079179b6e276aa6b4ec331326b51c279c3e234a0fc5998b336098375ad189c0bc274a616446d9aa851f10cda8653901d7f9e13a2e5ee0251f2f037e9324
|
data/.travis.yml
CHANGED
data/README.md
CHANGED
@@ -3,6 +3,15 @@
|
|
3
3
|
[![Gem Version](https://img.shields.io/gem/v/light-service.svg)](https://rubygems.org/gems/light-service)
|
4
4
|
[![Build Status](https://secure.travis-ci.org/adomokos/light-service.png)](http://travis-ci.org/adomokos/light-service)
|
5
5
|
[![Code Climate](https://codeclimate.com/github/adomokos/light-service.png)](https://codeclimate.com/github/adomokos/light-service)
|
6
|
+
[![Dependency Status](https://beta.gemnasium.com/badges/github.com/adomokos/light-service.svg)](https://beta.gemnasium.com/projects/github.com/adomokos/light-service)
|
7
|
+
[![License](https://img.shields.io/badge/license-MIT-green.svg)](http://opensource.org/licenses/MIT)
|
8
|
+
|
9
|
+
<br><br>
|
10
|
+
|
11
|
+
![Orchestrators-Deprecated](resources/orchestrators_deprecated.svg)
|
12
|
+
<br>Version 0.9.0 deprecates Orchestrators and moves all their functionalities into Organizers. Please check out [this PR](https://github.com/adomokos/light-service/pull/132) to see the changes.
|
13
|
+
|
14
|
+
<br><br>
|
6
15
|
|
7
16
|
What do you think of this code?
|
8
17
|
|
@@ -157,7 +166,7 @@ simple and elegant Rails code where I told the story of how LightService was ext
|
|
157
166
|
* [Error Codes](#error-codes)
|
158
167
|
* [Action Rollback](#action-rollback)
|
159
168
|
* [Localizing Messages](#localizing-messages)
|
160
|
-
* [
|
169
|
+
* [Orchestrator Logic in Organizers](#orchestrator-logic-in-organizers)
|
161
170
|
* [ContextFactory for Faster Action Testing](#contextfactory-for-faster-action-testing)
|
162
171
|
|
163
172
|
## Stopping the Series of Actions
|
@@ -576,7 +585,7 @@ end
|
|
576
585
|
|
577
586
|
To get the value of a `fail!` or `succeed!` message, simply call `#message` on the returned context.
|
578
587
|
|
579
|
-
##
|
588
|
+
## Orchestrator Logic in Organizers
|
580
589
|
|
581
590
|
The Organizer - Action combination works really well for simple use cases. However, as business logic gets more complex, or when LightService is used in an ETL workflow, the code that routes the different organizers becomes very complex and imperative. Let's look at a piece of code that does basic data transformations:
|
582
591
|
|
@@ -607,13 +616,13 @@ The `LightService::Context` is initialized with the first action, that context i
|
|
607
616
|
|
608
617
|
```ruby
|
609
618
|
class ExtractsTransformsLoadsData
|
610
|
-
extend LightService::
|
619
|
+
extend LightService::Organizer
|
611
620
|
|
612
|
-
def self.
|
621
|
+
def self.call(connection)
|
613
622
|
with(:connection => connection).reduce(steps)
|
614
623
|
end
|
615
624
|
|
616
|
-
def self.
|
625
|
+
def self.actions
|
617
626
|
[
|
618
627
|
RetrievesConnectionInfo,
|
619
628
|
PullsDataFromRemoteApi,
|
@@ -632,40 +641,23 @@ end
|
|
632
641
|
|
633
642
|
This code is much easier to reason about, it's less noisy and it captures the goal of LightService well: simple, declarative code that's easy to understand.
|
634
643
|
|
635
|
-
|
636
|
-
```
|
637
|
-
Orchestrators
|
638
|
-
|-> run - steps
|
639
|
-
Organizers
|
640
|
-
|-> call - actions
|
641
|
-
Actions
|
642
|
-
|-> execute
|
643
|
-
```
|
644
|
-
|
645
|
-
You can mix organizers with actions in the orchestrator steps, but mixing other organizers with actions in an organizer is discouraged for the sake of simplicity.
|
646
|
-
|
647
|
-
The 6 different constructs an orchestrator can have:
|
648
|
-
|
649
|
-
1. `reduce`
|
650
|
-
2. `reduce_until`
|
651
|
-
3. `reduce_if`
|
652
|
-
4. `iterate`
|
653
|
-
5. `execute`
|
654
|
-
6. `with_callback`
|
655
|
-
|
656
|
-
The `reduce` method needs no introduction, it behaves similarly to organizers' `reduce` method.
|
644
|
+
The 5 different orchestrator constructs an organizer can have:
|
657
645
|
|
658
|
-
`reduce_until`
|
646
|
+
1. `reduce_until`
|
647
|
+
2. `reduce_if`
|
648
|
+
3. `iterate`
|
649
|
+
4. `execute`
|
650
|
+
5. `with_callback`
|
659
651
|
|
660
|
-
`
|
652
|
+
`reduce_until` behaves like a while loop in imperative languages, it iterates until the provided predicate in the lambda evaluates to true. Take a look at [this acceptance test](spec/acceptance/organizer/reduce_until_spec.rb) to see how it's used.
|
661
653
|
|
662
|
-
`
|
654
|
+
`reduce_if` will reduce the included organizers and/or actions if the predicate in the lambda evaluates to true. [This acceptance test](spec/acceptance/organizer/reduce_if_spec.rb) describes this functionality.
|
663
655
|
|
664
|
-
|
656
|
+
`iterate` gives your iteration logic, the symbol you define there has to be in the context as a key. For example, to iterate over items you will use `iterate(:items)` in your steps, the context needs to have `items` as a key, otherwise it will fail. The organizer will singularize the collection name and will put the actual item into the context under that name. Remaining with the example above, each element will be accessible by the name `item` for the actions in the `iterate` steps. [This acceptance test](spec/acceptance/organizer/iterate_spec.rb) should provide you with an example.
|
665
657
|
|
666
|
-
|
658
|
+
To take advantage of another organizer or action, you might need to tweak the context a bit. Let's say you have a hash, and you need to iterate over its values in a series of action. To alter the context and have the values assigned into a variable, you need to create a new action with 1 line of code in it. That seems a lot of ceremony for a simple change. You can do that in a `execute` method like this `execute(->(ctx) { ctx[:some_values] = ctx.some_hash.values })`. [This test](spec/acceptance/organizer/execute_spec.rb) describes how you can use it.
|
667
659
|
|
668
|
-
|
660
|
+
Use `with_callback` when you want to execute actions with a deferred and controlled callback. It works similar to a Sax parser, I've used it for processing large files. The advantage of it is not having to keep large amount of data in memory. See [this acceptance test](spec/acceptance/organizer/with_callback_spec.rb) as a working example.
|
669
661
|
|
670
662
|
## ContextFactory for Faster Action Testing
|
671
663
|
|
data/RELEASES.md
CHANGED
@@ -1,5 +1,8 @@
|
|
1
1
|
A brief list of new features and changes introduced with the specified version.
|
2
2
|
|
3
|
+
### 0.9.0
|
4
|
+
* [Deprecate Orchestrator](https://github.com/adomokos/light-service/pull/132) by moving its functionality to Organizers.
|
5
|
+
|
3
6
|
### 0.8.4
|
4
7
|
* Only pass [default argument](https://github.com/adomokos/light-service/pull/123) to Hash#fetch in context if no block given.
|
5
8
|
|
data/lib/light-service.rb
CHANGED
@@ -7,9 +7,16 @@ require 'light-service/configuration'
|
|
7
7
|
require 'light-service/localization_adapter'
|
8
8
|
require 'light-service/context'
|
9
9
|
require 'light-service/context/key_verifier'
|
10
|
+
require 'light-service/organizer/scoped_reducable'
|
10
11
|
require 'light-service/organizer/with_reducer'
|
11
12
|
require 'light-service/organizer/with_reducer_log_decorator'
|
12
13
|
require 'light-service/organizer/with_reducer_factory'
|
14
|
+
require 'light-service/organizer/reduce_if'
|
15
|
+
require 'light-service/organizer/reduce_until'
|
16
|
+
require 'light-service/organizer/iterate'
|
17
|
+
require 'light-service/organizer/execute'
|
18
|
+
require 'light-service/organizer/with_callback'
|
19
|
+
require 'light-service/organizer/verify_call_method_exists'
|
13
20
|
require 'light-service/action'
|
14
21
|
require 'light-service/organizer'
|
15
22
|
require 'light-service/orchestrator'
|
@@ -11,11 +11,13 @@ module LightService
|
|
11
11
|
end
|
12
12
|
|
13
13
|
def reduce(steps, context = @context)
|
14
|
+
issue_deprecation_warning_for(__method__)
|
15
|
+
|
14
16
|
steps.each_with_object(context) do |step, ctx|
|
15
|
-
if step.respond_to?(:
|
16
|
-
step.execute(ctx)
|
17
|
-
elsif step.respond_to?(:call)
|
17
|
+
if step.respond_to?(:call)
|
18
18
|
step.call(ctx)
|
19
|
+
elsif step.respond_to?(:execute)
|
20
|
+
step.execute(ctx)
|
19
21
|
else
|
20
22
|
raise 'Pass either an action or organizer'
|
21
23
|
end
|
@@ -23,6 +25,8 @@ module LightService
|
|
23
25
|
end
|
24
26
|
|
25
27
|
def reduce_until(condition_block, steps)
|
28
|
+
issue_deprecation_warning_for(__method__)
|
29
|
+
|
26
30
|
lambda do |ctx|
|
27
31
|
return ctx if ctx.stop_processing?
|
28
32
|
|
@@ -36,6 +40,8 @@ module LightService
|
|
36
40
|
end
|
37
41
|
|
38
42
|
def reduce_if(condition_block, steps)
|
43
|
+
issue_deprecation_warning_for(__method__)
|
44
|
+
|
39
45
|
lambda do |ctx|
|
40
46
|
return ctx if ctx.stop_processing?
|
41
47
|
|
@@ -45,6 +51,8 @@ module LightService
|
|
45
51
|
end
|
46
52
|
|
47
53
|
def execute(code_block)
|
54
|
+
issue_deprecation_warning_for(__method__)
|
55
|
+
|
48
56
|
lambda do |ctx|
|
49
57
|
return ctx if ctx.stop_processing?
|
50
58
|
|
@@ -54,6 +62,8 @@ module LightService
|
|
54
62
|
end
|
55
63
|
|
56
64
|
def iterate(collection_key, steps)
|
65
|
+
issue_deprecation_warning_for(__method__)
|
66
|
+
|
57
67
|
lambda do |ctx|
|
58
68
|
return ctx if ctx.stop_processing?
|
59
69
|
|
@@ -69,6 +79,8 @@ module LightService
|
|
69
79
|
end
|
70
80
|
|
71
81
|
def with_callback(action, steps)
|
82
|
+
issue_deprecation_warning_for(__method__)
|
83
|
+
|
72
84
|
lambda do |ctx|
|
73
85
|
return ctx if ctx.stop_processing?
|
74
86
|
|
@@ -100,6 +112,12 @@ module LightService
|
|
100
112
|
|
101
113
|
ctx
|
102
114
|
end
|
115
|
+
|
116
|
+
def issue_deprecation_warning_for(method_name)
|
117
|
+
msg = "`Orchestrator##{method_name}` is DEPRECATED and will be " \
|
118
|
+
"removed, please switch to `Organizer##{method_name} instead. "
|
119
|
+
ActiveSupport::Deprecation.warn(msg)
|
120
|
+
end
|
103
121
|
end
|
104
122
|
end
|
105
123
|
end
|
@@ -17,7 +17,7 @@ module LightService
|
|
17
17
|
# In case this module is included
|
18
18
|
module ClassMethods
|
19
19
|
def with(data = {})
|
20
|
-
VerifyCallMethodExists.
|
20
|
+
VerifyCallMethodExists.run(self, caller(1..1).first)
|
21
21
|
data[:_aliases] = @aliases if @aliases
|
22
22
|
WithReducerFactory.make(self).with(data)
|
23
23
|
end
|
@@ -26,29 +26,24 @@ module LightService
|
|
26
26
|
with({}).reduce(actions)
|
27
27
|
end
|
28
28
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
class VerifyCallMethodExists
|
33
|
-
def self.call(klass, first_caller = '')
|
34
|
-
invoker_method = caller_method(first_caller)
|
35
|
-
return if invoker_method == 'call'
|
29
|
+
def reduce_if(condition_block, steps)
|
30
|
+
ReduceIf.run(self, condition_block, steps)
|
31
|
+
end
|
36
32
|
|
37
|
-
|
38
|
-
|
33
|
+
def reduce_until(condition_block, steps)
|
34
|
+
ReduceUntil.run(self, condition_block, steps)
|
35
|
+
end
|
39
36
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
"Please use #{klass}.call going forward."
|
44
|
-
ActiveSupport::Deprecation.warn(warning_msg)
|
45
|
-
end
|
37
|
+
def iterate(collection_key, steps)
|
38
|
+
Iterate.run(self, collection_key, steps)
|
39
|
+
end
|
46
40
|
|
47
|
-
|
48
|
-
|
41
|
+
def execute(code_block)
|
42
|
+
Execute.run(code_block)
|
43
|
+
end
|
49
44
|
|
50
|
-
|
51
|
-
|
45
|
+
def with_callback(action, steps)
|
46
|
+
WithCallback.run(self, action, steps)
|
52
47
|
end
|
53
48
|
end
|
54
49
|
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module LightService
|
2
|
+
module Organizer
|
3
|
+
class Iterate
|
4
|
+
extend ScopedReducable
|
5
|
+
|
6
|
+
def self.run(organizer, collection_key, steps)
|
7
|
+
lambda do |ctx|
|
8
|
+
return ctx if ctx.stop_processing?
|
9
|
+
|
10
|
+
collection = ctx[collection_key]
|
11
|
+
item_key = collection_key.to_s.singularize.to_sym
|
12
|
+
collection.each do |item|
|
13
|
+
ctx[item_key] = item
|
14
|
+
ctx = scoped_reduce(organizer, ctx, steps)
|
15
|
+
end
|
16
|
+
|
17
|
+
ctx
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module LightService
|
2
|
+
module Organizer
|
3
|
+
class ReduceIf
|
4
|
+
extend ScopedReducable
|
5
|
+
|
6
|
+
def self.run(organizer, condition_block, steps)
|
7
|
+
lambda do |ctx|
|
8
|
+
return ctx if ctx.stop_processing?
|
9
|
+
|
10
|
+
ctx = scoped_reduce(organizer, ctx, steps) \
|
11
|
+
if condition_block.call(ctx)
|
12
|
+
ctx
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module LightService
|
2
|
+
module Organizer
|
3
|
+
class ReduceUntil
|
4
|
+
extend ScopedReducable
|
5
|
+
|
6
|
+
def self.run(organizer, condition_block, steps)
|
7
|
+
lambda do |ctx|
|
8
|
+
return ctx if ctx.stop_processing?
|
9
|
+
|
10
|
+
loop do
|
11
|
+
ctx = scoped_reduce(organizer, ctx, steps)
|
12
|
+
break if condition_block.call(ctx) || ctx.failure?
|
13
|
+
end
|
14
|
+
|
15
|
+
ctx
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module LightService
|
2
|
+
module Organizer
|
3
|
+
module ScopedReducable
|
4
|
+
def scoped_reduce(organizer, ctx, steps)
|
5
|
+
ctx.reset_skip_remaining! unless ctx.failure?
|
6
|
+
ctx = organizer.with(ctx).reduce([steps])
|
7
|
+
ctx.reset_skip_remaining! unless ctx.failure?
|
8
|
+
|
9
|
+
ctx
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module LightService
|
2
|
+
module Organizer
|
3
|
+
# We need to make sure existing users will
|
4
|
+
# use `call` method name going forward.
|
5
|
+
# This should be removed eventually.
|
6
|
+
class VerifyCallMethodExists
|
7
|
+
def self.run(klass, first_caller = '')
|
8
|
+
invoker_method = caller_method(first_caller)
|
9
|
+
return if invoker_method == 'call'
|
10
|
+
|
11
|
+
call_method_exists = klass.methods.include?(:call)
|
12
|
+
return if call_method_exists
|
13
|
+
|
14
|
+
warning_msg = "The <#{klass.name}> class is an organizer, " \
|
15
|
+
"its entry method (the one that calls with & reduce) " \
|
16
|
+
"should be named `call`. " \
|
17
|
+
"Please use #{klass}.call going forward."
|
18
|
+
ActiveSupport::Deprecation.warn(warning_msg)
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.caller_method(first_caller)
|
22
|
+
return nil unless first_caller =~ /`(.*)'/
|
23
|
+
|
24
|
+
Regexp.last_match[1]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module LightService
|
2
|
+
module Organizer
|
3
|
+
class WithCallback
|
4
|
+
extend ScopedReducable
|
5
|
+
|
6
|
+
def self.run(organizer, action, steps)
|
7
|
+
lambda do |ctx|
|
8
|
+
return ctx if ctx.stop_processing?
|
9
|
+
|
10
|
+
# This will only allow 2 level deep nesting of callbacks
|
11
|
+
previous_callback = ctx[:callback]
|
12
|
+
|
13
|
+
ctx[:callback] = lambda do |context|
|
14
|
+
ctx = scoped_reduce(organizer, context, steps)
|
15
|
+
ctx
|
16
|
+
end
|
17
|
+
|
18
|
+
ctx = action.execute(ctx)
|
19
|
+
ctx[:callback] = previous_callback
|
20
|
+
|
21
|
+
ctx
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module LightService
|
2
2
|
module Organizer
|
3
3
|
class WithReducer
|
4
|
-
attr_reader :context
|
4
|
+
attr_reader :context
|
5
5
|
|
6
6
|
def with(data = {})
|
7
7
|
@context = LightService::Context.make(data)
|
@@ -13,6 +13,14 @@ module LightService
|
|
13
13
|
self
|
14
14
|
end
|
15
15
|
|
16
|
+
def around_each_handler
|
17
|
+
@around_each_handler ||= Class.new do
|
18
|
+
def self.call(_context)
|
19
|
+
yield
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
16
24
|
def reduce(*actions)
|
17
25
|
raise "No action(s) were provided" if actions.empty?
|
18
26
|
actions.flatten!
|
@@ -46,10 +54,12 @@ module LightService
|
|
46
54
|
private
|
47
55
|
|
48
56
|
def invoke_action(current_context, action)
|
49
|
-
return action.execute(current_context) unless around_each_handler
|
50
|
-
|
51
57
|
around_each_handler.call(current_context) do
|
52
|
-
action.
|
58
|
+
if action.respond_to?(:call)
|
59
|
+
action.call(current_context)
|
60
|
+
else
|
61
|
+
action.execute(current_context)
|
62
|
+
end
|
53
63
|
end
|
54
64
|
end
|
55
65
|
|