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
@@ -1,45 +1,595 @@
1
1
  = Multi-Work Type Example
2
2
 
3
- == Overview
3
+ == Purpose
4
4
 
5
- This example demonstrates how to handle multiple types of work items within a single Fractor supervisor. It shows how a single worker can process different work types intelligently, applying different strategies based on the work's type.
5
+ Demonstrates how to handle multiple types of work items within a single Fractor supervisor, showcasing polymorphic processing where a single worker can intelligently process different work types with type-specific strategies.
6
6
 
7
- == Key Concepts
7
+ == Focus
8
8
 
9
- * *Multiple Work Types*: Supporting different work classes within the same system
10
- * *Polymorphic Processing*: Workers that adapt their processing based on work type
11
- * *Type Detection*: Identifying and handling different work types appropriately
12
- * *Unified Workflow*: Managing diverse work through a common supervisor
9
+ This example focuses on demonstrating:
13
10
 
14
- == Example Explanation
11
+ * Multiple work type classes inheriting from `Fractor::Work`
12
+ * Polymorphic processing in workers using type detection
13
+ * Type-based processing strategies within a single worker
14
+ * Unified workflow managing heterogeneous work items
15
+ * Result classification and organization by work type
16
+ * Type-specific error handling
15
17
 
16
- This example implements a system that processes two distinct work types:
18
+ == Architecture
17
19
 
