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,627 @@
|
|
|
1
|
+
= API Data Aggregator Example
|
|
2
|
+
:toc:
|
|
3
|
+
:toclevels: 3
|
|
4
|
+
|
|
5
|
+
High-performance API data aggregator that fetches data from multiple API endpoints in parallel using Fractor::Workflow with circuit breaker pattern for resilience.
|
|
6
|
+
|
|
7
|
+
== Purpose
|
|
8
|
+
|
|
9
|
+
This example demonstrates:
|
|
10
|
+
|
|
11
|
+
* Parallel API fetching using Fractor::Workflow
|
|
12
|
+
* Circuit breaker pattern for fault tolerance
|
|
13
|
+
* Retry logic with exponential backoff
|
|
14
|
+
* Rate limiting to respect API constraints
|
|
15
|
+
* Data aggregation and enrichment from multiple sources
|
|
16
|
+
* Error recovery and graceful degradation
|
|
17
|
+
|
|
18
|
+
== Features
|
|
19
|
+
|
|
20
|
+
=== Circuit Breaker Pattern
|
|
21
|
+
|
|
22
|
+
The aggregator implements a circuit breaker to prevent cascading failures:
|
|
23
|
+
|
|
24
|
+
* **Closed State**: Normal operation, requests pass through
|
|
25
|
+
* **Open State**: After 3 failures, circuit opens and fails fast
|
|
26
|
+
* **Half-Open State**: After timeout, allows limited requests to test recovery
|
|
27
|
+
|
|
28
|
+
.Circuit breaker state transitions
|
|
29
|
+
[source]
|
|
30
|
+
----
|
|
31
|
+
┌─────────┐
|
|
32
|
+
│ Closed │ ◄──── Success ────┐
|
|
33
|
+
│ (Normal)│ │
|
|
34
|
+
└────┬────┘ │
|
|
35
|
+
│ │
|
|
36
|
+
Failure ×3 Half-Open
|
|
37
|
+
│ Succeeds ×2
|
|
38
|
+
▼ │
|
|
39
|
+
┌─────────┐ │
|
|
40
|
+
│ Open │ │
|
|
41
|
+
│(Failing)│ │
|
|
42
|
+
└────┬────┘ │
|
|
43
|
+
│ │
|
|
44
|
+
Timeout │
|
|
45
|
+
│ │
|
|
46
|
+
▼ │
|
|
47
|
+
┌──────────┐ │
|
|
48
|
+
│Half-Open │──────────────────┘
|
|
49
|
+
│ (Testing)│
|
|
50
|
+
└──────────┘
|
|
51
|
+
│
|
|
52
|
+
Failure
|
|
53
|
+
│
|
|
54
|
+
▼
|
|
55
|
+
Back to Open
|
|
56
|
+
----
|
|
57
|
+
|
|
58
|
+
=== Retry with Exponential Backoff
|
|
59
|
+
|
|
60
|
+
Failed requests are automatically retried with increasing delays:
|
|
61
|
+
|
|
62
|
+
* Attempt 1: Immediate
|
|
63
|
+
* Attempt 2: Wait 0.5 seconds
|
|
64
|
+
* Attempt 3: Wait 1.0 second
|
|
65
|
+
* Attempt 4: Wait 2.0 seconds (max attempts: 3)
|
|
66
|
+
|
|
67
|
+
=== Rate Limiting
|
|
68
|
+
|
|
69
|
+
Each endpoint respects configurable rate limits:
|
|
70
|
+
|
|
71
|
+
* Default: 0.1 second delay between requests
|
|
72
|
+
* Prevents overwhelming API servers
|
|
73
|
+
* Maintains good citizenship with external services
|
|
74
|
+
|
|
75
|
+
=== Data Enrichment
|
|
76
|
+
|
|
77
|
+
The aggregator combines data from multiple endpoints:
|
|
78
|
+
|
|
79
|
+
* Fetches users, products, orders, analytics
|
|
80
|
+
* Enriches orders with user and product details
|
|
81
|
+
* Calculates derived metrics (total prices, etc.)
|
|
82
|
+
|
|
83
|
+
== Architecture
|
|
84
|
+
|
|
85
|
+
=== Component Diagram
|
|
86
|
+
|
|
87
|
+
[source]
|
|
88
|
+
----
|
|
89
|
+
┌────────────────────────────────────────┐
|
|
90
|
+
│ APIAggregator (Main) │
|
|
91
|
+
│ - Manages endpoints │
|
|
92
|
+
│ - Orchestrates workflow │
|
|
93
|
+
│ - Aggregates results │
|
|
94
|
+
└────────────────────────────────────────┘
|
|
95
|
+
│
|
|
96
|
+
│ uses
|
|
97
|
+
▼
|
|
98
|
+
┌────────────────────────────────────────┐
|
|
99
|
+
│ Fractor::Workflow │
|
|
100
|
+
│ - Circuit breaker │
|
|
101
|
+
│ - Retry logic │
|
|
102
|
+
│ - Parallel execution │
|
|
103
|
+
└────────────────────────────────────────┘
|
|
104
|
+
│
|
|
105
|
+
┌───────┼───────┐
|
|
106
|
+
▼ ▼ ▼
|
|
107
|
+
┌────────┬────────┬────────┐
|
|
108
|
+
│ Users │Products│ Orders │
|
|
109
|
+
│ API │ API │ API │
|
|
110
|
+
└────────┴────────┴────────┘
|
|
111
|
+
│
|
|
112
|
+
│ returns
|
|
113
|
+
▼
|
|
114
|
+
┌────────────────────────────────────────┐
|
|
115
|
+
│ Aggregated Data │
|
|
116
|
+
│ - Combined results │
|
|
117
|
+
│ - Enriched information │
|
|
118
|
+
│ - Summary statistics │
|
|
119
|
+
└────────────────────────────────────────┘
|
|
120
|
+
│
|
|
121
|
+
│ formatted by
|
|
122
|
+
▼
|
|
123
|
+
┌────────────────────────────────────────┐
|
|
124
|
+
│ AggregationReport │
|
|
125
|
+
│ - Human-readable report │
|
|
126
|
+
│ - Statistics and summaries │
|
|
127
|
+
└────────────────────────────────────────┘
|
|
128
|
+
----
|
|
129
|
+
|
|
130
|
+
=== Class Structure
|
|
131
|
+
|
|
132
|
+
[source]
|
|
133
|
+
----
|
|
134
|
+
APIEndpoint
|
|
135
|
+
├── name: String
|
|
136
|
+
├── url: String
|
|
137
|
+
├── timeout: Integer
|
|
138
|
+
└── rate_limit_delay: Float
|
|
139
|
+
|
|
140
|
+
APIAggregator
|
|
141
|
+
├── endpoints: Array<APIEndpoint>
|
|
142
|
+
├── results: Hash
|
|
143
|
+
├── errors: Hash
|
|
144
|
+
├── add_endpoint(endpoint)
|
|
145
|
+
├── fetch_all(options)
|
|
146
|
+
└── aggregate_data(results)
|
|
147
|
+
|
|
148
|
+
MockAPIResponses (Module)
|
|
149
|
+
├── USERS_API: Array
|
|
150
|
+
├── PRODUCTS_API: Array
|
|
151
|
+
├── ORDERS_API: Array
|
|
152
|
+
├── ANALYTICS_API: Hash
|
|
153
|
+
└── get_response(endpoint_name, options)
|
|
154
|
+
|
|
155
|
+
AggregationReport
|
|
156
|
+
└── generate(data, output_file)
|
|
157
|
+
----
|
|
158
|
+
|
|
159
|
+
== Usage
|
|
160
|
+
|
|
161
|
+
=== Basic Usage
|
|
162
|
+
|
|
163
|
+
Run the aggregator with default settings:
|
|
164
|
+
|
|
165
|
+
[source,bash]
|
|
166
|
+
----
|
|
167
|
+
ruby api_aggregator.rb
|
|
168
|
+
----
|
|
169
|
+
|
|
170
|
+
Output:
|
|
171
|
+
|
|
172
|
+
[source]
|
|
173
|
+
----
|
|
174
|
+
=== API Data Aggregator with Circuit Breaker ===
|
|
175
|
+
|
|
176
|
+
Fetching data from 4 API endpoints...
|
|
177
|
+
Circuit breaker enabled with 3 failures threshold
|
|
178
|
+
Retry enabled with exponential backoff (max 3 attempts)
|
|
179
|
+
|
|
180
|
+
[users] Fetching from https://api.example.com/users...
|
|
181
|
+
[products] Fetching from https://api.example.com/products...
|
|
182
|
+
[orders] Fetching from https://api.example.com/orders...
|
|
183
|
+
[analytics] Fetching from https://api.example.com/analytics...
|
|
184
|
+
[users] ✓ Success (3 items)
|
|
185
|
+
[products] ✓ Success (3 items)
|
|
186
|
+
[orders] ✓ Success (3 items)
|
|
187
|
+
[analytics] ✓ Success (N/A items)
|
|
188
|
+
|
|
189
|
+
=== Aggregation Complete ===
|
|
190
|
+
Successful fetches: 4
|
|
191
|
+
Failed fetches: 0
|
|
192
|
+
Total API requests: 4
|
|
193
|
+
----
|
|
194
|
+
|
|
195
|
+
=== Simulate API Errors
|
|
196
|
+
|
|
197
|
+
Test circuit breaker behavior:
|
|
198
|
+
|
|
199
|
+
[source,bash]
|
|
200
|
+
----
|
|
201
|
+
ruby api_aggregator.rb --simulate-errors
|
|
202
|
+
----
|
|
203
|
+
|
|
204
|
+
This triggers API errors to demonstrate:
|
|
205
|
+
|
|
206
|
+
* Automatic retry with backoff
|
|
207
|
+
* Circuit breaker opening after threshold
|
|
208
|
+
* Fast-fail behavior when circuit is open
|
|
209
|
+
* Recovery when circuit enters half-open state
|
|
210
|
+
|
|
211
|
+
=== Simulate Slow Responses
|
|
212
|
+
|
|
213
|
+
Test timeout handling:
|
|
214
|
+
|
|
215
|
+
[source,bash]
|
|
216
|
+
----
|
|
217
|
+
ruby api_aggregator.rb --simulate-slow
|
|
218
|
+
----
|
|
219
|
+
|
|
220
|
+
=== Save Report to File
|
|
221
|
+
|
|
222
|
+
Generate and save aggregation report:
|
|
223
|
+
|
|
224
|
+
[source,bash]
|
|
225
|
+
----
|
|
226
|
+
ruby api_aggregator.rb -o reports/aggregation.txt
|
|
227
|
+
----
|
|
228
|
+
|
|
229
|
+
=== Command-Line Options
|
|
230
|
+
|
|
231
|
+
[source,bash]
|
|
232
|
+
----
|
|
233
|
+
Usage: api_aggregator.rb [options]
|
|
234
|
+
|
|
235
|
+
Options:
|
|
236
|
+
--simulate-errors Simulate API errors to test circuit breaker
|
|
237
|
+
--simulate-slow Simulate slow API responses
|
|
238
|
+
-o, --output FILE Output report file
|
|
239
|
+
-h, --help Show this message
|
|
240
|
+
----
|
|
241
|
+
|
|
242
|
+
== Examples
|
|
243
|
+
|
|
244
|
+
=== Example 1: Successful Aggregation
|
|
245
|
+
|
|
246
|
+
[source,bash]
|
|
247
|
+
----
|
|
248
|
+
$ ruby api_aggregator.rb
|
|
249
|
+
|
|
250
|
+
================================================================================
|
|
251
|
+
API AGGREGATION REPORT
|
|
252
|
+
================================================================================
|
|
253
|
+
|
|
254
|
+
SUMMARY
|
|
255
|
+
--------------------------------------------------------------------------------
|
|
256
|
+
Total Users: 3
|
|
257
|
+
Total Products: 3
|
|
258
|
+
Total Orders: 3
|
|
259
|
+
Endpoints Successful: 4
|
|
260
|
+
Endpoints Failed: 0
|
|
261
|
+
Timestamp: 2024-10-25T13:00:00+08:00
|
|
262
|
+
|
|
263
|
+
USERS (3)
|
|
264
|
+
--------------------------------------------------------------------------------
|
|
265
|
+
1. Alice Johnson <alice@example.com> [admin]
|
|
266
|
+
2. Bob Smith <bob@example.com> [user]
|
|
267
|
+
3. Carol Williams <carol@example.com> [user]
|
|
268
|
+
|
|
269
|
+
PRODUCTS (3)
|
|
270
|
+
--------------------------------------------------------------------------------
|
|
271
|
+
1. Laptop - $999.99 (Stock: 15)
|
|
272
|
+
2. Mouse - $29.99 (Stock: 50)
|
|
273
|
+
3. Keyboard - $79.99 (Stock: 30)
|
|
274
|
+
|
|
275
|
+
ENRICHED ORDERS (3)
|
|
276
|
+
--------------------------------------------------------------------------------
|
|
277
|
+
Order #1001:
|
|
278
|
+
User: Alice Johnson <alice@example.com>
|
|
279
|
+
Product: Laptop
|
|
280
|
+
Quantity: 1 × $999.99 = $999.99
|
|
281
|
+
Status: shipped
|
|
282
|
+
|
|
283
|
+
Order #1002:
|
|
284
|
+
User: Bob Smith <bob@example.com>
|
|
285
|
+
Product: Mouse
|
|
286
|
+
Quantity: 2 × $29.99 = $59.98
|
|
287
|
+
Status: pending
|
|
288
|
+
|
|
289
|
+
Order #1003:
|
|
290
|
+
User: Carol Williams <carol@example.com>
|
|
291
|
+
Product: Keyboard
|
|
292
|
+
Quantity: 1 × $79.99 = $79.99
|
|
293
|
+
Status: delivered
|
|
294
|
+
|
|
295
|
+
ANALYTICS
|
|
296
|
+
--------------------------------------------------------------------------------
|
|
297
|
+
Total users: 3
|
|
298
|
+
Total products: 3
|
|
299
|
+
Total orders: 3
|
|
300
|
+
Revenue: 1139.97
|
|
301
|
+
Timestamp: 2024-10-25T13:00:00+08:00
|
|
302
|
+
|
|
303
|
+
================================================================================
|
|
304
|
+
----
|
|
305
|
+
|
|
306
|
+
=== Example 2: Error Recovery with Circuit Breaker
|
|
307
|
+
|
|
308
|
+
[source,bash]
|
|
309
|
+
----
|
|
310
|
+
$ ruby api_aggregator.rb --simulate-errors
|
|
311
|
+
|
|
312
|
+
Fetching data from 4 API endpoints...
|
|
313
|
+
Circuit breaker enabled with 3 failures threshold
|
|
314
|
+
Retry enabled with exponential backoff (max 3 attempts)
|
|
315
|
+
|
|
316
|
+
[users] Fetching from https://api.example.com/users...
|
|
317
|
+
[users] ✗ Error: Simulated API error
|
|
318
|
+
[users] Retrying (attempt 2/3) after 0.5s...
|
|
319
|
+
[users] ✗ Error: Simulated API error
|
|
320
|
+
[users] Retrying (attempt 3/3) after 1.0s...
|
|
321
|
+
[users] ✗ Error: Simulated API error
|
|
322
|
+
[users] All retry attempts exhausted
|
|
323
|
+
|
|
324
|
+
Circuit breaker OPEN for workflow after 3 failures
|
|
325
|
+
[products] Fast-failing due to open circuit
|
|
326
|
+
[orders] Fast-failing due to open circuit
|
|
327
|
+
[analytics] Fast-failing due to open circuit
|
|
328
|
+
|
|
329
|
+
=== Aggregation Complete ===
|
|
330
|
+
Successful fetches: 0
|
|
331
|
+
Failed fetches: 4
|
|
332
|
+
Total API requests: 3
|
|
333
|
+
|
|
334
|
+
SUMMARY
|
|
335
|
+
--------------------------------------------------------------------------------
|
|
336
|
+
Endpoints Successful: 0
|
|
337
|
+
Endpoints Failed: 4
|
|
338
|
+
----
|
|
339
|
+
|
|
340
|
+
=== Example 3: Partial Success
|
|
341
|
+
|
|
342
|
+
When some endpoints succeed and others fail:
|
|
343
|
+
|
|
344
|
+
[source]
|
|
345
|
+
----
|
|
346
|
+
=== Aggregation Complete ===
|
|
347
|
+
Successful fetches: 2
|
|
348
|
+
Failed fetches: 2
|
|
349
|
+
Total API requests: 8
|
|
350
|
+
|
|
351
|
+
SUMMARY
|
|
352
|
+
--------------------------------------------------------------------------------
|
|
353
|
+
Total Users: 3
|
|
354
|
+
Total Products: 3
|
|
355
|
+
Total Orders: 0
|
|
356
|
+
Endpoints Successful: 2
|
|
357
|
+
Endpoints Failed: 2
|
|
358
|
+
----
|
|
359
|
+
|
|
360
|
+
The aggregator gracefully handles partial failures and returns available data.
|
|
361
|
+
|
|
362
|
+
== Implementation Details
|
|
363
|
+
|
|
364
|
+
=== Workflow Configuration
|
|
365
|
+
|
|
366
|
+
The aggregator uses [`Fractor::Workflow`](../../lib/fractor/workflow.rb:1) with:
|
|
367
|
+
|
|
368
|
+
[source,ruby]
|
|
369
|
+
----
|
|
370
|
+
workflow = Fractor::Workflow.new(name: "API Aggregation Workflow")
|
|
371
|
+
|
|
372
|
+
# Configure circuit breaker
|
|
373
|
+
workflow.configure_circuit_breaker(
|
|
374
|
+
failure_threshold: 3, # Open after 3 failures
|
|
375
|
+
timeout: 10, # Retest after 10 seconds
|
|
376
|
+
half_open_attempts: 2 # Need 2 successes to close
|
|
377
|
+
)
|
|
378
|
+
|
|
379
|
+
# Add tasks with retry
|
|
380
|
+
workflow.task(:users) do |context|
|
|
381
|
+
fetch_endpoint(users_endpoint, context)
|
|
382
|
+
end.retry_on(
|
|
383
|
+
StandardError,
|
|
384
|
+
max_attempts: 3,
|
|
385
|
+
backoff: :exponential,
|
|
386
|
+
base_delay: 0.5
|
|
387
|
+
)
|
|
388
|
+
----
|
|
389
|
+
|
|
390
|
+
=== Mock API Responses
|
|
391
|
+
|
|
392
|
+
For demonstration, the example uses mock data instead of real HTTP calls:
|
|
393
|
+
|
|
394
|
+
[source,ruby]
|
|
395
|
+
----
|
|
396
|
+
module MockAPIResponses
|
|
397
|
+
USERS_API = [
|
|
398
|
+
{ id: 1, name: "Alice Johnson", ... },
|
|
399
|
+
# ...
|
|
400
|
+
]
|
|
401
|
+
|
|
402
|
+
def self.get_response(endpoint_name, simulate_error: false, simulate_slow: false)
|
|
403
|
+
sleep(2) if simulate_slow
|
|
404
|
+
raise "Simulated API error" if simulate_error
|
|
405
|
+
|
|
406
|
+
{ status: "success", data: USERS_API, timestamp: Time.now.iso8601 }
|
|
407
|
+
end
|
|
408
|
+
end
|
|
409
|
+
----
|
|
410
|
+
|
|
411
|
+
In production, replace with actual HTTP calls using `Net::HTTP` or `HTTP.rb`.
|
|
412
|
+
|
|
413
|
+
=== Data Enrichment Logic
|
|
414
|
+
|
|
415
|
+
Orders are enriched by joining with users and products:
|
|
416
|
+
|
|
417
|
+
[source,ruby]
|
|
418
|
+
----
|
|
419
|
+
def enrich_orders(orders, users, products)
|
|
420
|
+
orders.map do |order|
|
|
421
|
+
user = users.find { |u| u[:id] == order[:user_id] }
|
|
422
|
+
product = products.find { |p| p[:id] == order[:product_id] }
|
|
423
|
+
|
|
424
|
+
order.merge(
|
|
425
|
+
user_name: user&.dig(:name),
|
|
426
|
+
user_email: user&.dig(:email),
|
|
427
|
+
product_name: product&.dig(:name),
|
|
428
|
+
product_price: product&.dig(:price),
|
|
429
|
+
total_price: (product&.dig(:price) || 0) * order[:quantity]
|
|
430
|
+
)
|
|
431
|
+
end
|
|
432
|
+
end
|
|
433
|
+
----
|
|
434
|
+
|
|
435
|
+
== Circuit Breaker States
|
|
436
|
+
|
|
437
|
+
=== Closed State (Normal Operation)
|
|
438
|
+
|
|
439
|
+
* All requests proceed normally
|
|
440
|
+
* Failures are counted
|
|
441
|
+
* If failures reach threshold (3), circuit opens
|
|
442
|
+
|
|
443
|
+
=== Open State (Failing Fast)
|
|
444
|
+
|
|
445
|
+
* All requests fail immediately without trying
|
|
446
|
+
* No actual API calls are made
|
|
447
|
+
* After timeout period (10s), transitions to half-open
|
|
448
|
+
|
|
449
|
+
=== Half-Open State (Testing Recovery)
|
|
450
|
+
|
|
451
|
+
* Limited number of requests allowed through (2)
|
|
452
|
+
* If requests succeed, circuit closes
|
|
453
|
+
* If requests fail, circuit reopens
|
|
454
|
+
|
|
455
|
+
== Error Handling
|
|
456
|
+
|
|
457
|
+
=== Retry Strategy
|
|
458
|
+
|
|
459
|
+
Each failed request is retried up to 3 times:
|
|
460
|
+
|
|
461
|
+
1. **First retry**: Wait 0.5 seconds
|
|
462
|
+
2. **Second retry**: Wait 1.0 second (2× base delay)
|
|
463
|
+
3. **Third retry**: Wait 2.0 seconds (4× base delay)
|
|
464
|
+
|
|
465
|
+
After 3 failures, the request is marked as failed.
|
|
466
|
+
|
|
467
|
+
=== Graceful Degradation
|
|
468
|
+
|
|
469
|
+
When some endpoints fail:
|
|
470
|
+
|
|
471
|
+
* Continue with available data
|
|
472
|
+
* Report partial results
|
|
473
|
+
* Include error information in summary
|
|
474
|
+
* Don't block entire aggregation
|
|
475
|
+
|
|
476
|
+
=== Error Types
|
|
477
|
+
|
|
478
|
+
* `StandardError`: Network errors, timeouts, API errors
|
|
479
|
+
* `CircuitBreakerError`: When circuit is open
|
|
480
|
+
* All errors are caught and reported in the errors hash
|
|
481
|
+
|
|
482
|
+
== Performance Considerations
|
|
483
|
+
|
|
484
|
+
=== Parallel Execution
|
|
485
|
+
|
|
486
|
+
All API endpoints are fetched in parallel using Fractor's workflow execution:
|
|
487
|
+
|
|
488
|
+
* Endpoints: 4
|
|
489
|
+
* Max parallelism: 4 (limited by worker count)
|
|
490
|
+
* Sequential time: ~0.4s (4 × 0.1s rate limit)
|
|
491
|
+
* Parallel time: ~0.1s (max of all delays)
|
|
492
|
+
|
|
493
|
+
=== Rate Limiting
|
|
494
|
+
|
|
495
|
+
Each endpoint has configurable rate limiting:
|
|
496
|
+
|
|
497
|
+
* Prevents overwhelming API servers
|
|
498
|
+
* Respects API provider guidelines
|
|
499
|
+
* Can be adjusted per endpoint
|
|
500
|
+
|
|
501
|
+
=== Memory Usage
|
|
502
|
+
|
|
503
|
+
The aggregator is memory-efficient:
|
|
504
|
+
|
|
505
|
+
* Streams results as they arrive
|
|
506
|
+
* No large intermediate buffers
|
|
507
|
+
* Garbage collection friendly
|
|
508
|
+
|
|
509
|
+
== Testing
|
|
510
|
+
|
|
511
|
+
Run the test suite:
|
|
512
|
+
|
|
513
|
+
[source,bash]
|
|
514
|
+
----
|
|
515
|
+
bundle exec rspec spec/examples/api_aggregator_spec.rb
|
|
516
|
+
----
|
|
517
|
+
|
|
518
|
+
The test suite covers:
|
|
519
|
+
|
|
520
|
+
* APIEndpoint configuration
|
|
521
|
+
* Mock API responses
|
|
522
|
+
* Workflow execution
|
|
523
|
+
* Circuit breaker behavior
|
|
524
|
+
* Retry logic
|
|
525
|
+
* Data aggregation
|
|
526
|
+
* Error handling
|
|
527
|
+
* Report generation
|
|
528
|
+
|
|
529
|
+
== Best Practices
|
|
530
|
+
|
|
531
|
+
=== Production Deployment
|
|
532
|
+
|
|
533
|
+
For production use:
|
|
534
|
+
|
|
535
|
+
1. **Replace mock responses** with real HTTP client
|
|
536
|
+
2. **Add authentication** (API keys, OAuth tokens)
|
|
537
|
+
3. **Configure timeouts** based on SLA requirements
|
|
538
|
+
4. **Adjust circuit breaker** thresholds for your use case
|
|
539
|
+
5. **Add monitoring** and alerting for failures
|
|
540
|
+
6. **Use connection pools** for efficiency
|
|
541
|
+
7. **Implement caching** for frequently accessed data
|
|
542
|
+
|
|
543
|
+
=== Configuration Guidelines
|
|
544
|
+
|
|
545
|
+
* **Circuit Breaker Threshold**: Set based on acceptable failure rate (typically 3-5)
|
|
546
|
+
* **Retry Attempts**: Balance responsiveness vs. resilience (typically 2-3)
|
|
547
|
+
* **Timeouts**: Set based on P99 latency + buffer (typically 5-10s)
|
|
548
|
+
* **Rate Limits**: Respect API provider guidelines (check documentation)
|
|
549
|
+
|
|
550
|
+
=== Error Monitoring
|
|
551
|
+
|
|
552
|
+
Monitor these metrics:
|
|
553
|
+
|
|
554
|
+
* Circuit breaker state changes
|
|
555
|
+
* Retry attempt counts
|
|
556
|
+
* Failed endpoint calls
|
|
557
|
+
* Response time percentiles
|
|
558
|
+
* Success/failure rates per endpoint
|
|
559
|
+
|
|
560
|
+
== Troubleshooting
|
|
561
|
+
|
|
562
|
+
=== Circuit Breaker Always Open
|
|
563
|
+
|
|
564
|
+
* Check failure threshold is appropriate
|
|
565
|
+
* Verify API endpoints are reachable
|
|
566
|
+
* Check for network connectivity issues
|
|
567
|
+
* Review timeout settings
|
|
568
|
+
|
|
569
|
+
=== Slow Performance
|
|
570
|
+
|
|
571
|
+
* Verify rate limit delays aren't excessive
|
|
572
|
+
* Check for slow API endpoints
|
|
573
|
+
* Consider reducing timeout values
|
|
574
|
+
* Review retry backoff configuration
|
|
575
|
+
|
|
576
|
+
=== Partial Data
|
|
577
|
+
|
|
578
|
+
* Check which endpoints are failing
|
|
579
|
+
* Review error messages in errors hash
|
|
580
|
+
* Verify API credentials/permissions
|
|
581
|
+
* Test endpoints individually
|
|
582
|
+
|
|
583
|
+
== Extending the Aggregator
|
|
584
|
+
|
|
585
|
+
=== Adding New Endpoints
|
|
586
|
+
|
|
587
|
+
[source,ruby]
|
|
588
|
+
----
|
|
589
|
+
aggregator.add_endpoint(APIEndpoint.new(
|
|
590
|
+
name: "inventory",
|
|
591
|
+
url: "https://api.example.com/inventory",
|
|
592
|
+
timeout: 5,
|
|
593
|
+
rate_limit_delay: 0.2
|
|
594
|
+
))
|
|
595
|
+
----
|
|
596
|
+
|
|
597
|
+
=== Custom Enrichment Logic
|
|
598
|
+
|
|
599
|
+
[source,ruby]
|
|
600
|
+
----
|
|
601
|
+
def custom_enrichment(results)
|
|
602
|
+
# Your custom data transformation
|
|
603
|
+
results[:custom_field] = calculate_something(results)
|
|
604
|
+
results
|
|
605
|
+
end
|
|
606
|
+
----
|
|
607
|
+
|
|
608
|
+
=== Alternative Retry Strategies
|
|
609
|
+
|
|
610
|
+
[source,ruby]
|
|
611
|
+
----
|
|
612
|
+
workflow.task(:endpoint) do
|
|
613
|
+
# Task logic
|
|
614
|
+
end.retry_on(
|
|
615
|
+
NetworkError,
|
|
616
|
+
max_attempts: 5,
|
|
617
|
+
backoff: :linear, # or :constant
|
|
618
|
+
base_delay: 1.0
|
|
619
|
+
)
|
|
620
|
+
----
|
|
621
|
+
|
|
622
|
+
== See Also
|
|
623
|
+
|
|
624
|
+
* link:../../README.adoc[Fractor Main Documentation]
|
|
625
|
+
* link:../../docs/workflows.adoc[Workflow Guide]
|
|
626
|
+
* link:../log_analyzer/README.adoc[Log Analyzer Example]
|
|
627
|
+
* link:../web_scraper/README.adoc[Web Scraper Example]
|