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.
- checksums.yaml +7 -0
- data/.envrc +1 -0
- data/.github/workflows/docs.yml +38 -0
- data/.gitignore +11 -0
- data/.travis.yml +3 -0
- data/CHANGELOG.md +61 -0
- data/COMMITS.md +196 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +330 -0
- data/Rakefile +9 -0
- data/bunny_farm.gemspec +30 -0
- data/config/bunny.yml.erb +29 -0
- data/config/bunny_test.yml.erb +29 -0
- data/config/hipchat.yml.erb +12 -0
- data/docs/api/configuration.md +9 -0
- data/docs/api/consumer.md +8 -0
- data/docs/api/message-class.md +419 -0
- data/docs/api/publisher.md +9 -0
- data/docs/architecture/integration.md +8 -0
- data/docs/architecture/message-flow.md +11 -0
- data/docs/architecture/overview.md +448 -0
- data/docs/architecture/scaling.md +8 -0
- data/docs/assets/actions_dsl_flow.svg +109 -0
- data/docs/assets/architecture_overview.svg +152 -0
- data/docs/assets/best_practices_patterns.svg +203 -0
- data/docs/assets/bunny_farm_logo.png +0 -0
- data/docs/assets/configuration_api_methods.svg +104 -0
- data/docs/assets/configuration_flow.svg +130 -0
- data/docs/assets/configuration_hierarchy.svg +70 -0
- data/docs/assets/data_processing_pipeline.svg +131 -0
- data/docs/assets/debugging_monitoring.svg +165 -0
- data/docs/assets/ecommerce_example_flow.svg +145 -0
- data/docs/assets/email_campaign_example.svg +127 -0
- data/docs/assets/environment_variables_map.svg +78 -0
- data/docs/assets/error_handling_flow.svg +114 -0
- data/docs/assets/favicon.ico +1 -0
- data/docs/assets/fields_dsl_structure.svg +89 -0
- data/docs/assets/instance_methods_lifecycle.svg +137 -0
- data/docs/assets/integration_patterns.svg +207 -0
- data/docs/assets/json_serialization_flow.svg +153 -0
- data/docs/assets/logo.svg +4 -0
- data/docs/assets/message_api_overview.svg +126 -0
- data/docs/assets/message_encapsulation.svg +113 -0
- data/docs/assets/message_lifecycle.svg +110 -0
- data/docs/assets/message_structure.svg +138 -0
- data/docs/assets/publisher_consumer_api.svg +120 -0
- data/docs/assets/scaling_deployment_patterns.svg +195 -0
- data/docs/assets/smart_routing_diagram.svg +131 -0
- data/docs/assets/system_architecture_overview.svg +155 -0
- data/docs/assets/task_scheduling_flow.svg +139 -0
- data/docs/assets/testing_strategies.svg +146 -0
- data/docs/assets/workflow_patterns.svg +183 -0
- data/docs/assets/yaml_config_structure.svg +72 -0
- data/docs/configuration/environment-variables.md +14 -0
- data/docs/configuration/overview.md +373 -0
- data/docs/configuration/programmatic-setup.md +10 -0
- data/docs/configuration/yaml-configuration.md +12 -0
- data/docs/core-features/configuration.md +528 -0
- data/docs/core-features/error-handling.md +82 -0
- data/docs/core-features/json-serialization.md +545 -0
- data/docs/core-features/message-design.md +406 -0
- data/docs/core-features/smart-routing.md +467 -0
- data/docs/core-features/task-scheduling.md +67 -0
- data/docs/core-features/workflow-support.md +112 -0
- data/docs/development/contributing.md +345 -0
- data/docs/development/roadmap.md +9 -0
- data/docs/development/testing.md +14 -0
- data/docs/examples/order-processing.md +10 -0
- data/docs/examples/overview.md +269 -0
- data/docs/examples/real-world.md +8 -0
- data/docs/examples/simple-producer-consumer.md +15 -0
- data/docs/examples/task-scheduler.md +9 -0
- data/docs/getting-started/basic-concepts.md +274 -0
- data/docs/getting-started/installation.md +122 -0
- data/docs/getting-started/quick-start.md +158 -0
- data/docs/index.md +106 -0
- data/docs/message-structure/actions-dsl.md +163 -0
- data/docs/message-structure/fields-dsl.md +146 -0
- data/docs/message-structure/instance-methods.md +115 -0
- data/docs/message-structure/overview.md +211 -0
- data/examples/README.md +212 -0
- data/examples/consumer.rb +41 -0
- data/examples/images/message_flow.svg +87 -0
- data/examples/images/order_workflow.svg +122 -0
- data/examples/images/producer_consumer.svg +96 -0
- data/examples/images/task_scheduler.svg +140 -0
- data/examples/order_processor.rb +238 -0
- data/examples/producer.rb +60 -0
- data/examples/simple_message.rb +43 -0
- data/examples/task_scheduler.rb +263 -0
- data/images/architecture_overview.svg +152 -0
- data/images/bunny_farm_logo.png +0 -0
- data/images/configuration_flow.svg +130 -0
- data/images/message_structure.svg +138 -0
- data/lib/bunny_farm/.irbrc +7 -0
- data/lib/bunny_farm/generic_consumer.rb +12 -0
- data/lib/bunny_farm/hash_ext.rb +37 -0
- data/lib/bunny_farm/init_bunny.rb +137 -0
- data/lib/bunny_farm/init_hipchat.rb +49 -0
- data/lib/bunny_farm/message.rb +218 -0
- data/lib/bunny_farm/message_elements.rb +25 -0
- data/lib/bunny_farm/version.rb +3 -0
- data/lib/bunny_farm.rb +9 -0
- data/mkdocs.yml +148 -0
- metadata +244 -0
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
# Message-Centric Design
|
|
2
|
+
|
|
3
|
+
BunnyFarm's core philosophy revolves around treating messages as first-class objects that encapsulate both data and behavior. This approach provides a clean, object-oriented way to handle background job processing.
|
|
4
|
+
|
|
5
|
+
## What is Message-Centric Design?
|
|
6
|
+
|
|
7
|
+
In traditional job queue systems, jobs are simple data structures passed to generic worker processes. BunnyFarm takes a different approach where each message type is a full Ruby class that defines:
|
|
8
|
+
|
|
9
|
+
- **Data structure** - What fields the message contains
|
|
10
|
+
- **Business logic** - What operations can be performed
|
|
11
|
+
- **Error handling** - How failures are managed
|
|
12
|
+
- **State management** - Success/failure tracking
|
|
13
|
+
|
|
14
|
+
## Benefits of Message Classes
|
|
15
|
+
|
|
16
|
+
### 1. Encapsulation
|
|
17
|
+
Related data and behavior stay together:
|
|
18
|
+
|
|
19
|
+
```ruby
|
|
20
|
+
class OrderMessage < BunnyFarm::Message
|
|
21
|
+
# Data structure
|
|
22
|
+
fields :order_id, :customer_email, :items
|
|
23
|
+
|
|
24
|
+
# Business logic
|
|
25
|
+
actions :validate, :process_payment, :ship
|
|
26
|
+
|
|
27
|
+
def validate
|
|
28
|
+
validate_order_data
|
|
29
|
+
validate_customer_info
|
|
30
|
+
success! if errors.empty?
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def process_payment
|
|
34
|
+
# Payment logic here
|
|
35
|
+
success!
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### 2. Discoverability
|
|
41
|
+
Easy to understand what a message can do:
|
|
42
|
+
|
|
43
|
+
```ruby
|
|
44
|
+
# Clear interface
|
|
45
|
+
OrderMessage.new.respond_to?(:validate) # => true
|
|
46
|
+
OrderMessage.new.respond_to?(:ship) # => true
|
|
47
|
+
OrderMessage.new.respond_to?(:fly_rocket) # => false
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### 3. Testability
|
|
51
|
+
Individual message types can be unit tested:
|
|
52
|
+
|
|
53
|
+
```ruby
|
|
54
|
+
class TestOrderMessage < Minitest::Test
|
|
55
|
+
def test_validation
|
|
56
|
+
message = OrderMessage.new
|
|
57
|
+
message[:order_id] = nil
|
|
58
|
+
message.validate
|
|
59
|
+
|
|
60
|
+
assert message.failed?
|
|
61
|
+
assert_includes message.errors, "Order ID required"
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### 4. Type Safety
|
|
67
|
+
Ruby's class system provides structure:
|
|
68
|
+
|
|
69
|
+
```ruby
|
|
70
|
+
class OrderMessage < BunnyFarm::Message
|
|
71
|
+
fields :order_id, :customer_email
|
|
72
|
+
|
|
73
|
+
def validate
|
|
74
|
+
failure("Order ID required") if @items[:order_id].nil?
|
|
75
|
+
failure("Invalid email") unless valid_email?(@items[:customer_email])
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
private
|
|
79
|
+
|
|
80
|
+
def valid_email?(email)
|
|
81
|
+
email =~ /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Message Anatomy
|
|
87
|
+
|
|
88
|
+
Every BunnyFarm message has four key components:
|
|
89
|
+
|
|
90
|
+
### 1. Class Definition
|
|
91
|
+
Inherit from `BunnyFarm::Message`:
|
|
92
|
+
|
|
93
|
+
```ruby
|
|
94
|
+
class CustomerMessage < BunnyFarm::Message
|
|
95
|
+
# Message definition
|
|
96
|
+
end
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### 2. Fields DSL
|
|
100
|
+
Define the data structure:
|
|
101
|
+
|
|
102
|
+
```ruby
|
|
103
|
+
fields :name, :email, :phone,
|
|
104
|
+
{ address: [:street, :city, :state, :zip] },
|
|
105
|
+
{ preferences: [:newsletter, :marketing] }
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### 3. Actions DSL
|
|
109
|
+
Define available operations:
|
|
110
|
+
|
|
111
|
+
```ruby
|
|
112
|
+
actions :register, :update_profile, :send_welcome_email
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### 4. Action Methods
|
|
116
|
+
Implement the business logic:
|
|
117
|
+
|
|
118
|
+
```ruby
|
|
119
|
+
def register
|
|
120
|
+
validate_customer_data
|
|
121
|
+
return unless successful?
|
|
122
|
+
|
|
123
|
+
create_account
|
|
124
|
+
return unless successful?
|
|
125
|
+
|
|
126
|
+
send_welcome_email
|
|
127
|
+
end
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Design Patterns
|
|
131
|
+
|
|
132
|
+
### Command Pattern
|
|
133
|
+
Each action is a command that can be executed:
|
|
134
|
+
|
|
135
|
+
```ruby
|
|
136
|
+
class ReportMessage < BunnyFarm::Message
|
|
137
|
+
actions :generate, :email, :archive
|
|
138
|
+
|
|
139
|
+
def generate
|
|
140
|
+
# Generate report
|
|
141
|
+
@report_data = create_report
|
|
142
|
+
success!
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def email
|
|
146
|
+
# Email the report
|
|
147
|
+
send_report_email(@report_data)
|
|
148
|
+
success!
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Usage
|
|
153
|
+
report = ReportMessage.new
|
|
154
|
+
report[:report_type] = 'monthly_sales'
|
|
155
|
+
report.publish('generate') # Execute generate command
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### State Machine Pattern
|
|
159
|
+
Messages can represent state transitions:
|
|
160
|
+
|
|
161
|
+
```ruby
|
|
162
|
+
class OrderMessage < BunnyFarm::Message
|
|
163
|
+
actions :place_order, :confirm_payment, :ship_order, :complete_order
|
|
164
|
+
|
|
165
|
+
def place_order
|
|
166
|
+
@items[:status] = 'pending'
|
|
167
|
+
validate_order
|
|
168
|
+
success!
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def confirm_payment
|
|
172
|
+
return failure("Order not pending") unless @items[:status] == 'pending'
|
|
173
|
+
|
|
174
|
+
@items[:status] = 'confirmed'
|
|
175
|
+
process_payment
|
|
176
|
+
success!
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### Template Method Pattern
|
|
182
|
+
Define common workflow structure:
|
|
183
|
+
|
|
184
|
+
```ruby
|
|
185
|
+
class ProcessingMessage < BunnyFarm::Message
|
|
186
|
+
def process
|
|
187
|
+
validate_input
|
|
188
|
+
return unless successful?
|
|
189
|
+
|
|
190
|
+
perform_work
|
|
191
|
+
return unless successful?
|
|
192
|
+
|
|
193
|
+
finalize_result
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
private
|
|
197
|
+
|
|
198
|
+
# Subclasses override these methods
|
|
199
|
+
def validate_input; raise NotImplementedError; end
|
|
200
|
+
def perform_work; raise NotImplementedError; end
|
|
201
|
+
def finalize_result; success!; end
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
class OrderProcessingMessage < ProcessingMessage
|
|
205
|
+
def validate_input
|
|
206
|
+
failure("Invalid order") unless valid_order?
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def perform_work
|
|
210
|
+
charge_payment
|
|
211
|
+
update_inventory
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
## Message Lifecycle
|
|
217
|
+
|
|
218
|
+
Understanding the message lifecycle helps design better message classes:
|
|
219
|
+
|
|
220
|
+
### 1. Creation
|
|
221
|
+
```ruby
|
|
222
|
+
message = OrderMessage.new
|
|
223
|
+
message[:order_id] = 12345
|
|
224
|
+
message[:customer_email] = 'customer@example.com'
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
### 2. Publishing
|
|
228
|
+
```ruby
|
|
229
|
+
message.publish('validate') # Routing key: OrderMessage.validate
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### 3. Consumption
|
|
233
|
+
```ruby
|
|
234
|
+
# Consumer receives message and calls validate method
|
|
235
|
+
def validate
|
|
236
|
+
# Business logic
|
|
237
|
+
success!
|
|
238
|
+
successful? # Returns true for ACK
|
|
239
|
+
end
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
### 4. Acknowledgment
|
|
243
|
+
- `true` return → Message acknowledged (ACK)
|
|
244
|
+
- `false` return → Message rejected (NACK)
|
|
245
|
+
|
|
246
|
+
## Best Practices
|
|
247
|
+
|
|
248
|
+
### 1. Single Responsibility
|
|
249
|
+
Each message class should handle one domain:
|
|
250
|
+
|
|
251
|
+
```ruby
|
|
252
|
+
# Good: Focused on orders
|
|
253
|
+
class OrderMessage < BunnyFarm::Message
|
|
254
|
+
actions :validate, :process, :ship
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
# Avoid: Too broad
|
|
258
|
+
class EverythingMessage < BunnyFarm::Message
|
|
259
|
+
actions :process_order, :send_email, :update_inventory, :generate_report
|
|
260
|
+
end
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
### 2. Clear Action Names
|
|
264
|
+
Use descriptive, verb-based action names:
|
|
265
|
+
|
|
266
|
+
```ruby
|
|
267
|
+
# Good: Clear intent
|
|
268
|
+
actions :validate_order, :process_payment, :ship_order, :send_confirmation
|
|
269
|
+
|
|
270
|
+
# Avoid: Vague names
|
|
271
|
+
actions :do_stuff, :handle, :process
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### 3. Proper Error Handling
|
|
275
|
+
Always handle errors gracefully:
|
|
276
|
+
|
|
277
|
+
```ruby
|
|
278
|
+
def process_payment
|
|
279
|
+
return failure("No payment info") unless payment_present?
|
|
280
|
+
|
|
281
|
+
begin
|
|
282
|
+
result = payment_gateway.charge(@items[:amount])
|
|
283
|
+
|
|
284
|
+
if result.success?
|
|
285
|
+
success!
|
|
286
|
+
else
|
|
287
|
+
failure("Payment failed: #{result.error_message}")
|
|
288
|
+
end
|
|
289
|
+
rescue PaymentGatewayError => e
|
|
290
|
+
failure("Gateway error: #{e.message}")
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
successful?
|
|
294
|
+
end
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
### 4. Idempotent Operations
|
|
298
|
+
Make operations safe to retry:
|
|
299
|
+
|
|
300
|
+
```ruby
|
|
301
|
+
def charge_payment
|
|
302
|
+
# Check if already processed
|
|
303
|
+
return success! if payment_already_charged?
|
|
304
|
+
|
|
305
|
+
# Process only if not done
|
|
306
|
+
result = charge_customer(@items[:amount])
|
|
307
|
+
result.success? ? success! : failure(result.error)
|
|
308
|
+
|
|
309
|
+
successful?
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
private
|
|
313
|
+
|
|
314
|
+
def payment_already_charged?
|
|
315
|
+
PaymentRecord.exists?(order_id: @items[:order_id])
|
|
316
|
+
end
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
### 5. Meaningful Field Structures
|
|
320
|
+
Design clear, hierarchical data structures:
|
|
321
|
+
|
|
322
|
+
```ruby
|
|
323
|
+
# Good: Clear hierarchy
|
|
324
|
+
fields :order_id, :total_amount,
|
|
325
|
+
{ customer: [:name, :email, :phone] },
|
|
326
|
+
{ billing_address: [:street, :city, :state, :zip] },
|
|
327
|
+
{ items: [:product_id, :quantity, :unit_price] }
|
|
328
|
+
|
|
329
|
+
# Avoid: Flat structure
|
|
330
|
+
fields :order_id, :customer_name, :customer_email, :customer_phone,
|
|
331
|
+
:billing_street, :billing_city, :billing_state, :billing_zip
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
## Advanced Patterns
|
|
335
|
+
|
|
336
|
+
### Message Inheritance
|
|
337
|
+
Create base classes for common functionality:
|
|
338
|
+
|
|
339
|
+
```ruby
|
|
340
|
+
class BaseProcessingMessage < BunnyFarm::Message
|
|
341
|
+
def process
|
|
342
|
+
start_processing
|
|
343
|
+
perform_work
|
|
344
|
+
complete_processing
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
private
|
|
348
|
+
|
|
349
|
+
def start_processing
|
|
350
|
+
@items[:started_at] = Time.current
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
def complete_processing
|
|
354
|
+
@items[:completed_at] = Time.current
|
|
355
|
+
success!
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
def perform_work
|
|
359
|
+
raise NotImplementedError, "Subclass must implement perform_work"
|
|
360
|
+
end
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
class OrderProcessingMessage < BaseProcessingMessage
|
|
364
|
+
fields :order_id, :customer_id
|
|
365
|
+
actions :process
|
|
366
|
+
|
|
367
|
+
private
|
|
368
|
+
|
|
369
|
+
def perform_work
|
|
370
|
+
validate_order
|
|
371
|
+
charge_payment
|
|
372
|
+
update_inventory
|
|
373
|
+
end
|
|
374
|
+
end
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
### Message Composition
|
|
378
|
+
Compose complex operations from simpler ones:
|
|
379
|
+
|
|
380
|
+
```ruby
|
|
381
|
+
class OrderWorkflowMessage < BunnyFarm::Message
|
|
382
|
+
actions :start_workflow
|
|
383
|
+
|
|
384
|
+
def start_workflow
|
|
385
|
+
# Chain multiple message types
|
|
386
|
+
validation_msg = OrderValidationMessage.new(@items)
|
|
387
|
+
validation_msg.publish('validate')
|
|
388
|
+
|
|
389
|
+
payment_msg = PaymentMessage.new(@items)
|
|
390
|
+
payment_msg.publish('charge')
|
|
391
|
+
|
|
392
|
+
shipping_msg = ShippingMessage.new(@items)
|
|
393
|
+
shipping_msg.publish('create_label')
|
|
394
|
+
|
|
395
|
+
success!
|
|
396
|
+
end
|
|
397
|
+
end
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
## Next Steps
|
|
401
|
+
|
|
402
|
+
Now that you understand message-centric design:
|
|
403
|
+
|
|
404
|
+
- **[Smart Routing](smart-routing.md)** - How messages find their destination
|
|
405
|
+
- **[JSON Serialization](json-serialization.md)** - Data format and serialization
|
|
406
|
+
- **[Message Structure](../message-structure/overview.md)** - Deep dive into implementation
|