18
- 1. *TextWork*: Handles text in various formats (plain text, Markdown, HTML, JSON)
19
- 2. *ImageWork*: Processes image data with different dimensions and formats
20
+ .Multi-Work Type Processing Flow
21
+ [source]
22
+ ----
23
+ Work Items (Mixed Types)
24
+
25
+ ├─→ TextWork (Markdown)
26
+ ├─→ TextWork (HTML)
27
+ ├─→ TextWork (JSON)
28
+ ├─→ ImageWork (JPEG)
29
+ ├─→ ImageWork (PNG)
30
+ └─→ ImageWork (GIF)
31
+
32
+
33
+ Work Queue
34
+
35
+ ├──────────────────┬──────────────────┐
36
+ │ │ │
37
+ ▼ ▼ ▼
38
+ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐
39
+ │MultiFormat │ │MultiFormat │ │MultiFormat │
40
+ │Worker │ │Worker │ │Worker │
41
+ │(Ractor 1) │ │(Ractor 2) │ │(Ractor 3) │
42
+ └──────────────┘ └──────────────┘ └──────────────┘
43
+ │ │ │
44
+ │ Type Detection & Processing │
45
+ │ │ │
46
+ ├─→ if TextWork ├─→ if ImageWork ├─→ if TextWork
47
+ │ process_text │ process_image │ process_text
48
+ │ │ │
49
+ ▼ ▼ ▼
50
+ Results (Typed)
51
+
52
+ ├─→ Text Results (format, word_count, char_count)
53
+ ├─→ Image Results (dimensions, filters, compression)
54
+ └─→ Errors (unsupported types)
55
+ ----
56
+
57
+ .Worker Type Detection Logic
58
+ [source]
59
+ ----
60
+ MultiFormatWorker.process(work)
61
+
62
+ ├─→ Is work a TextWork?
63
+ │ │
64
+ │ ├─→ Yes → process_text(work)
65
+ │ │ │
66
+ │ │ ├─→ format == :markdown?
67
+ │ │ │ └─→ process_markdown()
68
+ │ │ │
69
+ │ │ ├─→ format == :html?
70
+ │ │ │ └─→ process_html()
71
+ │ │ │
72
+ │ │ ├─→ format == :json?
73
+ │ │ │ └─→ process_json()
74
+ │ │ │
75
+ │ │ └─→ else (plain text)
76
+ │ │ └─→ upcase()
77
+ │ │
78
+ │ └─→ No → Is work an ImageWork?
79
+ │ │
80
+ │ ├─→ Yes → process_image(work)
81
+ │ │ │
82
+ │ │ └─→ Apply filters
83
+ │ │ (sharpen, contrast)
84
+ │ │
85
+ │ └─→ No → Return TypeError
86
+ │ (unsupported work type)
87
+
88
+ Return WorkResult
89
+ ----
90
+
91
+ == Key Components
92
+
93
+ === Work Type Classes
94
+
95
+ Define distinct work types for different content:
96
+
97
+ [source,ruby]
98
+ ----
99
+ class TextWork < Fractor::Work
100
+ def initialize(data, format = :plain, options = {})
101
+ super({ data: data, format: format, options: options }) # <1>
102
+ end
103
+
104
+ def data
105
+ input[:data] # <2>
106
+ end
107
+
108
+ def format
109
+ input[:format]
110
+ end
111
+
112
+ def to_s
113
+ "TextWork: format=#{format}, data=#{data[0..30]}..."
114
+ end
115
+ end
116
+
117
+ class ImageWork < Fractor::Work
118
+ def initialize(data, dimensions = [0, 0], format = :png)
119
+ super({ data: data, dimensions: dimensions, format: format })
120
+ end
121
+
122
+ def data
123
+ input[:data]
124
+ end
125
+
126
+ def dimensions
127
+ input[:dimensions]
128
+ end
129
+
130
+ def format
131
+ input[:format]
132
+ end
133
+ end
134
+ ----
135
+ <1> Store type-specific data in input hash
136
+ <2> Provide accessor methods for each work type
137
+
138
+ === Polymorphic Worker
139
+
140
+ Single worker handles multiple work types:
141
+
142
+ [source,ruby]
143
+ ----
144
+ class MultiFormatWorker < Fractor::Worker
145
+ def process(work)
146
+ # Type detection and routing
147
+ if work.is_a?(TextWork) # <1>
148
+ process_text(work) # <2>
149
+ elsif work.is_a?(ImageWork)
150
+ process_image(work)
151
+ else
152
+ # Handle unsupported types
153
+ error = TypeError.new("Unsupported work type: #{work.class}")
154
+ Fractor::WorkResult.new(error: error, work: work) # <3>
155
+ end
156
+ end
157
+
158
+ private
159
+
160
+ def process_text(work) # <4>
161
+ processed = case work.format
162
+ when :markdown then process_markdown(work.data)
163
+ when :html then process_html(work.data)
164
+ when :json then process_json(work.data)
165
+ else work.data.upcase
166
+ end
167
+
168
+ Fractor::WorkResult.new(
169
+ result: {
170
+ work_type: :text,
171
+ original_format: work.format,
172
+ transformed_data: processed,
173
+ metadata: {
174
+ word_count: processed.split(/\s+/).size,
175
+ char_count: processed.length
176
+ }
177
+ },
178
+ work: work
179
+ )
180
+ end
181
+
182
+ def process_image(work) # <5>
183
+ Fractor::WorkResult.new(
184
+ result: {
185
+ work_type: :image,
186
+ dimensions: work.dimensions,
187
+ format: work.format,
188
+ applied_filters: [:sharpen, :contrast],
189
+ processing_metadata: {
190
+ original_size: work.data.size,
191
+ processed_size: (work.data.size * 0.8).to_i
192
+ }
193
+ },
194
+ work: work
195
+ )
196
+ end
197
+ end
198
+ ----
199
+ <1> Use `is_a?` to detect work type
200
+ <2> Route to type-specific processing method
201
+ <3> Return error for unsupported types
202
+ <4> Text-specific processing with format handling
203
+ <5> Image-specific processing with filter application
204
+
205
+ === Mixed Content Processing
206
+
207
+ Process heterogeneous work items together:
208
+
209
+ [source,ruby]
210
+ ----
211
+ # Create supervisor with single worker pool
212
+ supervisor = Fractor::Supervisor.new(
213
+ worker_pools: [
214
+ { worker_class: MultiFormatWorker, num_workers: 4 } # <1>
215
+ ]
216
+ )
20
217
 
