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,163 @@
|
|
|
1
|
+
# Actions DSL
|
|
2
|
+
|
|
3
|
+
The Actions DSL defines the operations that your message can perform. Each action becomes a routable method that can be called when the message is consumed.
|
|
4
|
+
|
|
5
|
+
## Basic Actions
|
|
6
|
+
|
|
7
|
+
```ruby
|
|
8
|
+
class OrderMessage < BunnyFarm::Message
|
|
9
|
+
actions :validate, :process, :ship, :cancel
|
|
10
|
+
|
|
11
|
+
def validate
|
|
12
|
+
# Validation logic
|
|
13
|
+
success!
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def process
|
|
17
|
+
# Processing logic
|
|
18
|
+
success!
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def ship
|
|
22
|
+
# Shipping logic
|
|
23
|
+
success!
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def cancel
|
|
27
|
+
# Cancellation logic
|
|
28
|
+
success!
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Action Method Implementation
|
|
34
|
+
|
|
35
|
+
Each action must be implemented as a method:
|
|
36
|
+
|
|
37
|
+
```ruby
|
|
38
|
+
class PaymentMessage < BunnyFarm::Message
|
|
39
|
+
actions :authorize, :capture, :refund
|
|
40
|
+
|
|
41
|
+
def authorize
|
|
42
|
+
result = payment_gateway.authorize(@items[:amount], @items[:card_token])
|
|
43
|
+
|
|
44
|
+
if result.success?
|
|
45
|
+
@items[:authorization_id] = result.authorization_id
|
|
46
|
+
success!
|
|
47
|
+
else
|
|
48
|
+
failure("Authorization failed: #{result.error_message}")
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
successful?
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def capture
|
|
55
|
+
return failure("No authorization ID") unless @items[:authorization_id]
|
|
56
|
+
|
|
57
|
+
result = payment_gateway.capture(@items[:authorization_id])
|
|
58
|
+
|
|
59
|
+
if result.success?
|
|
60
|
+
@items[:transaction_id] = result.transaction_id
|
|
61
|
+
success!
|
|
62
|
+
else
|
|
63
|
+
failure("Capture failed: #{result.error_message}")
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
successful?
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Action Routing
|
|
72
|
+
|
|
73
|
+
Actions create routing keys automatically:
|
|
74
|
+
|
|
75
|
+
```ruby
|
|
76
|
+
# Publishing creates routing keys
|
|
77
|
+
message = PaymentMessage.new
|
|
78
|
+
message.publish('authorize') # Routing key: PaymentMessage.authorize
|
|
79
|
+
message.publish('capture') # Routing key: PaymentMessage.capture
|
|
80
|
+
message.publish('refund') # Routing key: PaymentMessage.refund
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Action Patterns
|
|
84
|
+
|
|
85
|
+
### CRUD Operations
|
|
86
|
+
```ruby
|
|
87
|
+
class UserMessage < BunnyFarm::Message
|
|
88
|
+
actions :create, :read, :update, :delete
|
|
89
|
+
end
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Workflow Actions
|
|
93
|
+
```ruby
|
|
94
|
+
class OrderWorkflow < BunnyFarm::Message
|
|
95
|
+
actions :start, :validate, :process_payment, :fulfill, :ship, :complete
|
|
96
|
+
end
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### State Machine Actions
|
|
100
|
+
```ruby
|
|
101
|
+
class DocumentMessage < BunnyFarm::Message
|
|
102
|
+
actions :draft, :review, :approve, :publish, :archive
|
|
103
|
+
end
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Action Validation
|
|
107
|
+
|
|
108
|
+
```ruby
|
|
109
|
+
class ValidatedActions < BunnyFarm::Message
|
|
110
|
+
actions :process
|
|
111
|
+
|
|
112
|
+
def process
|
|
113
|
+
# Validate before processing
|
|
114
|
+
return unless validate_preconditions
|
|
115
|
+
|
|
116
|
+
# Perform work
|
|
117
|
+
do_processing
|
|
118
|
+
|
|
119
|
+
# Validate after processing
|
|
120
|
+
return unless validate_postconditions
|
|
121
|
+
|
|
122
|
+
success!
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
private
|
|
126
|
+
|
|
127
|
+
def validate_preconditions
|
|
128
|
+
failure("Missing required data") unless required_data_present?
|
|
129
|
+
successful?
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def validate_postconditions
|
|
133
|
+
failure("Processing incomplete") unless processing_complete?
|
|
134
|
+
successful?
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## Best Practices
|
|
140
|
+
|
|
141
|
+
### 1. Use Descriptive Action Names
|
|
142
|
+
```ruby
|
|
143
|
+
# Good: Clear, descriptive names
|
|
144
|
+
actions :validate_order, :process_payment, :ship_order, :send_confirmation
|
|
145
|
+
|
|
146
|
+
# Avoid: Vague or generic names
|
|
147
|
+
actions :do_stuff, :handle, :process
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### 2. Single Responsibility
|
|
151
|
+
```ruby
|
|
152
|
+
# Good: Each action has one purpose
|
|
153
|
+
actions :validate_customer, :validate_inventory, :validate_payment
|
|
154
|
+
|
|
155
|
+
# Avoid: Actions that do too much
|
|
156
|
+
actions :validate_everything
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### 3. Logical Flow
|
|
160
|
+
```ruby
|
|
161
|
+
# Good: Actions follow logical order
|
|
162
|
+
actions :create_draft, :add_content, :review, :approve, :publish
|
|
163
|
+
```
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# Fields DSL
|
|
2
|
+
|
|
3
|
+
The Fields DSL is BunnyFarm's declarative syntax for defining the data structure of your messages. It provides a clean, readable way to specify expected fields and their organization.
|
|
4
|
+
|
|
5
|
+

|
|
6
|
+
|
|
7
|
+
## Basic Field Declaration
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
class UserMessage < BunnyFarm::Message
|
|
11
|
+
# Simple fields
|
|
12
|
+
fields :user_id, :email, :name, :created_at
|
|
13
|
+
end
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Nested Objects
|
|
17
|
+
|
|
18
|
+
```ruby
|
|
19
|
+
class CustomerMessage < BunnyFarm::Message
|
|
20
|
+
fields :customer_id, :email,
|
|
21
|
+
{ address: [:street, :city, :state, :zip] },
|
|
22
|
+
{ preferences: [:newsletter, :promotions, :sms] }
|
|
23
|
+
end
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Complex Structures
|
|
27
|
+
|
|
28
|
+
```ruby
|
|
29
|
+
class OrderMessage < BunnyFarm::Message
|
|
30
|
+
fields :order_id, :total, :tax, :shipping,
|
|
31
|
+
{ customer: [:name, :email, :phone] },
|
|
32
|
+
{ billing_address: [:street, :city, :state, :zip, :country] },
|
|
33
|
+
{ shipping_address: [:street, :city, :state, :zip, :country] },
|
|
34
|
+
{ items: [:product_id, :name, :quantity, :unit_price, :total_price] },
|
|
35
|
+
{ payment: [:method, :transaction_id, :status] }
|
|
36
|
+
end
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Data Access
|
|
40
|
+
|
|
41
|
+
```ruby
|
|
42
|
+
message = OrderMessage.new
|
|
43
|
+
|
|
44
|
+
# Simple fields
|
|
45
|
+
message[:order_id] = 12345
|
|
46
|
+
message[:total] = 99.99
|
|
47
|
+
|
|
48
|
+
# Nested objects
|
|
49
|
+
message[:customer] = {
|
|
50
|
+
name: "John Doe",
|
|
51
|
+
email: "john@example.com",
|
|
52
|
+
phone: "+1-555-123-4567"
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
# Arrays of objects
|
|
56
|
+
message[:items] = [
|
|
57
|
+
{
|
|
58
|
+
product_id: 1,
|
|
59
|
+
name: "Widget",
|
|
60
|
+
quantity: 2,
|
|
61
|
+
unit_price: 29.99,
|
|
62
|
+
total_price: 59.98
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
product_id: 2,
|
|
66
|
+
name: "Gadget",
|
|
67
|
+
quantity: 1,
|
|
68
|
+
unit_price: 39.99,
|
|
69
|
+
total_price: 39.99
|
|
70
|
+
}
|
|
71
|
+
]
|
|
72
|
+
|
|
73
|
+
# Access nested data
|
|
74
|
+
puts message[:customer][:name] # "John Doe"
|
|
75
|
+
puts message[:items][0][:product_id] # 1
|
|
76
|
+
puts message[:billing_address][:city] # Access nested fields
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Field Validation
|
|
80
|
+
|
|
81
|
+
```ruby
|
|
82
|
+
class ValidatedMessage < BunnyFarm::Message
|
|
83
|
+
fields :user_id, :email, :age
|
|
84
|
+
|
|
85
|
+
def validate
|
|
86
|
+
failure("User ID required") unless @items[:user_id]
|
|
87
|
+
failure("Invalid email") unless valid_email?(@items[:email])
|
|
88
|
+
failure("Age must be positive") unless @items[:age]&.positive?
|
|
89
|
+
|
|
90
|
+
success! if errors.empty?
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
private
|
|
94
|
+
|
|
95
|
+
def valid_email?(email)
|
|
96
|
+
email =~ /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Dynamic Field Access
|
|
102
|
+
|
|
103
|
+
```ruby
|
|
104
|
+
message = OrderMessage.new
|
|
105
|
+
|
|
106
|
+
# Get all field keys
|
|
107
|
+
field_keys = message.keys
|
|
108
|
+
# => [:order_id, :total, :customer, :items, ...]
|
|
109
|
+
|
|
110
|
+
# Check if field exists
|
|
111
|
+
has_customer = message.keys.include?(:customer)
|
|
112
|
+
|
|
113
|
+
# Iterate over fields
|
|
114
|
+
message.keys.each do |field|
|
|
115
|
+
value = message[field]
|
|
116
|
+
puts "#{field}: #{value}"
|
|
117
|
+
end
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Best Practices
|
|
121
|
+
|
|
122
|
+
### 1. Logical Grouping
|
|
123
|
+
```ruby
|
|
124
|
+
# Good: Related fields grouped together
|
|
125
|
+
fields :order_id, :status, :created_at,
|
|
126
|
+
{ customer: [:id, :name, :email] },
|
|
127
|
+
{ items: [:id, :name, :price] }
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### 2. Consistent Naming
|
|
131
|
+
```ruby
|
|
132
|
+
# Good: Consistent snake_case
|
|
133
|
+
fields :user_id, :created_at, :email_address
|
|
134
|
+
|
|
135
|
+
# Avoid: Mixed conventions
|
|
136
|
+
fields :userId, :created_at, :EmailAddress
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### 3. Meaningful Structure
|
|
140
|
+
```ruby
|
|
141
|
+
# Good: Clear hierarchy
|
|
142
|
+
fields { address: [:street, :city, :state, :zip] }
|
|
143
|
+
|
|
144
|
+
# Avoid: Flat structure
|
|
145
|
+
fields :address_street, :address_city, :address_state, :address_zip
|
|
146
|
+
```
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# Instance Methods
|
|
2
|
+
|
|
3
|
+
BunnyFarm message instances provide a rich set of methods for data access, state management, and message operations.
|
|
4
|
+
|
|
5
|
+
## Data Access Methods
|
|
6
|
+
|
|
7
|
+
### Hash-like Access
|
|
8
|
+
```ruby
|
|
9
|
+
message = OrderMessage.new
|
|
10
|
+
|
|
11
|
+
# Get values
|
|
12
|
+
order_id = message[:order_id]
|
|
13
|
+
customer_name = message[:customer][:name]
|
|
14
|
+
|
|
15
|
+
# Set values
|
|
16
|
+
message[:order_id] = 12345
|
|
17
|
+
message[:customer] = { name: "John", email: "john@example.com" }
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
### Field Introspection
|
|
21
|
+
```ruby
|
|
22
|
+
# Get all field keys
|
|
23
|
+
keys = message.keys # => [:order_id, :customer, :items]
|
|
24
|
+
|
|
25
|
+
# Check if field exists
|
|
26
|
+
has_customer = message.respond_to?(:[]) && message[:customer]
|
|
27
|
+
|
|
28
|
+
# Get field count
|
|
29
|
+
field_count = message.keys.length
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## State Management
|
|
33
|
+
|
|
34
|
+
### Success/Failure Tracking
|
|
35
|
+
```ruby
|
|
36
|
+
def process_order
|
|
37
|
+
validate_data
|
|
38
|
+
return unless successful?
|
|
39
|
+
|
|
40
|
+
charge_payment
|
|
41
|
+
return unless successful?
|
|
42
|
+
|
|
43
|
+
update_inventory
|
|
44
|
+
success! # Mark as successful
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Check state
|
|
48
|
+
if message.successful?
|
|
49
|
+
puts "Order processed successfully"
|
|
50
|
+
else
|
|
51
|
+
puts "Order failed: #{message.errors.join(', ')}"
|
|
52
|
+
end
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Error Management
|
|
56
|
+
```ruby
|
|
57
|
+
def validate
|
|
58
|
+
failure("Order ID required") unless @items[:order_id]
|
|
59
|
+
failure("Invalid email") unless valid_email?(@items[:email])
|
|
60
|
+
|
|
61
|
+
# Check error state
|
|
62
|
+
if failed?
|
|
63
|
+
puts "Validation errors: #{errors.join(', ')}"
|
|
64
|
+
else
|
|
65
|
+
success!
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Message Operations
|
|
71
|
+
|
|
72
|
+
### Publishing
|
|
73
|
+
```ruby
|
|
74
|
+
# Publish message
|
|
75
|
+
message.publish('process') # Routing key: MessageClass.process
|
|
76
|
+
|
|
77
|
+
# Check if publish succeeded
|
|
78
|
+
if message.successful?
|
|
79
|
+
puts "Message published successfully"
|
|
80
|
+
else
|
|
81
|
+
puts "Publish failed: #{message.errors.join(', ')}"
|
|
82
|
+
end
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Serialization
|
|
86
|
+
```ruby
|
|
87
|
+
# Convert to JSON
|
|
88
|
+
json_string = message.to_json
|
|
89
|
+
puts json_string
|
|
90
|
+
# => {"order_id":12345,"customer":{"name":"John"}}
|
|
91
|
+
|
|
92
|
+
# Get raw payload (if received message)
|
|
93
|
+
original_json = message.payload
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Utility Methods
|
|
97
|
+
|
|
98
|
+
### Inspection and Debugging
|
|
99
|
+
```ruby
|
|
100
|
+
# Inspect message contents
|
|
101
|
+
puts message.inspect
|
|
102
|
+
|
|
103
|
+
# Pretty print for debugging
|
|
104
|
+
puts JSON.pretty_generate(message.to_hash)
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Cloning and Copying
|
|
108
|
+
```ruby
|
|
109
|
+
# Create copy with same data
|
|
110
|
+
new_message = message.class.new
|
|
111
|
+
message.keys.each { |key| new_message[key] = message[key] }
|
|
112
|
+
|
|
113
|
+
# Publish copy with different action
|
|
114
|
+
new_message.publish('different_action')
|
|
115
|
+
```
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
# Message Structure Overview
|
|
2
|
+
|
|
3
|
+
Understanding how BunnyFarm messages are structured is key to using the library effectively. This section covers the anatomy of a BunnyFarm message and how the DSL components work together.
|
|
4
|
+
|
|
5
|
+
<img src="../assets/message_structure.svg" alt="Message Structure" width="100%">
|
|
6
|
+
|
|
7
|
+
## Message Class Anatomy
|
|
8
|
+
|
|
9
|
+
A BunnyFarm message class consists of several key components:
|
|
10
|
+
|
|
11
|
+
```ruby
|
|
12
|
+
class OrderMessage < BunnyFarm::Message
|
|
13
|
+
# 1. Fields DSL - Define data structure
|
|
14
|
+
fields :order_id, :total,
|
|
15
|
+
{ customer: [:name, :email] },
|
|
16
|
+
{ items: [:id, :qty] }
|
|
17
|
+
|
|
18
|
+
# 2. Actions DSL - Define available operations
|
|
19
|
+
actions :validate, :process_payment, :ship, :cancel
|
|
20
|
+
|
|
21
|
+
# 3. Action Methods - Business logic
|
|
22
|
+
def validate
|
|
23
|
+
# Validation logic here
|
|
24
|
+
success!
|
|
25
|
+
successful?
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def process_payment
|
|
29
|
+
# Payment processing logic
|
|
30
|
+
success!
|
|
31
|
+
successful?
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# 4. Helper Methods (private)
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def validate_customer
|
|
38
|
+
# Helper logic
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## DSL Components
|
|
44
|
+
|
|
45
|
+
### Fields DSL
|
|
46
|
+
The `fields` DSL defines the expected data structure for your messages:
|
|
47
|
+
|
|
48
|
+
- **Simple fields**: Basic data types like strings, numbers, booleans
|
|
49
|
+
- **Nested objects**: Complex data structures with sub-fields
|
|
50
|
+
- **Arrays**: Lists of items or objects
|
|
51
|
+
|
|
52
|
+
### Actions DSL
|
|
53
|
+
The `actions` DSL defines the operations your message can perform:
|
|
54
|
+
|
|
55
|
+
- Each action becomes a routable method
|
|
56
|
+
- Actions map to routing keys: `MessageClass.action`
|
|
57
|
+
- Must implement corresponding methods
|
|
58
|
+
|
|
59
|
+
## Instance Structure
|
|
60
|
+
|
|
61
|
+
When a message is instantiated, it contains several important instance variables:
|
|
62
|
+
|
|
63
|
+
### @items
|
|
64
|
+
Validated and structured data extracted from the raw JSON based on your fields definition.
|
|
65
|
+
|
|
66
|
+
```ruby
|
|
67
|
+
message[:customer][:name] # Access via hash-like interface
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### @elements
|
|
71
|
+
Raw JSON data as received from the message broker.
|
|
72
|
+
|
|
73
|
+
```ruby
|
|
74
|
+
message.elements # Access raw data
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### @payload
|
|
78
|
+
The original JSON string as received from RabbitMQ.
|
|
79
|
+
|
|
80
|
+
```ruby
|
|
81
|
+
message.payload # Original JSON string
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### @errors
|
|
85
|
+
Array of error messages accumulated during processing.
|
|
86
|
+
|
|
87
|
+
```ruby
|
|
88
|
+
message.errors # => ["Validation failed", "Payment declined"]
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Data Flow
|
|
92
|
+
|
|
93
|
+
The message goes through several stages:
|
|
94
|
+
|
|
95
|
+
1. **JSON Input** - Raw message data from RabbitMQ
|
|
96
|
+
2. **Parse & Validate** - Extract fields using DSL definition
|
|
97
|
+
3. **Route Action** - Call method based on routing key
|
|
98
|
+
4. **Execute Logic** - Run business logic in action method
|
|
99
|
+
5. **ACK/NACK** - Acknowledge to RabbitMQ based on success/failure
|
|
100
|
+
|
|
101
|
+
## Access Methods
|
|
102
|
+
|
|
103
|
+
BunnyFarm provides several ways to access and manipulate message data:
|
|
104
|
+
|
|
105
|
+
### Hash-like Access
|
|
106
|
+
```ruby
|
|
107
|
+
msg[:field] # Get value
|
|
108
|
+
msg[:field] = val # Set value
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### JSON Serialization
|
|
112
|
+
```ruby
|
|
113
|
+
msg.to_json # Convert to JSON string
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Field Inspection
|
|
117
|
+
```ruby
|
|
118
|
+
msg.keys # Get all available fields
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## State Management
|
|
122
|
+
|
|
123
|
+
Messages track their processing state throughout the lifecycle:
|
|
124
|
+
|
|
125
|
+
### Success States
|
|
126
|
+
```ruby
|
|
127
|
+
success! # Mark as successful
|
|
128
|
+
successful? # Check if successful
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Failure States
|
|
132
|
+
```ruby
|
|
133
|
+
failure(msg) # Mark as failed with reason
|
|
134
|
+
failed? # Check if failed
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Publishing
|
|
138
|
+
```ruby
|
|
139
|
+
msg.publish(action) # Send message with routing key
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Best Practices
|
|
143
|
+
|
|
144
|
+
### 1. Design Clear Field Structures
|
|
145
|
+
```ruby
|
|
146
|
+
# Good: Clear, hierarchical structure
|
|
147
|
+
fields :order_id, :amount,
|
|
148
|
+
{ customer: [:name, :email, :phone] },
|
|
149
|
+
{ billing_address: [:street, :city, :state, :zip] }
|
|
150
|
+
|
|
151
|
+
# Avoid: Flat, unclear structure
|
|
152
|
+
fields :order_id, :customer_name, :customer_email,
|
|
153
|
+
:billing_street, :billing_city # ... too flat
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### 2. Use Meaningful Action Names
|
|
157
|
+
```ruby
|
|
158
|
+
# Good: Descriptive action names
|
|
159
|
+
actions :validate_order, :process_payment, :ship_order, :send_confirmation
|
|
160
|
+
|
|
161
|
+
# Avoid: Generic action names
|
|
162
|
+
actions :process, :handle, :do_work
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### 3. Implement Proper Error Handling
|
|
166
|
+
```ruby
|
|
167
|
+
def process_payment
|
|
168
|
+
return failure("Missing payment info") unless payment_present?
|
|
169
|
+
return failure("Invalid amount") unless valid_amount?
|
|
170
|
+
|
|
171
|
+
charge_result = payment_gateway.charge(@items[:amount])
|
|
172
|
+
|
|
173
|
+
if charge_result.success?
|
|
174
|
+
success!
|
|
175
|
+
else
|
|
176
|
+
failure("Payment failed: #{charge_result.error}")
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
successful?
|
|
180
|
+
end
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### 4. Keep Methods Focused
|
|
184
|
+
Each action method should have a single, clear responsibility:
|
|
185
|
+
|
|
186
|
+
```ruby
|
|
187
|
+
def validate_order
|
|
188
|
+
validate_customer_info
|
|
189
|
+
validate_items
|
|
190
|
+
validate_shipping_address
|
|
191
|
+
|
|
192
|
+
success! if errors.empty?
|
|
193
|
+
successful?
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
private
|
|
197
|
+
|
|
198
|
+
def validate_customer_info
|
|
199
|
+
failure("Customer name required") if @items[:customer][:name].blank?
|
|
200
|
+
failure("Customer email required") if @items[:customer][:email].blank?
|
|
201
|
+
end
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
## Next Steps
|
|
205
|
+
|
|
206
|
+
Now that you understand message structure, explore:
|
|
207
|
+
|
|
208
|
+
- **[Fields DSL](fields-dsl.md)** - Detailed guide to defining data structures
|
|
209
|
+
- **[Actions DSL](actions-dsl.md)** - Complete actions DSL reference
|
|
210
|
+
- **[Instance Methods](instance-methods.md)** - All available instance methods
|
|
211
|
+
- **[Configuration](../configuration/overview.md)** - Message configuration options
|