flows 0.2.0 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/{build.yml → test.yml} +5 -10
- data/.gitignore +9 -1
- data/.mdlrc +1 -1
- data/.reek.yml +54 -0
- data/.rubocop.yml +26 -7
- data/.rubocop_todo.yml +27 -0
- data/.ruby-version +1 -1
- data/.yardopts +1 -0
- data/CHANGELOG.md +81 -0
- data/Gemfile +0 -6
- data/README.md +167 -363
- data/Rakefile +35 -1
- data/bin/.rubocop.yml +5 -0
- data/bin/all_the_errors +55 -0
- data/bin/benchmark +73 -105
- data/bin/benchmark_cli/compare.rb +118 -0
- data/bin/benchmark_cli/compare/a_plus_b.rb +22 -0
- data/bin/benchmark_cli/compare/base.rb +45 -0
- data/bin/benchmark_cli/compare/command.rb +47 -0
- data/bin/benchmark_cli/compare/ten_steps.rb +22 -0
- data/bin/benchmark_cli/examples.rb +23 -0
- data/bin/benchmark_cli/examples/.rubocop.yml +22 -0
- data/bin/benchmark_cli/examples/a_plus_b/dry_do.rb +23 -0
- data/bin/benchmark_cli/examples/a_plus_b/dry_transaction.rb +17 -0
- data/bin/benchmark_cli/examples/a_plus_b/flows_do.rb +22 -0
- data/bin/benchmark_cli/examples/a_plus_b/flows_railway.rb +13 -0
- data/bin/benchmark_cli/examples/a_plus_b/flows_scp.rb +13 -0
- data/bin/benchmark_cli/examples/a_plus_b/flows_scp_mut.rb +13 -0
- data/bin/benchmark_cli/examples/a_plus_b/flows_scp_oc.rb +21 -0
- data/bin/benchmark_cli/examples/a_plus_b/trailblazer.rb +15 -0
- data/bin/benchmark_cli/examples/ten_steps/dry_do.rb +70 -0
- data/bin/benchmark_cli/examples/ten_steps/dry_transaction.rb +64 -0
- data/bin/benchmark_cli/examples/ten_steps/flows_do.rb +69 -0
- data/bin/benchmark_cli/examples/ten_steps/flows_railway.rb +58 -0
- data/bin/benchmark_cli/examples/ten_steps/flows_scp.rb +58 -0
- data/bin/benchmark_cli/examples/ten_steps/flows_scp_mut.rb +58 -0
- data/bin/benchmark_cli/examples/ten_steps/flows_scp_oc.rb +66 -0
- data/bin/benchmark_cli/examples/ten_steps/trailblazer.rb +60 -0
- data/bin/benchmark_cli/helpers.rb +12 -0
- data/bin/benchmark_cli/ruby.rb +15 -0
- data/bin/benchmark_cli/ruby/command.rb +38 -0
- data/bin/benchmark_cli/ruby/method_exec.rb +71 -0
- data/bin/benchmark_cli/ruby/self_class.rb +69 -0
- data/bin/benchmark_cli/ruby/structs.rb +90 -0
- data/bin/console +1 -0
- data/bin/docserver +7 -0
- data/bin/errors +138 -0
- data/bin/errors_cli/contract_error_demo.rb +49 -0
- data/bin/errors_cli/di_error_demo.rb +38 -0
- data/bin/errors_cli/flow_error_demo.rb +22 -0
- data/bin/errors_cli/flows_router_error_demo.rb +15 -0
- data/bin/errors_cli/interface_error_demo.rb +17 -0
- data/bin/errors_cli/oc_error_demo.rb +40 -0
- data/bin/errors_cli/railway_error_demo.rb +10 -0
- data/bin/errors_cli/result_error_demo.rb +13 -0
- data/bin/errors_cli/scp_error_demo.rb +17 -0
- data/docs/README.md +3 -187
- data/docs/_sidebar.md +0 -24
- data/docs/index.html +1 -1
- data/flows.gemspec +27 -2
- data/forspell.dict +9 -0
- data/lefthook.yml +9 -0
- data/lib/flows.rb +11 -5
- data/lib/flows/contract.rb +402 -0
- data/lib/flows/contract/array.rb +55 -0
- data/lib/flows/contract/case_eq.rb +43 -0
- data/lib/flows/contract/compose.rb +77 -0
- data/lib/flows/contract/either.rb +53 -0
- data/lib/flows/contract/error.rb +24 -0
- data/lib/flows/contract/hash.rb +75 -0
- data/lib/flows/contract/hash_of.rb +70 -0
- data/lib/flows/contract/helpers.rb +22 -0
- data/lib/flows/contract/predicate.rb +34 -0
- data/lib/flows/contract/transformer.rb +50 -0
- data/lib/flows/contract/tuple.rb +70 -0
- data/lib/flows/flow.rb +96 -7
- data/lib/flows/flow/errors.rb +29 -0
- data/lib/flows/flow/node.rb +132 -0
- data/lib/flows/flow/router.rb +29 -0
- data/lib/flows/flow/router/custom.rb +59 -0
- data/lib/flows/flow/router/errors.rb +11 -0
- data/lib/flows/flow/router/simple.rb +25 -0
- data/lib/flows/plugin.rb +15 -0
- data/lib/flows/plugin/dependency_injector.rb +170 -0
- data/lib/flows/plugin/dependency_injector/dependency.rb +24 -0
- data/lib/flows/plugin/dependency_injector/dependency_definition.rb +16 -0
- data/lib/flows/plugin/dependency_injector/dependency_list.rb +55 -0
- data/lib/flows/plugin/dependency_injector/errors.rb +58 -0
- data/lib/flows/plugin/implicit_init.rb +45 -0
- data/lib/flows/plugin/interface.rb +84 -0
- data/lib/flows/plugin/output_contract.rb +85 -0
- data/lib/flows/plugin/output_contract/dsl.rb +48 -0
- data/lib/flows/plugin/output_contract/errors.rb +74 -0
- data/lib/flows/plugin/output_contract/wrapper.rb +55 -0
- data/lib/flows/plugin/profiler.rb +114 -0
- data/lib/flows/plugin/profiler/injector.rb +35 -0
- data/lib/flows/plugin/profiler/report.rb +48 -0
- data/lib/flows/plugin/profiler/report/events.rb +43 -0
- data/lib/flows/plugin/profiler/report/flat.rb +41 -0
- data/lib/flows/plugin/profiler/report/flat/method_report.rb +80 -0
- data/lib/flows/plugin/profiler/report/raw.rb +15 -0
- data/lib/flows/plugin/profiler/report/tree.rb +98 -0
- data/lib/flows/plugin/profiler/report/tree/calculated_node.rb +116 -0
- data/lib/flows/plugin/profiler/report/tree/node.rb +34 -0
- data/lib/flows/plugin/profiler/wrapper.rb +53 -0
- data/lib/flows/railway.rb +140 -34
- data/lib/flows/railway/dsl.rb +8 -18
- data/lib/flows/railway/errors.rb +8 -12
- data/lib/flows/railway/step.rb +24 -0
- data/lib/flows/railway/step_list.rb +38 -0
- data/lib/flows/result.rb +188 -2
- data/lib/flows/result/do.rb +158 -16
- data/lib/flows/result/err.rb +12 -6
- data/lib/flows/result/errors.rb +29 -17
- data/lib/flows/result/helpers.rb +25 -3
- data/lib/flows/result/ok.rb +12 -6
- data/lib/flows/shared_context_pipeline.rb +342 -0
- data/lib/flows/shared_context_pipeline/dsl.rb +12 -0
- data/lib/flows/shared_context_pipeline/dsl/callbacks.rb +35 -0
- data/lib/flows/shared_context_pipeline/dsl/tracks.rb +52 -0
- data/lib/flows/shared_context_pipeline/errors.rb +17 -0
- data/lib/flows/shared_context_pipeline/mutation_step.rb +30 -0
- data/lib/flows/shared_context_pipeline/router_definition.rb +21 -0
- data/lib/flows/shared_context_pipeline/step.rb +55 -0
- data/lib/flows/shared_context_pipeline/track.rb +54 -0
- data/lib/flows/shared_context_pipeline/track_list.rb +51 -0
- data/lib/flows/shared_context_pipeline/wrap.rb +73 -0
- data/lib/flows/util.rb +17 -0
- data/lib/flows/util/inheritable_singleton_vars.rb +86 -0
- data/lib/flows/util/inheritable_singleton_vars/dup_strategy.rb +100 -0
- data/lib/flows/util/inheritable_singleton_vars/isolation_strategy.rb +91 -0
- data/lib/flows/util/prepend_to_class.rb +191 -0
- data/lib/flows/version.rb +1 -1
- metadata +253 -38
- data/Gemfile.lock +0 -174
- data/bin/demo +0 -66
- data/bin/examples.rb +0 -195
- data/bin/profile_10steps +0 -106
- data/bin/ruby_benchmarks +0 -26
- data/docs/CNAME +0 -1
- data/docs/contributing/benchmarks_profiling.md +0 -3
- data/docs/contributing/local_development.md +0 -3
- data/docs/flow/direct_usage.md +0 -3
- data/docs/flow/general_idea.md +0 -3
- data/docs/operation/basic_usage.md +0 -1
- data/docs/operation/inject_steps.md +0 -3
- data/docs/operation/lambda_steps.md +0 -3
- data/docs/operation/result_shapes.md +0 -3
- data/docs/operation/routing_tracks.md +0 -3
- data/docs/operation/wrapping_steps.md +0 -3
- data/docs/overview/performance.md +0 -336
- data/docs/railway/basic_usage.md +0 -232
- data/docs/result_objects/basic_usage.md +0 -196
- data/docs/result_objects/do_notation.md +0 -139
- data/lib/flows/node.rb +0 -27
- data/lib/flows/operation.rb +0 -52
- data/lib/flows/operation/builder.rb +0 -130
- data/lib/flows/operation/builder/build_router.rb +0 -37
- data/lib/flows/operation/dsl.rb +0 -93
- data/lib/flows/operation/errors.rb +0 -75
- data/lib/flows/operation/executor.rb +0 -78
- data/lib/flows/railway/builder.rb +0 -68
- data/lib/flows/railway/executor.rb +0 -23
- data/lib/flows/result_router.rb +0 -14
- data/lib/flows/router.rb +0 -22
data/bin/ruby_benchmarks
DELETED
@@ -1,26 +0,0 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
# rubocop:disable all
|
3
|
-
|
4
|
-
require 'bundler/setup'
|
5
|
-
require 'benchmark/ips'
|
6
|
-
|
7
|
-
puts '-' * 50
|
8
|
-
puts '- method execution'
|
9
|
-
puts '-' * 50
|
10
|
-
|
11
|
-
class OneMethod
|
12
|
-
def meth
|
13
|
-
:ok
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
|
-
one_method = OneMethod.new
|
18
|
-
method_obj = one_method.method(:meth)
|
19
|
-
|
20
|
-
Benchmark.ips do |b|
|
21
|
-
b.report('native call') { one_method.meth }
|
22
|
-
b.report('send(...)') { one_method.send(:meth) }
|
23
|
-
b.report('Method#call') { method_obj.call }
|
24
|
-
|
25
|
-
b.compare!
|
26
|
-
end
|
data/docs/CNAME
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
flows.ffloyd.tech
|
data/docs/flow/direct_usage.md
DELETED
data/docs/flow/general_idea.md
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
# Basic Usage
|
@@ -1,336 +0,0 @@
|
|
1
|
-
# Performance
|
2
|
-
|
3
|
-
Host:
|
4
|
-
|
5
|
-
* MacBook Pro (13-inch, 2017, Four Thunderbolt 3 Ports)
|
6
|
-
* 3.1 GHz Intel Core i5
|
7
|
-
* 8 GB 2133 MHz LPDDR3
|
8
|
-
|
9
|
-
## Comparison with Trailblazer
|
10
|
-
|
11
|
-
`Flows::Railway` does not support tracks and routes, so it's reasonable to compare with `Flows::Operation` only.
|
12
|
-
|
13
|
-
`WITH_OP=1 WITH_TB=1 bin/benchmark` results:
|
14
|
-
|
15
|
-
```
|
16
|
-
--------------------------------------------------
|
17
|
-
- task: A + B, one step implementation
|
18
|
-
--------------------------------------------------
|
19
|
-
Warming up --------------------------------------
|
20
|
-
Flows::Operation (build once)
|
21
|
-
25.356k i/100ms
|
22
|
-
Flows::Operation (build each time)
|
23
|
-
9.168k i/100ms
|
24
|
-
Trailblazer::Operation
|
25
|
-
5.016k i/100ms
|
26
|
-
Calculating -------------------------------------
|
27
|
-
Flows::Operation (build once)
|
28
|
-
277.460k (± 1.2%) i/s - 1.395M in 5.027011s
|
29
|
-
Flows::Operation (build each time)
|
30
|
-
95.740k (± 2.7%) i/s - 485.904k in 5.079226s
|
31
|
-
Trailblazer::Operation
|
32
|
-
52.975k (± 1.8%) i/s - 265.848k in 5.020109s
|
33
|
-
|
34
|
-
Comparison:
|
35
|
-
Flows::Operation (build once): 277459.5 i/s
|
36
|
-
Flows::Operation (build each time): 95739.6 i/s - 2.90x slower
|
37
|
-
Trailblazer::Operation: 52974.6 i/s - 5.24x slower
|
38
|
-
|
39
|
-
|
40
|
-
--------------------------------------------------
|
41
|
-
- task: ten steps returns successful result
|
42
|
-
--------------------------------------------------
|
43
|
-
Warming up --------------------------------------
|
44
|
-
Flows::Operation (build once)
|
45
|
-
3.767k i/100ms
|
46
|
-
Flows::Operation (build each time)
|
47
|
-
1.507k i/100ms
|
48
|
-
Trailblazer::Operation
|
49
|
-
1.078k i/100ms
|
50
|
-
Calculating -------------------------------------
|
51
|
-
Flows::Operation (build once)
|
52
|
-
37.983k (± 2.9%) i/s - 192.117k in 5.062658s
|
53
|
-
Flows::Operation (build each time)
|
54
|
-
14.991k (± 4.2%) i/s - 75.350k in 5.035443s
|
55
|
-
Trailblazer::Operation
|
56
|
-
10.897k (± 2.8%) i/s - 54.978k in 5.049665s
|
57
|
-
|
58
|
-
Comparison:
|
59
|
-
Flows::Operation (build once): 37982.8 i/s
|
60
|
-
Flows::Operation (build each time): 14990.6 i/s - 2.53x slower
|
61
|
-
Trailblazer::Operation: 10896.9 i/s - 3.49x slower
|
62
|
-
```
|
63
|
-
|
64
|
-
## Comparison with Dry::Transaction
|
65
|
-
|
66
|
-
`Dry::Transaction` does not support tracks and branching so it's reasonable to compare with `Flows::Railway` only.
|
67
|
-
|
68
|
-
`WITH_RW=1 WITH_DRY=1 bin/benchmark` results:
|
69
|
-
|
70
|
-
```
|
71
|
-
--------------------------------------------------
|
72
|
-
- task: A + B, one step implementation
|
73
|
-
--------------------------------------------------
|
74
|
-
Warming up --------------------------------------
|
75
|
-
Flows::Railway (build once)
|
76
|
-
29.324k i/100ms
|
77
|
-
Flows::Railway (build each time)
|
78
|
-
11.159k i/100ms
|
79
|
-
Dry::Transaction (build once)
|
80
|
-
21.480k i/100ms
|
81
|
-
Dry::Transaction (build each time)
|
82
|
-
2.268k i/100ms
|
83
|
-
Calculating -------------------------------------
|
84
|
-
Flows::Railway (build once)
|
85
|
-
321.837k (± 1.3%) i/s - 1.613M in 5.012156s
|
86
|
-
Flows::Railway (build each time)
|
87
|
-
115.743k (± 2.6%) i/s - 580.268k in 5.016961s
|
88
|
-
Dry::Transaction (build once)
|
89
|
-
231.712k (± 1.7%) i/s - 1.160M in 5.007401s
|
90
|
-
Dry::Transaction (build each time)
|
91
|
-
23.093k (± 2.5%) i/s - 115.668k in 5.012311s
|
92
|
-
|
93
|
-
Comparison:
|
94
|
-
Flows::Railway (build once): 321837.4 i/s
|
95
|
-
Dry::Transaction (build once): 231712.5 i/s - 1.39x slower
|
96
|
-
Flows::Railway (build each time): 115743.1 i/s - 2.78x slower
|
97
|
-
Dry::Transaction (build each time): 23093.2 i/s - 13.94x slower
|
98
|
-
|
99
|
-
|
100
|
-
--------------------------------------------------
|
101
|
-
- task: ten steps returns successful result
|
102
|
-
--------------------------------------------------
|
103
|
-
Warming up --------------------------------------
|
104
|
-
Flows::Railway (build once)
|
105
|
-
5.607k i/100ms
|
106
|
-
Flows::Railway (build each time)
|
107
|
-
2.014k i/100ms
|
108
|
-
Dry::Transaction (build once)
|
109
|
-
2.918k i/100ms
|
110
|
-
Dry::Transaction (build each time)
|
111
|
-
275.000 i/100ms
|
112
|
-
Calculating -------------------------------------
|
113
|
-
Flows::Railway (build once)
|
114
|
-
57.765k (± 1.4%) i/s - 291.564k in 5.048484s
|
115
|
-
Flows::Railway (build each time)
|
116
|
-
20.413k (± 1.2%) i/s - 102.714k in 5.032467s
|
117
|
-
Dry::Transaction (build once)
|
118
|
-
29.597k (± 1.5%) i/s - 148.818k in 5.029422s
|
119
|
-
Dry::Transaction (build each time)
|
120
|
-
2.753k (± 2.0%) i/s - 14.025k in 5.096279s
|
121
|
-
|
122
|
-
Comparison:
|
123
|
-
Flows::Railway (build once): 57765.2 i/s
|
124
|
-
Dry::Transaction (build once): 29596.6 i/s - 1.95x slower
|
125
|
-
Flows::Railway (build each time): 20413.0 i/s - 2.83x slower
|
126
|
-
Dry::Transaction (build each time): 2753.2 i/s - 20.98x slower
|
127
|
-
```
|
128
|
-
|
129
|
-
## Railway vs Operation
|
130
|
-
|
131
|
-
`Flows::Railway` is created to improve performance in situations when you don't need tracks, branching and shape control (`Flows::Operation` has this features). So, it should be faster than `Flows::Operation`.
|
132
|
-
|
133
|
-
`WITH_OP=1 WITH_RW=1 bin/benchmark` results:
|
134
|
-
|
135
|
-
```
|
136
|
-
--------------------------------------------------
|
137
|
-
- task: A + B, one step implementation
|
138
|
-
--------------------------------------------------
|
139
|
-
Warming up --------------------------------------
|
140
|
-
Flows::Railway (build once)
|
141
|
-
29.440k i/100ms
|
142
|
-
Flows::Railway (build each time)
|
143
|
-
11.236k i/100ms
|
144
|
-
Flows::Operation (build once)
|
145
|
-
25.584k i/100ms
|
146
|
-
Flows::Operation (build each time)
|
147
|
-
9.161k i/100ms
|
148
|
-
Calculating -------------------------------------
|
149
|
-
Flows::Railway (build once)
|
150
|
-
315.648k (± 8.1%) i/s - 1.590M in 5.078736s
|
151
|
-
Flows::Railway (build each time)
|
152
|
-
117.747k (± 3.5%) i/s - 595.508k in 5.064191s
|
153
|
-
Flows::Operation (build once)
|
154
|
-
266.888k (±12.3%) i/s - 1.279M in 5.090531s
|
155
|
-
Flows::Operation (build each time)
|
156
|
-
91.424k (±11.0%) i/s - 458.050k in 5.097449s
|
157
|
-
|
158
|
-
Comparison:
|
159
|
-
Flows::Railway (build once): 315647.6 i/s
|
160
|
-
Flows::Operation (build once): 266888.4 i/s - same-ish: difference falls within error
|
161
|
-
Flows::Railway (build each time): 117747.2 i/s - 2.68x slower
|
162
|
-
Flows::Operation (build each time): 91423.7 i/s - 3.45x slower
|
163
|
-
|
164
|
-
|
165
|
-
--------------------------------------------------
|
166
|
-
- task: ten steps returns successful result
|
167
|
-
--------------------------------------------------
|
168
|
-
Warming up --------------------------------------
|
169
|
-
Flows::Railway (build once)
|
170
|
-
5.619k i/100ms
|
171
|
-
Flows::Railway (build each time)
|
172
|
-
2.009k i/100ms
|
173
|
-
Flows::Operation (build once)
|
174
|
-
3.650k i/100ms
|
175
|
-
Flows::Operation (build each time)
|
176
|
-
1.472k i/100ms
|
177
|
-
Calculating -------------------------------------
|
178
|
-
Flows::Railway (build once)
|
179
|
-
58.454k (± 2.8%) i/s - 292.188k in 5.002833s
|
180
|
-
Flows::Railway (build each time)
|
181
|
-
20.310k (± 2.4%) i/s - 102.459k in 5.047579s
|
182
|
-
Flows::Operation (build once)
|
183
|
-
38.556k (± 2.5%) i/s - 193.450k in 5.020871s
|
184
|
-
Flows::Operation (build each time)
|
185
|
-
15.222k (± 2.8%) i/s - 76.544k in 5.032272s
|
186
|
-
|
187
|
-
Comparison:
|
188
|
-
Flows::Railway (build once): 58453.8 i/s
|
189
|
-
Flows::Operation (build once): 38556.5 i/s - 1.52x slower
|
190
|
-
Flows::Railway (build each time): 20310.3 i/s - 2.88x slower
|
191
|
-
Flows::Operation (build each time): 15221.9 i/s - 3.84x slower
|
192
|
-
```
|
193
|
-
|
194
|
-
## Comparison with Plan Old Ruby Object
|
195
|
-
|
196
|
-
Of course, `flows` cannot be faster than naive implementation without any library usage. But it's nice to know how big infrastructure cost you pay.
|
197
|
-
|
198
|
-
`WITH_RW=1 WITH_PORO=1 bin/benchmark` results:
|
199
|
-
|
200
|
-
```
|
201
|
-
--------------------------------------------------
|
202
|
-
- task: A + B, one step implementation
|
203
|
-
--------------------------------------------------
|
204
|
-
Warming up --------------------------------------
|
205
|
-
Flows::Railway (build once)
|
206
|
-
29.276k i/100ms
|
207
|
-
Flows::Railway (build each time)
|
208
|
-
11.115k i/100ms
|
209
|
-
PORO 309.108k i/100ms
|
210
|
-
Calculating -------------------------------------
|
211
|
-
Flows::Railway (build once)
|
212
|
-
320.587k (± 3.5%) i/s - 1.610M in 5.029314s
|
213
|
-
Flows::Railway (build each time)
|
214
|
-
118.108k (± 3.0%) i/s - 600.210k in 5.086844s
|
215
|
-
PORO 9.998M (± 2.1%) i/s - 50.075M in 5.010848s
|
216
|
-
|
217
|
-
Comparison:
|
218
|
-
PORO: 9998276.0 i/s
|
219
|
-
Flows::Railway (build once): 320586.8 i/s - 31.19x slower
|
220
|
-
Flows::Railway (build each time): 118108.5 i/s - 84.65x slower
|
221
|
-
|
222
|
-
|
223
|
-
--------------------------------------------------
|
224
|
-
- task: ten steps returns successful result
|
225
|
-
--------------------------------------------------
|
226
|
-
Warming up --------------------------------------
|
227
|
-
Flows::Railway (build once)
|
228
|
-
5.671k i/100ms
|
229
|
-
Flows::Railway (build each time)
|
230
|
-
2.024k i/100ms
|
231
|
-
PORO 233.375k i/100ms
|
232
|
-
Calculating -------------------------------------
|
233
|
-
Flows::Railway (build once)
|
234
|
-
58.428k (± 1.6%) i/s - 294.892k in 5.048387s
|
235
|
-
Flows::Railway (build each time)
|
236
|
-
20.388k (± 3.9%) i/s - 103.224k in 5.070844s
|
237
|
-
PORO 4.937M (± 0.6%) i/s - 24.738M in 5.010488s
|
238
|
-
|
239
|
-
Comparison:
|
240
|
-
PORO: 4937372.3 i/s
|
241
|
-
Flows::Railway (build once): 58428.4 i/s - 84.50x slower
|
242
|
-
Flows::Railway (build each time): 20387.7 i/s - 242.17x slower
|
243
|
-
```
|
244
|
-
|
245
|
-
## All without PORO
|
246
|
-
|
247
|
-
`WITH_ALL=1 bin/benchmark` results:
|
248
|
-
|
249
|
-
```
|
250
|
-
--------------------------------------------------
|
251
|
-
- task: A + B, one step implementation
|
252
|
-
--------------------------------------------------
|
253
|
-
Warming up --------------------------------------
|
254
|
-
Flows::Railway (build once)
|
255
|
-
29.351k i/100ms
|
256
|
-
Flows::Railway (build each time)
|
257
|
-
11.044k i/100ms
|
258
|
-
Flows::Operation (build once)
|
259
|
-
25.475k i/100ms
|
260
|
-
Flows::Operation (build each time)
|
261
|
-
8.989k i/100ms
|
262
|
-
Dry::Transaction (build once)
|
263
|
-
21.082k i/100ms
|
264
|
-
Dry::Transaction (build each time)
|
265
|
-
2.272k i/100ms
|
266
|
-
Trailblazer::Operation
|
267
|
-
4.962k i/100ms
|
268
|
-
Calculating -------------------------------------
|
269
|
-
Flows::Railway (build once)
|
270
|
-
299.326k (±15.6%) i/s - 1.409M in 5.012398s
|
271
|
-
Flows::Railway (build each time)
|
272
|
-
116.186k (± 3.1%) i/s - 585.332k in 5.042902s
|
273
|
-
Flows::Operation (build once)
|
274
|
-
276.980k (± 3.1%) i/s - 1.401M in 5.064018s
|
275
|
-
Flows::Operation (build each time)
|
276
|
-
94.536k (± 2.6%) i/s - 476.417k in 5.042967s
|
277
|
-
Dry::Transaction (build once)
|
278
|
-
229.750k (± 1.6%) i/s - 1.160M in 5.048211s
|
279
|
-
Dry::Transaction (build each time)
|
280
|
-
23.381k (± 1.9%) i/s - 118.144k in 5.054920s
|
281
|
-
Trailblazer::Operation
|
282
|
-
50.936k (± 4.4%) i/s - 258.024k in 5.075897s
|
283
|
-
|
284
|
-
Comparison:
|
285
|
-
Flows::Railway (build once): 299325.9 i/s
|
286
|
-
Flows::Operation (build once): 276979.8 i/s - same-ish: difference falls within error
|
287
|
-
Dry::Transaction (build once): 229749.5 i/s - 1.30x slower
|
288
|
-
Flows::Railway (build each time): 116185.6 i/s - 2.58x slower
|
289
|
-
Flows::Operation (build each time): 94536.3 i/s - 3.17x slower
|
290
|
-
Trailblazer::Operation: 50936.0 i/s - 5.88x slower
|
291
|
-
Dry::Transaction (build each time): 23380.8 i/s - 12.80x slower
|
292
|
-
|
293
|
-
|
294
|
-
--------------------------------------------------
|
295
|
-
- task: ten steps returns successful result
|
296
|
-
--------------------------------------------------
|
297
|
-
Warming up --------------------------------------
|
298
|
-
Flows::Railway (build once)
|
299
|
-
5.734k i/100ms
|
300
|
-
Flows::Railway (build each time)
|
301
|
-
2.064k i/100ms
|
302
|
-
Flows::Operation (build once)
|
303
|
-
3.801k i/100ms
|
304
|
-
Flows::Operation (build each time)
|
305
|
-
1.502k i/100ms
|
306
|
-
Dry::Transaction (build once)
|
307
|
-
2.837k i/100ms
|
308
|
-
Dry::Transaction (build each time)
|
309
|
-
274.000 i/100ms
|
310
|
-
Trailblazer::Operation
|
311
|
-
1.079k i/100ms
|
312
|
-
Calculating -------------------------------------
|
313
|
-
Flows::Railway (build once)
|
314
|
-
58.541k (± 1.6%) i/s - 298.168k in 5.094712s
|
315
|
-
Flows::Railway (build each time)
|
316
|
-
20.626k (± 3.0%) i/s - 103.200k in 5.008021s
|
317
|
-
Flows::Operation (build once)
|
318
|
-
38.906k (± 2.7%) i/s - 197.652k in 5.084184s
|
319
|
-
Flows::Operation (build each time)
|
320
|
-
14.351k (±12.2%) i/s - 70.594k in 5.011606s
|
321
|
-
Dry::Transaction (build once)
|
322
|
-
29.588k (± 1.8%) i/s - 150.361k in 5.083603s
|
323
|
-
Dry::Transaction (build each time)
|
324
|
-
2.765k (± 1.8%) i/s - 13.974k in 5.054977s
|
325
|
-
Trailblazer::Operation
|
326
|
-
10.861k (± 2.1%) i/s - 55.029k in 5.069204s
|
327
|
-
|
328
|
-
Comparison:
|
329
|
-
Flows::Railway (build once): 58541.4 i/s
|
330
|
-
Flows::Operation (build once): 38906.4 i/s - 1.50x slower
|
331
|
-
Dry::Transaction (build once): 29587.8 i/s - 1.98x slower
|
332
|
-
Flows::Railway (build each time): 20626.0 i/s - 2.84x slower
|
333
|
-
Flows::Operation (build each time): 14351.1 i/s - 4.08x slower
|
334
|
-
Trailblazer::Operation: 10860.9 i/s - 5.39x slower
|
335
|
-
Dry::Transaction (build each time): 2765.3 i/s - 21.17x slower
|
336
|
-
```
|
data/docs/railway/basic_usage.md
DELETED
@@ -1,232 +0,0 @@
|
|
1
|
-
# Railway :: Basic Usage
|
2
|
-
|
3
|
-
`Flows::Railway` is an implementation of a Railway Programming pattern. You may read about this pattern in the following articles:
|
4
|
-
|
5
|
-
* [Programming on rails: Railway Oriented Programming](http://sandordargo.com/blog/2017/09/27/railway_oriented_programming) // it's not about Ruby on Rails
|
6
|
-
* [Railway Oriented Programming: A powerful Functional Programming pattern](https://medium.com/@naveenkumarmuguda/railway-oriented-programming-a-powerful-functional-programming-pattern-ab454e467f31)
|
7
|
-
* [Railway Oriented Programming in Elixir with Pattern Matching on Function Level and Pipelining](https://medium.com/elixirlabs/railway-oriented-programming-in-elixir-with-pattern-matching-on-function-level-and-pipelining-e53972cede98)
|
8
|
-
|
9
|
-
Let's review a simple task and solve it using `Flows::Railway`: you have to get a user by ID, get all user's blog posts and convert it to an array of HTML-strings. In such situation, we have to implement three parts of our task and compose it into something we can call, for example, from a Rails controller. Also, the first and third steps may fail (user not found, conversion to HTML failed). And if a step failed - we have to return failure info immediately. Let's draw this using a UML activity diagram:
|
10
|
-
|
11
|
-
```plantuml
|
12
|
-
@startuml
|
13
|
-
|Success Path|
|
14
|
-
start
|
15
|
-
-> id: Integer;
|
16
|
-
:fetch_user;
|
17
|
-
if (success?) then (yes)
|
18
|
-
-> user: User;
|
19
|
-
:get_blog_posts;
|
20
|
-
-> posts: Array<Post>;
|
21
|
-
:convert_to_html;
|
22
|
-
if (success?) then (yes)
|
23
|
-
-> posts_html: Array<String>;
|
24
|
-
stop
|
25
|
-
else (no)
|
26
|
-
|Failure|
|
27
|
-
-> message: String;
|
28
|
-
end
|
29
|
-
endif
|
30
|
-
else (no)
|
31
|
-
|Failure|
|
32
|
-
-> message: String;
|
33
|
-
end
|
34
|
-
endif
|
35
|
-
@enduml
|
36
|
-
```
|
37
|
-
|
38
|
-
And implement using `Flows::Railway`:
|
39
|
-
|
40
|
-
```ruby
|
41
|
-
class RenderUserBlogPosts
|
42
|
-
include Flows::Railway
|
43
|
-
|
44
|
-
step :fetch_user
|
45
|
-
step :get_blog_posts
|
46
|
-
step :convert_to_html
|
47
|
-
|
48
|
-
def fetch_user(id:)
|
49
|
-
user = User.find_by_id(id)
|
50
|
-
user ? ok(user: user) : err(message: "User #{id} not found")
|
51
|
-
end
|
52
|
-
|
53
|
-
def get_blog_posts(user:)
|
54
|
-
ok(posts: User.posts)
|
55
|
-
end
|
56
|
-
|
57
|
-
def convert_to_html(posts:)
|
58
|
-
posts_html = post.map(&:text).map do |text|
|
59
|
-
html = convert(text)
|
60
|
-
return err(message: "cannot convert to html: #{text}")
|
61
|
-
end
|
62
|
-
|
63
|
-
ok(posts_html: posts_html)
|
64
|
-
end
|
65
|
-
|
66
|
-
private
|
67
|
-
|
68
|
-
# returns String or nil
|
69
|
-
def convert(text)
|
70
|
-
# some implementation here
|
71
|
-
end
|
72
|
-
end
|
73
|
-
```
|
74
|
-
|
75
|
-
And execute it:
|
76
|
-
|
77
|
-
```ruby
|
78
|
-
# User with id = 1 exists and with id = 2 - doesn't
|
79
|
-
|
80
|
-
RenderUserBlogPosts.new.call(id: 1)
|
81
|
-
# => Flows::Result::Ok.new(posts_html: [...])
|
82
|
-
|
83
|
-
RenderUserBlogPosts.new.call(id: 2)
|
84
|
-
# => Flows::Result::Err.new(message: 'User 2 not found')
|
85
|
-
```
|
86
|
-
|
87
|
-
## Flows::Railway rules
|
88
|
-
|
89
|
-
* steps execution happens from the first to the last step
|
90
|
-
* input arguments (`Railway#call(...)`) becomes the input of the first step
|
91
|
-
* each step should return Result Object (`Flows::Result::Helpers` already included)
|
92
|
-
* if step returns failed result - execution stops and failed Result Object returned from Railway
|
93
|
-
* if step returns successful result - result data becomes arguments of the following step
|
94
|
-
* if the last step returns successful result - it becomes a result of a Railway execution
|
95
|
-
|
96
|
-
## Defining Steps
|
97
|
-
|
98
|
-
Two ways of step definition exist. First is by using an instance method:
|
99
|
-
|
100
|
-
```ruby
|
101
|
-
step :do_something
|
102
|
-
|
103
|
-
def do_something(**arguments)
|
104
|
-
# some implementation
|
105
|
-
# Result Object as return value
|
106
|
-
end
|
107
|
-
```
|
108
|
-
|
109
|
-
Second is by using lambda:
|
110
|
-
|
111
|
-
```ruby
|
112
|
-
step :do_something, ->(**arguments) { ok(some: 'data') }
|
113
|
-
```
|
114
|
-
|
115
|
-
Definition with lambda exists primarily for debugging/testing purposes. I recommend you to use method-based implementations for all your business logic. Also, this is good for consistency, readability, and maintenance. __Think about Railway as about small book: you have a "table of contents" in a form of step definitions and actual "chapters" in the same order in a form of public methods. And your private methods becomes something like "appendix".__
|
116
|
-
|
117
|
-
## Dependency Injection
|
118
|
-
|
119
|
-
By default, we search for step implementation methods in a class instance. But you may override method source and inject your own:
|
120
|
-
|
121
|
-
```ruby
|
122
|
-
class SayOk
|
123
|
-
include Flows::Railway
|
124
|
-
|
125
|
-
step :do_job
|
126
|
-
end
|
127
|
-
|
128
|
-
module Loud
|
129
|
-
extend Flows::Result::Helpers
|
130
|
-
|
131
|
-
def self.do_job
|
132
|
-
ok(text: 'OOOOKKKK!!!!')
|
133
|
-
end
|
134
|
-
end
|
135
|
-
|
136
|
-
module Normal
|
137
|
-
extend Flows::Result::Helpers
|
138
|
-
|
139
|
-
def self.do_job
|
140
|
-
ok(text: 'ok')
|
141
|
-
end
|
142
|
-
end
|
143
|
-
|
144
|
-
SayOk.new(method_source: Loud).call.unwrap
|
145
|
-
# => { text: 'OOOOKKKK!!!!' }
|
146
|
-
|
147
|
-
SayOk.new(method_source: Normal).call.unwrap
|
148
|
-
# => { text: 'ok' }
|
149
|
-
```
|
150
|
-
|
151
|
-
When you change your method source original class is no longer used for methods lookup. But what if we want to just override one of the steps? We can:
|
152
|
-
|
153
|
-
```ruby
|
154
|
-
class SayOk
|
155
|
-
include Flows::Railway
|
156
|
-
|
157
|
-
step :do_job
|
158
|
-
|
159
|
-
def do_job
|
160
|
-
ok(text: 'ok')
|
161
|
-
end
|
162
|
-
end
|
163
|
-
|
164
|
-
say_loud = -> { ok(text: 'OOOOKKKK!!!!') } # or anything with implemented #call method
|
165
|
-
|
166
|
-
SayOk.new.call.unwrap
|
167
|
-
# => { text: 'OOOOKKKK!!!!' }
|
168
|
-
|
169
|
-
SayOk.new(deps: { do_job: say_loud }).call.unwrap
|
170
|
-
# => { text: 'ok' }
|
171
|
-
```
|
172
|
-
|
173
|
-
Moreover, you can mix both approaches. Injecting using `deps:` has higher priority.
|
174
|
-
|
175
|
-
## Pre-building and Performance
|
176
|
-
|
177
|
-
As mentioned before, railway execution consists of two phases: build (`.new`) and run (`#call`). And the build phase is expensive. You may compare overheads when you build a railway each time:
|
178
|
-
|
179
|
-
```
|
180
|
-
$ WITH_RW=1 bin/benchmark
|
181
|
-
|
182
|
-
--------------------------------------------------
|
183
|
-
- task: A + B, one step implementation
|
184
|
-
--------------------------------------------------
|
185
|
-
Warming up --------------------------------------
|
186
|
-
Flows::Railway (build once)
|
187
|
-
30.995k i/100ms
|
188
|
-
Flows::Railway (build each time)
|
189
|
-
11.553k i/100ms
|
190
|
-
Calculating -------------------------------------
|
191
|
-
Flows::Railway (build once)
|
192
|
-
347.682k (± 2.1%) i/s - 1.767M in 5.083828s
|
193
|
-
Flows::Railway (build each time)
|
194
|
-
122.908k (± 4.2%) i/s - 623.862k in 5.085459s
|
195
|
-
|
196
|
-
Comparison:
|
197
|
-
Flows::Railway (build once): 347681.6 i/s
|
198
|
-
Flows::Railway (build each time): 122908.0 i/s - 2.83x slower
|
199
|
-
|
200
|
-
|
201
|
-
--------------------------------------------------
|
202
|
-
- task: ten steps returns successful result
|
203
|
-
--------------------------------------------------
|
204
|
-
Warming up --------------------------------------
|
205
|
-
Flows::Railway (build once)
|
206
|
-
6.130k i/100ms
|
207
|
-
Flows::Railway (build each time)
|
208
|
-
2.168k i/100ms
|
209
|
-
Calculating -------------------------------------
|
210
|
-
Flows::Railway (build once)
|
211
|
-
63.202k (± 1.6%) i/s - 318.760k in 5.044862s
|
212
|
-
Flows::Railway (build each time)
|
213
|
-
21.645k (± 3.6%) i/s - 108.400k in 5.014725s
|
214
|
-
|
215
|
-
Comparison:
|
216
|
-
Flows::Railway (build once): 63202.5 i/s
|
217
|
-
Flows::Railway (build each time): 21645.2 i/s - 2.92x slower
|
218
|
-
```
|
219
|
-
|
220
|
-
As the benchmark shows your infrastructure code overhead from Flows will be almost three times lower when you build your railways at 'compile' time. I mean something like that:
|
221
|
-
|
222
|
-
```ruby
|
223
|
-
class MyClass
|
224
|
-
MY_RAILWAY = MyRailway.new # this string will be executed on a class loading stage
|
225
|
-
|
226
|
-
def my_method
|
227
|
-
MY_RAILWAY.call
|
228
|
-
end
|
229
|
-
end
|
230
|
-
```
|
231
|
-
|
232
|
-
But if you don't care much about performance - build each time will be fast enough. Check out [Performance](overview/performance.md) page to see a bigger picture.
|