21
- A single worker type (`MultiFormatWorker`) is capable of handling both work types, adapting its processing strategies based on the work's class.
218
+ # Add mixed work types
219
+ text_works = text_items.map do |item|
220
+ TextWork.new(item[:data], item[:format])
221
+ end
222
+ supervisor.add_work_items(text_works) # <2>
22
223
 
23
- == Features Demonstrated
224
+ image_works = image_items.map do |item|
225
+ ImageWork.new(item[:data], item[:dimensions], item[:format])
226
+ end
227
+ supervisor.add_work_items(image_works) # <3>
24
228
 
25
- * Creating and using multiple work type classes
26
- * Designing workers that can handle diverse work types
27
- * Type-based processing logic
28
- * Proper error handling across different work types
29
- * Classification and reporting of heterogeneous results
229
+ # Process all work together
230
+ supervisor.run # <4>
30
231
 
31
- == Running the Example
232
+ # Classify results by type
233
+ results.results.each do |result|
234
+ if result.work.is_a?(TextWork)
235
+ text_results << result
236
+ elsif result.work.is_a?(ImageWork)
237
+ image_results << result
238
+ end
239
+ end
240
+ ----
241
+ <1> Single worker pool handles all types
242
+ <2> Add text work items
243
+ <3> Add image work items
244
+ <4> Process all work in parallel
245
+
246
+ == Usage
32
247
 
33
- [source,sh]
248
+ Run the example from the project root:
249
+
250
+ [source,shell]
34
251
  ----
35
252
  ruby examples/multi_work_type/multi_work_type.rb
36
253
  ----
37
254
 
38
255
  == Expected Output
39
256
 
