ruby_reactor 0.1.0
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 +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +98 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/README.md +570 -0
- data/Rakefile +12 -0
- data/documentation/DAG.md +457 -0
- data/documentation/README.md +123 -0
- data/documentation/async_reactors.md +369 -0
- data/documentation/composition.md +199 -0
- data/documentation/core_concepts.md +662 -0
- data/documentation/data_pipelines.md +224 -0
- data/documentation/examples/inventory_management.md +749 -0
- data/documentation/examples/order_processing.md +365 -0
- data/documentation/examples/payment_processing.md +654 -0
- data/documentation/getting_started.md +224 -0
- data/documentation/retry_configuration.md +357 -0
- data/lib/ruby_reactor/async_router.rb +91 -0
- data/lib/ruby_reactor/configuration.rb +41 -0
- data/lib/ruby_reactor/context.rb +169 -0
- data/lib/ruby_reactor/context_serializer.rb +164 -0
- data/lib/ruby_reactor/dependency_graph.rb +126 -0
- data/lib/ruby_reactor/dsl/compose_builder.rb +86 -0
- data/lib/ruby_reactor/dsl/map_builder.rb +112 -0
- data/lib/ruby_reactor/dsl/reactor.rb +151 -0
- data/lib/ruby_reactor/dsl/step_builder.rb +177 -0
- data/lib/ruby_reactor/dsl/template_helpers.rb +36 -0
- data/lib/ruby_reactor/dsl/validation_helpers.rb +35 -0
- data/lib/ruby_reactor/error/base.rb +16 -0
- data/lib/ruby_reactor/error/compensation_error.rb +8 -0
- data/lib/ruby_reactor/error/context_too_large_error.rb +11 -0
- data/lib/ruby_reactor/error/dependency_error.rb +8 -0
- data/lib/ruby_reactor/error/deserialization_error.rb +11 -0
- data/lib/ruby_reactor/error/input_validation_error.rb +29 -0
- data/lib/ruby_reactor/error/schema_version_error.rb +11 -0
- data/lib/ruby_reactor/error/step_failure_error.rb +18 -0
- data/lib/ruby_reactor/error/undo_error.rb +8 -0
- data/lib/ruby_reactor/error/validation_error.rb +8 -0
- data/lib/ruby_reactor/executor/compensation_manager.rb +79 -0
- data/lib/ruby_reactor/executor/graph_manager.rb +41 -0
- data/lib/ruby_reactor/executor/input_validator.rb +39 -0
- data/lib/ruby_reactor/executor/result_handler.rb +103 -0
- data/lib/ruby_reactor/executor/retry_manager.rb +156 -0
- data/lib/ruby_reactor/executor/step_executor.rb +319 -0
- data/lib/ruby_reactor/executor.rb +123 -0
- data/lib/ruby_reactor/map/collector.rb +65 -0
- data/lib/ruby_reactor/map/element_executor.rb +154 -0
- data/lib/ruby_reactor/map/execution.rb +60 -0
- data/lib/ruby_reactor/map/helpers.rb +67 -0
- data/lib/ruby_reactor/max_retries_exhausted_failure.rb +19 -0
- data/lib/ruby_reactor/reactor.rb +75 -0
- data/lib/ruby_reactor/retry_context.rb +92 -0
- data/lib/ruby_reactor/retry_queued_result.rb +26 -0
- data/lib/ruby_reactor/sidekiq_workers/map_collector_worker.rb +13 -0
- data/lib/ruby_reactor/sidekiq_workers/map_element_worker.rb +13 -0
- data/lib/ruby_reactor/sidekiq_workers/map_execution_worker.rb +15 -0
- data/lib/ruby_reactor/sidekiq_workers/worker.rb +55 -0
- data/lib/ruby_reactor/step/compose_step.rb +107 -0
- data/lib/ruby_reactor/step/map_step.rb +234 -0
- data/lib/ruby_reactor/step.rb +33 -0
- data/lib/ruby_reactor/storage/adapter.rb +51 -0
- data/lib/ruby_reactor/storage/configuration.rb +15 -0
- data/lib/ruby_reactor/storage/redis_adapter.rb +140 -0
- data/lib/ruby_reactor/template/base.rb +15 -0
- data/lib/ruby_reactor/template/element.rb +25 -0
- data/lib/ruby_reactor/template/input.rb +48 -0
- data/lib/ruby_reactor/template/result.rb +48 -0
- data/lib/ruby_reactor/template/value.rb +22 -0
- data/lib/ruby_reactor/validation/base.rb +26 -0
- data/lib/ruby_reactor/validation/input_validator.rb +62 -0
- data/lib/ruby_reactor/validation/schema_builder.rb +17 -0
- data/lib/ruby_reactor/version.rb +5 -0
- data/lib/ruby_reactor.rb +159 -0
- data/sig/ruby_reactor.rbs +4 -0
- metadata +178 -0
|
@@ -0,0 +1,457 @@
|
|
|
1
|
+
# DAG Execution and Saga Patterns in RubyReactor
|
|
2
|
+
|
|
3
|
+
RubyReactor uses Directed Acyclic Graphs (DAGs) to manage step dependencies and implements saga patterns for reliable distributed transactions. This document explains how DAG execution works and how saga patterns ensure consistency across complex workflows.
|
|
4
|
+
|
|
5
|
+
## Directed Acyclic Graphs (DAGs)
|
|
6
|
+
|
|
7
|
+
A DAG is a graph with directed edges and no cycles, ensuring that step dependencies can be resolved in a deterministic order.
|
|
8
|
+
|
|
9
|
+
### DAG Structure
|
|
10
|
+
|
|
11
|
+
```mermaid
|
|
12
|
+
graph TD
|
|
13
|
+
A[Step A] --> C[Step C]
|
|
14
|
+
B[Step B] --> C
|
|
15
|
+
C --> D[Step D]
|
|
16
|
+
C --> E[Step E]
|
|
17
|
+
D --> F[Step F]
|
|
18
|
+
E --> F
|
|
19
|
+
|
|
20
|
+
style A fill:#e1f5fe
|
|
21
|
+
style B fill:#e1f5fe
|
|
22
|
+
style C fill:#fff3e0
|
|
23
|
+
style D fill:#e8f5e8
|
|
24
|
+
style E fill:#e8f5e8
|
|
25
|
+
style F fill:#ffebee
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
**Legend:**
|
|
29
|
+
- 🔵 Independent steps (can run in parallel)
|
|
30
|
+
- 🟡 Dependent steps (wait for prerequisites)
|
|
31
|
+
- 🟢 Ready to execute
|
|
32
|
+
- 🔴 Final step
|
|
33
|
+
|
|
34
|
+
### DAG Execution Algorithm
|
|
35
|
+
|
|
36
|
+
```mermaid
|
|
37
|
+
graph TD
|
|
38
|
+
A[Start Execution] --> B[Build Dependency Graph]
|
|
39
|
+
B --> C[Identify Ready Steps<br/>no unmet dependencies]
|
|
40
|
+
C --> D{Ready Steps<br/>Available?}
|
|
41
|
+
D -->|No| E[Execution Complete]
|
|
42
|
+
D -->|Yes| F[Execute Ready Steps<br/>in Parallel]
|
|
43
|
+
F --> G[Update Dependencies<br/>Mark steps complete]
|
|
44
|
+
G --> H[Collect Results]
|
|
45
|
+
H --> I{Execution<br/>Successful?}
|
|
46
|
+
I -->|Yes| C
|
|
47
|
+
I -->|No| J[Initiate Saga Compensation]
|
|
48
|
+
J --> K[Execution Failed]
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Dependency Resolution
|
|
52
|
+
|
|
53
|
+
```mermaid
|
|
54
|
+
graph TD
|
|
55
|
+
A[Step Dependencies] --> B[Topological Sort]
|
|
56
|
+
B --> C[Execution Order<br/>A → B → C → D]
|
|
57
|
+
C --> D[Parallel Execution<br/>Where Possible]
|
|
58
|
+
|
|
59
|
+
subgraph "Level 1"
|
|
60
|
+
A1[Independent Steps]
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
subgraph "Level 2"
|
|
64
|
+
B1[Steps depending on Level 1]
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
subgraph "Level 3"
|
|
68
|
+
C1[Steps depending on Level 2]
|
|
69
|
+
end
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Saga Patterns
|
|
73
|
+
|
|
74
|
+
RubyReactor implements saga patterns to ensure consistency across distributed operations. Sagas provide transactional semantics for long-running business processes.
|
|
75
|
+
|
|
76
|
+
### Saga Execution Flow
|
|
77
|
+
|
|
78
|
+
```mermaid
|
|
79
|
+
graph TD
|
|
80
|
+
A[Saga Initiated] --> B[Execute Step 1]
|
|
81
|
+
B --> C{Step 1<br/>Success?}
|
|
82
|
+
C -->|Yes| D[Execute Step 2]
|
|
83
|
+
C -->|No| E[Compensation Step 1]
|
|
84
|
+
D --> F{Step 2<br/>Success?}
|
|
85
|
+
F -->|Yes| G[Execute Step 3]
|
|
86
|
+
F -->|No| H[Compensate Step 2, Undo Step 1]
|
|
87
|
+
G --> I{Step 3<br/>Success?}
|
|
88
|
+
I -->|Yes| J[Saga Complete]
|
|
89
|
+
I -->|No| K[Compensate Step 3<br/>Then Undo Steps 2 and 1]
|
|
90
|
+
H --> L[Saga Failed]
|
|
91
|
+
K --> L
|
|
92
|
+
E --> L
|
|
93
|
+
|
|
94
|
+
style A fill:#e3f2fd
|
|
95
|
+
style J fill:#e8f5e8
|
|
96
|
+
style L fill:#ffebee
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Compensation Strategies
|
|
100
|
+
|
|
101
|
+
#### Backward Recovery (Rollback)
|
|
102
|
+
|
|
103
|
+
```mermaid
|
|
104
|
+
graph TD
|
|
105
|
+
A[Forward Execution] --> B[Step A Success]
|
|
106
|
+
B --> C[Step B Success]
|
|
107
|
+
C --> D[Step C Fails]
|
|
108
|
+
D --> E[Start Compensation]
|
|
109
|
+
E --> F[Compensate C]
|
|
110
|
+
F --> G[Undo B]
|
|
111
|
+
G --> H[Undo A]
|
|
112
|
+
H --> I[Consistent State Restored]
|
|
113
|
+
|
|
114
|
+
style A fill:#e3f2fd
|
|
115
|
+
style I fill:#e8f5e8
|
|
116
|
+
style D fill:#ffebee
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
#### Forward Recovery (Retry)
|
|
120
|
+
|
|
121
|
+
```mermaid
|
|
122
|
+
graph TD
|
|
123
|
+
A[Step Fails] --> B{Can Retry?}
|
|
124
|
+
B -->|Yes| C[Calculate Backoff]
|
|
125
|
+
C --> D[Schedule Retry]
|
|
126
|
+
D --> E[Retry Execution]
|
|
127
|
+
E --> F{Retry<br/>Success?}
|
|
128
|
+
F -->|Yes| G[Continue Saga]
|
|
129
|
+
F -->|No| B
|
|
130
|
+
B -->|No| H[Compensation Required]
|
|
131
|
+
|
|
132
|
+
style G fill:#e8f5e8
|
|
133
|
+
style H fill:#ffebee
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Complex Saga Scenarios
|
|
137
|
+
|
|
138
|
+
### Order Processing Saga
|
|
139
|
+
|
|
140
|
+
```mermaid
|
|
141
|
+
graph TD
|
|
142
|
+
A[Order Submitted] --> B[Validate Order]
|
|
143
|
+
B --> C{Valid?}
|
|
144
|
+
C -->|No| D[Saga Failed]
|
|
145
|
+
C -->|Yes| E[Reserve Inventory]
|
|
146
|
+
E --> F{Reserved?}
|
|
147
|
+
F -->|No| G[Saga Failed]
|
|
148
|
+
F -->|Yes| H[Process Payment]
|
|
149
|
+
H --> I{Payment OK?}
|
|
150
|
+
I -->|No| J[Release Inventory<br/>Compensation]
|
|
151
|
+
I -->|Yes| K[Update Order Status]
|
|
152
|
+
K --> L[Send Confirmation]
|
|
153
|
+
L --> M[Saga Complete]
|
|
154
|
+
|
|
155
|
+
J --> D
|
|
156
|
+
G --> D
|
|
157
|
+
|
|
158
|
+
style A fill:#e3f2fd
|
|
159
|
+
style M fill:#e8f5e8
|
|
160
|
+
style D fill:#ffebee
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
## DAG + Saga Integration
|
|
165
|
+
|
|
166
|
+
RubyReactor combines DAG execution with saga patterns for complex workflows:
|
|
167
|
+
|
|
168
|
+
```mermaid
|
|
169
|
+
graph TD
|
|
170
|
+
subgraph "DAG Execution"
|
|
171
|
+
A1[Build Graph] --> B1[Resolve Dependencies]
|
|
172
|
+
B1 --> C1[Execute in Order]
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
subgraph "Saga Pattern"
|
|
176
|
+
D1[Monitor Execution] --> E1{Step Fails?}
|
|
177
|
+
E1 -->|No| F1[Continue]
|
|
178
|
+
E1 -->|Yes| G1[Trigger Compensation]
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
subgraph "Retry Mechanism"
|
|
182
|
+
H1[Failed Step] --> I1{Can Retry?}
|
|
183
|
+
I1 -->|Yes| J1[Requeue Job]
|
|
184
|
+
I1 -->|No| K1[Final Compensation]
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
C1 --> D1
|
|
188
|
+
G1 --> H1
|
|
189
|
+
J1 --> A1
|
|
190
|
+
|
|
191
|
+
style F1 fill:#e8f5e8
|
|
192
|
+
style K1 fill:#ffebee
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## Execution States
|
|
196
|
+
|
|
197
|
+
### Step States
|
|
198
|
+
|
|
199
|
+
```mermaid
|
|
200
|
+
stateDiagram-v2
|
|
201
|
+
[*] --> Pending
|
|
202
|
+
Pending --> Running
|
|
203
|
+
Running --> Completed
|
|
204
|
+
Running --> Failed
|
|
205
|
+
Failed --> Compensating
|
|
206
|
+
Compensating --> Compensated
|
|
207
|
+
Compensated --> [*]
|
|
208
|
+
Failed --> Retrying
|
|
209
|
+
Retrying --> Pending
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### Saga States
|
|
213
|
+
|
|
214
|
+
```mermaid
|
|
215
|
+
stateDiagram-v2
|
|
216
|
+
[*] --> Initiated
|
|
217
|
+
Initiated --> Executing
|
|
218
|
+
Executing --> Completed
|
|
219
|
+
Executing --> Failing
|
|
220
|
+
Failing --> Compensating
|
|
221
|
+
Compensating --> Failed
|
|
222
|
+
Failed --> [*]
|
|
223
|
+
Completed --> [*]
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
## Error Handling Patterns
|
|
227
|
+
|
|
228
|
+
### Cascading Compensation
|
|
229
|
+
|
|
230
|
+
```mermaid
|
|
231
|
+
graph TD
|
|
232
|
+
A[Root Cause Failure] --> B[Step N Fails]
|
|
233
|
+
B --> C[Mark Step N for Compensation]
|
|
234
|
+
C --> D[Stop Downstream Steps]
|
|
235
|
+
D --> E[Execute Compensation Chain]
|
|
236
|
+
E --> F[Step N-1 Compensation]
|
|
237
|
+
F --> G[Step N-2 Compensation]
|
|
238
|
+
G --> H[...continue to Step 1]
|
|
239
|
+
H --> I[Consistent State Achieved]
|
|
240
|
+
|
|
241
|
+
style A fill:#ffebee
|
|
242
|
+
style I fill:#e8f5e8
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### Partial Success Handling
|
|
246
|
+
|
|
247
|
+
```mermaid
|
|
248
|
+
graph TD
|
|
249
|
+
A[Multi-Step Process] --> B[Step 1 Success]
|
|
250
|
+
B --> C[Step 2 Success]
|
|
251
|
+
C --> D[Step 3 Fails]
|
|
252
|
+
D --> E{Essential<br/>Step?}
|
|
253
|
+
E -->|Yes| F[Full Compensation Required]
|
|
254
|
+
E -->|No| G[Partial Success Acceptable]
|
|
255
|
+
G --> H[Continue with Successful Steps]
|
|
256
|
+
H --> I[Manual Intervention for Failed Step]
|
|
257
|
+
|
|
258
|
+
style G fill:#fff3e0
|
|
259
|
+
style I fill:#fff3e0
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
## Performance Considerations
|
|
263
|
+
|
|
264
|
+
### Parallel Execution in DAGs
|
|
265
|
+
|
|
266
|
+
```mermaid
|
|
267
|
+
graph TD
|
|
268
|
+
A[Level 1<br/>Parallel Steps] --> B[Level 2<br/>Parallel Steps]
|
|
269
|
+
B --> C[Level 3<br/>Parallel Steps]
|
|
270
|
+
|
|
271
|
+
subgraph "Level 1 (3 steps)"
|
|
272
|
+
A1[Step A] & A2[Step B] & A3[Step C]
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
subgraph "Level 2 (2 steps)"
|
|
276
|
+
B1[Step D] & B2[Step E]
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
subgraph "Level 3 (1 step)"
|
|
280
|
+
C1[Step F]
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
A1 --> B1
|
|
284
|
+
A2 --> B1
|
|
285
|
+
A3 --> B2
|
|
286
|
+
B1 --> C1
|
|
287
|
+
B2 --> C1
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
### Saga Overhead
|
|
291
|
+
|
|
292
|
+
```mermaid
|
|
293
|
+
graph TD
|
|
294
|
+
A[Normal Execution] --> B[Fast Path<br/>No Compensation]
|
|
295
|
+
C[Saga Execution] --> D[State Tracking<br/>Compensation Logic]
|
|
296
|
+
D --> E[Increased Complexity]
|
|
297
|
+
|
|
298
|
+
A --> F[Better Performance]
|
|
299
|
+
C --> G[Reliability Benefits]
|
|
300
|
+
|
|
301
|
+
style F fill:#e8f5e8
|
|
302
|
+
style G fill:#e8f5e8
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
## Monitoring and Observability
|
|
306
|
+
|
|
307
|
+
### Saga Metrics
|
|
308
|
+
|
|
309
|
+
```mermaid
|
|
310
|
+
graph TD
|
|
311
|
+
A[Saga Metrics] --> B[Completion Rate]
|
|
312
|
+
A --> C[Average Duration]
|
|
313
|
+
A --> D[Compensation Frequency]
|
|
314
|
+
A --> E[Retry Attempts]
|
|
315
|
+
A --> F[Failure Patterns]
|
|
316
|
+
|
|
317
|
+
B --> G[Business Health]
|
|
318
|
+
C --> H[Performance Monitoring]
|
|
319
|
+
D --> I[Reliability Insights]
|
|
320
|
+
E --> J[Resilience Metrics]
|
|
321
|
+
F --> K[Root Cause Analysis]
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
### DAG Execution Monitoring
|
|
325
|
+
|
|
326
|
+
```mermaid
|
|
327
|
+
graph TD
|
|
328
|
+
A[DAG Monitoring] --> B[Step Dependencies]
|
|
329
|
+
A --> C[Execution Order]
|
|
330
|
+
A --> D[Parallel Execution]
|
|
331
|
+
A --> E[Bottleneck Identification]
|
|
332
|
+
|
|
333
|
+
B --> F[Dependency Health]
|
|
334
|
+
C --> G[Execution Flow]
|
|
335
|
+
D --> H[Concurrency Optimization]
|
|
336
|
+
E --> I[Performance Tuning]
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
## Best Practices
|
|
340
|
+
|
|
341
|
+
### DAG Design
|
|
342
|
+
|
|
343
|
+
1. **Keep it Simple**: Minimize dependencies to maximize parallelism
|
|
344
|
+
2. **Clear Dependencies**: Make step relationships explicit
|
|
345
|
+
3. **Avoid Cycles**: Ensure DAG remains acyclic
|
|
346
|
+
4. **Test Execution Order**: Verify topological sort produces expected order
|
|
347
|
+
|
|
348
|
+
### Saga Implementation
|
|
349
|
+
|
|
350
|
+
1. **Idempotent Operations**: Design steps to be safely retryable
|
|
351
|
+
2. **Compensation Logic**: Always implement proper undo operations
|
|
352
|
+
3. **State Tracking**: Maintain sufficient state for compensation
|
|
353
|
+
4. **Timeout Handling**: Set appropriate timeouts for long-running sagas
|
|
354
|
+
|
|
355
|
+
### Error Handling
|
|
356
|
+
|
|
357
|
+
1. **Graceful Degradation**: Handle partial failures appropriately
|
|
358
|
+
2. **Circuit Breakers**: Prevent cascade failures
|
|
359
|
+
3. **Monitoring**: Track saga health and failure patterns
|
|
360
|
+
4. **Recovery Procedures**: Document manual recovery processes
|
|
361
|
+
|
|
362
|
+
## Implementation Examples
|
|
363
|
+
|
|
364
|
+
### Simple DAG with Saga
|
|
365
|
+
|
|
366
|
+
```ruby
|
|
367
|
+
class OrderProcessingReactor < RubyReactor::Reactor
|
|
368
|
+
async true
|
|
369
|
+
|
|
370
|
+
step :validate_order do
|
|
371
|
+
run { validate_order_logic }
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
step :reserve_inventory do
|
|
375
|
+
argument :order, result(:validate_order)
|
|
376
|
+
run do |args, _context|
|
|
377
|
+
reserve_inventory_logic(args[:order])
|
|
378
|
+
end
|
|
379
|
+
|
|
380
|
+
compensate do
|
|
381
|
+
# Release reservation
|
|
382
|
+
release_inventory_logic
|
|
383
|
+
end
|
|
384
|
+
end
|
|
385
|
+
|
|
386
|
+
step :process_payment do
|
|
387
|
+
argument :inventory_result, result(:reserve_inventory)
|
|
388
|
+
run do |args, _context|
|
|
389
|
+
process_payment_logic(args[:inventory_result])
|
|
390
|
+
end
|
|
391
|
+
|
|
392
|
+
compensate do
|
|
393
|
+
# Refund payment
|
|
394
|
+
refund_payment_logic
|
|
395
|
+
end
|
|
396
|
+
end
|
|
397
|
+
|
|
398
|
+
step :confirm_order do
|
|
399
|
+
argument :payment_result, result(:process_payment)
|
|
400
|
+
run do |args, _context|
|
|
401
|
+
confirm_order_logic(args[:payment_result])
|
|
402
|
+
end
|
|
403
|
+
end
|
|
404
|
+
end
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
### Complex DAG with Parallel Execution
|
|
408
|
+
|
|
409
|
+
```ruby
|
|
410
|
+
class ComplexWorkflowReactor < RubyReactor::Reactor
|
|
411
|
+
async true
|
|
412
|
+
|
|
413
|
+
# Level 1 - Independent steps
|
|
414
|
+
step :validate_input do
|
|
415
|
+
run { validate_input_logic }
|
|
416
|
+
end
|
|
417
|
+
|
|
418
|
+
step :check_permissions do
|
|
419
|
+
run { check_permissions_logic }
|
|
420
|
+
end
|
|
421
|
+
|
|
422
|
+
# Level 2 - Depends on level 1
|
|
423
|
+
step :process_data do
|
|
424
|
+
argument :validation_result, result(:validate_input)
|
|
425
|
+
argument :permissions_result, result(:check_permissions)
|
|
426
|
+
run do |args, _context|
|
|
427
|
+
process_data_logic(args[:validation_result], args[:permissions_result])
|
|
428
|
+
end
|
|
429
|
+
end
|
|
430
|
+
|
|
431
|
+
# Level 3 - Parallel steps depending on level 2
|
|
432
|
+
step :send_notification do
|
|
433
|
+
argument :data_result, result(:process_data)
|
|
434
|
+
run do |args, _context|
|
|
435
|
+
send_notification_logic(args[:data_result])
|
|
436
|
+
end
|
|
437
|
+
end
|
|
438
|
+
|
|
439
|
+
step :update_audit_log do
|
|
440
|
+
argument :data_result, result(:process_data)
|
|
441
|
+
run do |args, _context|
|
|
442
|
+
update_audit_log_logic(args[:data_result])
|
|
443
|
+
end
|
|
444
|
+
end
|
|
445
|
+
|
|
446
|
+
# Level 4 - Depends on level 3
|
|
447
|
+
step :cleanup do
|
|
448
|
+
argument :notification_result, result(:send_notification)
|
|
449
|
+
argument :audit_result, result(:update_audit_log)
|
|
450
|
+
run do |args, _context|
|
|
451
|
+
cleanup_logic(args[:notification_result], args[:audit_result])
|
|
452
|
+
end
|
|
453
|
+
end
|
|
454
|
+
end
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
This DAG allows `send_notification` and `update_audit_log` to execute in parallel after `process_data` completes, demonstrating how RubyReactor maximizes concurrency while maintaining dependency ordering
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# RubyReactor Documentation
|
|
2
|
+
|
|
3
|
+
RubyReactor is a powerful Ruby framework for building reliable, sequential business processes with built-in error handling, compensation, and rollback capabilities.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [Getting Started](getting_started.md)
|
|
8
|
+
- [Core Concepts](core_concepts.md)
|
|
9
|
+
- [DAG Execution and Saga Patterns](DAG.md)
|
|
10
|
+
- [Async Reactors](async_reactors.md)
|
|
11
|
+
- [Retry Configuration](retry_configuration.md)
|
|
12
|
+
- [Migration Guide](migration_guide.md)
|
|
13
|
+
- [Examples](examples/)
|
|
14
|
+
- [Order Processing](examples/order_processing.md)
|
|
15
|
+
- [Payment Processing](examples/payment_processing.md)
|
|
16
|
+
- [Inventory Management](examples/inventory_management.md)
|
|
17
|
+
- [API Reference](api_reference.md)
|
|
18
|
+
|
|
19
|
+
## Quick Start
|
|
20
|
+
|
|
21
|
+
```ruby
|
|
22
|
+
require 'ruby_reactor'
|
|
23
|
+
|
|
24
|
+
class OrderProcessingReactor < RubyReactor::Reactor
|
|
25
|
+
step :validate_order do
|
|
26
|
+
run { validate_order_logic }
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
step :process_payment do
|
|
30
|
+
run { process_payment_logic }
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
step :send_confirmation do
|
|
34
|
+
run { send_email_confirmation }
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Execute synchronously
|
|
39
|
+
result = OrderProcessingReactor.run(order_id: 123)
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Key Features
|
|
43
|
+
|
|
44
|
+
- **Sequential Execution**: Steps execute in dependency order
|
|
45
|
+
- **Error Handling**: Automatic compensation and rollback on failures
|
|
46
|
+
- **Async Support**: Full reactor async or step-level async execution
|
|
47
|
+
- **Non-Blocking Retries**: Job requeuing instead of blocking workers
|
|
48
|
+
- **Sidekiq Integration**: Seamless background processing
|
|
49
|
+
- **Retry Configuration**: Flexible retry policies per step
|
|
50
|
+
|
|
51
|
+
## Architecture
|
|
52
|
+
|
|
53
|
+
RubyReactor provides two execution models:
|
|
54
|
+
|
|
55
|
+
1. **Synchronous**: All steps execute in the current thread
|
|
56
|
+
2. **Asynchronous**: Steps execute in Sidekiq workers with non-blocking retries
|
|
57
|
+
|
|
58
|
+
### Execution Flow
|
|
59
|
+
|
|
60
|
+
```mermaid
|
|
61
|
+
graph TD
|
|
62
|
+
A[Input Validation] --> B[Build Dependency Graph]
|
|
63
|
+
B --> C[Execute Steps Sequentially]
|
|
64
|
+
C --> D{Step<br/>Success?}
|
|
65
|
+
D -->|Yes| E{More<br/>Steps?}
|
|
66
|
+
D -->|No| F[Run Compensation<br/>in Reverse Order]
|
|
67
|
+
E -->|Yes| C
|
|
68
|
+
E -->|No| G[Return Success Result]
|
|
69
|
+
F --> H[Return Failure Result]
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Async Execution Models
|
|
73
|
+
|
|
74
|
+
```mermaid
|
|
75
|
+
graph TD
|
|
76
|
+
A[Client Request] --> B{Async<br/>Model?}
|
|
77
|
+
B -->|Full Reactor| C[Queue All Steps<br/>to Sidekiq]
|
|
78
|
+
B -->|Step-Level| D[Execute Sync Steps<br/>Until First Async]
|
|
79
|
+
C --> E[Sidekiq Worker<br/>Executes All Steps]
|
|
80
|
+
D --> F[Queue Remaining Steps<br/>to Sidekiq]
|
|
81
|
+
F --> G[Sidekiq Worker<br/>Executes Remaining Steps]
|
|
82
|
+
E --> H{Result?}
|
|
83
|
+
G --> H
|
|
84
|
+
H -->|Success| I[Return Success]
|
|
85
|
+
H -->|Failure| J[Compensation in Worker]
|
|
86
|
+
J --> K[Return Failure]
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
- **Full Reactor Async**: Entire reactor executes in background
|
|
90
|
+
- **Step-Level Async**: Handoff to background at first async step
|
|
91
|
+
|
|
92
|
+
## Error Handling
|
|
93
|
+
|
|
94
|
+
RubyReactor provides comprehensive error handling:
|
|
95
|
+
|
|
96
|
+
- **Step Failures**: Automatic retry with configurable backoff
|
|
97
|
+
- **Compensation**: Undo operations for failed steps
|
|
98
|
+
- **Rollback**: Complete transaction rollback on critical failures
|
|
99
|
+
- **Non-Blocking**: Workers freed during retry delays
|
|
100
|
+
|
|
101
|
+
## Performance
|
|
102
|
+
|
|
103
|
+
- **Zero Blocking**: No threads blocked during retry delays
|
|
104
|
+
- **Scalable**: Linear scaling with worker pool size
|
|
105
|
+
- **Efficient**: Optimized context serialization and deserialization
|
|
106
|
+
|
|
107
|
+
## Requirements
|
|
108
|
+
|
|
109
|
+
- Ruby 2.7+
|
|
110
|
+
- Redis (for async execution)
|
|
111
|
+
- Sidekiq (for background processing)
|
|
112
|
+
|
|
113
|
+
## Installation
|
|
114
|
+
|
|
115
|
+
Add to your Gemfile:
|
|
116
|
+
|
|
117
|
+
```ruby
|
|
118
|
+
gem 'ruby_reactor'
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Contributing
|
|
122
|
+
|
|
123
|
+
See [CONTRIBUTING.md](../CONTRIBUTING.md) for development setup and contribution guidelines.
|