fractor 0.1.6 → 0.1.7

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.
Files changed (172) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +227 -102
  3. data/README.adoc +113 -1940
  4. data/docs/.lycheeignore +16 -0
  5. data/docs/Gemfile +24 -0
  6. data/docs/README.md +157 -0
  7. data/docs/_config.yml +151 -0
  8. data/docs/_features/error-handling.adoc +1192 -0
  9. data/docs/_features/index.adoc +80 -0
  10. data/docs/_features/monitoring.adoc +589 -0
  11. data/docs/_features/signal-handling.adoc +202 -0
  12. data/docs/_features/workflows.adoc +1235 -0
  13. data/docs/_guides/continuous-mode.adoc +736 -0
  14. data/docs/_guides/cookbook.adoc +1133 -0
  15. data/docs/_guides/index.adoc +55 -0
  16. data/docs/_guides/pipeline-mode.adoc +730 -0
  17. data/docs/_guides/troubleshooting.adoc +358 -0
  18. data/docs/_pages/architecture.adoc +1390 -0
  19. data/docs/_pages/core-concepts.adoc +1392 -0
  20. data/docs/_pages/design-principles.adoc +862 -0
  21. data/docs/_pages/getting-started.adoc +290 -0
  22. data/docs/_pages/installation.adoc +143 -0
  23. data/docs/_reference/api.adoc +1080 -0
  24. data/docs/_reference/error-reporting.adoc +670 -0
  25. data/docs/_reference/examples.adoc +181 -0
  26. data/docs/_reference/index.adoc +96 -0
  27. data/docs/_reference/troubleshooting.adoc +862 -0
  28. data/docs/_tutorials/complex-workflows.adoc +1022 -0
  29. data/docs/_tutorials/data-processing-pipeline.adoc +740 -0
  30. data/docs/_tutorials/first-application.adoc +384 -0
  31. data/docs/_tutorials/index.adoc +48 -0
  32. data/docs/_tutorials/long-running-services.adoc +931 -0
  33. data/docs/assets/images/favicon-16.png +0 -0
  34. data/docs/assets/images/favicon-32.png +0 -0
  35. data/docs/assets/images/favicon-48.png +0 -0
  36. data/docs/assets/images/favicon.ico +0 -0
  37. data/docs/assets/images/favicon.png +0 -0
  38. data/docs/assets/images/favicon.svg +45 -0
  39. data/docs/assets/images/fractor-icon.svg +49 -0
  40. data/docs/assets/images/fractor-logo.svg +61 -0
  41. data/docs/index.adoc +131 -0
  42. data/docs/lychee.toml +39 -0
  43. data/examples/api_aggregator/README.adoc +627 -0
  44. data/examples/api_aggregator/api_aggregator.rb +376 -0
  45. data/examples/auto_detection/README.adoc +407 -29
  46. data/examples/continuous_chat_common/message_protocol.rb +1 -1
  47. data/examples/error_reporting.rb +207 -0
  48. data/examples/file_processor/README.adoc +170 -0
  49. data/examples/file_processor/file_processor.rb +615 -0
  50. data/examples/file_processor/sample_files/invalid.csv +1 -0
  51. data/examples/file_processor/sample_files/orders.xml +24 -0
  52. data/examples/file_processor/sample_files/products.json +23 -0
  53. data/examples/file_processor/sample_files/users.csv +6 -0
  54. data/examples/hierarchical_hasher/README.adoc +629 -41
  55. data/examples/image_processor/README.adoc +610 -0
  56. data/examples/image_processor/image_processor.rb +349 -0
  57. data/examples/image_processor/processed_images/sample_10_processed.jpg.json +12 -0
  58. data/examples/image_processor/processed_images/sample_1_processed.jpg.json +12 -0
  59. data/examples/image_processor/processed_images/sample_2_processed.jpg.json +12 -0
  60. data/examples/image_processor/processed_images/sample_3_processed.jpg.json +12 -0
  61. data/examples/image_processor/processed_images/sample_4_processed.jpg.json +12 -0
  62. data/examples/image_processor/processed_images/sample_5_processed.jpg.json +12 -0
  63. data/examples/image_processor/processed_images/sample_6_processed.jpg.json +12 -0
  64. data/examples/image_processor/processed_images/sample_7_processed.jpg.json +12 -0
  65. data/examples/image_processor/processed_images/sample_8_processed.jpg.json +12 -0
  66. data/examples/image_processor/processed_images/sample_9_processed.jpg.json +12 -0
  67. data/examples/image_processor/test_images/sample_1.png +1 -0
  68. data/examples/image_processor/test_images/sample_10.png +1 -0
  69. data/examples/image_processor/test_images/sample_2.png +1 -0
  70. data/examples/image_processor/test_images/sample_3.png +1 -0
  71. data/examples/image_processor/test_images/sample_4.png +1 -0
  72. data/examples/image_processor/test_images/sample_5.png +1 -0
  73. data/examples/image_processor/test_images/sample_6.png +1 -0
  74. data/examples/image_processor/test_images/sample_7.png +1 -0
  75. data/examples/image_processor/test_images/sample_8.png +1 -0
  76. data/examples/image_processor/test_images/sample_9.png +1 -0
  77. data/examples/log_analyzer/README.adoc +662 -0
  78. data/examples/log_analyzer/log_analyzer.rb +579 -0
  79. data/examples/log_analyzer/sample_logs/apache.log +20 -0
  80. data/examples/log_analyzer/sample_logs/json.log +15 -0
  81. data/examples/log_analyzer/sample_logs/nginx.log +15 -0
  82. data/examples/log_analyzer/sample_logs/rails.log +29 -0
  83. data/examples/multi_work_type/README.adoc +576 -26
  84. data/examples/performance_monitoring.rb +120 -0
  85. data/examples/pipeline_processing/README.adoc +740 -26
  86. data/examples/pipeline_processing/pipeline_processing.rb +2 -2
  87. data/examples/priority_work_example.rb +155 -0
  88. data/examples/producer_subscriber/README.adoc +889 -46
  89. data/examples/scatter_gather/README.adoc +829 -27
  90. data/examples/simple/README.adoc +347 -0
  91. data/examples/specialized_workers/README.adoc +622 -26
  92. data/examples/specialized_workers/specialized_workers.rb +44 -8
  93. data/examples/stream_processor/README.adoc +206 -0
  94. data/examples/stream_processor/stream_processor.rb +284 -0
  95. data/examples/web_scraper/README.adoc +625 -0
  96. data/examples/web_scraper/web_scraper.rb +285 -0
  97. data/examples/workflow/README.adoc +406 -0
  98. data/examples/workflow/circuit_breaker/README.adoc +360 -0
  99. data/examples/workflow/circuit_breaker/circuit_breaker_workflow.rb +225 -0
  100. data/examples/workflow/conditional/README.adoc +483 -0
  101. data/examples/workflow/conditional/conditional_workflow.rb +215 -0
  102. data/examples/workflow/dead_letter_queue/README.adoc +374 -0
  103. data/examples/workflow/dead_letter_queue/dead_letter_queue_workflow.rb +217 -0
  104. data/examples/workflow/fan_out/README.adoc +381 -0
  105. data/examples/workflow/fan_out/fan_out_workflow.rb +202 -0
  106. data/examples/workflow/retry/README.adoc +248 -0
  107. data/examples/workflow/retry/retry_workflow.rb +195 -0
  108. data/examples/workflow/simple_linear/README.adoc +267 -0
  109. data/examples/workflow/simple_linear/simple_linear_workflow.rb +175 -0
  110. data/examples/workflow/simplified/README.adoc +329 -0
  111. data/examples/workflow/simplified/simplified_workflow.rb +222 -0
  112. data/exe/fractor +10 -0
  113. data/lib/fractor/cli.rb +288 -0
  114. data/lib/fractor/configuration.rb +307 -0
  115. data/lib/fractor/continuous_server.rb +60 -65
  116. data/lib/fractor/error_formatter.rb +72 -0
  117. data/lib/fractor/error_report_generator.rb +152 -0
  118. data/lib/fractor/error_reporter.rb +244 -0
  119. data/lib/fractor/error_statistics.rb +147 -0
  120. data/lib/fractor/execution_tracer.rb +162 -0
  121. data/lib/fractor/logger.rb +230 -0
  122. data/lib/fractor/main_loop_handler.rb +406 -0
  123. data/lib/fractor/main_loop_handler3.rb +135 -0
  124. data/lib/fractor/main_loop_handler4.rb +299 -0
  125. data/lib/fractor/performance_metrics_collector.rb +181 -0
  126. data/lib/fractor/performance_monitor.rb +215 -0
  127. data/lib/fractor/performance_report_generator.rb +202 -0
  128. data/lib/fractor/priority_work.rb +93 -0
  129. data/lib/fractor/priority_work_queue.rb +189 -0
  130. data/lib/fractor/result_aggregator.rb +32 -0
  131. data/lib/fractor/shutdown_handler.rb +168 -0
  132. data/lib/fractor/signal_handler.rb +80 -0
  133. data/lib/fractor/supervisor.rb +382 -269
  134. data/lib/fractor/supervisor_logger.rb +88 -0
  135. data/lib/fractor/version.rb +1 -1
  136. data/lib/fractor/work.rb +12 -0
  137. data/lib/fractor/work_distribution_manager.rb +151 -0
  138. data/lib/fractor/work_queue.rb +20 -0
  139. data/lib/fractor/work_result.rb +181 -9
  140. data/lib/fractor/worker.rb +73 -0
  141. data/lib/fractor/workflow/builder.rb +210 -0
  142. data/lib/fractor/workflow/chain_builder.rb +169 -0
  143. data/lib/fractor/workflow/circuit_breaker.rb +183 -0
  144. data/lib/fractor/workflow/circuit_breaker_orchestrator.rb +208 -0
  145. data/lib/fractor/workflow/circuit_breaker_registry.rb +112 -0
  146. data/lib/fractor/workflow/dead_letter_queue.rb +334 -0
  147. data/lib/fractor/workflow/execution_hooks.rb +39 -0
  148. data/lib/fractor/workflow/execution_strategy.rb +225 -0
  149. data/lib/fractor/workflow/execution_trace.rb +134 -0
  150. data/lib/fractor/workflow/helpers.rb +191 -0
  151. data/lib/fractor/workflow/job.rb +290 -0
  152. data/lib/fractor/workflow/job_dependency_validator.rb +120 -0
  153. data/lib/fractor/workflow/logger.rb +110 -0
  154. data/lib/fractor/workflow/pre_execution_context.rb +193 -0
  155. data/lib/fractor/workflow/retry_config.rb +156 -0
  156. data/lib/fractor/workflow/retry_orchestrator.rb +184 -0
  157. data/lib/fractor/workflow/retry_strategy.rb +93 -0
  158. data/lib/fractor/workflow/structured_logger.rb +30 -0
  159. data/lib/fractor/workflow/type_compatibility_validator.rb +222 -0
  160. data/lib/fractor/workflow/visualizer.rb +211 -0
  161. data/lib/fractor/workflow/workflow_context.rb +132 -0
  162. data/lib/fractor/workflow/workflow_executor.rb +669 -0
  163. data/lib/fractor/workflow/workflow_result.rb +55 -0
  164. data/lib/fractor/workflow/workflow_validator.rb +295 -0
  165. data/lib/fractor/workflow.rb +333 -0
  166. data/lib/fractor/wrapped_ractor.rb +66 -101
  167. data/lib/fractor/wrapped_ractor3.rb +161 -0
  168. data/lib/fractor/wrapped_ractor4.rb +242 -0
  169. data/lib/fractor.rb +92 -4
  170. metadata +179 -6
  171. data/tests/sample.rb.bak +0 -309
  172. data/tests/sample_working.rb.bak +0 -209