40
- The example will show:
41
- * Processing of multiple work types
42
- * Different processing strategies applied to each type
43
- * Type-specific result formats
44
- * Performance statistics for each work type
45
- * Aggregated results organized by type
257
+ [example]
258
+ ====
259
+ [source]
260
+ ----
261
+ Starting Multi-Work Type Processing Example
262
+ ==========================================
263
+ This example demonstrates processing different types of work items:
264
+ 1. Text documents in various formats (plain, markdown, HTML, JSON)
265
+ 2. Image data with different formats and dimensions
266
+ Both are processed by the same worker but with different strategies
267
+
268
+ Processing with 4 workers...
269
+
270
+ Processed 4 text items and 3 image items
271
+ Encountered 0 errors
272
+ Processing Results:
273
+ -----------------
274
+ Total items: 7
275
+ Processed text items: 4
276
+ Processed image items: 3
277
+ Errors: 0
278
+
279
+ Text Processing Results:
280
+ Text Item 1 (plain):
281
+ THIS IS A PLAIN TEXT DOCUMENT. IT HAS NO SPECIAL FORMATTING.
282
+ Word count: 10
283
+ Character count: 59
284
+
285
+ Text Item 2 (markdown):
286
+ Processed Markdown: 87 chars, 1 headers, 1 links
287
+ Word count: 18
288
+ Character count: 182
289
+
290
+ Text Item 3 (html):
291
+ Processed HTML: 74 chars, 4 tags
292
+ Word count: 22
293
+ Character count: 126
294
+
295
+ Text Item 4 (json):
296
+ Processed JSON: 3 top-level keys
297
+ Word count: 11
298
+ Character count: 89
299
+
300
+ Image Processing Results:
301
+ Image Item 1 (jpeg):
302
+ Dimensions: 800x600
303
+ Applied filters: sharpen, contrast
304
+ Compression: 20%
305
+
306
+ Image Item 2 (png):
307
+ Dimensions: 1024x768
308
+ Applied filters: sharpen, contrast
309
+ Compression: 20%
310
+
311
+ Image Item 3 (gif):
312
+ Dimensions: 320x240
313
+ Applied filters: sharpen, contrast
314
+ Compression: 20%
315
+
316
+ Processing completed in 0.24 seconds
317
+ ----
318
+ ====
319
+
320
+ == Learning Points
321
+
322
+ === Polymorphic Processing
323
+
324
+ Single worker processes multiple types:
325
+
326
+ * **Type Detection**: Use `is_a?` to identify work type
327
+ * **Routing**: Direct to type-specific processing methods
328
+ * **Encapsulation**: Keep type-specific logic in private methods
329
+ * **Error Handling**: Return error for unsupported types
330
+
331
+ Benefits:
332
+ - Simpler supervisor configuration (one worker pool)
333
+ - Code reuse for common processing logic
334
+ - Flexible work type combinations
335
+ - Easier maintenance
336
+
337
+ === Type-Specific Strategies
338
+
339
+ Different processing for each work type:
340
+
341
+ [source,ruby]
342
+ ----
343
+ def process_text(work)
344
+ case work.format
345
+ when :markdown
346
+ # Parse headers, links, emphasis
347
+ process_markdown(work.data)
348
+ when :html
349
+ # Extract tags, process DOM
350
+ process_html(work.data)
351
+ when :json
352
+ # Parse JSON, validate structure
353
+ process_json(work.data)
354
+ else
355
+ # Default text processing
356
+ work.data.upcase
357
+ end
358
+ end
359
+
360
+ def process_image(work)
361
+ # Apply image-specific operations
362
+ filters = [:sharpen, :contrast]
363
+ apply_filters(work.data, filters)
364
+ end
365
+ ----
366
+
367
+ === Work Type Design
368
+
369
+ Best practices for creating work types:
370
+
371
+ [source,ruby]
372
+ ----
373
+ # Good: Clear, focused work types
374
+ class TextWork < Fractor::Work
375
+ def initialize(data, format, options = {})
376
+ super({ data: data, format: format, options: options })
377
+ end
378
+
379
+ # Provide accessors for all attributes
380
+ def data; input[:data]; end
381
+ def format; input[:format]; end
382
+ def options; input[:options]; end
383
+
384
+ # Helpful string representation
385
+ def to_s
386
+ "TextWork: format=#{format}"
387
+ end
388
+ end
389
+
390
+ # Avoid: Overly generic work types
391
+ class GenericWork < Fractor::Work # Too generic
392
+ def initialize(data, type, metadata)
393
+ super({ data: data, type: type, metadata: metadata })
394
+ end
395
+ end
396
+ ----
397
+
398
+ === Result Classification
399
+
400
+ Organize results by work type:
401
+
402
+ [source,ruby]
403
+ ----
404
+ results = {
405
+ text: [],
406
+ image: [],
407
+ errors: []
408
+ }
409
+
410
+ supervisor.results.results.each do |result|
411
+ case result.work
412
+ when TextWork
413
+ results[:text] << result.result
414
+ when ImageWork
415
+ results[:image] << result.result
416
+ end
417
+ end
418
+
419
+ supervisor.results.errors.each do |error_result|
420
+ results[:errors] << {
421
+ error: error_result.error,
422
+ work_type: error_result.work.class.name
423
+ }
424
+ end
425
+ ----
426
+
427
+ === Alternative Patterns
428
+
429
+ ==== Specialized Workers (Alternative Approach)
430
+
431
+ Instead of one polymorphic worker, use specialized workers:
432
+
433
+ [source,ruby]
434
+ ----
435
+ # Separate workers for each type
436
+ class TextWorker < Fractor::Worker
437
+ def process(work)
438
+ return error_result unless work.is_a?(TextWork)
439
+ # Process text only
440
+ end
441
+ end
442
+
443
+ class ImageWorker < Fractor::Worker
444
+ def process(work)
445
+ return error_result unless work.is_a?(ImageWork)
446
+ # Process images only
447
+ end
448
+ end
449
+
450
+ # Multiple worker pools
451
+ supervisor = Fractor::Supervisor.new(
452
+ worker_pools: [
453
+ { worker_class: TextWorker, num_workers: 4 },
454
+ { worker_class: ImageWorker, num_workers: 2 }
455
+ ]
456
+ )
457
+ ----
458
+
459
+ Benefits:
460
+ - Clearer separation of concerns
461
+ - Independent scaling for each type
462
+ - Easier to test individual workers
463
+
464
+ Trade-offs:
465
+ - More complex supervisor configuration
466
+ - Less flexible for ad-hoc type combinations
467
+
468
+ ==== Type Registry Pattern
469
+
470
+ For many work types, use a registry:
471
+
472
+ [source,ruby]
473
+ ----
474
+ class MultiTypeWorker < Fractor::Worker
475
+ PROCESSORS = {
476
+ TextWork => :process_text,
477
+ ImageWork => :process_image,
478
+ VideoWork => :process_video
479
+ }
480
+
481
+ def process(work)
482
+ processor = PROCESSORS[work.class]
483
+
484
+ if processor
485
+ send(processor, work)
486
+ else
487
+ unsupported_type_error(work)
488
+ end
489
+ end
490
+ end
491
+ ----
492
+
493
+ == Use Cases
494
+
495
+ === Content Management System
496
+
497
+ Process different content types:
498
+
499
+ [source,ruby]
500
+ ----
501
+ # Articles, images, videos in one workflow
502
+ supervisor = Fractor::Supervisor.new(
503
+ worker_pools: [
504
+ { worker_class: ContentProcessor }
505
+ ]
506
+ )
507
+
508
+ supervisor.add_work_items([
509
+ ArticleWork.new(content: "...", format: :markdown),
510
+ ImageWork.new(data: "...", dimensions: [1200, 800]),
511
+ VideoWork.new(data: "...", duration: 120)
512
+ ])
513
+ ----
514
+
515
+ === Data Pipeline
516
+
517
+ ETL with multiple data formats:
518
+
519
+ [source,ruby]
520
+ ----
521
+ # CSV, JSON, XML from different sources
522
+ supervisor = Fractor::Supervisor.new(
523
+ worker_pools: [
524
+ { worker_class: DataExtractor }
525
+ ]
526
+ )
527
+
528
+ supervisor.add_work_items([
529
+ CsvWork.new(file: "data.csv"),
530
+ JsonWork.new(file: "data.json"),
531
+ XmlWork.new(file: "data.xml")
532
+ ])
533
+ ----
534
+
535
+ === Batch Processing
536
+
537
+ Mixed batch operations:
538
+
539
+ [source,ruby]
540
+ ----
541
+ # Emails, notifications, reports
542
+ supervisor = Fractor::Supervisor.new(
543
+ worker_pools: [
544
+ { worker_class: BatchProcessor }
545
+ ]
546
+ )
547
+
548
+ supervisor.add_work_items([
549
+ EmailWork.new(to: "user@example.com", template: :welcome),
550
+ SmsWork.new(to: "+1234567890", message: "..."),
551
+ ReportWork.new(type: :monthly, period: "2024-01")
552
+ ])
553
+ ----
554
+
555
+ == Performance Considerations
556
+
557
+ === Type Detection Overhead
558
+
559
+ Type checking has minimal overhead:
560
+
561
+ [source,ruby]
562
+ ----
563
+ # Fast: Direct class check
564
+ if work.is_a?(TextWork)
565
+ process_text(work)
566
+ end
567
+
568
+ # Slower: String comparison (avoid)
569
+ if work.class.name == "TextWork"
570
+ process_text(work)
571
+ end
572
+ ----
573
+
574
+ === Worker Pool Sizing
575
+
576
+ Balance workers based on work type distribution:
577
+
578
+ [source,ruby]
579
+ ----
580
+ # If 80% text, 20% images
581
+ supervisor = Fractor::Supervisor.new(
582
+ worker_pools: [
583
+ { worker_class: TextWorker, num_workers: 8 },
584
+ { worker_class: ImageWorker, num_workers: 2 }
585
+ ]
586
+ )
587
+ ----
588
+
589
+ == Next Steps
590
+
591
+ After understanding multi-work type processing, explore:
592
+
593
+ * link:../specialized_workers/README.adoc[Specialized Workers] - Separate worker pools for different work types
594
+ * link:../simple/README.adoc[Simple Example] - Basic Fractor concepts
595
+ * link:../pipeline_processing/README.adoc[Pipeline Processing] - Sequential multi-stage processing