simple_flow 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.
- checksums.yaml +7 -0
- data/.envrc +1 -0
- data/.github/workflows/deploy-github-pages.yml +52 -0
- data/.rubocop.yml +57 -0
- data/CHANGELOG.md +4 -0
- data/COMMITS.md +196 -0
- data/LICENSE +21 -0
- data/README.md +481 -0
- data/Rakefile +15 -0
- data/benchmarks/parallel_vs_sequential.rb +98 -0
- data/benchmarks/pipeline_overhead.rb +130 -0
- data/docs/api/middleware.md +468 -0
- data/docs/api/parallel-step.md +363 -0
- data/docs/api/pipeline.md +382 -0
- data/docs/api/result.md +375 -0
- data/docs/concurrent/best-practices.md +687 -0
- data/docs/concurrent/introduction.md +246 -0
- data/docs/concurrent/parallel-steps.md +418 -0
- data/docs/concurrent/performance.md +481 -0
- data/docs/core-concepts/flow-control.md +452 -0
- data/docs/core-concepts/middleware.md +389 -0
- data/docs/core-concepts/overview.md +219 -0
- data/docs/core-concepts/pipeline.md +315 -0
- data/docs/core-concepts/result.md +168 -0
- data/docs/core-concepts/steps.md +391 -0
- data/docs/development/benchmarking.md +443 -0
- data/docs/development/contributing.md +380 -0
- data/docs/development/dagwood-concepts.md +435 -0
- data/docs/development/testing.md +514 -0
- data/docs/getting-started/examples.md +197 -0
- data/docs/getting-started/installation.md +62 -0
- data/docs/getting-started/quick-start.md +218 -0
- data/docs/guides/choosing-concurrency-model.md +441 -0
- data/docs/guides/complex-workflows.md +440 -0
- data/docs/guides/data-fetching.md +478 -0
- data/docs/guides/error-handling.md +635 -0
- data/docs/guides/file-processing.md +505 -0
- data/docs/guides/validation-patterns.md +496 -0
- data/docs/index.md +169 -0
- data/examples/.gitignore +3 -0
- data/examples/01_basic_pipeline.rb +112 -0
- data/examples/02_error_handling.rb +178 -0
- data/examples/03_middleware.rb +186 -0
- data/examples/04_parallel_automatic.rb +221 -0
- data/examples/05_parallel_explicit.rb +279 -0
- data/examples/06_real_world_ecommerce.rb +288 -0
- data/examples/07_real_world_etl.rb +277 -0
- data/examples/08_graph_visualization.rb +246 -0
- data/examples/09_pipeline_visualization.rb +266 -0
- data/examples/10_concurrency_control.rb +235 -0
- data/examples/11_sequential_dependencies.rb +243 -0
- data/examples/12_none_constant.rb +161 -0
- data/examples/README.md +374 -0
- data/examples/regression_test/01_basic_pipeline.txt +38 -0
- data/examples/regression_test/02_error_handling.txt +92 -0
- data/examples/regression_test/03_middleware.txt +61 -0
- data/examples/regression_test/04_parallel_automatic.txt +86 -0
- data/examples/regression_test/05_parallel_explicit.txt +80 -0
- data/examples/regression_test/06_real_world_ecommerce.txt +53 -0
- data/examples/regression_test/07_real_world_etl.txt +58 -0
- data/examples/regression_test/08_graph_visualization.txt +429 -0
- data/examples/regression_test/09_pipeline_visualization.txt +305 -0
- data/examples/regression_test/10_concurrency_control.txt +96 -0
- data/examples/regression_test/11_sequential_dependencies.txt +86 -0
- data/examples/regression_test/12_none_constant.txt +64 -0
- data/examples/regression_test.rb +105 -0
- data/lib/simple_flow/dependency_graph.rb +120 -0
- data/lib/simple_flow/dependency_graph_visualizer.rb +326 -0
- data/lib/simple_flow/middleware.rb +36 -0
- data/lib/simple_flow/parallel_executor.rb +80 -0
- data/lib/simple_flow/pipeline.rb +405 -0
- data/lib/simple_flow/result.rb +88 -0
- data/lib/simple_flow/step_tracker.rb +58 -0
- data/lib/simple_flow/version.rb +5 -0
- data/lib/simple_flow.rb +41 -0
- data/mkdocs.yml +146 -0
- data/pipeline_graph.dot +51 -0
- data/pipeline_graph.html +60 -0
- data/pipeline_graph.mmd +19 -0
- metadata +127 -0
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require_relative '../lib/simple_flow'
|
|
5
|
+
require 'timecop'
|
|
6
|
+
Timecop.travel(Time.local(2001, 9, 11, 7, 0, 0))
|
|
7
|
+
|
|
8
|
+
# Direct pipeline visualization - no need to recreate dependency structure!
|
|
9
|
+
|
|
10
|
+
puts "=" * 60
|
|
11
|
+
puts "Direct Pipeline Visualization"
|
|
12
|
+
puts "=" * 60
|
|
13
|
+
puts
|
|
14
|
+
|
|
15
|
+
# Example 1: Visualize directly from pipeline
|
|
16
|
+
puts "Example 1: Simple Pipeline Visualization"
|
|
17
|
+
puts "-" * 60
|
|
18
|
+
puts
|
|
19
|
+
|
|
20
|
+
pipeline = SimpleFlow::Pipeline.new do
|
|
21
|
+
step :validate, ->(result) {
|
|
22
|
+
result.with_context(:validated, true).continue(result.value)
|
|
23
|
+
}, depends_on: :none
|
|
24
|
+
|
|
25
|
+
step :fetch_data, ->(result) {
|
|
26
|
+
result.with_context(:data, [1, 2, 3]).continue(result.value)
|
|
27
|
+
}, depends_on: [:validate]
|
|
28
|
+
|
|
29
|
+
step :process_data, ->(result) {
|
|
30
|
+
result.continue(result.value * 2)
|
|
31
|
+
}, depends_on: [:fetch_data]
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Visualize directly - no manual graph creation needed!
|
|
35
|
+
puts pipeline.visualize_ascii
|
|
36
|
+
puts
|
|
37
|
+
|
|
38
|
+
# Example 2: E-commerce pipeline with parallel steps
|
|
39
|
+
puts "\n" + "=" * 60
|
|
40
|
+
puts "Example 2: E-commerce Pipeline (Automatic Visualization)"
|
|
41
|
+
puts "=" * 60
|
|
42
|
+
puts
|
|
43
|
+
|
|
44
|
+
ecommerce_pipeline = SimpleFlow::Pipeline.new do
|
|
45
|
+
step :validate_order, ->(result) {
|
|
46
|
+
result.continue(result.value)
|
|
47
|
+
}, depends_on: :none
|
|
48
|
+
|
|
49
|
+
# These will run in parallel
|
|
50
|
+
step :check_inventory, ->(result) {
|
|
51
|
+
result.with_context(:inventory, :ok).continue(result.value)
|
|
52
|
+
}, depends_on: [:validate_order]
|
|
53
|
+
|
|
54
|
+
step :calculate_shipping, ->(result) {
|
|
55
|
+
result.with_context(:shipping, 10).continue(result.value)
|
|
56
|
+
}, depends_on: [:validate_order]
|
|
57
|
+
|
|
58
|
+
step :calculate_totals, ->(result) {
|
|
59
|
+
result.continue(result.value)
|
|
60
|
+
}, depends_on: [:check_inventory, :calculate_shipping]
|
|
61
|
+
|
|
62
|
+
step :process_payment, ->(result) {
|
|
63
|
+
result.continue(result.value)
|
|
64
|
+
}, depends_on: [:calculate_totals]
|
|
65
|
+
|
|
66
|
+
step :reserve_inventory, ->(result) {
|
|
67
|
+
result.continue(result.value)
|
|
68
|
+
}, depends_on: [:process_payment]
|
|
69
|
+
|
|
70
|
+
step :create_shipment, ->(result) {
|
|
71
|
+
result.continue(result.value)
|
|
72
|
+
}, depends_on: [:reserve_inventory]
|
|
73
|
+
|
|
74
|
+
# These will run in parallel
|
|
75
|
+
step :send_email, ->(result) {
|
|
76
|
+
result.continue(result.value)
|
|
77
|
+
}, depends_on: [:create_shipment]
|
|
78
|
+
|
|
79
|
+
step :send_sms, ->(result) {
|
|
80
|
+
result.continue(result.value)
|
|
81
|
+
}, depends_on: [:create_shipment]
|
|
82
|
+
|
|
83
|
+
step :finalize_order, ->(result) {
|
|
84
|
+
result.continue("Order complete!")
|
|
85
|
+
}, depends_on: [:send_email, :send_sms]
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Display the visualization
|
|
89
|
+
puts ecommerce_pipeline.visualize_ascii
|
|
90
|
+
puts
|
|
91
|
+
|
|
92
|
+
# Example 3: Get execution plan
|
|
93
|
+
puts "\n" + "=" * 60
|
|
94
|
+
puts "Example 3: Execution Plan (Direct from Pipeline)"
|
|
95
|
+
puts "=" * 60
|
|
96
|
+
puts
|
|
97
|
+
|
|
98
|
+
puts ecommerce_pipeline.execution_plan
|
|
99
|
+
puts
|
|
100
|
+
|
|
101
|
+
# Example 4: Export to different formats
|
|
102
|
+
puts "\n" + "=" * 60
|
|
103
|
+
puts "Example 4: Export Formats (Direct from Pipeline)"
|
|
104
|
+
puts "=" * 60
|
|
105
|
+
puts
|
|
106
|
+
|
|
107
|
+
# Export to Graphviz DOT
|
|
108
|
+
File.write('pipeline_graph.dot', ecommerce_pipeline.visualize_dot)
|
|
109
|
+
puts "✓ Exported to Graphviz DOT: pipeline_graph.dot"
|
|
110
|
+
puts " Generate image: dot -Tpng pipeline_graph.dot -o pipeline.png"
|
|
111
|
+
puts
|
|
112
|
+
|
|
113
|
+
# Export to Mermaid
|
|
114
|
+
File.write('pipeline_graph.mmd', ecommerce_pipeline.visualize_mermaid)
|
|
115
|
+
puts "✓ Exported to Mermaid: pipeline_graph.mmd"
|
|
116
|
+
puts " View at: https://mermaid.live/"
|
|
117
|
+
puts
|
|
118
|
+
|
|
119
|
+
# Export to HTML (need the visualizer object for this)
|
|
120
|
+
if visualizer = ecommerce_pipeline.visualize
|
|
121
|
+
File.write('pipeline_graph.html', visualizer.to_html(title: "E-commerce Pipeline"))
|
|
122
|
+
puts "✓ Exported to HTML: pipeline_graph.html"
|
|
123
|
+
puts " Open in browser for interactive visualization"
|
|
124
|
+
end
|
|
125
|
+
puts
|
|
126
|
+
|
|
127
|
+
# Example 5: ETL Pipeline
|
|
128
|
+
puts "\n" + "=" * 60
|
|
129
|
+
puts "Example 5: ETL Pipeline Visualization"
|
|
130
|
+
puts "=" * 60
|
|
131
|
+
puts
|
|
132
|
+
|
|
133
|
+
etl_pipeline = SimpleFlow::Pipeline.new do
|
|
134
|
+
# Extract phase - all run in parallel
|
|
135
|
+
step :extract_users, ->(result) {
|
|
136
|
+
result.with_context(:users, []).continue(result.value)
|
|
137
|
+
}, depends_on: :none
|
|
138
|
+
|
|
139
|
+
step :extract_orders, ->(result) {
|
|
140
|
+
result.with_context(:orders, []).continue(result.value)
|
|
141
|
+
}, depends_on: :none
|
|
142
|
+
|
|
143
|
+
step :extract_products, ->(result) {
|
|
144
|
+
result.with_context(:products, []).continue(result.value)
|
|
145
|
+
}, depends_on: :none
|
|
146
|
+
|
|
147
|
+
# Transform phase - all run in parallel after extraction
|
|
148
|
+
step :transform_users, ->(result) {
|
|
149
|
+
result.continue(result.value)
|
|
150
|
+
}, depends_on: [:extract_users]
|
|
151
|
+
|
|
152
|
+
step :transform_orders, ->(result) {
|
|
153
|
+
result.continue(result.value)
|
|
154
|
+
}, depends_on: [:extract_orders]
|
|
155
|
+
|
|
156
|
+
step :transform_products, ->(result) {
|
|
157
|
+
result.continue(result.value)
|
|
158
|
+
}, depends_on: [:extract_products]
|
|
159
|
+
|
|
160
|
+
# Aggregate phase - can run in parallel
|
|
161
|
+
step :aggregate_user_stats, ->(result) {
|
|
162
|
+
result.continue(result.value)
|
|
163
|
+
}, depends_on: [:transform_users, :transform_orders]
|
|
164
|
+
|
|
165
|
+
step :aggregate_category_stats, ->(result) {
|
|
166
|
+
result.continue(result.value)
|
|
167
|
+
}, depends_on: [:transform_products]
|
|
168
|
+
|
|
169
|
+
# Validate
|
|
170
|
+
step :validate_data, ->(result) {
|
|
171
|
+
result.continue(result.value)
|
|
172
|
+
}, depends_on: [:aggregate_user_stats]
|
|
173
|
+
|
|
174
|
+
# Load
|
|
175
|
+
step :prepare_output, ->(result) {
|
|
176
|
+
result.continue("ETL Complete!")
|
|
177
|
+
}, depends_on: [:validate_data, :aggregate_category_stats]
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
puts etl_pipeline.execution_plan
|
|
181
|
+
puts
|
|
182
|
+
|
|
183
|
+
# Example 6: Check if pipeline can be visualized
|
|
184
|
+
puts "\n" + "=" * 60
|
|
185
|
+
puts "Example 6: Checking Visualization Availability"
|
|
186
|
+
puts "=" * 60
|
|
187
|
+
puts
|
|
188
|
+
|
|
189
|
+
# Pipeline with named steps - can be visualized
|
|
190
|
+
named_pipeline = SimpleFlow::Pipeline.new do
|
|
191
|
+
step :step_a, ->(r) { r.continue(r.value) }, depends_on: :none
|
|
192
|
+
step :step_b, ->(r) { r.continue(r.value) }, depends_on: [:step_a]
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# Pipeline with unnamed steps - cannot be auto-visualized
|
|
196
|
+
unnamed_pipeline = SimpleFlow::Pipeline.new do
|
|
197
|
+
step ->(r) { r.continue(r.value) }
|
|
198
|
+
step ->(r) { r.continue(r.value) }
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
puts "Named pipeline has dependency graph? #{!named_pipeline.dependency_graph.nil?}"
|
|
202
|
+
puts "Unnamed pipeline has dependency graph? #{!unnamed_pipeline.dependency_graph.nil?}"
|
|
203
|
+
puts
|
|
204
|
+
puts "Note: Only pipelines with named steps and dependencies can be auto-visualized"
|
|
205
|
+
puts
|
|
206
|
+
|
|
207
|
+
# Example 7: Working with the dependency graph directly
|
|
208
|
+
puts "\n" + "=" * 60
|
|
209
|
+
puts "Example 7: Advanced - Access Dependency Graph"
|
|
210
|
+
puts "=" * 60
|
|
211
|
+
puts
|
|
212
|
+
|
|
213
|
+
if graph = ecommerce_pipeline.dependency_graph
|
|
214
|
+
puts "Pipeline dependency information:"
|
|
215
|
+
puts " Total steps: #{graph.dependencies.size}"
|
|
216
|
+
puts " Execution phases: #{graph.parallel_order.size}"
|
|
217
|
+
puts " Parallel groups:"
|
|
218
|
+
graph.parallel_order.each_with_index do |group, idx|
|
|
219
|
+
puts " Phase #{idx + 1}: #{group.join(', ')}"
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
puts
|
|
223
|
+
|
|
224
|
+
# Example 8: Compare different pipeline structures
|
|
225
|
+
puts "\n" + "=" * 60
|
|
226
|
+
puts "Example 8: Pipeline Structure Comparison"
|
|
227
|
+
puts "=" * 60
|
|
228
|
+
puts
|
|
229
|
+
|
|
230
|
+
# Linear pipeline
|
|
231
|
+
linear = SimpleFlow::Pipeline.new do
|
|
232
|
+
step :step1, ->(r) { r.continue(r.value) }, depends_on: :none
|
|
233
|
+
step :step2, ->(r) { r.continue(r.value) }, depends_on: [:step1]
|
|
234
|
+
step :step3, ->(r) { r.continue(r.value) }, depends_on: [:step2]
|
|
235
|
+
step :step4, ->(r) { r.continue(r.value) }, depends_on: [:step3]
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
# Parallel pipeline
|
|
239
|
+
parallel = SimpleFlow::Pipeline.new do
|
|
240
|
+
step :start, ->(r) { r.continue(r.value) }, depends_on: :none
|
|
241
|
+
step :task1, ->(r) { r.continue(r.value) }, depends_on: [:start]
|
|
242
|
+
step :task2, ->(r) { r.continue(r.value) }, depends_on: [:start]
|
|
243
|
+
step :task3, ->(r) { r.continue(r.value) }, depends_on: [:start]
|
|
244
|
+
step :end, ->(r) { r.continue(r.value) }, depends_on: [:task1, :task2, :task3]
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
puts "Linear Pipeline:"
|
|
248
|
+
puts linear.execution_plan
|
|
249
|
+
puts
|
|
250
|
+
|
|
251
|
+
puts "\nParallel Pipeline:"
|
|
252
|
+
puts parallel.execution_plan
|
|
253
|
+
puts
|
|
254
|
+
|
|
255
|
+
puts "=" * 60
|
|
256
|
+
puts "Direct pipeline visualization completed!"
|
|
257
|
+
puts
|
|
258
|
+
puts "Key Takeaway:"
|
|
259
|
+
puts " No need to manually recreate dependency structures!"
|
|
260
|
+
puts " Just call pipeline.visualize_ascii, pipeline.visualize_dot, etc."
|
|
261
|
+
puts
|
|
262
|
+
puts "Generated files:"
|
|
263
|
+
puts " - pipeline_graph.dot"
|
|
264
|
+
puts " - pipeline_graph.mmd"
|
|
265
|
+
puts " - pipeline_graph.html"
|
|
266
|
+
puts "=" * 60
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require_relative '../lib/simple_flow'
|
|
5
|
+
require 'timecop'
|
|
6
|
+
Timecop.travel(Time.local(2001, 9, 11, 7, 0, 0))
|
|
7
|
+
|
|
8
|
+
# Controlling Concurrency Model Per Pipeline
|
|
9
|
+
|
|
10
|
+
puts "=" * 60
|
|
11
|
+
puts "Per-Pipeline Concurrency Control"
|
|
12
|
+
puts "=" * 60
|
|
13
|
+
puts
|
|
14
|
+
|
|
15
|
+
# Check async availability
|
|
16
|
+
async_available = SimpleFlow::ParallelExecutor.async_available?
|
|
17
|
+
puts "Async gem available: #{async_available ? '✓ Yes' : '✗ No'}"
|
|
18
|
+
puts
|
|
19
|
+
|
|
20
|
+
# Example 1: Auto-detect (default behavior)
|
|
21
|
+
puts "Example 1: Auto-Detect Concurrency (Default)"
|
|
22
|
+
puts "-" * 60
|
|
23
|
+
puts
|
|
24
|
+
|
|
25
|
+
pipeline_auto = SimpleFlow::Pipeline.new do # concurrency: :auto is default
|
|
26
|
+
step ->(result) {
|
|
27
|
+
puts " Processing with auto-detected concurrency..."
|
|
28
|
+
result.continue(result.value)
|
|
29
|
+
}
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
puts "Pipeline concurrency setting: #{pipeline_auto.concurrency}"
|
|
33
|
+
puts "Will use: #{async_available ? 'Async (fiber-based)' : 'Threads'}"
|
|
34
|
+
puts
|
|
35
|
+
|
|
36
|
+
# Example 2: Force threads (even if async is available)
|
|
37
|
+
puts "\nExample 2: Force Threads"
|
|
38
|
+
puts "-" * 60
|
|
39
|
+
puts
|
|
40
|
+
|
|
41
|
+
pipeline_threads = SimpleFlow::Pipeline.new(concurrency: :threads) do
|
|
42
|
+
parallel do
|
|
43
|
+
step ->(result) {
|
|
44
|
+
puts " [Thread-based] Task A running..."
|
|
45
|
+
sleep 0.05
|
|
46
|
+
result.with_context(:task_a, :done).continue(result.value)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
step ->(result) {
|
|
50
|
+
puts " [Thread-based] Task B running..."
|
|
51
|
+
sleep 0.05
|
|
52
|
+
result.with_context(:task_b, :done).continue(result.value)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
step ->(result) {
|
|
56
|
+
puts " [Thread-based] Task C running..."
|
|
57
|
+
sleep 0.05
|
|
58
|
+
result.with_context(:task_c, :done).continue(result.value)
|
|
59
|
+
}
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
puts "Pipeline concurrency setting: #{pipeline_threads.concurrency}"
|
|
64
|
+
puts "Will use: Ruby Threads (even if async is available)"
|
|
65
|
+
|
|
66
|
+
result = pipeline_threads.call(SimpleFlow::Result.new(nil))
|
|
67
|
+
puts "Result context: #{result.context}"
|
|
68
|
+
puts
|
|
69
|
+
|
|
70
|
+
# Example 3: Force async (requires async gem)
|
|
71
|
+
puts "\nExample 3: Force Async"
|
|
72
|
+
puts "-" * 60
|
|
73
|
+
puts
|
|
74
|
+
|
|
75
|
+
if async_available
|
|
76
|
+
pipeline_async = SimpleFlow::Pipeline.new(concurrency: :async) do
|
|
77
|
+
step :validate, ->(result) {
|
|
78
|
+
puts " [Async] Validating..."
|
|
79
|
+
result.continue(result.value)
|
|
80
|
+
}, depends_on: :none
|
|
81
|
+
|
|
82
|
+
step :fetch_a, ->(result) {
|
|
83
|
+
puts " [Async] Fetching A..."
|
|
84
|
+
sleep 0.05
|
|
85
|
+
result.with_context(:a, :done).continue(result.value)
|
|
86
|
+
}, depends_on: [:validate]
|
|
87
|
+
|
|
88
|
+
step :fetch_b, ->(result) {
|
|
89
|
+
puts " [Async] Fetching B..."
|
|
90
|
+
sleep 0.05
|
|
91
|
+
result.with_context(:b, :done).continue(result.value)
|
|
92
|
+
}, depends_on: [:validate]
|
|
93
|
+
|
|
94
|
+
step :merge, ->(result) {
|
|
95
|
+
puts " [Async] Merging results..."
|
|
96
|
+
result.continue("Complete")
|
|
97
|
+
}, depends_on: [:fetch_a, :fetch_b]
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
puts "Pipeline concurrency setting: #{pipeline_async.concurrency}"
|
|
101
|
+
puts "Will use: Async (fiber-based concurrency)"
|
|
102
|
+
|
|
103
|
+
result2 = pipeline_async.call_parallel(SimpleFlow::Result.new(nil))
|
|
104
|
+
puts "Result: #{result2.value}"
|
|
105
|
+
puts "Context: #{result2.context}"
|
|
106
|
+
else
|
|
107
|
+
puts "Cannot create async pipeline - async gem not available"
|
|
108
|
+
puts "Would raise: ArgumentError: Concurrency set to :async but async gem is not available"
|
|
109
|
+
end
|
|
110
|
+
puts
|
|
111
|
+
|
|
112
|
+
# Example 4: Different pipelines, different concurrency
|
|
113
|
+
puts "\nExample 4: Mixed Concurrency in Same Application"
|
|
114
|
+
puts "-" * 60
|
|
115
|
+
puts
|
|
116
|
+
|
|
117
|
+
# Low-volume user pipeline - threads are simpler
|
|
118
|
+
user_pipeline = SimpleFlow::Pipeline.new(concurrency: :threads) do
|
|
119
|
+
step :validate, ->(result) {
|
|
120
|
+
puts " [User/Threads] Validating user..."
|
|
121
|
+
result.continue(result.value)
|
|
122
|
+
}, depends_on: :none
|
|
123
|
+
|
|
124
|
+
step :fetch_profile, ->(result) {
|
|
125
|
+
puts " [User/Threads] Fetching profile..."
|
|
126
|
+
sleep 0.02
|
|
127
|
+
result.with_context(:profile, { name: "John" }).continue(result.value)
|
|
128
|
+
}, depends_on: [:validate]
|
|
129
|
+
|
|
130
|
+
step :fetch_settings, ->(result) {
|
|
131
|
+
puts " [User/Threads] Fetching settings..."
|
|
132
|
+
sleep 0.02
|
|
133
|
+
result.with_context(:settings, { theme: "dark" }).continue(result.value)
|
|
134
|
+
}, depends_on: [:validate]
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# High-volume batch pipeline - use async if available
|
|
138
|
+
batch_concurrency = async_available ? :async : :threads
|
|
139
|
+
batch_pipeline = SimpleFlow::Pipeline.new(concurrency: batch_concurrency) do
|
|
140
|
+
step :load, ->(result) {
|
|
141
|
+
puts " [Batch/#{batch_concurrency.to_s.capitalize}] Loading batch..."
|
|
142
|
+
result.continue(result.value)
|
|
143
|
+
}, depends_on: :none
|
|
144
|
+
|
|
145
|
+
step :process_batch_1, ->(result) {
|
|
146
|
+
puts " [Batch/#{batch_concurrency.to_s.capitalize}] Processing batch 1..."
|
|
147
|
+
sleep 0.02
|
|
148
|
+
result.with_context(:batch_1, :done).continue(result.value)
|
|
149
|
+
}, depends_on: [:load]
|
|
150
|
+
|
|
151
|
+
step :process_batch_2, ->(result) {
|
|
152
|
+
puts " [Batch/#{batch_concurrency.to_s.capitalize}] Processing batch 2..."
|
|
153
|
+
sleep 0.02
|
|
154
|
+
result.with_context(:batch_2, :done).continue(result.value)
|
|
155
|
+
}, depends_on: [:load]
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
puts "User pipeline uses: #{user_pipeline.concurrency}"
|
|
159
|
+
puts "Batch pipeline uses: #{batch_pipeline.concurrency}"
|
|
160
|
+
puts
|
|
161
|
+
|
|
162
|
+
puts "Running user pipeline..."
|
|
163
|
+
user_result = user_pipeline.call_parallel(SimpleFlow::Result.new({ user_id: 123 }))
|
|
164
|
+
puts "User result: #{user_result.context}"
|
|
165
|
+
|
|
166
|
+
puts "\nRunning batch pipeline..."
|
|
167
|
+
batch_result = batch_pipeline.call_parallel(SimpleFlow::Result.new({ batch_id: 456 }))
|
|
168
|
+
puts "Batch result: #{batch_result.context}"
|
|
169
|
+
puts
|
|
170
|
+
|
|
171
|
+
# Example 5: Error handling for invalid concurrency
|
|
172
|
+
puts "\nExample 5: Error Handling"
|
|
173
|
+
puts "-" * 60
|
|
174
|
+
puts
|
|
175
|
+
|
|
176
|
+
puts "Valid options: :auto, :threads, :async"
|
|
177
|
+
puts
|
|
178
|
+
|
|
179
|
+
begin
|
|
180
|
+
invalid_pipeline = SimpleFlow::Pipeline.new(concurrency: :invalid) do
|
|
181
|
+
step ->(result) { result.continue(result.value) }
|
|
182
|
+
end
|
|
183
|
+
rescue ArgumentError => e
|
|
184
|
+
puts "✓ Caught expected error for invalid concurrency:"
|
|
185
|
+
puts " #{e.message}"
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
puts
|
|
189
|
+
|
|
190
|
+
unless async_available
|
|
191
|
+
begin
|
|
192
|
+
async_pipeline = SimpleFlow::Pipeline.new(concurrency: :async) do
|
|
193
|
+
step ->(result) { result.continue(result.value) }
|
|
194
|
+
end
|
|
195
|
+
rescue ArgumentError => e
|
|
196
|
+
puts "✓ Caught expected error when async not available:"
|
|
197
|
+
puts " #{e.message}"
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
puts
|
|
202
|
+
|
|
203
|
+
# Example 6: Checking pipeline concurrency setting
|
|
204
|
+
puts "\nExample 6: Inspecting Concurrency Settings"
|
|
205
|
+
puts "-" * 60
|
|
206
|
+
puts
|
|
207
|
+
|
|
208
|
+
pipelines = [
|
|
209
|
+
SimpleFlow::Pipeline.new, # default
|
|
210
|
+
SimpleFlow::Pipeline.new(concurrency: :auto), # explicit auto
|
|
211
|
+
SimpleFlow::Pipeline.new(concurrency: :threads), # threads
|
|
212
|
+
]
|
|
213
|
+
|
|
214
|
+
if async_available
|
|
215
|
+
pipelines << SimpleFlow::Pipeline.new(concurrency: :async) # async
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
pipelines.each_with_index do |pipeline, index|
|
|
219
|
+
puts "Pipeline #{index + 1}:"
|
|
220
|
+
puts " Concurrency: #{pipeline.concurrency}"
|
|
221
|
+
puts " Async available: #{pipeline.async_available?}"
|
|
222
|
+
puts
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
puts "=" * 60
|
|
226
|
+
puts "Concurrency control examples completed!"
|
|
227
|
+
puts "=" * 60
|
|
228
|
+
puts
|
|
229
|
+
puts "Key Takeaways:"
|
|
230
|
+
puts " • concurrency: :auto (default) - auto-detects best option"
|
|
231
|
+
puts " • concurrency: :threads - always uses Ruby threads"
|
|
232
|
+
puts " • concurrency: :async - requires async gem, uses fibers"
|
|
233
|
+
puts " • Different pipelines can use different concurrency models"
|
|
234
|
+
puts " • Choose based on your specific workload requirements"
|
|
235
|
+
puts
|