@@ -0,0 +1,175 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../../lib/fractor"
4
+
5
+ # This example demonstrates a simple linear workflow with three sequential jobs.
6
+ # Each job processes data and passes it to the next job.
7
+
8
+ # ============================================
9
+ # DATA MODELS
10
+ # ============================================
11
+
12
+ module SimpleLinear
13
+ # Simple data models (in production, use Lutaml::Model::Serializable)
14
+ class TextData
15
+ attr_accessor :text
16
+
17
+ def initialize(text:)
18
+ @text = text
19
+ end
20
+ end
21
+
22
+ class UppercaseOutput
23
+ attr_accessor :uppercased_text, :char_count
24
+
25
+ def initialize(uppercased_text:, char_count:)
26
+ @uppercased_text = uppercased_text
27
+ @char_count = char_count
28
+ end
29
+ end
30
+
31
+ class ReversedOutput
32
+ attr_accessor :reversed_text, :word_count
33
+
34
+ def initialize(reversed_text:, word_count:)
35
+ @reversed_text = reversed_text
36
+ @word_count = word_count
37
+ end
38
+ end
39
+
40
+ class FinalOutput
41
+ attr_accessor :result, :total_operations
42
+
43
+ def initialize(result:, total_operations:)
44
+ @result = result
45
+ @total_operations = total_operations
46
+ end
47
+ end
48
+ end
49
+
50
+ # ============================================
51
+ # WORKERS
52
+ # ============================================
53
+
54
+ module SimpleLinearExample
55
+ class UppercaseWorker < Fractor::Worker
56
+ input_type SimpleLinear::TextData
57
+ output_type SimpleLinear::UppercaseOutput
58
+
59
+ def process(work)
60
+ input = work.input
61
+ uppercased = input.text.upcase
62
+
63
+ output = SimpleLinear::UppercaseOutput.new(
64
+ uppercased_text: uppercased,
65
+ char_count: uppercased.length,
66
+ )
67
+
68
+ Fractor::WorkResult.new(result: output, work: work)
69
+ end
70
+ end
71
+
72
+ class ReverseWorker < Fractor::Worker
73
+ input_type SimpleLinear::UppercaseOutput
74
+ output_type SimpleLinear::ReversedOutput
75
+
76
+ def process(work)
77
+ input = work.input
78
+ reversed = input.uppercased_text.reverse
79
+
80
+ output = SimpleLinear::ReversedOutput.new(
81
+ reversed_text: reversed,
82
+ word_count: reversed.split.size,
83
+ )
84
+
85
+ Fractor::WorkResult.new(result: output, work: work)
86
+ end
87
+ end
88
+
89
+ class FinalizeWorker < Fractor::Worker
90
+ input_type SimpleLinear::ReversedOutput
91
+ output_type SimpleLinear::FinalOutput
92
+
93
+ def process(work)
94
+ input = work.input
95
+
96
+ output = SimpleLinear::FinalOutput.new(
97
+ result: input.reversed_text,
98
+ total_operations: 3,
99
+ )
100
+
101
+ Fractor::WorkResult.new(result: output, work: work)
102
+ end
103
+ end
104
+ end
105
+
106
+ # ============================================
107
+ # WORKFLOW DEFINITION
108
+ # ============================================
109
+
110
+ class SimpleLinearWorkflow < Fractor::Workflow
111
+ workflow "simple-linear" do
112
+ # Define workflow input and output types
113
+ input_type SimpleLinear::TextData
114
+ output_type SimpleLinear::FinalOutput
115
+
116
+ # Define start and end points
117
+ start_with "uppercase"
118
+ end_with "finalize"
119
+
120
+ # Job 1: Uppercase the text
121
+ job "uppercase" do
122
+ runs_with SimpleLinearExample::UppercaseWorker
123
+ inputs_from_workflow
124
+ end
125
+
126
+ # Job 2: Reverse the uppercased text
127
+ job "reverse" do
128
+ needs "uppercase"
129
+ runs_with SimpleLinearExample::ReverseWorker
130
+ inputs_from_job "uppercase"
131
+ end
132
+
133
+ # Job 3: Finalize the result
134
+ job "finalize" do
135
+ needs "reverse"
136
+ runs_with SimpleLinearExample::FinalizeWorker
137
+ inputs_from_job "reverse"
138
+ outputs_to_workflow
139
+ terminates_workflow
140
+ end
141
+ end
142
+ end
143
+
144
+ # ============================================
145
+ # USAGE
146
+ # ============================================
147
+
148
+ if __FILE__ == $PROGRAM_NAME
149
+ puts "Simple Linear Workflow Example"
150
+ puts "=" * 50
151
+ puts
152
+
153
+ # Create input
154
+ input = SimpleLinear::TextData.new(text: "hello world from fractor")
155
+ puts "Input: #{input.text}"
156
+ puts
157
+
158
+ # Execute workflow
159
+ workflow = SimpleLinearWorkflow.new
160
+ result = workflow.execute(input: input)
161
+
162
+ # Display results
163
+ puts "Workflow Results:"
164
+ puts "-" * 50
165
+ puts "Status: #{result.success? ? 'SUCCESS' : 'FAILED'}"
166
+ puts "Execution Time: #{result.execution_time.round(3)}s"
167
+ puts "Completed Jobs: #{result.completed_jobs.join(', ')}"
168
+ puts
169
+ puts "Final Output:"
170
+ puts " Result: #{result.output.result}"
171
+ puts " Total Operations: #{result.output.total_operations}"
172
+ puts
173
+
174
+ # Expected output: "ROTCARF MORF DLROW OLLEH"
175
+ end
@@ -0,0 +1,329 @@
1
+ = Simplified Workflow Syntax
2
+
3
+ This example demonstrates the simplified workflow syntax that reduces boilerplate and makes workflow definition more intuitive.
4
+
5
+ == Overview
6
+
7
+ The simplified syntax provides three major improvements:
8
+
9
+ 1. **Smart Auto-Wiring**: Automatically infers input sources from dependencies
10
+ 2. **Smart Defaults**: Auto-detects start and end jobs
11
+ 3. **Shorthand Syntax**: Concise job definitions with keyword parameters
12
+
13
+ Additionally, two new APIs are provided:
14
+
15
+ 4. **Workflow.define**: Create workflows without inheritance
16
+ 5. **Chain API**: Fluent interface for linear workflows
17
+
18
+ == Running the Example
19
+
20
+ [source,shell]
21
+ ----
22
+ ruby examples/workflow/simplified/simplified_workflow.rb
23
+ ----
24
+
25
+ == Approach 1: Shorthand Syntax with Auto-Wiring
26
+
27
+ === General
28
+
29
+ The shorthand syntax allows defining jobs concisely while smart defaults handle boilerplate configuration.
30
+
31
+ === Benefits
32
+
33
+ * No need for `start_with` and `end_with` declarations
34
+ * No need for explicit `inputs_from_*` calls for single dependencies
35
+ * Automatic detection of workflow entry and exit points
36
+
37
+ === Usage
38
+
39
+ [source,ruby]
40
+ ----
41
+ class MyWorkflow < Fractor::Workflow
42
+ workflow "example" do
43
+ job "uppercase", UppercaseWorker
44
+ job "reverse", ReverseWorker, needs: "uppercase"
45
+ job "finalize", FinalizeWorker, needs: "reverse"
46
+ end
47
+ end
48
+ ----
49
+
50
+ Instead of the verbose form:
51
+
52
+ [source,ruby]
53
+ ----
54
+ class MyWorkflow < Fractor::Workflow
55
+ workflow "example" do
56
+ start_with "uppercase"
57
+ end_with "finalize"
58
+
59
+ job "uppercase" do
60
+ runs_with UppercaseWorker
61
+ inputs_from_workflow
62
+ end
63
+
64
+ job "reverse" do
65
+ needs "uppercase"
66
+ runs_with ReverseWorker
67
+ inputs_from_job "uppercase"
68
+ end
69
+
70
+ job "finalize" do
71
+ needs "reverse"
72
+ runs_with FinalizeWorker
73
+ inputs_from_job "reverse"
74
+ outputs_to_workflow
75
+ terminates_workflow
76
+ end
77
+ end
78
+ end
79
+ ----
80
+
81
+ == Approach 2: Workflow.define (No Inheritance)
82
+
83
+ === General
84
+
85
+ Create workflow classes without explicit inheritance, useful for dynamic workflow generation or storing workflows in variables.
86
+
87
+ === Benefits
88
+
89
+ * No need to create a class definition
90
+ * Can be stored in constants or variables
91
+ * Returns workflow class directly
92
+
93
+ === Usage
94
+
95
+ [source,ruby]
96
+ ----
97
+ MyWorkflow = Fractor::Workflow.define("example") do
98
+ job "uppercase", UppercaseWorker
99
+ job "reverse", ReverseWorker, needs: "uppercase"
100
+ job "finalize", FinalizeWorker, needs: "reverse"
101
+ end
102
+
103
+ # Use it the same way
104
+ workflow = MyWorkflow.new
105
+ result = workflow.execute(input: data)
106
+ ----
107
+
108
+ == Approach 3: Chain API (Fluent Interface)
109
+
110
+ === General
111
+
112
+ The Chain API provides a fluent interface specifically optimized for linear (sequential) workflows.
113
+
114
+ === Benefits
115
+
116
+ * Most concise syntax for linear workflows
117
+ * Chainable method calls
118
+ * No explicit dependency management needed
119
+ * Reads naturally: step → step → step
120
+
121
+ === Usage
122
+
123
+ [source,ruby]
124
+ ----
125
+ workflow_class = Fractor::Workflow.chain("pipeline")
126
+ .step("uppercase", UppercaseWorker)
127
+ .step("reverse", ReverseWorker)
128
+ .step("finalize", FinalizeWorker)
129
+ .build
130
+
131
+ workflow = workflow_class.new
132
+ result = workflow.execute(input: data)
133
+ ----
134
+
135
+ == Smart Auto-Wiring Rules
136
+
137
+ The auto-wiring system follows these rules:
138
+
139
+ === Single Dependency
140
+
141
+ Jobs with exactly one dependency automatically wire inputs from that dependency:
142
+
143
+ [source,ruby]
144
+ ----
145
+ job "process", ProcessWorker, needs: "validate"
146
+ # Automatically adds: inputs_from_job "validate"
147
+ ----
148
+
149
+ === No Dependencies (Start Jobs)
150
+
151
+ Jobs with no dependencies automatically wire inputs from the workflow:
152
+
153
+ [source,ruby]
154
+ ----
155
+ job "start", StartWorker
156
+ # Automatically adds: inputs_from_workflow
157
+ ----
158
+
159
+ === Multiple Dependencies
160
+
161
+ Jobs with multiple dependencies require explicit input configuration:
162
+
163
+ [source,ruby]
164
+ ----
165
+ job "merge", MergeWorker, needs: %w[process1 process2],
166
+ inputs: { "process1" => { data: :data1 }, "process2" => { data: :data2 } }
167
+ ----
168
+
169
+ == Smart Defaults
170
+
171
+ === Start Job Detection
172
+
173
+ When only one job has no dependencies, it is automatically set as the start job.
174
+
175
+ === End Job Detection
176
+
177
+ Jobs with no dependents (leaf nodes) are automatically marked as end jobs with:
178
+
179
+ * `outputs_to_workflow`
180
+ * `terminates_workflow`
181
+
182
+ === Multiple Start Jobs
183
+
184
+ When multiple jobs have no dependencies, you must explicitly specify `start_with`:
185
+
186
+ [source,ruby]
187
+ ----
188
+ workflow "multi-start" do
189
+ start_with "primary" # Required when ambiguous
190
+
191
+ job "primary", PrimaryWorker
192
+ job "secondary", SecondaryWorker
193
+ job "merge", MergeWorker, needs: %w[primary secondary]
194
+ end
195
+ ----
196
+
197
+ == Shorthand Job Syntax
198
+
199
+ === Worker Class Parameter
200
+
201
+ Pass the worker class as the second parameter:
202
+
203
+ [source,ruby]
204
+ ----
205
+ job "process", ProcessWorker
206
+ ----
207
+
208
+ === Keyword Parameters
209
+
210
+ All job configuration can be passed as keyword parameters:
211
+
212
+ [source,ruby]
213
+ ----
214
+ job "process", ProcessWorker,
215
+ needs: "validate", # Dependencies
216
+ inputs: :workflow, # Input source
217
+ outputs: :workflow, # Output destination
218
+ workers: 3, # Parallel workers
219
+ condition: ->(ctx) { ... } # Conditional execution
220
+ ----
221
+
222
+ === Mixing with DSL Block
223
+
224
+ Shorthand syntax can be combined with DSL blocks:
225
+
226
+ [source,ruby]
227
+ ----
228
+ job "process", ProcessWorker, needs: "validate" do
229
+ parallel_workers 5
230
+ # Additional DSL configuration
231
+ end
232
+ ----
233
+
234
+ == Comparison: Before vs After
235
+
236
+ === Linear Workflow (3 jobs)
237
+
238
+ ==== Before (Verbose - 20 lines)
239
+
240
+ [source,ruby]
241
+ ----
242
+ class Pipeline < Fractor::Workflow
243
+ workflow "pipeline" do
244
+ input_type InputData
245
+ output_type OutputData
246
+ start_with "step1"
247
+ end_with "step3"
248
+
249
+ job "step1" do
250
+ runs_with Worker1
251
+ inputs_from_workflow
252
+ end
253
+
254
+ job "step2" do
255
+ needs "step1"
256
+ runs_with Worker2
257
+ inputs_from_job "step1"
258
+ end
259
+
260
+ job "step3" do
261
+ needs "step2"
262
+ runs_with Worker3
263
+ inputs_from_job "step2"
264
+ outputs_to_workflow
265
+ terminates_workflow
266
+ end
267
+ end
268
+ end
269
+ ----
270
+
271
+ ==== After (Shorthand - 6 lines)
272
+
273
+ [source,ruby]
274
+ ----
275
+ class Pipeline < Fractor::Workflow
276
+ workflow "pipeline" do
277
+ job "step1", Worker1
278
+ job "step2", Worker2, needs: "step1"
279
+ job "step3", Worker3, needs: "step2"
280
+ end
281
+ end
282
+ ----
283
+
284
+ ==== After (Chain API - 5 lines)
285
+
286
+ [source,ruby]
287
+ ----
288
+ Pipeline = Fractor::Workflow.chain("pipeline")
289
+ .step("step1", Worker1)
290
+ .step("step2", Worker2)
291
+ .step("step3", Worker3)
292
+ .build
293
+ ----
294
+
295
+ == Backward Compatibility
296
+
297
+ All existing verbose syntax continues to work. The simplified syntax is purely additive:
298
+
299
+ * Explicit `start_with` and `end_with` override auto-detection
300
+ * Explicit `inputs_from_*` overrides auto-wiring
301
+ * DSL blocks still work as before
302
+ * All validation rules remain the same
303
+
304
+ == When to Use Each Approach
305
+
306
+ === Use Shorthand Syntax When:
307
+
308
+ * You have simple linear or branching workflows
309
+ * Dependencies are straightforward
310
+ * You want to keep class-based definition
311
+
312
+ === Use Workflow.define When:
313
+
314
+ * You need to generate workflows dynamically
315
+ * You want to store workflows in variables
316
+ * You prefer functional style over inheritance
317
+
318
+ === Use Chain API When:
319
+
320
+ * You have strictly linear workflows
321
+ * You want maximum conciseness
322
+ * Dependencies are purely sequential
323
+
324
+ === Use Verbose Syntax When:
325
+
326
+ * You have complex input mappings
327
+ * You need fine-grained control
328
+ * Multiple jobs depend on multiple sources
329
+ * Documentation/clarity is more important than conciseness
@@ -0,0 +1,222 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../../lib/fractor"
4
+
5
+ # This example demonstrates the simplified workflow syntax options:
6
+ # 1. Shorthand syntax with auto-wiring
7
+ # 2. Workflow.define (no inheritance)
8
+ # 3. Chain API for linear workflows
9
+
10
+ # ============================================
11
+ # DATA MODELS
12
+ # ============================================
13
+
14
+ class TextData
15
+ attr_accessor :text
16
+
17
+ def initialize(text:)
18
+ @text = text
19
+ end
20
+ end
21
+
22
+ class UppercaseOutput
23
+ attr_accessor :uppercased_text
24
+
25
+ def initialize(uppercased_text:)
26
+ @uppercased_text = uppercased_text
27
+ end
28
+ end
29
+
30
+ class ReversedOutput
31
+ attr_accessor :reversed_text
32
+
33
+ def initialize(reversed_text:)
34
+ @reversed_text = reversed_text
35
+ end
36
+ end
37
+
38
+ class FinalOutput
39
+ attr_accessor :result
40
+
41
+ def initialize(result:)
42
+ @result = result
43
+ end
44
+ end
45
+
46
+ # ============================================
47
+ # WORKERS
48
+ # ============================================
49
+
50
+ module SimplifiedExample
51
+ class UppercaseWorker < Fractor::Worker
52
+ input_type TextData
53
+ output_type UppercaseOutput
54
+
55
+ def process(work)
56
+ input = work.input
57
+ output = UppercaseOutput.new(uppercased_text: input.text.upcase)
58
+ Fractor::WorkResult.new(result: output, work: work)
59
+ end
60
+ end
61
+
62
+ class ReverseWorker < Fractor::Worker
63
+ input_type UppercaseOutput
64
+ output_type ReversedOutput
65
+
66
+ def process(work)
67
+ input = work.input
68
+ output = ReversedOutput.new(reversed_text: input.uppercased_text.reverse)
69
+ Fractor::WorkResult.new(result: output, work: work)
70
+ end
71
+ end
72
+
73
+ class FinalizeWorker < Fractor::Worker
74
+ input_type ReversedOutput
75
+ output_type FinalOutput
76
+
77
+ def process(work)
78
+ input = work.input
79
+ output = FinalOutput.new(result: input.reversed_text)
80
+ Fractor::WorkResult.new(result: output, work: work)
81
+ end
82
+ end
83
+ end
84
+
85
+ # ============================================
86
+ # APPROACH 1: SHORTHAND SYNTAX WITH AUTO-WIRING
87
+ # ============================================
88
+ # Benefits:
89
+ # - Reduced boilerplate
90
+ # - Auto-infers inputs from dependencies
91
+ # - Auto-detects start/end jobs
92
+ # - Still uses class inheritance
93
+
94
+ class ShorthandWorkflow < Fractor::Workflow
95
+ workflow "shorthand-example" do
96
+ # No need for start_with/end_with - auto-detected!
97
+ # No need for inputs_from_* - auto-wired from dependencies!
98
+
99
+ job "uppercase", SimplifiedExample::UppercaseWorker
100
+ job "reverse", SimplifiedExample::ReverseWorker, needs: "uppercase"
101
+ job "finalize", SimplifiedExample::FinalizeWorker, needs: "reverse"
102
+ end
103
+ end
104
+
105
+ # ============================================
106
+ # APPROACH 2: WORKFLOW.DEFINE (NO INHERITANCE)
107
+ # ============================================
108
+ # Benefits:
109
+ # - No need to create a class
110
+ # - Returns workflow class directly
111
+ # - Can be stored in variables
112
+
113
+ SimplifiedWorkflow = Fractor::Workflow.define("simplified-example") do
114
+ job "uppercase", SimplifiedExample::UppercaseWorker
115
+ job "reverse", SimplifiedExample::ReverseWorker, needs: "uppercase"
116
+ job "finalize", SimplifiedExample::FinalizeWorker, needs: "reverse"
117
+ end
118
+
119
+ # ============================================
120
+ # APPROACH 3: CHAIN API (FLUENT)
121
+ # ============================================
122
+ # Benefits:
123
+ # - Most concise for linear workflows
124
+ # - Fluent/chainable API
125
+ # - No explicit dependency management needed
126
+
127
+ ChainWorkflow = Fractor::Workflow.chain("chain-example")
128
+ .step("uppercase", SimplifiedExample::UppercaseWorker)
129
+ .step("reverse", SimplifiedExample::ReverseWorker)
130
+ .step("finalize", SimplifiedExample::FinalizeWorker)
131
+ .build
132
+
133
+ # ============================================
134
+ # COMPARISON: BEFORE VS AFTER
135
+ # ============================================
136
+
137
+ # BEFORE (Verbose - from simple_linear example):
138
+ # class SimpleLinearWorkflow < Fractor::Workflow
139
+ # workflow "simple-linear" do
140
+ # input_type TextData
141
+ # output_type FinalOutput
142
+ # start_with "uppercase"
143
+ # end_with "finalize"
144
+ #
145
+ # job "uppercase" do
146
+ # runs_with SimpleLinearExample::UppercaseWorker
147
+ # inputs_from_workflow
148
+ # end
149
+ #
150
+ # job "reverse" do
151
+ # needs "uppercase"
152
+ # runs_with SimpleLinearExample::ReverseWorker
153
+ # inputs_from_job "uppercase"
154
+ # end
155
+ #
156
+ # job "finalize" do
157
+ # needs "reverse"
158
+ # runs_with SimpleLinearExample::FinalizeWorker
159
+ # inputs_from_job "reverse"
160
+ # outputs_to_workflow
161
+ # terminates_workflow
162
+ # end
163
+ # end
164
+ # end
165
+
166
+ # AFTER (Shorthand):
167
+ # class ShorthandWorkflow < Fractor::Workflow
168
+ # workflow "shorthand-example" do
169
+ # job "uppercase", SimplifiedExample::UppercaseWorker
170
+ # job "reverse", SimplifiedExample::ReverseWorker, needs: "uppercase"
171
+ # job "finalize", SimplifiedExample::FinalizeWorker, needs: "reverse"
172
+ # end
173
+ # end
174
+
175
+ # AFTER (Workflow.define):
176
+ # SimplifiedWorkflow = Fractor::Workflow.define("simplified-example") do
177
+ # job "uppercase", SimplifiedExample::UppercaseWorker
178
+ # job "reverse", SimplifiedExample::ReverseWorker, needs: "uppercase"
179
+ # job "finalize", SimplifiedExample::FinalizeWorker, needs: "reverse"
180
+ # end
181
+
182
+ # AFTER (Chain API):
183
+ # ChainWorkflow = Fractor::Workflow.chain("chain-example")
184
+ # .step("uppercase", SimplifiedExample::UppercaseWorker)
185
+ # .step("reverse", SimplifiedExample::ReverseWorker)
186
+ # .step("finalize", SimplifiedExample::FinalizeWorker)
187
+ # .build
188
+
189
+ # ============================================
190
+ # USAGE
191
+ # ============================================
192
+
193
+ if __FILE__ == $PROGRAM_NAME
194
+ puts "Simplified Workflow Syntax Examples"
195
+ puts "=" * 60
196
+ puts
197
+
198
+ input = TextData.new(text: "hello world")
199
+
200
+ # Test all three approaches
201
+ [
202
+ ["Shorthand Syntax", ShorthandWorkflow],
203
+ ["Workflow.define", SimplifiedWorkflow],
204
+ ["Chain API", ChainWorkflow],
205
+ ].each do |name, workflow_class|
206
+ puts "#{name}:"
207
+ puts "-" * 60
208
+
209
+ workflow = workflow_class.new
210
+ result = workflow.execute(input: input)
211
+
212
+ puts "Status: #{result.success? ? 'SUCCESS' : 'FAILED'}"
213
+ puts "Result: #{result.output.result}"
214
+ puts "Jobs: #{result.completed_jobs.join(' → ')}"
215
+ puts
216
+ end
217
+
218
+ # Show visualization
219
+ puts "Workflow Diagram (ASCII):"
220
+ puts "-" * 60
221
+ ShorthandWorkflow.print_diagram
222
+ end