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
|
@@ -0,0 +1,862 @@
|
|
|
1
|
+
---
|
|
2
|
+
layout: default
|
|
3
|
+
title: Design principles
|
|
4
|
+
nav_order: 4
|
|
5
|
+
---
|
|
6
|
+
= Design principles
|
|
7
|
+
|
|
8
|
+
== Overview
|
|
9
|
+
|
|
10
|
+
This guide explains the design philosophy, core principles, and key decisions that shaped Fractor's architecture. Understanding these principles helps you use Fractor effectively and make informed decisions when building applications.
|
|
11
|
+
|
|
12
|
+
== Core principles
|
|
13
|
+
|
|
14
|
+
=== Function-driven architecture
|
|
15
|
+
|
|
16
|
+
Fractor is built around the concept of functions (workers) operating on data (work items), rather than objects managing state.
|
|
17
|
+
|
|
18
|
+
.Function-driven vs object-oriented state management
|
|
19
|
+
[source]
|
|
20
|
+
----
|
|
21
|
+
Traditional OOP (Stateful Objects)
|
|
22
|
+
┌────────────────────────────────┐
|
|
23
|
+
│ WorkerObject │
|
|
24
|
+
│ ┌──────────────────────────┐ │
|
|
25
|
+
│ │ State │ │
|
|
26
|
+
│ │ - @counter │ │
|
|
27
|
+
│ │ - @connections │ │
|
|
28
|
+
│ │ - @cache │ │
|
|
29
|
+
│ └──────────────────────────┘ │
|
|
30
|
+
│ │ │
|
|
31
|
+
│ │ Methods modify state │
|
|
32
|
+
│ └──────────────────────────── │
|
|
33
|
+
│ │
|
|
34
|
+
│ Problem: State shared across │
|
|
35
|
+
│ threads leads to race conditions
|
|
36
|
+
└────────────────────────────────┘
|
|
37
|
+
|
|
38
|
+
Fractor (Function-Driven)
|
|
39
|
+
┌────────────────────────────────┐
|
|
40
|
+
│ Worker │
|
|
41
|
+
│ ┌──────────────────────────┐ │
|
|
42
|
+
│ │ process(work) │ │
|
|
43
|
+
│ │ └─> Transform input │ │
|
|
44
|
+
│ │ └─> Return result │ │
|
|
45
|
+
│ └──────────────────────────┘ │
|
|
46
|
+
│ │
|
|
47
|
+
│ Input → Function → Output │
|
|
48
|
+
│ No shared state │
|
|
49
|
+
│ Isolated in Ractor │
|
|
50
|
+
│ │
|
|
51
|
+
│ Benefit: True parallelism │
|
|
52
|
+
│ without race conditions │
|
|
53
|
+
└────────────────────────────────┘
|
|
54
|
+
----
|
|
55
|
+
|
|
56
|
+
*Rationale*:
|
|
57
|
+
|
|
58
|
+
* Functions are naturally parallelizable
|
|
59
|
+
* No shared mutable state eliminates race conditions
|
|
60
|
+
* Easier to reason about and test
|
|
61
|
+
* Maps well to Ractor's isolation model
|
|
62
|
+
|
|
63
|
+
=== Separation of concerns
|
|
64
|
+
|
|
65
|
+
Each component has a single, well-defined responsibility:
|
|
66
|
+
|
|
67
|
+
[cols="2,3,3"]
|
|
68
|
+
|===
|
|
69
|
+
|Component |Concern |What It Doesn't Do
|
|
70
|
+
|
|
71
|
+
|[`Work`](../../lib/fractor/work.rb)
|
|
72
|
+
|Hold input data
|
|
73
|
+
|Processing logic, validation, transformation
|
|
74
|
+
|
|
75
|
+
|[`Worker`](../../lib/fractor/worker.rb)
|
|
76
|
+
|Process work items
|
|
77
|
+
|Work distribution, result aggregation, lifecycle management
|
|
78
|
+
|
|
79
|
+
|[`Supervisor`](../../lib/fractor/supervisor.rb)
|
|
80
|
+
|Orchestrate execution
|
|
81
|
+
|Business logic, data transformation
|
|
82
|
+
|
|
83
|
+
|[`WrappedRactor`](../../lib/fractor/wrapped_ractor.rb)
|
|
84
|
+
|Manage Ractor lifecycle
|
|
85
|
+
|Work processing, distribution strategy
|
|
86
|
+
|
|
87
|
+
|[`ResultAggregator`](../../lib/fractor/result_aggregator.rb)
|
|
88
|
+
|Collect results
|
|
89
|
+
|Processing logic, distribution
|
|
90
|
+
|===
|
|
91
|
+
|
|
92
|
+
*Benefits*:
|
|
93
|
+
|
|
94
|
+
* Each component can be tested independently
|
|
95
|
+
* Changes are localized to affected components
|
|
96
|
+
* Easy to understand and maintain
|
|
97
|
+
* Clear interfaces between components
|
|
98
|
+
|
|
99
|
+
=== Composability
|
|
100
|
+
|
|
101
|
+
Fractor components are designed to work together flexibly:
|
|
102
|
+
|
|
103
|
+
.Composition examples
|
|
104
|
+
[example]
|
|
105
|
+
====
|
|
106
|
+
[source,ruby]
|
|
107
|
+
----
|
|
108
|
+
# Basic composition: Work + Worker + Supervisor
|
|
109
|
+
class DataWork < Fractor::Work; end
|
|
110
|
+
class DataWorker < Fractor::Worker; end
|
|
111
|
+
supervisor = Fractor::Supervisor.new(
|
|
112
|
+
worker_pools: [{ worker_class: DataWorker }]
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
# Add queue for continuous processing
|
|
116
|
+
work_queue = Fractor::WorkQueue.new
|
|
117
|
+
work_queue.register_with_supervisor(supervisor)
|
|
118
|
+
|
|
119
|
+
# Wrap in ContinuousServer for production
|
|
120
|
+
server = Fractor::ContinuousServer.new(
|
|
121
|
+
worker_pools: [{ worker_class: DataWorker }],
|
|
122
|
+
work_queue: work_queue
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
# Add error handling
|
|
126
|
+
server.on_error do |error_result|
|
|
127
|
+
ErrorReporter.notify(error_result)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Add result processing
|
|
131
|
+
server.on_result do |result|
|
|
132
|
+
ResultProcessor.handle(result)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# Compose into Workflow
|
|
136
|
+
class ProcessingPipeline < Fractor::Workflow
|
|
137
|
+
workflow "pipeline" do
|
|
138
|
+
job "extract", DataWorker
|
|
139
|
+
job "transform", TransformWorker, needs: "extract"
|
|
140
|
+
job "load", LoadWorker, needs: "transform"
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
----
|
|
144
|
+
====
|
|
145
|
+
|
|
146
|
+
*Design decision*: Components use interface-based composition rather than inheritance-based hierarchies. This makes it easy to combine components in new ways without modifying existing code.
|
|
147
|
+
|
|
148
|
+
=== Extensibility
|
|
149
|
+
|
|
150
|
+
Fractor is designed to be extended without modifying core code:
|
|
151
|
+
|
|
152
|
+
==== Extension points
|
|
153
|
+
|
|
154
|
+
[cols="2,3,3"]
|
|
155
|
+
|===
|
|
156
|
+
|Extension Point |How to Extend |Example Use Case
|
|
157
|
+
|
|
158
|
+
|Work classes
|
|
159
|
+
|Subclass `Fractor::Work`
|
|
160
|
+
|Add domain-specific convenience methods
|
|
161
|
+
|
|
162
|
+
|Worker classes
|
|
163
|
+
|Subclass `Fractor::Worker`
|
|
164
|
+
|Implement custom processing logic
|
|
165
|
+
|
|
166
|
+
|Result callbacks
|
|
167
|
+
|Register with `ResultAggregator`
|
|
168
|
+
|Custom logging, monitoring, notifications
|
|
169
|
+
|
|
170
|
+
|Work sources
|
|
171
|
+
|Register with `Supervisor`
|
|
172
|
+
|Pull from external queues, APIs, databases
|
|
173
|
+
|
|
174
|
+
|Workflow DSL
|
|
175
|
+
|Define jobs and dependencies
|
|
176
|
+
|Custom multi-step processing graphs
|
|
177
|
+
|
|
178
|
+
|Error handling
|
|
179
|
+
|Use `WorkResult` error metadata
|
|
180
|
+
|Smart retry, circuit breakers, DLQ
|
|
181
|
+
|===
|
|
182
|
+
|
|
183
|
+
.Extending with custom work source
|
|
184
|
+
[example]
|
|
185
|
+
====
|
|
186
|
+
[source,ruby]
|
|
187
|
+
----
|
|
188
|
+
# Custom work source from external API
|
|
189
|
+
supervisor.register_work_source do
|
|
190
|
+
# Poll external API
|
|
191
|
+
response = api_client.poll_for_work
|
|
192
|
+
|
|
193
|
+
# Transform to Work objects
|
|
194
|
+
response.items.map { |item| MyWork.new(item) }
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
# Or use WorkQueue for thread-safe pushing
|
|
198
|
+
queue = Fractor::WorkQueue.new
|
|
199
|
+
queue.register_with_supervisor(supervisor)
|
|
200
|
+
|
|
201
|
+
# External thread can push work safely
|
|
202
|
+
Thread.new do
|
|
203
|
+
loop do
|
|
204
|
+
item = external_source.next
|
|
205
|
+
queue << MyWork.new(item)
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
----
|
|
209
|
+
====
|
|
210
|
+
|
|
211
|
+
*Principle*: Open for extension, closed for modification. Users extend Fractor through well-defined interfaces without touching framework code.
|
|
212
|
+
|
|
213
|
+
== Design decisions
|
|
214
|
+
|
|
215
|
+
=== Why Ractors over threads?
|
|
216
|
+
|
|
217
|
+
[cols="1,2,2"]
|
|
218
|
+
|===
|
|
219
|
+
|Aspect |Threads (with GIL) |Ractors (Fractor)
|
|
220
|
+
|
|
221
|
+
|Parallelism
|
|
222
|
+
|Pseudo-parallel (GIL limits)
|
|
223
|
+
|True parallel execution
|
|
224
|
+
|
|
225
|
+
|Memory model
|
|
226
|
+
|Shared mutable state
|
|
227
|
+
|Isolated memory spaces
|
|
228
|
+
|
|
229
|
+
|Race conditions
|
|
230
|
+
|Possible, requires locks
|
|
231
|
+
|Impossible (no shared state)
|
|
232
|
+
|
|
233
|
+
|CPU utilization
|
|
234
|
+
|Single core (Ruby GIL)
|
|
235
|
+
|All cores simultaneously
|
|
236
|
+
|
|
237
|
+
|Complexity
|
|
238
|
+
|Simple API, complex safety
|
|
239
|
+
|Structured messages, guaranteed safety
|
|
240
|
+
|===
|
|
241
|
+
|
|
242
|
+
.Performance comparison
|
|
243
|
+
[example]
|
|
244
|
+
====
|
|
245
|
+
CPU-bound workload (10,000 calculations):
|
|
246
|
+
|
|
247
|
+
[source]
|
|
248
|
+
----
|
|
249
|
+
Threads (GIL-limited):
|
|
250
|
+
4 threads: ~10 seconds (no speedup due to GIL)
|
|
251
|
+
8 threads: ~10 seconds (still limited by GIL)
|
|
252
|
+
|
|
253
|
+
Ractors (Fractor):
|
|
254
|
+
4 ractors: ~2.5 seconds (4x speedup)
|
|
255
|
+
8 ractors: ~1.25 seconds (8x speedup)
|
|
256
|
+
----
|
|
257
|
+
====
|
|
258
|
+
|
|
259
|
+
*Decision*: Use Ractors for true parallelism despite:
|
|
260
|
+
|
|
261
|
+
* Newer feature (Ruby 3.0+)
|
|
262
|
+
* More restrictive sharing rules
|
|
263
|
+
* Less ecosystem maturity
|
|
264
|
+
|
|
265
|
+
The benefits of true parallelism and guaranteed thread safety outweigh these limitations for Fractor's use cases.
|
|
266
|
+
|
|
267
|
+
=== Why message passing?
|
|
268
|
+
|
|
269
|
+
Fractor uses message passing between Supervisor and Ractors instead of shared state.
|
|
270
|
+
|
|
271
|
+
.Communication approaches
|
|
272
|
+
[source]
|
|
273
|
+
----
|
|
274
|
+
Shared State (Traditional)
|
|
275
|
+
┌──────────┐ ┌──────────┐
|
|
276
|
+
│ Thread 1 │───▶│ Shared │◀───│ Thread 2 │
|
|
277
|
+
└──────────┘ │ Memory │ └──────────┘
|
|
278
|
+
│ │
|
|
279
|
+
│ Requires │
|
|
280
|
+
│ Locks │
|
|
281
|
+
└──────────┘
|
|
282
|
+
▲
|
|
283
|
+
Problem: Race conditions,
|
|
284
|
+
deadlocks, complexity
|
|
285
|
+
|
|
286
|
+
Message Passing (Fractor)
|
|
287
|
+
┌──────────┐ ┌──────────┐
|
|
288
|
+
│ Ractor 1 │────┐ ┌─▶│ Ractor 2 │
|
|
289
|
+
└──────────┘ │ │ └──────────┘
|
|
290
|
+
▼ ▼
|
|
291
|
+
Message Queue
|
|
292
|
+
(Copy semantics)
|
|
293
|
+
|
|
294
|
+
Benefits: No locks,
|
|
295
|
+
no race conditions,
|
|
296
|
+
predictable behavior
|
|
297
|
+
----
|
|
298
|
+
|
|
299
|
+
*Trade-offs*:
|
|
300
|
+
|
|
301
|
+
Advantages:
|
|
302
|
+
|
|
303
|
+
* No race conditions possible
|
|
304
|
+
* No deadlocks or lock contention
|
|
305
|
+
* Predictable, sequential message handling
|
|
306
|
+
* Natural fit for actor model
|
|
307
|
+
|
|
308
|
+
Disadvantages:
|
|
309
|
+
|
|
310
|
+
* Messages must be copied or shareable (immutable)
|
|
311
|
+
* Slightly higher memory usage per message
|
|
312
|
+
* Cannot share large mutable structures directly
|
|
313
|
+
|
|
314
|
+
*Decision*: Message passing selected because:
|
|
315
|
+
|
|
316
|
+
. Safety guarantees are paramount for parallel processing
|
|
317
|
+
. Message copying overhead is minimal for typical work items
|
|
318
|
+
. Immutability is a best practice anyway
|
|
319
|
+
. Enables reliable distributed systems patterns
|
|
320
|
+
|
|
321
|
+
=== Why immutable Work objects?
|
|
322
|
+
|
|
323
|
+
Work objects must be shareable (immutable or frozen) to cross Ractor boundaries.
|
|
324
|
+
|
|
325
|
+
[source,ruby]
|
|
326
|
+
----
|
|
327
|
+
# Why immutability is required
|
|
328
|
+
class MutableWork < Fractor::Work
|
|
329
|
+
def initialize(data)
|
|
330
|
+
@data = data # Mutable reference
|
|
331
|
+
end
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
# This fails - cannot share mutable objects
|
|
335
|
+
work = MutableWork.new({ value: 42 })
|
|
336
|
+
ractor.send(work) # Ractor::IsolationError
|
|
337
|
+
|
|
338
|
+
# Solution: Freeze data
|
|
339
|
+
class ImmutableWork < Fractor::Work
|
|
340
|
+
def initialize(data)
|
|
341
|
+
super(data.freeze) # Frozen, thus shareable
|
|
342
|
+
end
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
work = ImmutableWork.new({ value: 42 })
|
|
346
|
+
ractor.send(work) # ✓ Works - frozen data is shareable
|
|
347
|
+
----
|
|
348
|
+
|
|
349
|
+
*Benefits of immutability*:
|
|
350
|
+
|
|
351
|
+
. *Thread safety*: Cannot be modified during processing
|
|
352
|
+
. *Predictability*: Input data unchanged throughout pipeline
|
|
353
|
+
. *Debugging*: Work object state never changes
|
|
354
|
+
. *Optimization*: Ruby can optimize frozen objects
|
|
355
|
+
|
|
356
|
+
*Trade-off*: Must create new objects for mutations instead of modifying in place. This is acceptable because:
|
|
357
|
+
|
|
358
|
+
* Work items are typically small
|
|
359
|
+
* Functional transformation is clearer
|
|
360
|
+
* Memory overhead is negligible
|
|
361
|
+
* GC handles short-lived objects efficiently
|
|
362
|
+
|
|
363
|
+
=== API design rationale
|
|
364
|
+
|
|
365
|
+
Fractor's API follows these design principles:
|
|
366
|
+
|
|
367
|
+
==== Explicit over implicit
|
|
368
|
+
|
|
369
|
+
[source,ruby]
|
|
370
|
+
----
|
|
371
|
+
# Explicit: Clear what's happening
|
|
372
|
+
supervisor = Fractor::Supervisor.new(
|
|
373
|
+
worker_pools: [
|
|
374
|
+
{ worker_class: MyWorker, num_workers: 4 }
|
|
375
|
+
],
|
|
376
|
+
continuous_mode: false
|
|
377
|
+
)
|
|
378
|
+
|
|
379
|
+
# vs Implicit (rejected design)
|
|
380
|
+
# supervisor = Fractor.auto # Too much magic
|
|
381
|
+
----
|
|
382
|
+
|
|
383
|
+
*Rationale*: Explicit configuration makes behavior predictable and debuggable.
|
|
384
|
+
|
|
385
|
+
==== Sensible defaults with override capability
|
|
386
|
+
|
|
387
|
+
[source,ruby]
|
|
388
|
+
----
|
|
389
|
+
# Defaults work well
|
|
390
|
+
supervisor = Fractor::Supervisor.new(
|
|
391
|
+
worker_pools: [{ worker_class: MyWorker }]
|
|
392
|
+
# num_workers: auto-detected from CPU count
|
|
393
|
+
# continuous_mode: false (pipeline mode)
|
|
394
|
+
)
|
|
395
|
+
|
|
396
|
+
# But can override when needed
|
|
397
|
+
supervisor = Fractor::Supervisor.new(
|
|
398
|
+
worker_pools: [
|
|
399
|
+
{ worker_class: MyWorker, num_workers: 16 }
|
|
400
|
+
],
|
|
401
|
+
continuous_mode: true
|
|
402
|
+
)
|
|
403
|
+
----
|
|
404
|
+
|
|
405
|
+
*Principle*: Optimize for the common case, but allow customization.
|
|
406
|
+
|
|
407
|
+
==== Consistent naming conventions
|
|
408
|
+
|
|
409
|
+
[cols="2,3,2"]
|
|
410
|
+
|===
|
|
411
|
+
|Pattern |Examples |Rationale
|
|
412
|
+
|
|
413
|
+
|Nouns for data
|
|
414
|
+
|`Work`, `WorkResult`, `WorkQueue`
|
|
415
|
+
|Represents state/data
|
|
416
|
+
|
|
417
|
+
|Verbs for actions
|
|
418
|
+
|`process()`, `execute()`, `run()`
|
|
419
|
+
|Represents operations
|
|
420
|
+
|
|
421
|
+
|Descriptive adjectives
|
|
422
|
+
|`PriorityWork`, `WrappedRactor`, `ContinuousServer`
|
|
423
|
+
|Clarifies purpose
|
|
424
|
+
|
|
425
|
+
|`on_*` for callbacks
|
|
426
|
+
|`on_result`, `on_error`, `on_add`
|
|
427
|
+
|Clear callback intent
|
|
428
|
+
|===
|
|
429
|
+
|
|
430
|
+
==== Progressive disclosure
|
|
431
|
+
|
|
432
|
+
API complexity reveals itself as needed:
|
|
433
|
+
|
|
434
|
+
[source]
|
|
435
|
+
----
|
|
436
|
+
Level 1: Simple pipeline
|
|
437
|
+
supervisor = Fractor::Supervisor.new(
|
|
438
|
+
worker_pools: [{ worker_class: Worker }]
|
|
439
|
+
)
|
|
440
|
+
supervisor.add_work_items(work_items)
|
|
441
|
+
supervisor.run
|
|
442
|
+
|
|
443
|
+
Level 2: Continuous mode
|
|
444
|
+
supervisor = Fractor::Supervisor.new(
|
|
445
|
+
worker_pools: [{ worker_class: Worker }],
|
|
446
|
+
continuous_mode: true
|
|
447
|
+
)
|
|
448
|
+
supervisor.register_work_source { fetch_work }
|
|
449
|
+
supervisor.run
|
|
450
|
+
|
|
451
|
+
Level 3: Production with server
|
|
452
|
+
server = Fractor::ContinuousServer.new(
|
|
453
|
+
worker_pools: [{ worker_class: Worker }],
|
|
454
|
+
work_queue: queue,
|
|
455
|
+
log_file: "fractor.log"
|
|
456
|
+
)
|
|
457
|
+
server.on_result { |r| process(r) }
|
|
458
|
+
server.on_error { |e| handle(e) }
|
|
459
|
+
server.run
|
|
460
|
+
|
|
461
|
+
Level 4: Workflows
|
|
462
|
+
class Pipeline < Fractor::Workflow
|
|
463
|
+
workflow "pipeline" do
|
|
464
|
+
job "a", WorkerA
|
|
465
|
+
job "b", WorkerB, needs: "a"
|
|
466
|
+
configure_dead_letter_queue(max_size: 1000)
|
|
467
|
+
end
|
|
468
|
+
end
|
|
469
|
+
result = Pipeline.new.execute(input: data)
|
|
470
|
+
----
|
|
471
|
+
|
|
472
|
+
*Principle*: Start simple, add complexity only when needed.
|
|
473
|
+
|
|
474
|
+
== Trade-offs
|
|
475
|
+
|
|
476
|
+
=== Performance vs simplicity
|
|
477
|
+
|
|
478
|
+
==== Trade-off: Ractor overhead
|
|
479
|
+
|
|
480
|
+
*Performance cost*: Creating Ractors has overhead (~1-2ms per Ractor, ~1MB memory)
|
|
481
|
+
|
|
482
|
+
*Simplicity gain*: Automatic parallelism without manual thread management
|
|
483
|
+
|
|
484
|
+
*Decision*: Accept overhead because:
|
|
485
|
+
|
|
486
|
+
* One-time cost at startup
|
|
487
|
+
* Amortized over long-running processing
|
|
488
|
+
* Benefits of parallelism far outweigh cost for work items > 5ms
|
|
489
|
+
|
|
490
|
+
==== Trade-off: Message copying
|
|
491
|
+
|
|
492
|
+
*Performance cost*: Objects copied when sent between Ractors
|
|
493
|
+
|
|
494
|
+
*Simplicity gain*: No shared state, no locks, guaranteed safety
|
|
495
|
+
|
|
496
|
+
*Decision*: Accept copying because:
|
|
497
|
+
|
|
498
|
+
* Work items should be small and focused
|
|
499
|
+
* Copying is fast for typical objects
|
|
500
|
+
* Safety guarantees are worth the cost
|
|
501
|
+
* Can use frozen/shareable objects to avoid some copying
|
|
502
|
+
|
|
503
|
+
=== Flexibility vs constraints
|
|
504
|
+
|
|
505
|
+
==== Trade-off: Structured message protocol
|
|
506
|
+
|
|
507
|
+
*Constraint*: Must use specific message format (`{ type:, result:, processor: }`)
|
|
508
|
+
|
|
509
|
+
*Flexibility loss*: Cannot send arbitrary data between Supervisor and Ractors
|
|
510
|
+
|
|
511
|
+
*Benefit*: Predictable, debuggable message flow
|
|
512
|
+
|
|
513
|
+
*Decision*: Structure is beneficial because:
|
|
514
|
+
|
|
515
|
+
* Makes debugging easier (can trace all messages)
|
|
516
|
+
* Enables future features (message logging, inspection)
|
|
517
|
+
* Cost is minimal (just a Hash wrapper)
|
|
518
|
+
* Structure prevents ad-hoc communication bugs
|
|
519
|
+
|
|
520
|
+
==== Trade-off: Work/Worker class requirements
|
|
521
|
+
|
|
522
|
+
*Constraint*: Must subclass `Fractor::Work` and `Fractor::Worker`
|
|
523
|
+
|
|
524
|
+
*Flexibility loss*: Cannot use arbitrary objects/functions
|
|
525
|
+
|
|
526
|
+
*Benefit*: Type safety, validation, consistent interface
|
|
527
|
+
|
|
528
|
+
*Decision*: Classes provide:
|
|
529
|
+
|
|
530
|
+
* Clear contracts and interfaces
|
|
531
|
+
* Better error messages
|
|
532
|
+
* IDE support and autocomplete
|
|
533
|
+
* Extension points for future features
|
|
534
|
+
|
|
535
|
+
=== Safety vs speed
|
|
536
|
+
|
|
537
|
+
==== Trade-off: Immutable work objects
|
|
538
|
+
|
|
539
|
+
*Safety*: Frozen objects prevent modification bugs
|
|
540
|
+
|
|
541
|
+
*Speed cost*: Cannot modify in place, must create new objects
|
|
542
|
+
|
|
543
|
+
*Decision*: Safety prioritized because:
|
|
544
|
+
|
|
545
|
+
* Bugs from mutation are hard to debug
|
|
546
|
+
* Functional transformation is clearer
|
|
547
|
+
* Cost is minimal for typical use cases
|
|
548
|
+
* Can optimize hot paths if needed
|
|
549
|
+
|
|
550
|
+
==== Trade-off: Error capture and wrapping
|
|
551
|
+
|
|
552
|
+
*Safety*: All errors captured in `WorkResult`, none silently swallowed
|
|
553
|
+
|
|
554
|
+
*Speed cost*: Exception handling overhead
|
|
555
|
+
|
|
556
|
+
*Decision*: Comprehensive error handling because:
|
|
557
|
+
|
|
558
|
+
* Silent failures are worse than slowdowns
|
|
559
|
+
* Debugging production issues requires error context
|
|
560
|
+
* Overhead is negligible (<1%)
|
|
561
|
+
* Enables sophisticated error handling (retry, DLQ, etc.)
|
|
562
|
+
|
|
563
|
+
== Design patterns employed
|
|
564
|
+
|
|
565
|
+
=== Actor model
|
|
566
|
+
|
|
567
|
+
Fractor implements the actor model for concurrency:
|
|
568
|
+
|
|
569
|
+
[source]
|
|
570
|
+
----
|
|
571
|
+
Actor = WrappedRactor + Worker
|
|
572
|
+
|
|
573
|
+
Actor Properties:
|
|
574
|
+
1. Isolated state (each Ractor has own memory)
|
|
575
|
+
2. Message-driven (communicate via send/receive)
|
|
576
|
+
3. Asynchronous processing (non-blocking sends)
|
|
577
|
+
4. Location transparent (don't care which Ractor)
|
|
578
|
+
----
|
|
579
|
+
|
|
580
|
+
*Why actor model?*
|
|
581
|
+
|
|
582
|
+
* Proven pattern for concurrent systems
|
|
583
|
+
* Natural fit for Ractor capabilities
|
|
584
|
+
* Scales well (can add more actors easily)
|
|
585
|
+
* Fault tolerant (actor failures isolated)
|
|
586
|
+
|
|
587
|
+
=== Supervisor pattern
|
|
588
|
+
|
|
589
|
+
[`Supervisor`](../../lib/fractor/supervisor.rb) monitors and manages worker Ractors:
|
|
590
|
+
|
|
591
|
+
[source]
|
|
592
|
+
----
|
|
593
|
+
Supervisor Responsibilities:
|
|
594
|
+
├── Start workers
|
|
595
|
+
├── Distribute work
|
|
596
|
+
├── Monitor health
|
|
597
|
+
├── Collect results
|
|
598
|
+
├── Handle signals
|
|
599
|
+
└── Graceful shutdown
|
|
600
|
+
----
|
|
601
|
+
|
|
602
|
+
*Benefits*:
|
|
603
|
+
|
|
604
|
+
* Centralized lifecycle management
|
|
605
|
+
* Fault isolation and recovery
|
|
606
|
+
* Observable system state
|
|
607
|
+
* Coordinated shutdown
|
|
608
|
+
|
|
609
|
+
=== Producer-consumer pattern
|
|
610
|
+
|
|
611
|
+
Work flows from producers to consumers via queues:
|
|
612
|
+
|
|
613
|
+
[source]
|
|
614
|
+
----
|
|
615
|
+
Producers → WorkQueue → Supervisor → Workers (Consumers)
|
|
616
|
+
|
|
617
|
+
Multiple producers can feed one queue
|
|
618
|
+
Multiple workers can consume from one queue
|
|
619
|
+
Queue provides backpressure and buffering
|
|
620
|
+
----
|
|
621
|
+
|
|
622
|
+
*Why this pattern?*
|
|
623
|
+
|
|
624
|
+
* Decouples production from consumption
|
|
625
|
+
* Enables different production/consumption rates
|
|
626
|
+
* Buffers temporary load spikes
|
|
627
|
+
* Natural fit for continuous mode
|
|
628
|
+
|
|
629
|
+
=== Pipeline pattern
|
|
630
|
+
|
|
631
|
+
Workflows implement staged processing:
|
|
632
|
+
|
|
633
|
+
[source]
|
|
634
|
+
----
|
|
635
|
+
Input → Stage 1 → Stage 2 → Stage 3 → Output
|
|
636
|
+
|
|
637
|
+
Each stage:
|
|
638
|
+
- Independent worker pool
|
|
639
|
+
- Consumes previous stage output
|
|
640
|
+
- Can run in parallel with other stages
|
|
641
|
+
----
|
|
642
|
+
|
|
643
|
+
*Benefits*:
|
|
644
|
+
|
|
645
|
+
* Clear data flow
|
|
646
|
+
* Each stage independently scalable
|
|
647
|
+
* Easy to add/remove/reorder stages
|
|
648
|
+
* Type-safe stage boundaries
|
|
649
|
+
|
|
650
|
+
=== Builder pattern
|
|
651
|
+
|
|
652
|
+
Workflows use builder pattern for construction:
|
|
653
|
+
|
|
654
|
+
[source,ruby]
|
|
655
|
+
----
|
|
656
|
+
# Fluent API for workflow construction
|
|
657
|
+
workflow = Fractor::Workflow.chain("pipeline")
|
|
658
|
+
.step("parse", ParseWorker)
|
|
659
|
+
.step("validate", ValidateWorker)
|
|
660
|
+
.step("transform", TransformWorker)
|
|
661
|
+
.build
|
|
662
|
+
|
|
663
|
+
# Or using DSL
|
|
664
|
+
class Pipeline < Fractor::Workflow
|
|
665
|
+
workflow "pipeline" do
|
|
666
|
+
job "parse", ParseWorker
|
|
667
|
+
job "validate", ValidateWorker, needs: "parse"
|
|
668
|
+
end
|
|
669
|
+
end
|
|
670
|
+
----
|
|
671
|
+
|
|
672
|
+
*Why builder pattern?*
|
|
673
|
+
|
|
674
|
+
* Expressive DSL
|
|
675
|
+
* Validation before execution
|
|
676
|
+
* Immutable after construction
|
|
677
|
+
* Clear separation: definition vs execution
|
|
678
|
+
|
|
679
|
+
== Design evolution
|
|
680
|
+
|
|
681
|
+
=== Learning from production use
|
|
682
|
+
|
|
683
|
+
Fractor's design evolved based on real-world usage:
|
|
684
|
+
|
|
685
|
+
==== Early design: Shared queue
|
|
686
|
+
|
|
687
|
+
*Original*: Workers directly accessed shared queue
|
|
688
|
+
|
|
689
|
+
[source,ruby]
|
|
690
|
+
----
|
|
691
|
+
# Early design (rejected)
|
|
692
|
+
class Worker
|
|
693
|
+
def initialize(queue)
|
|
694
|
+
@queue = queue
|
|
695
|
+
end
|
|
696
|
+
|
|
697
|
+
def run
|
|
698
|
+
loop do
|
|
699
|
+
work = @queue.pop # Shared state!
|
|
700
|
+
process(work)
|
|
701
|
+
end
|
|
702
|
+
end
|
|
703
|
+
end
|
|
704
|
+
----
|
|
705
|
+
|
|
706
|
+
*Problem*: Race conditions, complex locking, unclear ownership
|
|
707
|
+
|
|
708
|
+
*Evolution*: Supervisor owns queue and distributes work
|
|
709
|
+
|
|
710
|
+
[source,ruby]
|
|
711
|
+
----
|
|
712
|
+
# Current design
|
|
713
|
+
class Supervisor
|
|
714
|
+
def run
|
|
715
|
+
loop do
|
|
716
|
+
ractor, message = Ractor.select(*ractors)
|
|
717
|
+
# Supervisor controls all work distribution
|
|
718
|
+
if work_available?
|
|
719
|
+
ractor.send(next_work)
|
|
720
|
+
end
|
|
721
|
+
end
|
|
722
|
+
end
|
|
723
|
+
end
|
|
724
|
+
----
|
|
725
|
+
|
|
726
|
+
*Benefit*: Clear ownership, no shared state, predictable flow
|
|
727
|
+
|
|
728
|
+
==== Evolution: Error handling
|
|
729
|
+
|
|
730
|
+
*Original*: Errors crashed Ractors
|
|
731
|
+
|
|
732
|
+
*Problem*: Lost work, no error context, hard to debug
|
|
733
|
+
|
|
734
|
+
*Evolution 1*: Catch and log errors
|
|
735
|
+
|
|
736
|
+
*Problem*: Errors logged but work lost, no retry possible
|
|
737
|
+
|
|
738
|
+
*Evolution 2*: `WorkResult` with error metadata
|
|
739
|
+
|
|
740
|
+
[source,ruby]
|
|
741
|
+
----
|
|
742
|
+
WorkResult.new(
|
|
743
|
+
error: exception,
|
|
744
|
+
work: original_work, # Can retry!
|
|
745
|
+
error_category: :network, # Categorized
|
|
746
|
+
error_context: { ... } # Rich context
|
|
747
|
+
)
|
|
748
|
+
----
|
|
749
|
+
|
|
750
|
+
*Benefit*: Errors don't lose work, rich context, enables smart retry
|
|
751
|
+
|
|
752
|
+
==== Evolution: Priority handling
|
|
753
|
+
|
|
754
|
+
*Original*: FIFO queue only
|
|
755
|
+
|
|
756
|
+
*Problem*: Critical work delayed by low-priority work
|
|
757
|
+
|
|
758
|
+
*Evolution*: `PriorityWorkQueue` with aging
|
|
759
|
+
|
|
760
|
+
*Benefit*: Priority-based processing, no starvation
|
|
761
|
+
|
|
762
|
+
These evolutions demonstrate Fractor's commitment to learning from production use and continuously improving while maintaining backward compatibility.
|
|
763
|
+
|
|
764
|
+
== Philosophical foundations
|
|
765
|
+
|
|
766
|
+
=== Favor composition over inheritance
|
|
767
|
+
|
|
768
|
+
Fractor uses composition to combine behavior:
|
|
769
|
+
|
|
770
|
+
[source,ruby]
|
|
771
|
+
----
|
|
772
|
+
# Composition: ContinuousServer has-a Supervisor
|
|
773
|
+
class ContinuousServer
|
|
774
|
+
def initialize(worker_pools:)
|
|
775
|
+
@supervisor = Supervisor.new(
|
|
776
|
+
worker_pools: worker_pools,
|
|
777
|
+
continuous_mode: true
|
|
778
|
+
)
|
|
779
|
+
end
|
|
780
|
+
end
|
|
781
|
+
|
|
782
|
+
# Not inheritance: ContinuousServer is-a Supervisor
|
|
783
|
+
class ContinuousServer < Supervisor # ✗ Rejected
|
|
784
|
+
end
|
|
785
|
+
----
|
|
786
|
+
|
|
787
|
+
*Rationale*: Composition is more flexible and doesn't create tight coupling.
|
|
788
|
+
|
|
789
|
+
=== Make the right thing easy
|
|
790
|
+
|
|
791
|
+
API design encourages best practices:
|
|
792
|
+
|
|
793
|
+
[source,ruby]
|
|
794
|
+
----
|
|
795
|
+
# Easy: Type-safe workflow
|
|
796
|
+
class Pipeline < Fractor::Workflow
|
|
797
|
+
workflow "pipeline" do
|
|
798
|
+
input_type InputModel
|
|
799
|
+
output_type OutputModel
|
|
800
|
+
|
|
801
|
+
job "process", ProcessWorker
|
|
802
|
+
end
|
|
803
|
+
end
|
|
804
|
+
|
|
805
|
+
# Hard: Skip type safety
|
|
806
|
+
# (Must explicitly avoid it)
|
|
807
|
+
----
|
|
808
|
+
|
|
809
|
+
*Principle*: Default path should be the best path.
|
|
810
|
+
|
|
811
|
+
=== Optimize for debugging
|
|
812
|
+
|
|
813
|
+
Production issues are inevitable, so Fractor optimizes for debuggability:
|
|
814
|
+
|
|
815
|
+
* All errors captured with context
|
|
816
|
+
* Optional execution tracing
|
|
817
|
+
* Clear error messages with suggestions
|
|
818
|
+
* Structured logging support
|
|
819
|
+
* Dead letter queue for failed items
|
|
820
|
+
* Workflow visualization
|
|
821
|
+
|
|
822
|
+
*Philosophy*: Time spent debugging production issues far exceeds time spent writing code, so invest in debuggability.
|
|
823
|
+
|
|
824
|
+
=== Fail explicitly, not silently
|
|
825
|
+
|
|
826
|
+
Fractor never silently swallows errors:
|
|
827
|
+
|
|
828
|
+
[source,ruby]
|
|
829
|
+
----
|
|
830
|
+
# Every error path is explicit
|
|
831
|
+
case result
|
|
832
|
+
when success?
|
|
833
|
+
process_success(result.result)
|
|
834
|
+
when failure?
|
|
835
|
+
handle_error(result.error) # Must handle
|
|
836
|
+
end
|
|
837
|
+
|
|
838
|
+
# No: result.result_or_nil # Silent failure
|
|
839
|
+
# No: rescue; nil; end # Swallow errors
|
|
840
|
+
----
|
|
841
|
+
|
|
842
|
+
*Philosophy*: Explicit failures are easier to debug than silent corruption.
|
|
843
|
+
|
|
844
|
+
== Conclusion
|
|
845
|
+
|
|
846
|
+
Fractor's design reflects these core values:
|
|
847
|
+
|
|
848
|
+
* *Safety first*: Correctness over performance
|
|
849
|
+
* *Clarity*: Explicit over implicit
|
|
850
|
+
* *Composability*: Small pieces, loosely joined
|
|
851
|
+
* *Pragmatism*: Solve real problems simply
|
|
852
|
+
* *Evolution*: Learn and improve continuously
|
|
853
|
+
|
|
854
|
+
These principles guide all design decisions and help Fractor remain simple, safe, and effective for parallel processing in Ruby.
|
|
855
|
+
|
|
856
|
+
== Next steps
|
|
857
|
+
|
|
858
|
+
* Read link:core-concepts/[Core Concepts] for component details
|
|
859
|
+
* Read link:../architecture/[Architecture Guide] for system design
|
|
860
|
+
* Try link:../guides/pipeline-mode/[Pipeline Mode] to see principles in action
|
|
861
|
+
* Try link:../guides/continuous-mode/[Continuous Mode] for long-running services
|
|
862
|
+
* Study link:../reference/examples/[Examples] to see patterns applied
|