bunny_farm 0.1.2

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 (106) hide show
  1. checksums.yaml +7 -0
  2. data/.envrc +1 -0
  3. data/.github/workflows/docs.yml +38 -0
  4. data/.gitignore +11 -0
  5. data/.travis.yml +3 -0
  6. data/CHANGELOG.md +61 -0
  7. data/COMMITS.md +196 -0
  8. data/Gemfile +4 -0
  9. data/LICENSE.txt +21 -0
  10. data/README.md +330 -0
  11. data/Rakefile +9 -0
  12. data/bunny_farm.gemspec +30 -0
  13. data/config/bunny.yml.erb +29 -0
  14. data/config/bunny_test.yml.erb +29 -0
  15. data/config/hipchat.yml.erb +12 -0
  16. data/docs/api/configuration.md +9 -0
  17. data/docs/api/consumer.md +8 -0
  18. data/docs/api/message-class.md +419 -0
  19. data/docs/api/publisher.md +9 -0
  20. data/docs/architecture/integration.md +8 -0
  21. data/docs/architecture/message-flow.md +11 -0
  22. data/docs/architecture/overview.md +448 -0
  23. data/docs/architecture/scaling.md +8 -0
  24. data/docs/assets/actions_dsl_flow.svg +109 -0
  25. data/docs/assets/architecture_overview.svg +152 -0
  26. data/docs/assets/best_practices_patterns.svg +203 -0
  27. data/docs/assets/bunny_farm_logo.png +0 -0
  28. data/docs/assets/configuration_api_methods.svg +104 -0
  29. data/docs/assets/configuration_flow.svg +130 -0
  30. data/docs/assets/configuration_hierarchy.svg +70 -0
  31. data/docs/assets/data_processing_pipeline.svg +131 -0
  32. data/docs/assets/debugging_monitoring.svg +165 -0
  33. data/docs/assets/ecommerce_example_flow.svg +145 -0
  34. data/docs/assets/email_campaign_example.svg +127 -0
  35. data/docs/assets/environment_variables_map.svg +78 -0
  36. data/docs/assets/error_handling_flow.svg +114 -0
  37. data/docs/assets/favicon.ico +1 -0
  38. data/docs/assets/fields_dsl_structure.svg +89 -0
  39. data/docs/assets/instance_methods_lifecycle.svg +137 -0
  40. data/docs/assets/integration_patterns.svg +207 -0
  41. data/docs/assets/json_serialization_flow.svg +153 -0
  42. data/docs/assets/logo.svg +4 -0
  43. data/docs/assets/message_api_overview.svg +126 -0
  44. data/docs/assets/message_encapsulation.svg +113 -0
  45. data/docs/assets/message_lifecycle.svg +110 -0
  46. data/docs/assets/message_structure.svg +138 -0
  47. data/docs/assets/publisher_consumer_api.svg +120 -0
  48. data/docs/assets/scaling_deployment_patterns.svg +195 -0
  49. data/docs/assets/smart_routing_diagram.svg +131 -0
  50. data/docs/assets/system_architecture_overview.svg +155 -0
  51. data/docs/assets/task_scheduling_flow.svg +139 -0
  52. data/docs/assets/testing_strategies.svg +146 -0
  53. data/docs/assets/workflow_patterns.svg +183 -0
  54. data/docs/assets/yaml_config_structure.svg +72 -0
  55. data/docs/configuration/environment-variables.md +14 -0
  56. data/docs/configuration/overview.md +373 -0
  57. data/docs/configuration/programmatic-setup.md +10 -0
  58. data/docs/configuration/yaml-configuration.md +12 -0
  59. data/docs/core-features/configuration.md +528 -0
  60. data/docs/core-features/error-handling.md +82 -0
  61. data/docs/core-features/json-serialization.md +545 -0
  62. data/docs/core-features/message-design.md +406 -0
  63. data/docs/core-features/smart-routing.md +467 -0
  64. data/docs/core-features/task-scheduling.md +67 -0
  65. data/docs/core-features/workflow-support.md +112 -0
  66. data/docs/development/contributing.md +345 -0
  67. data/docs/development/roadmap.md +9 -0
  68. data/docs/development/testing.md +14 -0
  69. data/docs/examples/order-processing.md +10 -0
  70. data/docs/examples/overview.md +269 -0
  71. data/docs/examples/real-world.md +8 -0
  72. data/docs/examples/simple-producer-consumer.md +15 -0
  73. data/docs/examples/task-scheduler.md +9 -0
  74. data/docs/getting-started/basic-concepts.md +274 -0
  75. data/docs/getting-started/installation.md +122 -0
  76. data/docs/getting-started/quick-start.md +158 -0
  77. data/docs/index.md +106 -0
  78. data/docs/message-structure/actions-dsl.md +163 -0
  79. data/docs/message-structure/fields-dsl.md +146 -0
  80. data/docs/message-structure/instance-methods.md +115 -0
  81. data/docs/message-structure/overview.md +211 -0
  82. data/examples/README.md +212 -0
  83. data/examples/consumer.rb +41 -0
  84. data/examples/images/message_flow.svg +87 -0
  85. data/examples/images/order_workflow.svg +122 -0
  86. data/examples/images/producer_consumer.svg +96 -0
  87. data/examples/images/task_scheduler.svg +140 -0
  88. data/examples/order_processor.rb +238 -0
  89. data/examples/producer.rb +60 -0
  90. data/examples/simple_message.rb +43 -0
  91. data/examples/task_scheduler.rb +263 -0
  92. data/images/architecture_overview.svg +152 -0
  93. data/images/bunny_farm_logo.png +0 -0
  94. data/images/configuration_flow.svg +130 -0
  95. data/images/message_structure.svg +138 -0
  96. data/lib/bunny_farm/.irbrc +7 -0
  97. data/lib/bunny_farm/generic_consumer.rb +12 -0
  98. data/lib/bunny_farm/hash_ext.rb +37 -0
  99. data/lib/bunny_farm/init_bunny.rb +137 -0
  100. data/lib/bunny_farm/init_hipchat.rb +49 -0
  101. data/lib/bunny_farm/message.rb +218 -0
  102. data/lib/bunny_farm/message_elements.rb +25 -0
  103. data/lib/bunny_farm/version.rb +3 -0
  104. data/lib/bunny_farm.rb +9 -0
  105. data/mkdocs.yml +148 -0
  106. metadata +244 -0
