flows 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/build.yml +43 -0
  3. data/.mdlrc +1 -0
  4. data/.rubocop.yml +25 -0
  5. data/Gemfile +6 -0
  6. data/Gemfile.lock +80 -25
  7. data/README.md +170 -44
  8. data/bin/benchmark +65 -42
  9. data/bin/examples.rb +37 -1
  10. data/bin/profile_10steps +48 -6
  11. data/docs/.nojekyll +0 -0
  12. data/docs/CNAME +1 -0
  13. data/docs/README.md +197 -0
  14. data/docs/_sidebar.md +26 -0
  15. data/docs/contributing/benchmarks_profiling.md +3 -0
  16. data/docs/contributing/local_development.md +3 -0
  17. data/docs/flow/direct_usage.md +3 -0
  18. data/docs/flow/general_idea.md +3 -0
  19. data/docs/index.html +30 -0
  20. data/docs/operation/basic_usage.md +1 -0
  21. data/docs/operation/inject_steps.md +3 -0
  22. data/docs/operation/lambda_steps.md +3 -0
  23. data/docs/operation/result_shapes.md +3 -0
  24. data/docs/operation/routing_tracks.md +3 -0
  25. data/docs/operation/wrapping_steps.md +3 -0
  26. data/docs/overview/performance.md +336 -0
  27. data/docs/railway/basic_usage.md +232 -0
  28. data/docs/result_objects/basic_usage.md +196 -0
  29. data/docs/result_objects/do_notation.md +139 -0
  30. data/flows.gemspec +2 -0
  31. data/forspell.dict +8 -0
  32. data/lefthook.yml +12 -0
  33. data/lib/flows.rb +2 -0
  34. data/lib/flows/flow.rb +1 -1
  35. data/lib/flows/operation.rb +1 -3
  36. data/lib/flows/operation/builder.rb +2 -2
  37. data/lib/flows/operation/dsl.rb +21 -0
  38. data/lib/flows/railway.rb +48 -0
  39. data/lib/flows/railway/builder.rb +68 -0
  40. data/lib/flows/railway/dsl.rb +28 -0
  41. data/lib/flows/railway/errors.rb +21 -0
  42. data/lib/flows/railway/executor.rb +23 -0
  43. data/lib/flows/result.rb +1 -0
  44. data/lib/flows/result/do.rb +30 -0
  45. data/lib/flows/result_router.rb +1 -1
  46. data/lib/flows/version.rb +1 -1
  47. metadata +59 -3
  48. data/.travis.yml +0 -8
@@ -0,0 +1,26 @@
1
+ * Overview
2
+ * [Getting Started](README.md)
3
+ * [Performance](overview/performance.md)
4
+
5
+ * Result Objects
6
+ * [Basic Usage](result_objects/basic_usage.md)
7
+ * [Do Notation](result_objects/do_notation.md)
8
+
9
+ * Railway
10
+ * [Basic Usage](railway/basic_usage.md)
11
+
12
+ * Operation
13
+ * [Basics](operation/basic_usage.md)
14
+ * [Result Shapes](operation/result_shapes.md)
15
+ * [Routing & Tracks](operation/routing_tracks.md)
16
+ * [Lambda Steps](operation/lambda_steps.md)
17
+ * [Inject Steps](operation/inject_steps.md)
18
+ * [Wrapping Steps](operation/wrapping_steps.md)
19
+
20
+ * Flow
21
+ * [General Idea](flow/general_idea.md)
22
+ * [Direct Usage](flow/direct_usage.md)
23
+
24
+ * Contributing
25
+ * [Local Development](contributing/local_development.md)
26
+ * [Benchmarks & Profiling](contributing/benchmarks_profiling.md)
@@ -0,0 +1,3 @@
1
+ # Benchmarks & Profiling
2
+
3
+ TODO
@@ -0,0 +1,3 @@
1
+ # Local Development
2
+
3
+ TODO
@@ -0,0 +1,3 @@
1
+ # Direct Usage
2
+
3
+ TODO
@@ -0,0 +1,3 @@
1
+ # General Idea
2
+
3
+ TODO
@@ -0,0 +1,30 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <title>Flows</title>
6
+ <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
7
+ <meta name="description" content="Flows documentation">
8
+ <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
9
+ <link rel="stylesheet" href="//unpkg.com/docsify/lib/themes/vue.css">
10
+ </head>
11
+ <body>
12
+ <div id="app">Please wait...</div>
13
+
14
+ <script>
15
+ window.$docsify = {
16
+ name: 'Flows',
17
+ repo: 'ffloyd/flows',
18
+ auto2top: true,
19
+ noEmoji: true,
20
+ loadSidebar: true,
21
+ subMaxLevel: 2
22
+ }
23
+ </script>
24
+
25
+ <script src="//unpkg.com/docsify/lib/docsify.min.js"></script>
26
+ <script src="//unpkg.com/prismjs/components/prism-bash.min.js"></script>
27
+ <script src="//unpkg.com/prismjs/components/prism-ruby.min.js"></script>
28
+ <script src="//unpkg.com/docsify-plantuml/dist/docsify-plantuml.min.js"></script>
29
+ </body>
30
+ </html>
@@ -0,0 +1 @@
1
+ # Basic Usage
@@ -0,0 +1,3 @@
1
+ # Inject Steps
2
+
3
+ TODO
@@ -0,0 +1,3 @@
1
+ # Lambda Steps
2
+
3
+ TODO
@@ -0,0 +1,3 @@
1
+ # Result Shapes
2
+
3
+ TODO
@@ -0,0 +1,3 @@
1
+ # Routing & Tracks
2
+
3
+ TODO
@@ -0,0 +1,3 @@
1
+ # Wrapping Steps
2
+
3
+ TODO
@@ -0,0 +1,336 @@
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
+ ```
@@ -0,0 +1,232 @@
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.