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
data/Rakefile ADDED
@@ -0,0 +1,9 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ end
7
+
8
+ task :default => :test
9
+
@@ -0,0 +1,30 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+
5
+ require 'bunny_farm/version'
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = "bunny_farm"
9
+ spec.version = BunnyFarm::VERSION
10
+ spec.authors = ["Dewayne VanHoozer"]
11
+ spec.email = ["dvanhoozer@gmail.com"]
12
+
13
+ spec.summary = %q{ Simple AMQP/JSON background job manager for RabbitMQ }
14
+ spec.description = %q{ A lightweight Ruby gem for managing background jobs using RabbitMQ. Messages are encapsulated as classes with JSON serialization and routing keys in the format MessageClassName.action for simple, organized job processing. }
15
+ spec.homepage = "https://github.com/MadBomber/bunny_farm"
16
+ spec.license = "MIT"
17
+
18
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency "activesupport"
22
+ spec.add_dependency "concurrent-ruby"
23
+ spec.add_dependency "hashie"
24
+ spec.add_dependency "bunny"
25
+
26
+ spec.add_development_dependency "bundler" #, "~> 1.8"
27
+ spec.add_development_dependency "rake" #, "~> 10.0"
28
+ spec.add_development_dependency "minitest"
29
+
30
+ end
@@ -0,0 +1,29 @@
1
+ defaults: &defaults
2
+ host: <%= ENV['AMQP_HOST'] || 'localhost' %>
3
+ vhost: <%= ENV['AMQP_VHOST'] || 'sandbox' %>
4
+ port: <%= (ENV['AMQP_PORT'] || 5672).to_i %>
5
+ user: <%= ENV['AMQP_USER'] || 'guest' %>
6
+ pass: <%= ENV['AMQP_PASS'] || 'guest' %>
7
+ exchange_name: <%= ENV['AMQP_EXCHANGE'] || 'sandbox' %>
8
+ queue_name: <%= ENV['AMQP_QUEUE'] || 'my_queue' %>
9
+ routing_key: <%= ENV['AMQP_ROUTING_KEY'] || "'#.new'" %>
10
+ app_name: <%= ENV['AMQP_APP_NAME'] || 'bunny_farm_job' %>
11
+ heartbeat: :server # defualt: will use RabbitMQ setting
12
+ threaded: true # default
13
+ network_recovery_interval: 15.0 # default is in seconds
14
+ automatically_recover: true # default
15
+ frame_max: 131072 # default
16
+
17
+ development:
18
+ <<: *defaults
19
+
20
+ test:
21
+ <<: *defaults
22
+
23
+ production:
24
+ <<: *defaults
25
+ host: amqp.example.com
26
+ vhost: bunny_farm
27
+
28
+
29
+
@@ -0,0 +1,29 @@
1
+ defaults: &defaults
2
+ host: <%= ENV['AMQP_HOST'] || 'localhost' %>
3
+ vhost: <%= ENV['AMQP_VHOST'] || '/' %>
4
+ port: <%= (ENV['AMQP_PORT'] || 5672).to_i %>
5
+ user: <%= ENV['AMQP_USER'] || 'guest' %>
6
+ pass: <%= ENV['AMQP_PASS'] || 'guest' %>
7
+ exchange_name: <%= ENV['AMQP_EXCHANGE'] || 'test_exchange' %>
8
+ queue_name: <%= ENV['AMQP_QUEUE'] || 'test_queue' %>
9
+ routing_key: <%= ENV['AMQP_ROUTING_KEY'] || "'#.test'" %>
10
+ app_name: <%= ENV['AMQP_APP_NAME'] || 'bunny_farm_test' %>
11
+ heartbeat: :server # defualt: will use RabbitMQ setting
12
+ threaded: true # default
13
+ network_recovery_interval: 15.0 # default is in seconds
14
+ automatically_recover: true # default
15
+ frame_max: 131072 # default
16
+
17
+ development:
18
+ <<: *defaults
19
+
20
+ test:
21
+ <<: *defaults
22
+ vhost: /
23
+ user: guest
24
+ pass: guest
25
+
26
+ production:
27
+ <<: *defaults
28
+ host: amqp.example.com
29
+ vhost: bunny_farm
@@ -0,0 +1,12 @@
1
+ defaults: &defaults
2
+ room: <%= ENV['HIPCHAT_ROOM'] || 'my_room' %>
3
+ token: <%= ENV['HIPCHAT_TOKEN'] || 'my_tpken' %>
4
+
5
+ development:
6
+ <<: *defaults
7
+
8
+ test:
9
+ <<: *defaults
10
+
11
+ production:
12
+ <<: *defaults
@@ -0,0 +1,9 @@
1
+ # Configuration API
2
+
3
+ Programmatic configuration interface.
4
+
5
+ ```ruby
6
+ BunnyFarm.config do
7
+ # Configuration options
8
+ end
9
+ ```
@@ -0,0 +1,8 @@
1
+ # Consumer API
2
+
3
+ API for consuming and processing messages.
4
+
5
+ ## Starting Consumer
6
+ ```ruby
7
+ BunnyFarm.manage
8
+ ```
@@ -0,0 +1,419 @@
1
+ # Message Class API Reference
2
+
3
+ The `BunnyFarm::Message` class is the foundation of the BunnyFarm library. All your message classes inherit from this base class to gain message processing capabilities.
4
+
5
+ ## Class Definition
6
+
7
+ ```ruby
8
+ class YourMessage < BunnyFarm::Message
9
+ fields :field1, :field2, { nested: [:field3, :field4] }
10
+ actions :action1, :action2
11
+
12
+ def action1
13
+ # Your processing logic
14
+ end
15
+ end
16
+ ```
17
+
18
+ ## Class Methods
19
+
20
+ ### `fields(*field_definitions)`
21
+
22
+ Defines the expected data structure for the message.
23
+
24
+ **Parameters:**
25
+ - `field_definitions` - Variable number of field definitions
26
+
27
+ **Field Definition Types:**
28
+
29
+ ```ruby
30
+ # Simple fields
31
+ fields :name, :email, :age
32
+
33
+ # Nested objects
34
+ fields { customer: [:name, :email, :phone] }
35
+
36
+ # Mixed definitions
37
+ fields :order_id, :total,
38
+ { customer: [:name, :email] },
39
+ { items: [:product_id, :quantity, :price] }
40
+ ```
41
+
42
+ **Example:**
43
+ ```ruby
44
+ class OrderMessage < BunnyFarm::Message
45
+ fields :order_id, :total,
46
+ { customer: [:name, :email, :phone] },
47
+ { billing_address: [:street, :city, :state, :zip] },
48
+ { items: [:product_id, :quantity, :price] }
49
+ end
50
+ ```
51
+
52
+ ### `actions(*action_names)`
53
+
54
+ Defines the available actions (operations) for this message type.
55
+
56
+ **Parameters:**
57
+ - `action_names` - Variable number of action symbols
58
+
59
+ **Example:**
60
+ ```ruby
61
+ class OrderMessage < BunnyFarm::Message
62
+ actions :validate, :process_payment, :fulfill, :ship, :cancel
63
+
64
+ def validate
65
+ # Validation logic
66
+ end
67
+
68
+ def process_payment
69
+ # Payment logic
70
+ end
71
+ end
72
+ ```
73
+
74
+ **Routing Keys:**
75
+ Each action creates a routing key in the format: `MessageClassName.action`
76
+ - `OrderMessage.validate`
77
+ - `OrderMessage.process_payment`
78
+ - `OrderMessage.fulfill`
79
+
80
+ ## Instance Methods
81
+
82
+ ### Data Access
83
+
84
+ #### `[](key)`
85
+ Get a field value using hash-like syntax.
86
+
87
+ **Parameters:**
88
+ - `key` - Field name as symbol or string
89
+
90
+ **Returns:**
91
+ - Field value or `nil` if not set
92
+
93
+ **Example:**
94
+ ```ruby
95
+ message = OrderMessage.new
96
+ value = message[:customer_name]
97
+ nested = message[:customer][:email]
98
+ ```
99
+
100
+ #### `[]=(key, value)`
101
+ Set a field value using hash-like syntax.
102
+
103
+ **Parameters:**
104
+ - `key` - Field name as symbol or string
105
+ - `value` - Value to set
106
+
107
+ **Example:**
108
+ ```ruby
109
+ message[:customer_name] = "John Doe"
110
+ message[:customer] = { name: "John", email: "john@example.com" }
111
+ ```
112
+
113
+ #### `keys`
114
+ Get all available field keys.
115
+
116
+ **Returns:**
117
+ - Array of field keys
118
+
119
+ **Example:**
120
+ ```ruby
121
+ message.keys # => [:order_id, :customer, :items]
122
+ ```
123
+
124
+ ### State Management
125
+
126
+ #### `success!`
127
+ Mark the current operation as successful.
128
+
129
+ **Example:**
130
+ ```ruby
131
+ def process_order
132
+ # ... processing logic
133
+ success!
134
+ end
135
+ ```
136
+
137
+ #### `failure(error_message)`
138
+ Mark the current operation as failed with an error message.
139
+
140
+ **Parameters:**
141
+ - `error_message` - String describing the failure
142
+
143
+ **Example:**
144
+ ```ruby
145
+ def process_payment
146
+ if payment_declined?
147
+ failure("Payment was declined by bank")
148
+ end
149
+ end
150
+ ```
151
+
152
+ #### `successful?`
153
+ Check if the current operation was successful.
154
+
155
+ **Returns:**
156
+ - `true` if successful, `false` otherwise
157
+
158
+ **Example:**
159
+ ```ruby
160
+ def process_order
161
+ validate_order
162
+ return unless successful?
163
+
164
+ process_payment
165
+ return unless successful?
166
+
167
+ ship_order
168
+ end
169
+ ```
170
+
171
+ #### `failed?`
172
+ Check if the current operation failed.
173
+
174
+ **Returns:**
175
+ - `true` if failed, `false` otherwise
176
+
177
+ **Example:**
178
+ ```ruby
179
+ if message.failed?
180
+ puts "Processing failed: #{message.errors.join(', ')}"
181
+ end
182
+ ```
183
+
184
+ #### `errors`
185
+ Get array of error messages accumulated during processing.
186
+
187
+ **Returns:**
188
+ - Array of error message strings
189
+
190
+ **Example:**
191
+ ```ruby
192
+ def validate
193
+ failure("Name is required") if @items[:name].blank?
194
+ failure("Email is invalid") unless valid_email?(@items[:email])
195
+ end
196
+
197
+ # Later...
198
+ if message.failed?
199
+ message.errors # => ["Name is required", "Email is invalid"]
200
+ end
201
+ ```
202
+
203
+ ### Message Operations
204
+
205
+ #### `publish(action)`
206
+ Publish the message with the specified action.
207
+
208
+ **Parameters:**
209
+ - `action` - Action name as string or symbol
210
+
211
+ **Returns:**
212
+ - `true` if published successfully, `false` otherwise
213
+
214
+ **Example:**
215
+ ```ruby
216
+ message = OrderMessage.new
217
+ message[:order_id] = 12345
218
+ message[:customer] = { name: "John", email: "john@example.com" }
219
+
220
+ if message.publish('process')
221
+ puts "Message published successfully"
222
+ else
223
+ puts "Failed to publish: #{message.errors.join(', ')}"
224
+ end
225
+ ```
226
+
227
+ **Routing Key:**
228
+ The message will be routed using: `MessageClassName.action`
229
+
230
+ #### `to_json`
231
+ Convert the message data to JSON string.
232
+
233
+ **Returns:**
234
+ - JSON string representation of the message data
235
+
236
+ **Example:**
237
+ ```ruby
238
+ message[:name] = "John"
239
+ message[:email] = "john@example.com"
240
+ json = message.to_json
241
+ # => '{"name":"John","email":"john@example.com"}'
242
+ ```
243
+
244
+ ## Instance Variables
245
+
246
+ ### `@items`
247
+ Hash containing the validated and structured message data based on field definitions.
248
+
249
+ **Access:**
250
+ - Direct: `@items[:field_name]`
251
+ - Hash-like: `message[:field_name]`
252
+
253
+ ### `@elements`
254
+ Hash containing the raw message data as received from the message broker.
255
+
256
+ ### `@payload`
257
+ String containing the original JSON payload as received from RabbitMQ.
258
+
259
+ ### `@errors`
260
+ Array containing error messages accumulated during processing.
261
+
262
+ ## Action Method Implementation
263
+
264
+ When you define actions, you must implement corresponding methods:
265
+
266
+ ```ruby
267
+ class OrderMessage < BunnyFarm::Message
268
+ actions :validate, :process, :ship
269
+
270
+ def validate
271
+ # Validation logic here
272
+ validate_customer_info
273
+ validate_order_items
274
+ validate_shipping_address
275
+
276
+ # Mark as successful if no errors
277
+ success! if errors.empty?
278
+
279
+ # Return true for ACK, false for NACK
280
+ successful?
281
+ end
282
+
283
+ def process
284
+ # Processing logic
285
+ charge_payment
286
+ update_inventory
287
+
288
+ if payment_successful? && inventory_updated?
289
+ success!
290
+ else
291
+ failure("Order processing failed")
292
+ end
293
+
294
+ successful?
295
+ end
296
+
297
+ def ship
298
+ # Shipping logic
299
+ create_shipping_label
300
+ notify_carrier
301
+ send_tracking_info
302
+
303
+ success!
304
+ successful?
305
+ end
306
+
307
+ private
308
+
309
+ def validate_customer_info
310
+ failure("Customer name required") if @items[:customer][:name].blank?
311
+ failure("Customer email required") if @items[:customer][:email].blank?
312
+ end
313
+
314
+ def charge_payment
315
+ # Payment processing logic
316
+ end
317
+ end
318
+ ```
319
+
320
+ ## Error Handling Patterns
321
+
322
+ ### Basic Error Handling
323
+ ```ruby
324
+ def risky_operation
325
+ begin
326
+ perform_external_call
327
+ success!
328
+ rescue ExternalServiceError => e
329
+ failure("External service failed: #{e.message}")
330
+ rescue StandardError => e
331
+ failure("Unexpected error: #{e.message}")
332
+ end
333
+
334
+ successful?
335
+ end
336
+ ```
337
+
338
+ ### Validation with Multiple Errors
339
+ ```ruby
340
+ def validate
341
+ failure("Name required") if @items[:name].blank?
342
+ failure("Email required") if @items[:email].blank?
343
+ failure("Invalid email format") unless valid_email?
344
+
345
+ success! if errors.empty?
346
+ successful?
347
+ end
348
+ ```
349
+
350
+ ### Conditional Processing
351
+ ```ruby
352
+ def process_order
353
+ validate_order
354
+ return unless successful?
355
+
356
+ authorize_payment
357
+ return unless successful?
358
+
359
+ fulfill_order
360
+ return unless successful?
361
+
362
+ send_confirmation
363
+ end
364
+ ```
365
+
366
+ ## Best Practices
367
+
368
+ ### 1. Always Return Success Status
369
+ Action methods should always return the result of `successful?`:
370
+
371
+ ```ruby
372
+ def my_action
373
+ # ... processing logic
374
+ success! # or failure(...)
375
+ successful? # Always return this
376
+ end
377
+ ```
378
+
379
+ ### 2. Use Descriptive Error Messages
380
+ Provide clear, actionable error messages:
381
+
382
+ ```ruby
383
+ # Good
384
+ failure("Customer email format is invalid: #{email}")
385
+
386
+ # Avoid
387
+ failure("Invalid input")
388
+ ```
389
+
390
+ ### 3. Implement Idempotent Operations
391
+ Make operations safe to retry:
392
+
393
+ ```ruby
394
+ def charge_payment
395
+ return success! if already_charged?
396
+
397
+ # Perform charge only if not already done
398
+ result = payment_gateway.charge(@items[:amount])
399
+
400
+ if result.success?
401
+ success!
402
+ else
403
+ failure("Payment failed: #{result.error}")
404
+ end
405
+
406
+ successful?
407
+ end
408
+ ```
409
+
410
+ ### 4. Keep Actions Focused
411
+ Each action should have a single, clear responsibility:
412
+
413
+ ```ruby
414
+ # Good: Focused actions
415
+ actions :validate_order, :process_payment, :ship_order
416
+
417
+ # Avoid: Overly broad actions
418
+ actions :handle_order # Too generic
419
+ ```
@@ -0,0 +1,9 @@
1
+ # Publisher API
2
+
3
+ API for publishing messages to RabbitMQ.
4
+
5
+ ## Publishing Messages
6
+ ```ruby
7
+ message = OrderMessage.new
8
+ message.publish('process')
9
+ ```
@@ -0,0 +1,8 @@
1
+ # Integration Patterns
2
+
3
+ Common integration scenarios and patterns.
4
+
5
+ ## Event-Driven Architecture
6
+ - Service communication
7
+ - Event sourcing
8
+ - CQRS patterns
@@ -0,0 +1,11 @@
1
+ # Message Flow
2
+
3
+ Detailed message processing flow through the system.
4
+
5
+ ## Flow Stages
6
+ 1. Creation
7
+ 2. Publishing
8
+ 3. Routing
9
+ 4. Consumption
10
+ 5. Processing
11
+ 6. Acknowledgment