ruby_reactor 0.3.2 → 0.4.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.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/.release-please-config.json +15 -0
  3. data/.release-please-manifest.json +3 -0
  4. data/.tool-versions +1 -0
  5. data/CHANGELOG.md +13 -0
  6. data/README.md +80 -4
  7. data/lib/ruby_reactor/context_serializer.rb +10 -1
  8. data/lib/ruby_reactor/map/result_enumerator.rb +4 -3
  9. data/lib/ruby_reactor/rate_limit.rb +2 -2
  10. data/lib/ruby_reactor/sidekiq_workers/worker.rb +58 -1
  11. data/lib/ruby_reactor/version.rb +1 -1
  12. metadata +7 -52
  13. data/documentation/DAG.md +0 -457
  14. data/documentation/README.md +0 -135
  15. data/documentation/async_reactors.md +0 -381
  16. data/documentation/composition.md +0 -199
  17. data/documentation/core_concepts.md +0 -676
  18. data/documentation/data_pipelines.md +0 -230
  19. data/documentation/examples/inventory_management.md +0 -748
  20. data/documentation/examples/order_processing.md +0 -380
  21. data/documentation/examples/payment_processing.md +0 -565
  22. data/documentation/getting_started.md +0 -242
  23. data/documentation/images/failed_order_processing.png +0 -0
  24. data/documentation/images/payment_workflow.png +0 -0
  25. data/documentation/interrupts.md +0 -163
  26. data/documentation/locks_and_semaphores.md +0 -459
  27. data/documentation/retry_configuration.md +0 -362
  28. data/documentation/testing.md +0 -994
  29. data/gui/.gitignore +0 -24
  30. data/gui/README.md +0 -73
  31. data/gui/eslint.config.js +0 -23
  32. data/gui/index.html +0 -13
  33. data/gui/package-lock.json +0 -5925
  34. data/gui/package.json +0 -46
  35. data/gui/postcss.config.js +0 -6
  36. data/gui/public/vite.svg +0 -1
  37. data/gui/src/App.css +0 -42
  38. data/gui/src/App.tsx +0 -51
  39. data/gui/src/assets/react.svg +0 -1
  40. data/gui/src/components/DagVisualizer.tsx +0 -424
  41. data/gui/src/components/Dashboard.tsx +0 -163
  42. data/gui/src/components/ErrorBoundary.tsx +0 -47
  43. data/gui/src/components/ReactorDetail.tsx +0 -135
  44. data/gui/src/components/StepInspector.tsx +0 -492
  45. data/gui/src/components/__tests__/DagVisualizer.test.tsx +0 -140
  46. data/gui/src/components/__tests__/ReactorDetail.test.tsx +0 -111
  47. data/gui/src/components/__tests__/StepInspector.test.tsx +0 -408
  48. data/gui/src/globals.d.ts +0 -7
  49. data/gui/src/index.css +0 -14
  50. data/gui/src/lib/utils.ts +0 -13
  51. data/gui/src/main.tsx +0 -14
  52. data/gui/src/test/setup.ts +0 -11
  53. data/gui/tailwind.config.js +0 -11
  54. data/gui/tsconfig.app.json +0 -28
  55. data/gui/tsconfig.json +0 -7
  56. data/gui/tsconfig.node.json +0 -26
  57. data/gui/vite.config.ts +0 -8
  58. data/gui/vitest.config.ts +0 -13
