fractor 0.1.6 → 0.1.8
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 +4 -4
- data/.rubocop_todo.yml +227 -102
- data/README.adoc +113 -1940
- data/docs/.lycheeignore +16 -0
- data/docs/Gemfile +24 -0
- data/docs/README.md +157 -0
- data/docs/_config.yml +151 -0
- data/docs/_features/error-handling.adoc +1192 -0
- data/docs/_features/index.adoc +80 -0
- data/docs/_features/monitoring.adoc +589 -0
- data/docs/_features/signal-handling.adoc +202 -0
- data/docs/_features/workflows.adoc +1235 -0
- data/docs/_guides/continuous-mode.adoc +736 -0
- data/docs/_guides/cookbook.adoc +1133 -0
- data/docs/_guides/index.adoc +55 -0
- data/docs/_guides/pipeline-mode.adoc +730 -0
- data/docs/_guides/troubleshooting.adoc +358 -0
- data/docs/_pages/architecture.adoc +1390 -0
- data/docs/_pages/core-concepts.adoc +1392 -0
- data/docs/_pages/design-principles.adoc +862 -0
- data/docs/_pages/getting-started.adoc +290 -0
- data/docs/_pages/installation.adoc +143 -0
- data/docs/_reference/api.adoc +1080 -0
- data/docs/_reference/error-reporting.adoc +670 -0
- data/docs/_reference/examples.adoc +181 -0
- data/docs/_reference/index.adoc +96 -0
- data/docs/_reference/troubleshooting.adoc +862 -0
- data/docs/_tutorials/complex-workflows.adoc +1022 -0
- data/docs/_tutorials/data-processing-pipeline.adoc +740 -0
- data/docs/_tutorials/first-application.adoc +384 -0
- data/docs/_tutorials/index.adoc +48 -0
- data/docs/_tutorials/long-running-services.adoc +931 -0
- data/docs/assets/images/favicon-16.png +0 -0
- data/docs/assets/images/favicon-32.png +0 -0
- data/docs/assets/images/favicon-48.png +0 -0
- data/docs/assets/images/favicon.ico +0 -0
- data/docs/assets/images/favicon.png +0 -0
- data/docs/assets/images/favicon.svg +45 -0
- data/docs/assets/images/fractor-icon.svg +49 -0
- data/docs/assets/images/fractor-logo.svg +61 -0
- data/docs/index.adoc +131 -0
- data/docs/lychee.toml +39 -0
- data/examples/api_aggregator/README.adoc +627 -0
- data/examples/api_aggregator/api_aggregator.rb +376 -0
- data/examples/auto_detection/README.adoc +407 -29
- data/examples/continuous_chat_common/message_protocol.rb +1 -1
- data/examples/error_reporting.rb +207 -0
- data/examples/file_processor/README.adoc +170 -0
- data/examples/file_processor/file_processor.rb +615 -0
- data/examples/file_processor/sample_files/invalid.csv +1 -0
- data/examples/file_processor/sample_files/orders.xml +24 -0
- data/examples/file_processor/sample_files/products.json +23 -0
- data/examples/file_processor/sample_files/users.csv +6 -0
- data/examples/hierarchical_hasher/README.adoc +629 -41
- data/examples/image_processor/README.adoc +610 -0
- data/examples/image_processor/image_processor.rb +349 -0
- data/examples/image_processor/processed_images/sample_10_processed.jpg.json +12 -0
- data/examples/image_processor/processed_images/sample_1_processed.jpg.json +12 -0
- data/examples/image_processor/processed_images/sample_2_processed.jpg.json +12 -0
- data/examples/image_processor/processed_images/sample_3_processed.jpg.json +12 -0
- data/examples/image_processor/processed_images/sample_4_processed.jpg.json +12 -0
- data/examples/image_processor/processed_images/sample_5_processed.jpg.json +12 -0
- data/examples/image_processor/processed_images/sample_6_processed.jpg.json +12 -0
- data/examples/image_processor/processed_images/sample_7_processed.jpg.json +12 -0
- data/examples/image_processor/processed_images/sample_8_processed.jpg.json +12 -0
- data/examples/image_processor/processed_images/sample_9_processed.jpg.json +12 -0
- data/examples/image_processor/test_images/sample_1.png +1 -0
- data/examples/image_processor/test_images/sample_10.png +1 -0
- data/examples/image_processor/test_images/sample_2.png +1 -0
- data/examples/image_processor/test_images/sample_3.png +1 -0
- data/examples/image_processor/test_images/sample_4.png +1 -0
- data/examples/image_processor/test_images/sample_5.png +1 -0
- data/examples/image_processor/test_images/sample_6.png +1 -0
- data/examples/image_processor/test_images/sample_7.png +1 -0
- data/examples/image_processor/test_images/sample_8.png +1 -0
- data/examples/image_processor/test_images/sample_9.png +1 -0
- data/examples/log_analyzer/README.adoc +662 -0
- data/examples/log_analyzer/log_analyzer.rb +579 -0
- data/examples/log_analyzer/sample_logs/apache.log +20 -0
- data/examples/log_analyzer/sample_logs/json.log +15 -0
- data/examples/log_analyzer/sample_logs/nginx.log +15 -0
- data/examples/log_analyzer/sample_logs/rails.log +29 -0
- data/examples/multi_work_type/README.adoc +576 -26
- data/examples/performance_monitoring.rb +120 -0
- data/examples/pipeline_processing/README.adoc +740 -26
- data/examples/pipeline_processing/pipeline_processing.rb +2 -2
- data/examples/priority_work_example.rb +155 -0
- data/examples/producer_subscriber/README.adoc +889 -46
- data/examples/scatter_gather/README.adoc +829 -27
- data/examples/simple/README.adoc +347 -0
- data/examples/specialized_workers/README.adoc +622 -26
- data/examples/specialized_workers/specialized_workers.rb +44 -8
- data/examples/stream_processor/README.adoc +206 -0
- data/examples/stream_processor/stream_processor.rb +284 -0
- data/examples/web_scraper/README.adoc +625 -0
- data/examples/web_scraper/web_scraper.rb +285 -0
- data/examples/workflow/README.adoc +406 -0
- data/examples/workflow/circuit_breaker/README.adoc +360 -0
- data/examples/workflow/circuit_breaker/circuit_breaker_workflow.rb +225 -0
- data/examples/workflow/conditional/README.adoc +483 -0
- data/examples/workflow/conditional/conditional_workflow.rb +215 -0
- data/examples/workflow/dead_letter_queue/README.adoc +374 -0
- data/examples/workflow/dead_letter_queue/dead_letter_queue_workflow.rb +217 -0
- data/examples/workflow/fan_out/README.adoc +381 -0
- data/examples/workflow/fan_out/fan_out_workflow.rb +202 -0
- data/examples/workflow/retry/README.adoc +248 -0
- data/examples/workflow/retry/retry_workflow.rb +195 -0
- data/examples/workflow/simple_linear/README.adoc +267 -0
- data/examples/workflow/simple_linear/simple_linear_workflow.rb +175 -0
- data/examples/workflow/simplified/README.adoc +329 -0
- data/examples/workflow/simplified/simplified_workflow.rb +222 -0
- data/exe/fractor +10 -0
- data/lib/fractor/cli.rb +288 -0
- data/lib/fractor/configuration.rb +307 -0
- data/lib/fractor/continuous_server.rb +60 -65
- data/lib/fractor/error_formatter.rb +72 -0
- data/lib/fractor/error_report_generator.rb +152 -0
- data/lib/fractor/error_reporter.rb +244 -0
- data/lib/fractor/error_statistics.rb +147 -0
- data/lib/fractor/execution_tracer.rb +162 -0
- data/lib/fractor/logger.rb +230 -0
- data/lib/fractor/main_loop_handler.rb +406 -0
- data/lib/fractor/main_loop_handler3.rb +135 -0
- data/lib/fractor/main_loop_handler4.rb +299 -0
- data/lib/fractor/performance_metrics_collector.rb +181 -0
- data/lib/fractor/performance_monitor.rb +215 -0
- data/lib/fractor/performance_report_generator.rb +202 -0
- data/lib/fractor/priority_work.rb +93 -0
- data/lib/fractor/priority_work_queue.rb +189 -0
- data/lib/fractor/result_aggregator.rb +32 -0
- data/lib/fractor/shutdown_handler.rb +168 -0
- data/lib/fractor/signal_handler.rb +80 -0
- data/lib/fractor/supervisor.rb +382 -269
- data/lib/fractor/supervisor_logger.rb +88 -0
- data/lib/fractor/version.rb +1 -1
- data/lib/fractor/work.rb +12 -0
- data/lib/fractor/work_distribution_manager.rb +151 -0
- data/lib/fractor/work_queue.rb +20 -0
- data/lib/fractor/work_result.rb +181 -9
- data/lib/fractor/worker.rb +73 -0
- data/lib/fractor/workflow/builder.rb +210 -0
- data/lib/fractor/workflow/chain_builder.rb +169 -0
- data/lib/fractor/workflow/circuit_breaker.rb +183 -0
- data/lib/fractor/workflow/circuit_breaker_orchestrator.rb +208 -0
- data/lib/fractor/workflow/circuit_breaker_registry.rb +112 -0
- data/lib/fractor/workflow/dead_letter_queue.rb +334 -0
- data/lib/fractor/workflow/execution_hooks.rb +39 -0
- data/lib/fractor/workflow/execution_strategy.rb +225 -0
- data/lib/fractor/workflow/execution_trace.rb +134 -0
- data/lib/fractor/workflow/helpers.rb +191 -0
- data/lib/fractor/workflow/job.rb +290 -0
- data/lib/fractor/workflow/job_dependency_validator.rb +120 -0
- data/lib/fractor/workflow/logger.rb +110 -0
- data/lib/fractor/workflow/pre_execution_context.rb +193 -0
- data/lib/fractor/workflow/retry_config.rb +156 -0
- data/lib/fractor/workflow/retry_orchestrator.rb +184 -0
- data/lib/fractor/workflow/retry_strategy.rb +93 -0
- data/lib/fractor/workflow/structured_logger.rb +30 -0
- data/lib/fractor/workflow/type_compatibility_validator.rb +222 -0
- data/lib/fractor/workflow/visualizer.rb +211 -0
- data/lib/fractor/workflow/workflow_context.rb +132 -0
- data/lib/fractor/workflow/workflow_executor.rb +669 -0
- data/lib/fractor/workflow/workflow_result.rb +55 -0
- data/lib/fractor/workflow/workflow_validator.rb +295 -0
- data/lib/fractor/workflow.rb +333 -0
- data/lib/fractor/wrapped_ractor.rb +66 -101
- data/lib/fractor/wrapped_ractor3.rb +161 -0
- data/lib/fractor/wrapped_ractor4.rb +242 -0
- data/lib/fractor.rb +92 -4
- metadata +179 -6
- data/tests/sample.rb.bak +0 -309
- data/tests/sample_working.rb.bak +0 -209
|
@@ -1,45 +1,595 @@
|
|
|
1
1
|
= Multi-Work Type Example
|
|
2
2
|
|
|
3
|
-
==
|
|
3
|
+
== Purpose
|
|
4
4
|
|
|
5
|
-
|
|
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
|
-
==
|
|
7
|
+
== Focus
|
|
8
8
|
|
|
9
|
-
|
|
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
|
-
|
|
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
|
-
|
|
18
|
+
== Architecture
|
|
17
19
|
|
|
18
|
-
|
|
19
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
26
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|