@@ -0,0 +1,467 @@
1
+ # Smart Routing
2
+
3
+ BunnyFarm's smart routing system automatically creates predictable routing keys based on your message classes and actions. This eliminates the need for manual routing configuration while maintaining full transparency and debuggability.
4
+
5
+ ## How Smart Routing Works
6
+
7
+ ### Routing Key Pattern
8
+
9
+ BunnyFarm uses a simple, predictable pattern for routing keys:
10
+
11
+ ```
12
+ MessageClassName.action
13
+ ```
14
+
15
+ **Examples:**
16
+ - `OrderMessage.process`
17
+ - `EmailMessage.send`
18
+ - `ReportMessage.generate`
19
+ - `UserRegistrationMessage.create_account`
20
+
21
+ ### Automatic Generation
22
+
23
+ When you publish a message, the routing key is automatically generated:
24
+
25
+ ```ruby
26
+ class OrderMessage < BunnyFarm::Message
27
+ actions :validate, :process, :ship
28
+ end
29
+
30
+ # Publishing automatically creates routing keys
31
+ message = OrderMessage.new
32
+ message.publish('validate') # Routing key: OrderMessage.validate
33
+ message.publish('process') # Routing key: OrderMessage.process
34
+ message.publish('ship') # Routing key: OrderMessage.ship
35
+ ```
36
+
37
+ ## Routing Flow
38
+
39
+ ### 1. Publisher Side
40
+
41
+ ```ruby
42
+ # 1. Create message
43
+ order_msg = OrderMessage.new
44
+ order_msg[:order_id] = 12345
45
+
46
+ # 2. Publish with action
47
+ order_msg.publish('process')
48
+
49
+ # 3. BunnyFarm generates routing key: "OrderMessage.process"
50
+ # 4. Message sent to RabbitMQ with this routing key
51
+ ```
52
+
53
+ ### 2. RabbitMQ Routing
54
+
55
+ ```
56
+ Publisher → Exchange → Queue(s) → Consumer(s)
57
+
58
+ Routes based on
59
+ "OrderMessage.process"
60
+ ```
61
+
62
+ ### 3. Consumer Side
63
+
64
+ ```ruby
65
+ # 1. Consumer receives message with routing key "OrderMessage.process"
66
+ # 2. BunnyFarm parses routing key → class: OrderMessage, action: process
67
+ # 3. Message deserialized to OrderMessage instance
68
+ # 4. The 'process' method is called automatically
69
+
70
+ class OrderMessage < BunnyFarm::Message
71
+ def process
72
+ # This method is called automatically
73
+ puts "Processing order #{@items[:order_id]}"
74
+ success!
75
+ end
76
+ end
77
+ ```
78
+
79
+ ## Exchange and Queue Configuration
80
+
81
+ ### Topic Exchange
82
+
83
+ BunnyFarm typically uses RabbitMQ's **topic exchange** for flexible routing:
84
+
85
+ ```ruby
86
+ BunnyFarm.config do
87
+ exchange_name 'bunny_farm_exchange'
88
+ exchange_type :topic # Supports pattern matching
89
+ end
90
+ ```
91
+
92
+ ### Queue Bindings
93
+
94
+ Queues can bind to specific routing patterns:
95
+
96
+ ```ruby
97
+ # Bind to all OrderMessage actions
98
+ queue.bind(exchange, routing_key: 'OrderMessage.*')
99
+
100
+ # Bind to specific actions only
101
+ queue.bind(exchange, routing_key: 'OrderMessage.process')
102
+
103
+ # Bind to all messages
104
+ queue.bind(exchange, routing_key: '#')
105
+
106
+ # Bind to all validation actions across message types
107
+ queue.bind(exchange, routing_key: '*.validate')
108
+ ```
109
+
110
+ ## Routing Patterns
111
+
112
+ ### 1. Single Message Type, Single Action
113
+
114
+ **Use case:** Dedicated worker for specific operations
115
+
116
+ ```ruby
117
+ # Worker specializes in order processing
118
+ BunnyFarm.config do
119
+ queue_name 'order_processing'
120
+ routing_key 'OrderMessage.process'
121
+ end
122
+
123
+ # Only processes OrderMessage.process
124
+ BunnyFarm.manage
125
+ ```
126
+
127
+ ### 2. Single Message Type, Multiple Actions
128
+
129
+ **Use case:** Worker handles all operations for one domain
130
+
131
+ ```ruby
132
+ # Worker handles all order operations
133
+ BunnyFarm.config do
134
+ queue_name 'order_worker'
135
+ routing_key 'OrderMessage.*' # All OrderMessage actions
136
+ end
137
+
138
+ class OrderMessage < BunnyFarm::Message
139
+ actions :validate, :process, :ship, :cancel
140
+
141
+ def validate; end # Handled by order_worker
142
+ def process; end # Handled by order_worker
143
+ def ship; end # Handled by order_worker
144
+ def cancel; end # Handled by order_worker
145
+ end
146
+ ```
147
+
148
+ ### 3. Multiple Message Types, Specific Actions
149
+
150
+ **Use case:** Worker specializes in one type of operation across domains
151
+
152
+ ```ruby
153
+ # Worker handles validation for all message types
154
+ BunnyFarm.config do
155
+ queue_name 'validation_worker'
156
+ routing_key '*.validate' # All validation actions
157
+ end
158
+
159
+ # These would all be handled by validation_worker:
160
+ # OrderMessage.validate
161
+ # CustomerMessage.validate
162
+ # ProductMessage.validate
163
+ ```
164
+
165
+ ### 4. Multiple Message Types, All Actions
166
+
167
+ **Use case:** General-purpose worker
168
+
169
+ ```ruby
170
+ # Worker handles everything
171
+ BunnyFarm.config do
172
+ queue_name 'general_worker'
173
+ routing_key '#' # All messages
174
+ end
175
+ ```
176
+
177
+ ## Advanced Routing Scenarios
178
+
179
+ ### Priority Queues
180
+
181
+ Route urgent messages to high-priority queues:
182
+
183
+ ```ruby
184
+ class UrgentOrderMessage < BunnyFarm::Message
185
+ actions :process_immediately
186
+ end
187
+
188
+ # High-priority worker
189
+ BunnyFarm.config do
190
+ queue_name 'urgent_orders'
191
+ routing_key 'UrgentOrderMessage.*'
192
+ queue_options do
193
+ arguments 'x-max-priority' => 10
194
+ end
195
+ end
196
+
197
+ # Regular priority worker
198
+ BunnyFarm.config do
199
+ queue_name 'regular_orders'
200
+ routing_key 'OrderMessage.*'
201
+ end
202
+ ```
203
+
204
+ ### Geographic Routing
205
+
206
+ Route messages based on regions:
207
+
208
+ ```ruby
209
+ class USOrderMessage < BunnyFarm::Message
210
+ actions :process, :ship
211
+ end
212
+
213
+ class EUOrderMessage < BunnyFarm::Message
214
+ actions :process, :ship
215
+ end
216
+
217
+ # US worker
218
+ BunnyFarm.config do
219
+ queue_name 'us_orders'
220
+ routing_key 'USOrderMessage.*'
221
+ end
222
+
223
+ # EU worker
224
+ BunnyFarm.config do
225
+ queue_name 'eu_orders'
226
+ routing_key 'EUOrderMessage.*'
227
+ end
228
+ ```
229
+
230
+ ### Load Balancing
231
+
232
+ Multiple workers can consume from the same queue:
233
+
234
+ ```ruby
235
+ # Worker 1
236
+ BunnyFarm.config do
237
+ app_id 'worker_1'
238
+ queue_name 'order_processing'
239
+ routing_key 'OrderMessage.process'
240
+ end
241
+
242
+ # Worker 2
243
+ BunnyFarm.config do
244
+ app_id 'worker_2'
245
+ queue_name 'order_processing' # Same queue
246
+ routing_key 'OrderMessage.process'
247
+ end
248
+
249
+ # RabbitMQ automatically load balances between workers
250
+ ```
251
+
252
+ ## Routing Key Introspection
253
+
254
+ ### Debugging Routing
255
+
256
+ Check what routing key will be generated:
257
+
258
+ ```ruby
259
+ class OrderMessage < BunnyFarm::Message
260
+ actions :process
261
+ end
262
+
263
+ message = OrderMessage.new
264
+ routing_key = "#{message.class.name}.process"
265
+ puts routing_key # => "OrderMessage.process"
266
+ ```
267
+
268
+ ### Runtime Routing Information
269
+
270
+ Access routing information during processing:
271
+
272
+ ```ruby
273
+ class OrderMessage < BunnyFarm::Message
274
+ def process
275
+ puts "Processing with routing key: #{self.class.name}.process"
276
+ puts "Message class: #{self.class.name}"
277
+ puts "Action: process"
278
+
279
+ success!
280
+ end
281
+ end
282
+ ```
283
+
284
+ ## Error Routing
285
+
286
+ ### Dead Letter Queues
287
+
288
+ Failed messages can be routed to error queues:
289
+
290
+ ```ruby
291
+ BunnyFarm.config do
292
+ queue_name 'order_processing'
293
+ routing_key 'OrderMessage.*'
294
+
295
+ queue_options do
296
+ arguments({
297
+ 'x-dead-letter-exchange' => 'failed_messages',
298
+ 'x-dead-letter-routing-key' => 'failed.OrderMessage'
299
+ })
300
+ end
301
+ end
302
+ ```
303
+
304
+ ### Retry Queues
305
+
306
+ Implement retry logic with delayed routing:
307
+
308
+ ```ruby
309
+ class OrderMessage < BunnyFarm::Message
310
+ def process
311
+ begin
312
+ perform_processing
313
+ success!
314
+ rescue RetryableError => e
315
+ if retry_count < 3
316
+ # Publish to retry queue with delay
317
+ retry_message = self.class.new(@items)
318
+ retry_message[:retry_count] = retry_count + 1
319
+ retry_message.publish_delayed('process', delay: 30.seconds)
320
+ success! # Don't NACK original message
321
+ else
322
+ failure("Max retries exceeded: #{e.message}")
323
+ end
324
+ end
325
+
326
+ successful?
327
+ end
328
+
329
+ private
330
+
331
+ def retry_count
332
+ @items[:retry_count] || 0
333
+ end
334
+ end
335
+ ```
336
+
337
+ ## Monitoring and Debugging
338
+
339
+ ### RabbitMQ Management UI
340
+
341
+ Use RabbitMQ's management interface to monitor routing:
342
+
343
+ 1. **Exchanges tab** - See message routing statistics
344
+ 2. **Queues tab** - Monitor queue depths and consumption rates
345
+ 3. **Connections tab** - View active publishers and consumers
346
+
347
+ ### Routing Metrics
348
+
349
+ Track routing patterns in your application:
350
+
351
+ ```ruby
352
+ class OrderMessage < BunnyFarm::Message
353
+ def process
354
+ # Track routing metrics
355
+ Metrics.increment("message.routed.#{self.class.name}.process")
356
+
357
+ # Your processing logic
358
+ perform_order_processing
359
+
360
+ Metrics.increment("message.processed.#{self.class.name}.process")
361
+ success!
362
+ end
363
+ end
364
+ ```
365
+
366
+ ### Logging Routing Events
367
+
368
+ Log routing information for debugging:
369
+
370
+ ```ruby
371
+ class OrderMessage < BunnyFarm::Message
372
+ def process
373
+ logger.info "Processing message",
374
+ routing_key: "#{self.class.name}.process",
375
+ message_id: @items[:order_id]
376
+
377
+ # Processing logic
378
+ success!
379
+ end
380
+ end
381
+ ```
382
+
383
+ ## Best Practices
384
+
385
+ ### 1. Predictable Naming
386
+
387
+ Use clear, consistent class and action names:
388
+
389
+ ```ruby
390
+ # Good: Clear domain and action
391
+ class CustomerRegistrationMessage < BunnyFarm::Message
392
+ actions :validate_email, :create_account, :send_welcome
393
+ end
394
+
395
+ # Avoid: Vague or abbreviated names
396
+ class CRM < BunnyFarm::Message
397
+ actions :val, :cr8, :snd
398
+ end
399
+ ```
400
+
401
+ ### 2. Logical Grouping
402
+
403
+ Group related actions in the same message class:
404
+
405
+ ```ruby
406
+ # Good: Related order operations
407
+ class OrderMessage < BunnyFarm::Message
408
+ actions :validate, :process_payment, :fulfill, :ship, :complete
409
+ end
410
+
411
+ # Avoid: Mixing unrelated operations
412
+ class MixedMessage < BunnyFarm::Message
413
+ actions :process_order, :send_email, :backup_database, :clean_temp_files
414
+ end
415
+ ```
416
+
417
+ ### 3. Queue Design
418
+
419
+ Design queues around processing capabilities:
420
+
421
+ ```ruby
422
+ # Good: Separate concerns
423
+ BunnyFarm.config do
424
+ case worker_type
425
+ when 'payment_processor'
426
+ routing_key '*.process_payment'
427
+ when 'email_sender'
428
+ routing_key '*.send_email'
429
+ when 'order_validator'
430
+ routing_key 'OrderMessage.validate'
431
+ end
432
+ end
433
+ ```
434
+
435
+ ### 4. Error Handling
436
+
437
+ Plan for routing errors:
438
+
439
+ ```ruby
440
+ def process
441
+ validate_message_structure
442
+ return unless successful?
443
+
444
+ perform_business_logic
445
+ return unless successful?
446
+
447
+ log_success
448
+ end
449
+
450
+ private
451
+
452
+ def validate_message_structure
453
+ required_fields = [:order_id, :customer_id, :amount]
454
+ missing = required_fields.select { |field| @items[field].nil? }
455
+
456
+ failure("Missing required fields: #{missing.join(', ')}") if missing.any?
457
+ end
458
+ ```
459
+
460
+ ## Next Steps
461
+
462
+ Understanding smart routing enables you to:
463
+
464
+ - **[Configure](../configuration/overview.md)** routing for your specific needs
465
+ - **[Design message structures](../message-structure/overview.md)** that route effectively
466
+ - **[Scale your architecture](../architecture/scaling.md)** with proper queue design
467
+ - **[Handle errors](error-handling.md)** with appropriate routing strategies
@@ -0,0 +1,67 @@
1
+ # Task Scheduling
2
+
3
+ BunnyFarm supports delayed message processing and task scheduling, allowing you to build sophisticated time-based workflows and recurring operations.
4
+
5
+ ![Task Scheduling Flow](../assets/task_scheduling_flow.svg)
6
+
7
+ ## Delayed Messages
8
+
9
+ ```ruby
10
+ class ScheduledTask < BunnyFarm::Message
11
+ actions :schedule, :execute
12
+
13
+ def schedule
14
+ # Schedule execution for later
15
+ delay_seconds = @items[:delay] || 3600 # 1 hour default
16
+
17
+ # Use RabbitMQ delayed message plugin or custom delay queue
18
+ publish_delayed('execute', delay: delay_seconds)
19
+ success!
20
+ end
21
+
22
+ def execute
23
+ puts "Executing scheduled task at #{Time.current}"
24
+ perform_scheduled_work
25
+ success!
26
+ end
27
+ end
28
+ ```
29
+
30
+ ## Recurring Tasks
31
+
32
+ ```ruby
33
+ def execute_recurring
34
+ perform_work
35
+
36
+ # Schedule next execution
37
+ next_run = @items[:interval] || 3600 # 1 hour
38
+ self.class.new(@items).publish_delayed('execute_recurring', delay: next_run)
39
+
40
+ success!
41
+ end
42
+ ```
43
+
44
+ ## Retry Patterns
45
+
46
+ ```ruby
47
+ def execute_with_retry
48
+ begin
49
+ risky_operation
50
+ success!
51
+ rescue => e
52
+ retry_count = (@items[:retry_count] || 0) + 1
53
+
54
+ if retry_count < 3
55
+ delay = 2 ** retry_count # Exponential backoff
56
+
57
+ retry_msg = self.class.new(@items)
58
+ retry_msg[:retry_count] = retry_count
59
+ retry_msg.publish_delayed('execute_with_retry', delay: delay)
60
+
61
+ success!
62
+ else
63
+ failure("Max retries exceeded: #{e.message}")
64
+ end
65
+ end
66
+ end
67
+ ```
@@ -0,0 +1,112 @@
1
+ # Workflow Support
2
+
3
+ BunnyFarm excels at building complex, multi-step workflows where messages can trigger subsequent operations, creating sophisticated business processes through message chaining and coordination.
4
+
5
+ ![Workflow Patterns](../assets/workflow_patterns.svg)
6
+
7
+ ## Workflow Patterns
8
+
9
+ ### Sequential Workflows
10
+
11
+ Messages can trigger follow-up messages in sequence:
12
+
13
+ ```ruby
14
+ class OrderWorkflow < BunnyFarm::Message
15
+ actions :start_order, :validate, :process_payment, :fulfill, :ship, :complete
16
+
17
+ def start_order
18
+ # Step 1: Validate order
19
+ self.class.new(@items).publish('validate')
20
+ success!
21
+ end
22
+
23
+ def validate
24
+ validate_order_data
25
+ return unless successful?
26
+
27
+ # Step 2: Process payment
28
+ self.class.new(@items).publish('process_payment')
29
+ success!
30
+ end
31
+
32
+ def process_payment
33
+ charge_customer
34
+ return unless successful?
35
+
36
+ # Step 3: Fulfill order
37
+ self.class.new(@items).publish('fulfill')
38
+ success!
39
+ end
40
+ end
41
+ ```
42
+
43
+ ### Conditional Workflows
44
+
45
+ ```ruby
46
+ def process_order
47
+ validate_order
48
+ return unless successful?
49
+
50
+ if @items[:requires_approval]
51
+ self.class.new(@items).publish('request_approval')
52
+ else
53
+ self.class.new(@items).publish('auto_process')
54
+ end
55
+
56
+ success!
57
+ end
58
+ ```
59
+
60
+ ### Parallel Workflows
61
+
62
+ ```ruby
63
+ def start_parallel_processing
64
+ # Trigger multiple parallel operations
65
+ InventoryMessage.new(@items).publish('reserve_items')
66
+ PaymentMessage.new(@items).publish('authorize_payment')
67
+ ShippingMessage.new(@items).publish('calculate_shipping')
68
+
69
+ success!
70
+ end
71
+ ```
72
+
73
+ ## State Management
74
+
75
+ ### Workflow State Tracking
76
+
77
+ ```ruby
78
+ class OrderWorkflow < BunnyFarm::Message
79
+ def initialize
80
+ super
81
+ @items[:workflow_state] = 'initialized'
82
+ @items[:completed_steps] = []
83
+ end
84
+
85
+ def validate
86
+ @items[:workflow_state] = 'validating'
87
+
88
+ perform_validation
89
+
90
+ if successful?
91
+ @items[:completed_steps] << 'validate'
92
+ @items[:workflow_state] = 'validated'
93
+ trigger_next_step
94
+ end
95
+ end
96
+
97
+ private
98
+
99
+ def trigger_next_step
100
+ case @items[:workflow_state]
101
+ when 'validated'
102
+ self.class.new(@items).publish('process_payment')
103
+ when 'payment_processed'
104
+ self.class.new(@items).publish('fulfill')
105
+ end
106
+ end
107
+ end
108
+ ```
109
+
110
+ ## Next Steps
111
+
112
+ Workflows enable complex business processes through message orchestration.