@@ -1,380 +0,0 @@
1
- # Order Processing Reactor Example
2
-
3
- This example demonstrates a complete order processing workflow with validation, payment processing, inventory management, and notifications.
4
-
5
- ## Overview
6
-
7
- The OrderProcessingReactor handles the complete order fulfillment process:
8
-
9
- 1. **Validate Order**: Ensure order exists and is processable
10
- 2. **Check Inventory**: Verify all items are available
11
- 3. **Reserve Inventory**: Temporarily reserve items
12
- 4. **Process Payment**: Charge the customer's payment method
13
- 5. **Update Inventory**: Permanently decrement inventory
14
- 6. **Send Confirmation**: Email order confirmation
15
-
16
- ### Order Processing Workflow
17
-
18
- ```mermaid
19
- graph TD
20
- A[Order Submitted] --> B[validate_order]
21
- B --> C{Order<br/>Valid?}
22
- C -->|No| D[Fail: Invalid Order]
23
- C -->|Yes| E[check_inventory]
24
- E --> F{Inventory<br/>Available?}
25
- F -->|No| G[Fail: Insufficient Stock]
26
- F -->|Yes| H[reserve_inventory]
27
- H --> I{Reservation<br/>Successful?}
28
- I -->|No| J[Fail: Reservation Error]
29
- I -->|Yes| K[process_payment]
30
- K --> L{Payment<br/>Successful?}
31
- L -->|No| M[Compensate: Release Reservation]
32
- L -->|Yes| N[update_inventory]
33
- N --> O{Update<br/>Successful?}
34
- O -->|No| P[Compensate: Refund + Release]
35
- O -->|Yes| Q[update_order_status]
36
- Q --> R[send_confirmation]
37
- R --> S[Success: Order Complete]
38
- ```
39
-
40
- ## Implementation
41
-
42
- ```ruby
43
- class OrderProcessingReactor < RubyReactor::Reactor
44
- async true # Enable asynchronous execution
45
-
46
- # Reactor-level retry defaults
47
- retry_defaults max_attempts: 3, backoff: :exponential, base_delay: 2.seconds
48
-
49
- input :order_id do
50
- required(:order_id).filled(:string)
51
- end
52
-
53
- step :validate_order do
54
- argument :order_id, input(:order_id)
55
-
56
- run do |args, _ctx|
57
- order = Order.find_by(id: args[:order_id])
58
- return Failure("Order not found") unless order
59
- return Failure("Order already processed") if order.processed?
60
- return Failure("Order cancelled") if order.cancelled?
61
-
62
- Success({ order: order })
63
- end
64
- end
65
-
66
- step :check_inventory do
67
- argument :order, result(:validate_order, :order)
68
-
69
- run do |args, _ctx|
70
- unavailable_items = []
71
-
72
- args[:order].items.each do |item|
73
- product = Product.find(item.product_id)
74
- if product.inventory_count < item.quantity
75
- unavailable_items << {
76
- product_id: item.product_id,
77
- requested: item.quantity,
78
- available: product.inventory_count
79
- }
80
- end
81
- end
82
-
83
- return Failure("Insufficient inventory: #{unavailable_items}") unless unavailable_items.empty?
84
-
85
- Success({ inventory_checked: true })
86
- end
87
- end
88
-
89
- step :reserve_inventory do
90
- argument :order, result(:validate_order, :order)
91
-
92
- run do |args, _ctx|
93
- reservation_id = InventoryService.reserve_items(args[:order].items)
94
- return Failure("Inventory reservation failed") unless reservation_id
95
-
96
- Success({ reservation_id: reservation_id })
97
- end
98
-
99
- undo do |result, _args, _ctx|
100
- # Release reservation when a later step fails
101
- InventoryService.release_reservation(result[:reservation_id]) if result[:reservation_id]
102
- Success()
103
- end
104
- end
105
-
106
- step :process_payment do
107
- argument :order, result(:validate_order, :order)
108
-
109
- # Payment processing needs careful retry handling
110
- retries max_attempts: 2, backoff: :fixed, base_delay: 30.seconds
111
-
112
- run do |args, _ctx|
113
- order = args[:order]
114
- payment_result = PaymentService.charge(
115
- amount: order.total,
116
- currency: order.currency,
117
- card_token: order.customer.card_token,
118
- description: "Order ##{order.id}"
119
- )
120
-
121
- return Failure("Payment failed: #{payment_result.error}") unless payment_result.success?
122
-
123
- Success({ payment_id: payment_result.id, payment_amount: order.total })
124
- end
125
-
126
- undo do |result, _args, _ctx|
127
- PaymentService.refund(result[:payment_id]) if result[:payment_id]
128
- Success()
129
- end
130
- end
131
-
132
- step :update_inventory do
133
- argument :order, result(:validate_order, :order)
134
- argument :reservation_id, result(:reserve_inventory, :reservation_id)
135
-
136
- run do |args, _ctx|
137
- success = InventoryService.confirm_reservation(args[:reservation_id])
138
- return Failure("Inventory update failed") unless success
139
-
140
- Success({ inventory_updated: true })
141
- end
142
-
143
- undo do |_result, args, _ctx|
144
- InventoryService.restore_from_reservation(args[:reservation_id]) if args[:reservation_id]
145
- Success()
146
- end
147
- end
148
-
149
- step :update_order_status do
150
- argument :order, result(:validate_order, :order)
151
- argument :payment_id, result(:process_payment, :payment_id)
152
-
153
- run do |args, _ctx|
154
- args[:order].update!(
155
- status: :completed,
156
- payment_id: args[:payment_id],
157
- processed_at: Time.current
158
- )
159
-
160
- Success({ order_completed: true })
161
- end
162
- end
163
-
164
- step :send_confirmation do
165
- argument :order, result(:validate_order, :order)
166
- argument :payment_id, result(:process_payment, :payment_id)
167
-
168
- retries max_attempts: 3, backoff: :linear, base_delay: 10.seconds
169
-
170
- run do |args, _ctx|
171
- email_result = EmailService.send_order_confirmation(
172
- to: args[:order].customer.email,
173
- order: args[:order],
174
- payment_id: args[:payment_id]
175
- )
176
-
177
- return Failure("Confirmation email failed") unless email_result.success?
178
-
179
- Success({ confirmation_sent: true })
180
- end
181
- end
182
-
183
- returns :send_confirmation
184
- end
185
- ```
186
-
187
- ## Usage
188
-
189
- ### Asynchronous Execution
190
-
191
- ```ruby
192
- # Start order processing asynchronously
193
- async_result = OrderProcessingReactor.run(order_id: 12345)
194
- async_result.execution_id # UUID for state lookup
195
-
196
- # Reload state later (e.g. from a polling endpoint)
197
- reactor = OrderProcessingReactor.find(async_result.execution_id)
198
- case reactor.context.status.to_s
199
- when "completed"
200
- puts "Order processed successfully!"
201
- payment_id = reactor.context.intermediate_results[:process_payment][:payment_id]
202
- puts "Payment ID: #{payment_id}"
203
- when "failed"
204
- failure = reactor.result # RubyReactor::Failure
205
- puts "Order processing failed at #{failure.step_name}: #{failure.error}"
206
- # Could trigger manual review process
207
- when "running"
208
- puts "Still processing..."
209
- end
210
- ```
211
-
212
- ### Synchronous Execution (for testing)
213
-
214
- ```ruby
215
- # For testing or immediate processing
216
- reactor = OrderProcessingReactor.new
217
- result = reactor.run(order_id: 12345)
218
-
219
- if result.success?
220
- puts "Order completed!"
221
- puts "Steps completed: #{reactor.context.intermediate_results.keys}"
222
- else
223
- puts "Failed at step: #{result.step_name}"
224
- puts "Error: #{result.error}"
225
- end
226
- ```
227
-
228
- ## Error Scenarios
229
-
230
- ### Insufficient Inventory
231
-
232
- ```
233
- Step: check_inventory fails
234
- → Compensation: none (no state changes yet)
235
- → Result: failure with inventory details
236
- ```
237
-
238
- ### Payment Failure
239
-
240
- ```
241
- Step: process_payment fails
242
- → Compensation: release_inventory (reservation_id)
243
- → Result: failure with payment error
244
- ```
245
-
246
- ### Email Failure
247
-
248
- ```
249
- Step: send_confirmation fails (after successful payment/inventory update)
250
- → Compensation: refund_payment → restore_inventory
251
- → Result: failure (but order is actually complete - manual confirmation may be needed)
252
- ```
253
-
254
- ## Testing
255
-
256
- ```ruby
257
- RSpec.describe OrderProcessingReactor do
258
- let(:order) { create(:order, :pending) }
259
-
260
- context "successful order processing" do
261
- it "completes all steps successfully" do
262
- # Mock all external services
263
- allow(Order).to receive(:find_by).and_return(order)
264
- allow(InventoryService).to receive(:reserve_items).and_return("res_123")
265
- allow(PaymentService).to receive(:charge).and_return(successful_payment)
266
- allow(InventoryService).to receive(:confirm_reservation).and_return(true)
267
- allow(EmailService).to receive(:send_order_confirmation).and_return(successful_email)
268
-
269
- subject = test_reactor(OrderProcessingReactor, order_id: order.id)
270
-
271
- expect(subject).to be_success
272
- expect(subject).to have_run_step(:send_confirmation)
273
- end
274
- end
275
-
276
- context "payment failure" do
277
- it "compensates inventory reservation" do
278
- allow(Order).to receive(:find_by).and_return(order)
279
- allow(InventoryService).to receive(:reserve_items).and_return("res_123")
280
- allow(PaymentService).to receive(:charge).and_return(failed_payment)
281
-
282
- expect(InventoryService).to receive(:release_reservation).with("res_123")
283
-
284
- subject = test_reactor(OrderProcessingReactor, order_id: order.id)
285
-
286
- expect(subject).to be_failure
287
- expect(subject.error).to include("Payment failed")
288
- end
289
- end
290
- end
291
- ```
292
-
293
- ## Monitoring
294
-
295
- Key metrics to track:
296
-
297
- ```ruby
298
- # Success rates
299
- order_processing_success_rate
300
- payment_success_rate
301
- inventory_reservation_success_rate
302
-
303
- # Performance
304
- average_order_processing_time
305
- payment_processing_latency
306
-
307
- # Error rates
308
- inventory_insufficient_rate
309
- payment_failure_rate
310
- email_delivery_failure_rate
311
- ```
312
-
313
- ## Scaling Considerations
314
-
315
- - **High Volume**: Use async execution with multiple Sidekiq workers
316
- - **Payment Processing**: Implement idempotency keys for payment providers
317
- - **Inventory**: Use optimistic locking or database transactions
318
- - **Email**: Queue emails separately to avoid blocking order completion
319
-
320
- ## Extensions
321
-
322
- ### Partial Order Processing
323
-
324
- ```ruby
325
- class PartialOrderProcessingReactor < OrderProcessingReactor
326
- # Override to allow partial fulfillment
327
- step :check_inventory do
328
- argument :order, result(:validate_order, :order)
329
-
330
- run do |args, _ctx|
331
- available_items, unavailable_items = partition_available_items(args[:order].items)
332
-
333
- if available_items.any? && unavailable_items.any?
334
- partial_order = create_partial_order(args[:order], available_items)
335
- Success({ partial_order: partial_order, unavailable_items: unavailable_items })
336
- elsif available_items.empty?
337
- Failure("No items available")
338
- else
339
- Success({ inventory_checked: true })
340
- end
341
- end
342
- end
343
- end
344
- ```
345
-
346
- ### Order Cancellation
347
-
348
- ```ruby
349
- class OrderCancellationReactor < RubyReactor::Reactor
350
- input :order_id do
351
- required(:order_id).filled(:string)
352
- end
353
-
354
- step :load_order do
355
- argument :order_id, input(:order_id)
356
-
357
- run do |args, _ctx|
358
- order = Order.find_by(id: args[:order_id])
359
- return Failure("Order not found") unless order
360
- Success({ order: order })
361
- end
362
- end
363
-
364
- step :cancel_order do
365
- argument :order, result(:load_order, :order)
366
-
367
- run do |args, _ctx|
368
- order = args[:order]
369
- # Only cancel if not already completed
370
- if order.completed?
371
- PaymentService.refund(order.payment_id)
372
- InventoryService.restore_order_items(order)
373
- end
374
-
375
- order.update!(status: :cancelled)
376
- Success({ cancelled: true })
377
- end
378
- end
379
- end
380
- ```