smart_message 0.0.10 → 0.0.12
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 +4 -4
- data/.github/workflows/deploy-github-pages.yml +38 -0
- data/.gitignore +5 -0
- data/CHANGELOG.md +30 -0
- data/Gemfile.lock +35 -4
- data/README.md +169 -71
- data/Rakefile +29 -4
- data/docs/assets/images/ddq_architecture.svg +130 -0
- data/docs/assets/images/dlq_architecture.svg +115 -0
- data/docs/assets/images/enhanced-dual-publishing.svg +136 -0
- data/docs/assets/images/enhanced-fluent-api.svg +149 -0
- data/docs/assets/images/enhanced-microservices-routing.svg +115 -0
- data/docs/assets/images/enhanced-pattern-matching.svg +107 -0
- data/docs/assets/images/fluent-api-demo.svg +59 -0
- data/docs/assets/images/performance-comparison.svg +161 -0
- data/docs/assets/images/redis-basic-architecture.svg +53 -0
- data/docs/assets/images/redis-enhanced-architecture.svg +88 -0
- data/docs/assets/images/redis-queue-architecture.svg +101 -0
- data/docs/assets/images/smart_message.jpg +0 -0
- data/docs/assets/images/smart_message_walking.jpg +0 -0
- data/docs/assets/images/smartmessage_architecture_overview.svg +173 -0
- data/docs/assets/images/transport-comparison-matrix.svg +171 -0
- data/docs/assets/javascripts/mathjax.js +17 -0
- data/docs/assets/stylesheets/extra.css +51 -0
- data/docs/{addressing.md → core-concepts/addressing.md} +5 -7
- data/docs/{architecture.md → core-concepts/architecture.md} +78 -138
- data/docs/{dispatcher.md → core-concepts/dispatcher.md} +21 -21
- data/docs/{message_filtering.md → core-concepts/message-filtering.md} +2 -3
- data/docs/{message_processing.md → core-concepts/message-processing.md} +17 -17
- data/docs/{troubleshooting.md → development/troubleshooting.md} +7 -7
- data/docs/{examples.md → getting-started/examples.md} +115 -89
- data/docs/{getting-started.md → getting-started/quick-start.md} +47 -18
- data/docs/guides/redis-queue-getting-started.md +697 -0
- data/docs/guides/redis-queue-patterns.md +889 -0
- data/docs/guides/redis-queue-production.md +1091 -0
- data/docs/index.md +64 -0
- data/docs/{dead_letter_queue.md → reference/dead-letter-queue.md} +2 -3
- data/docs/{logging.md → reference/logging.md} +1 -1
- data/docs/{message_deduplication.md → reference/message-deduplication.md} +1 -0
- data/docs/{proc_handlers_summary.md → reference/proc-handlers.md} +7 -6
- data/docs/{serializers.md → reference/serializers.md} +3 -5
- data/docs/{transports.md → reference/transports.md} +133 -11
- data/docs/transports/memory-transport.md +374 -0
- data/docs/transports/redis-enhanced-transport.md +524 -0
- data/docs/transports/redis-queue-transport.md +1304 -0
- data/docs/transports/redis-transport-comparison.md +496 -0
- data/docs/transports/redis-transport.md +509 -0
- data/examples/README.md +98 -5
- data/examples/city_scenario/911_emergency_call_flow.svg +99 -0
- data/examples/city_scenario/README.md +515 -0
- data/examples/city_scenario/ai_visitor_intelligence_flow.svg +108 -0
- data/examples/city_scenario/citizen.rb +195 -0
- data/examples/city_scenario/city_diagram.svg +125 -0
- data/examples/city_scenario/common/health_monitor.rb +80 -0
- data/examples/city_scenario/common/logger.rb +30 -0
- data/examples/city_scenario/emergency_dispatch_center.rb +270 -0
- data/examples/city_scenario/fire_department.rb +446 -0
- data/examples/city_scenario/fire_emergency_flow.svg +95 -0
- data/examples/city_scenario/health_department.rb +100 -0
- data/examples/city_scenario/health_monitoring_system.svg +130 -0
- data/examples/city_scenario/house.rb +244 -0
- data/examples/city_scenario/local_bank.rb +217 -0
- data/examples/city_scenario/messages/emergency_911_message.rb +81 -0
- data/examples/city_scenario/messages/emergency_resolved_message.rb +43 -0
- data/examples/city_scenario/messages/fire_dispatch_message.rb +43 -0
- data/examples/city_scenario/messages/fire_emergency_message.rb +45 -0
- data/examples/city_scenario/messages/health_check_message.rb +22 -0
- data/examples/city_scenario/messages/health_status_message.rb +35 -0
- data/examples/city_scenario/messages/police_dispatch_message.rb +46 -0
- data/examples/city_scenario/messages/silent_alarm_message.rb +38 -0
- data/examples/city_scenario/police_department.rb +316 -0
- data/examples/city_scenario/redis_monitor.rb +129 -0
- data/examples/city_scenario/redis_stats.rb +743 -0
- data/examples/city_scenario/room_for_improvement.md +240 -0
- data/examples/city_scenario/security_emergency_flow.svg +95 -0
- data/examples/city_scenario/service_internal_architecture.svg +154 -0
- data/examples/city_scenario/smart_message_ai_agent.rb +364 -0
- data/examples/city_scenario/start_demo.sh +236 -0
- data/examples/city_scenario/stop_demo.sh +106 -0
- data/examples/city_scenario/visitor.rb +631 -0
- data/examples/{10_message_deduplication.rb → memory/01_message_deduplication_demo.rb} +1 -1
- data/examples/{09_dead_letter_queue_demo.rb → memory/02_dead_letter_queue_demo.rb} +13 -40
- data/examples/{01_point_to_point_orders.rb → memory/03_point_to_point_orders.rb} +1 -1
- data/examples/{02_publish_subscribe_events.rb → memory/04_publish_subscribe_events.rb} +2 -2
- data/examples/{03_many_to_many_chat.rb → memory/05_many_to_many_chat.rb} +4 -4
- data/examples/{show_me.rb → memory/06_pretty_print_demo.rb} +1 -1
- data/examples/{05_proc_handlers.rb → memory/07_proc_handlers_demo.rb} +2 -2
- data/examples/{06_custom_logger_example.rb → memory/08_custom_logger_demo.rb} +17 -14
- data/examples/{07_error_handling_scenarios.rb → memory/09_error_handling_demo.rb} +4 -4
- data/examples/{08_entity_addressing_basic.rb → memory/10_entity_addressing_basic.rb} +8 -8
- data/examples/{08_entity_addressing_with_filtering.rb → memory/11_entity_addressing_with_filtering.rb} +6 -6
- data/examples/{09_regex_filtering_microservices.rb → memory/12_regex_filtering_microservices.rb} +2 -2
- data/examples/{10_header_block_configuration.rb → memory/13_header_block_configuration.rb} +6 -6
- data/examples/{11_global_configuration_example.rb → memory/14_global_configuration_demo.rb} +19 -8
- data/examples/{show_logger.rb → memory/15_logger_demo.rb} +1 -1
- data/examples/memory/README.md +163 -0
- data/examples/memory/memory_transport_architecture.svg +90 -0
- data/examples/memory/point_to_point_pattern.svg +94 -0
- data/examples/memory/publish_subscribe_pattern.svg +125 -0
- data/examples/{04_redis_smart_home_iot.rb → redis/01_smart_home_iot_demo.rb} +5 -5
- data/examples/redis/README.md +230 -0
- data/examples/redis/alert_system_flow.svg +127 -0
- data/examples/redis/dashboard_status_flow.svg +107 -0
- data/examples/redis/device_command_flow.svg +113 -0
- data/examples/redis/redis_transport_architecture.svg +115 -0
- data/examples/{smart_home_iot_dataflow.md → redis/smart_home_iot_dataflow.md} +4 -116
- data/examples/redis/smart_home_system_architecture.svg +133 -0
- data/examples/redis_enhanced/README.md +319 -0
- data/examples/redis_enhanced/enhanced_01_basic_patterns.rb +233 -0
- data/examples/redis_enhanced/enhanced_02_fluent_api.rb +331 -0
- data/examples/redis_enhanced/enhanced_03_dual_publishing.rb +281 -0
- data/examples/redis_enhanced/enhanced_04_advanced_routing.rb +419 -0
- data/examples/redis_queue/01_basic_messaging.rb +221 -0
- data/examples/redis_queue/01_comprehensive_examples.rb +508 -0
- data/examples/redis_queue/02_pattern_routing.rb +405 -0
- data/examples/redis_queue/03_fluent_api.rb +422 -0
- data/examples/redis_queue/04_load_balancing.rb +486 -0
- data/examples/redis_queue/05_microservices.rb +735 -0
- data/examples/redis_queue/06_emergency_alerts.rb +777 -0
- data/examples/redis_queue/07_queue_management.rb +587 -0
- data/examples/redis_queue/README.md +366 -0
- data/examples/redis_queue/enhanced_01_basic_patterns.rb +233 -0
- data/examples/redis_queue/enhanced_02_fluent_api.rb +331 -0
- data/examples/redis_queue/enhanced_03_dual_publishing.rb +281 -0
- data/examples/redis_queue/enhanced_04_advanced_routing.rb +419 -0
- data/examples/redis_queue/redis_queue_architecture.svg +148 -0
- data/ideas/README.md +41 -0
- data/ideas/agents.md +1001 -0
- data/ideas/database_transport.md +980 -0
- data/ideas/improvement.md +359 -0
- data/ideas/meshage.md +1788 -0
- data/ideas/message_discovery.md +178 -0
- data/ideas/message_schema.md +1381 -0
- data/lib/smart_message/.idea/.gitignore +8 -0
- data/lib/smart_message/.idea/markdown.xml +6 -0
- data/lib/smart_message/.idea/misc.xml +4 -0
- data/lib/smart_message/.idea/modules.xml +8 -0
- data/lib/smart_message/.idea/smart_message.iml +16 -0
- data/lib/smart_message/.idea/vcs.xml +6 -0
- data/lib/smart_message/addressing.rb +15 -0
- data/lib/smart_message/base.rb +0 -2
- data/lib/smart_message/configuration.rb +1 -1
- data/lib/smart_message/logger.rb +15 -4
- data/lib/smart_message/plugins.rb +5 -2
- data/lib/smart_message/serializer.rb +14 -0
- data/lib/smart_message/transport/redis_enhanced_transport.rb +399 -0
- data/lib/smart_message/transport/redis_queue_transport.rb +555 -0
- data/lib/smart_message/transport/registry.rb +1 -0
- data/lib/smart_message/transport.rb +34 -1
- data/lib/smart_message/version.rb +1 -1
- data/lib/smart_message.rb +5 -52
- data/mkdocs.yml +184 -0
- data/p2p_plan.md +326 -0
- data/p2p_roadmap.md +287 -0
- data/smart_message.gemspec +2 -0
- data/smart_message.svg +51 -0
- metadata +170 -44
- data/docs/README.md +0 -57
- data/examples/dead_letters.jsonl +0 -12
- data/examples/temp.txt +0 -94
- data/examples/tmux_chat/README.md +0 -283
- data/examples/tmux_chat/bot_agent.rb +0 -278
- data/examples/tmux_chat/human_agent.rb +0 -199
- data/examples/tmux_chat/room_monitor.rb +0 -160
- data/examples/tmux_chat/shared_chat_system.rb +0 -328
- data/examples/tmux_chat/start_chat_demo.sh +0 -190
- data/examples/tmux_chat/stop_chat_demo.sh +0 -22
- /data/docs/{properties.md → core-concepts/properties.md} +0 -0
- /data/docs/{ideas_to_think_about.md → development/ideas.md} +0 -0
@@ -0,0 +1,889 @@
|
|
1
|
+
# Redis Queue Transport - Advanced Routing Patterns
|
2
|
+
|
3
|
+
This guide covers advanced routing patterns and use cases for the Redis Queue Transport, helping you implement sophisticated message routing architectures.
|
4
|
+
|
5
|
+
## Pattern Syntax Reference
|
6
|
+
|
7
|
+
### Wildcard Meanings
|
8
|
+
|
9
|
+
| Symbol | Matches | Examples |
|
10
|
+
|--------|---------|----------|
|
11
|
+
| `#` | Zero or more words | `#` matches `a`, `a.b`, `a.b.c` |
|
12
|
+
| `*` | Exactly one word | `*` matches `a` but not `a.b` |
|
13
|
+
| `.` | Word separator | Literal dot character |
|
14
|
+
|
15
|
+
### Pattern Structure
|
16
|
+
|
17
|
+
All patterns follow the routing key format:
|
18
|
+
```
|
19
|
+
namespace.message_type.from_uuid.to_uuid
|
20
|
+
```
|
21
|
+
|
22
|
+
Common pattern examples:
|
23
|
+
- `#.*.service_name` - All messages TO service_name
|
24
|
+
- `#.sender.*` - All messages FROM sender
|
25
|
+
- `namespace.#.*.*` - All messages in namespace
|
26
|
+
- `#.#.#.broadcast` - All broadcast messages
|
27
|
+
|
28
|
+
## Basic Routing Patterns
|
29
|
+
|
30
|
+
### 1. Service-to-Service Communication
|
31
|
+
|
32
|
+
```ruby
|
33
|
+
# API Gateway routing requests to services
|
34
|
+
transport = SmartMessage::Transport::RedisQueueTransport.new
|
35
|
+
|
36
|
+
class ServiceRequest < SmartMessage::Base
|
37
|
+
transport :redis_queue
|
38
|
+
property :service, required: true
|
39
|
+
property :operation, required: true
|
40
|
+
property :payload, default: {}
|
41
|
+
end
|
42
|
+
|
43
|
+
# Route to user service
|
44
|
+
transport.subscribe_pattern("#.*.user_service") do |msg_class, data|
|
45
|
+
request = JSON.parse(data)
|
46
|
+
puts "👤 User Service: #{request['operation']}"
|
47
|
+
|
48
|
+
case request['operation']
|
49
|
+
when 'create_user'
|
50
|
+
create_user(request['payload'])
|
51
|
+
when 'get_user'
|
52
|
+
get_user(request['payload']['user_id'])
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# Route to payment service
|
57
|
+
transport.subscribe_pattern("#.*.payment_service") do |msg_class, data|
|
58
|
+
request = JSON.parse(data)
|
59
|
+
puts "💳 Payment Service: #{request['operation']}"
|
60
|
+
|
61
|
+
case request['operation']
|
62
|
+
when 'process_payment'
|
63
|
+
process_payment(request['payload'])
|
64
|
+
when 'refund_payment'
|
65
|
+
refund_payment(request['payload'])
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# API Gateway publishes requests
|
70
|
+
ServiceRequest.new(
|
71
|
+
service: 'user_service',
|
72
|
+
operation: 'create_user',
|
73
|
+
payload: { name: 'John Doe', email: 'john@example.com' },
|
74
|
+
_sm_header: { from: 'api_gateway', to: 'user_service' }
|
75
|
+
).publish
|
76
|
+
```
|
77
|
+
|
78
|
+
### 2. Event-Driven Architecture
|
79
|
+
|
80
|
+
```ruby
|
81
|
+
# Domain events with smart routing
|
82
|
+
class OrderEvent < SmartMessage::Base
|
83
|
+
transport :redis_queue
|
84
|
+
property :event_type, required: true
|
85
|
+
property :order_id, required: true
|
86
|
+
property :data, default: {}
|
87
|
+
end
|
88
|
+
|
89
|
+
# Multiple services react to order events
|
90
|
+
class InventoryService
|
91
|
+
def self.start
|
92
|
+
transport = SmartMessage::Transport::RedisQueueTransport.new
|
93
|
+
|
94
|
+
# Listen for order events that affect inventory
|
95
|
+
transport.subscribe_pattern("order.#.*.*") do |msg_class, data|
|
96
|
+
event = JSON.parse(data)
|
97
|
+
|
98
|
+
case event['event_type']
|
99
|
+
when 'order_placed'
|
100
|
+
reserve_inventory(event['order_id'], event['data']['items'])
|
101
|
+
when 'order_cancelled'
|
102
|
+
release_inventory(event['order_id'])
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
class ShippingService
|
109
|
+
def self.start
|
110
|
+
transport = SmartMessage::Transport::RedisQueueTransport.new
|
111
|
+
|
112
|
+
# Listen for paid orders
|
113
|
+
transport.subscribe_pattern("order.#.payment_service.*") do |msg_class, data|
|
114
|
+
event = JSON.parse(data)
|
115
|
+
|
116
|
+
if event['event_type'] == 'payment_confirmed'
|
117
|
+
schedule_shipping(event['order_id'])
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
# Publish order events
|
124
|
+
OrderEvent.new(
|
125
|
+
event_type: 'order_placed',
|
126
|
+
order_id: 'ORD-123',
|
127
|
+
data: { items: [{ sku: 'BOOK-001', qty: 2 }], customer_id: 'CUST-456' },
|
128
|
+
_sm_header: { from: 'order_service', to: 'inventory_service' }
|
129
|
+
).publish
|
130
|
+
```
|
131
|
+
|
132
|
+
### 3. Multi-Tenant Routing
|
133
|
+
|
134
|
+
```ruby
|
135
|
+
# Tenant isolation through routing patterns
|
136
|
+
class TenantMessage < SmartMessage::Base
|
137
|
+
transport :redis_queue
|
138
|
+
property :tenant_id, required: true
|
139
|
+
property :data, required: true
|
140
|
+
end
|
141
|
+
|
142
|
+
class TenantService
|
143
|
+
def initialize(tenant_id)
|
144
|
+
@tenant_id = tenant_id
|
145
|
+
@transport = SmartMessage::Transport::RedisQueueTransport.new
|
146
|
+
setup_subscriptions
|
147
|
+
end
|
148
|
+
|
149
|
+
private
|
150
|
+
|
151
|
+
def setup_subscriptions
|
152
|
+
# Only receive messages for this tenant
|
153
|
+
pattern = "#.#{@tenant_id}_*.*"
|
154
|
+
|
155
|
+
@transport.subscribe_pattern(pattern) do |msg_class, data|
|
156
|
+
message = JSON.parse(data)
|
157
|
+
puts "🏢 Tenant #{@tenant_id} processing: #{msg_class}"
|
158
|
+
process_tenant_message(message)
|
159
|
+
end
|
160
|
+
|
161
|
+
# Admin broadcasts to all tenants
|
162
|
+
@transport.subscribe_pattern("#.admin.*.broadcast") do |msg_class, data|
|
163
|
+
message = JSON.parse(data)
|
164
|
+
puts "📢 Admin broadcast to tenant #{@tenant_id}: #{message['subject']}"
|
165
|
+
process_admin_broadcast(message)
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
# Start tenant services
|
171
|
+
tenant_1_service = TenantService.new('tenant_123')
|
172
|
+
tenant_2_service = TenantService.new('tenant_456')
|
173
|
+
|
174
|
+
# Publish tenant-specific messages
|
175
|
+
TenantMessage.new(
|
176
|
+
tenant_id: 'tenant_123',
|
177
|
+
data: { user_count: 50 },
|
178
|
+
_sm_header: { from: 'tenant_123_analytics', to: 'tenant_123_dashboard' }
|
179
|
+
).publish
|
180
|
+
|
181
|
+
# Admin broadcast
|
182
|
+
AdminBroadcast.new(
|
183
|
+
subject: 'Scheduled maintenance tonight',
|
184
|
+
message: 'System will be down from 2-4 AM',
|
185
|
+
_sm_header: { from: 'admin', to: 'broadcast' }
|
186
|
+
).publish
|
187
|
+
```
|
188
|
+
|
189
|
+
## Advanced Routing Scenarios
|
190
|
+
|
191
|
+
### 4. Priority-Based Routing
|
192
|
+
|
193
|
+
```ruby
|
194
|
+
# Priority-based message routing
|
195
|
+
class PriorityMessage < SmartMessage::Base
|
196
|
+
transport :redis_queue
|
197
|
+
property :priority, required: true # critical, high, normal, low
|
198
|
+
property :data, required: true
|
199
|
+
end
|
200
|
+
|
201
|
+
class PriorityRouter
|
202
|
+
def self.start
|
203
|
+
transport = SmartMessage::Transport::RedisQueueTransport.new
|
204
|
+
|
205
|
+
# Route based on priority in the FROM field
|
206
|
+
transport.subscribe_pattern("priority.#.*.*") do |msg_class, data|
|
207
|
+
message = JSON.parse(data)
|
208
|
+
|
209
|
+
target_service = case message['priority']
|
210
|
+
when 'critical'
|
211
|
+
'critical_processor'
|
212
|
+
when 'high'
|
213
|
+
'high_priority_processor'
|
214
|
+
else
|
215
|
+
'normal_processor'
|
216
|
+
end
|
217
|
+
|
218
|
+
# Re-route to priority-specific service
|
219
|
+
PriorityMessage.new(
|
220
|
+
priority: message['priority'],
|
221
|
+
data: message['data'],
|
222
|
+
_sm_header: {
|
223
|
+
from: 'priority_router',
|
224
|
+
to: target_service
|
225
|
+
}
|
226
|
+
).publish
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
# Priority-specific processors
|
232
|
+
class CriticalProcessor
|
233
|
+
def self.start
|
234
|
+
transport = SmartMessage::Transport::RedisQueueTransport.new
|
235
|
+
|
236
|
+
transport.subscribe_pattern("#.*.critical_processor") do |msg_class, data|
|
237
|
+
message = JSON.parse(data)
|
238
|
+
puts "🚨 CRITICAL: Processing #{message['data']} immediately"
|
239
|
+
# Process with highest priority
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
# Publish priority messages
|
245
|
+
PriorityMessage.new(
|
246
|
+
priority: 'critical',
|
247
|
+
data: { alert: 'System overload detected' },
|
248
|
+
_sm_header: { from: 'monitoring', to: 'priority_router' }
|
249
|
+
).publish
|
250
|
+
```
|
251
|
+
|
252
|
+
### 5. Geographic Routing
|
253
|
+
|
254
|
+
```ruby
|
255
|
+
# Geographic region-based routing
|
256
|
+
class GeographicMessage < SmartMessage::Base
|
257
|
+
transport :redis_queue
|
258
|
+
property :region, required: true # us-east, us-west, eu, asia
|
259
|
+
property :data, required: true
|
260
|
+
end
|
261
|
+
|
262
|
+
# Regional processors
|
263
|
+
class RegionalProcessor
|
264
|
+
def initialize(region)
|
265
|
+
@region = region
|
266
|
+
@transport = SmartMessage::Transport::RedisQueueTransport.new
|
267
|
+
setup_subscriptions
|
268
|
+
end
|
269
|
+
|
270
|
+
private
|
271
|
+
|
272
|
+
def setup_subscriptions
|
273
|
+
# Process messages for this region
|
274
|
+
pattern = "#.*.#{@region}_processor"
|
275
|
+
|
276
|
+
@transport.subscribe_pattern(pattern) do |msg_class, data|
|
277
|
+
message = JSON.parse(data)
|
278
|
+
puts "🌍 #{@region.upcase} processing: #{message['data']}"
|
279
|
+
process_regional_data(message['data'])
|
280
|
+
end
|
281
|
+
|
282
|
+
# Global broadcasts
|
283
|
+
@transport.subscribe_pattern("#.*.global") do |msg_class, data|
|
284
|
+
message = JSON.parse(data)
|
285
|
+
puts "🌎 Global message in #{@region}: #{message['data']}"
|
286
|
+
process_global_message(message['data'])
|
287
|
+
end
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
# Geographic router
|
292
|
+
class GeographicRouter
|
293
|
+
def self.start
|
294
|
+
transport = SmartMessage::Transport::RedisQueueTransport.new
|
295
|
+
|
296
|
+
transport.subscribe_pattern("geo.#.*.*") do |msg_class, data|
|
297
|
+
message = JSON.parse(data)
|
298
|
+
|
299
|
+
# Route to regional processor
|
300
|
+
target = "#{message['region']}_processor"
|
301
|
+
|
302
|
+
GeographicMessage.new(
|
303
|
+
region: message['region'],
|
304
|
+
data: message['data'],
|
305
|
+
_sm_header: {
|
306
|
+
from: 'geo_router',
|
307
|
+
to: target
|
308
|
+
}
|
309
|
+
).publish
|
310
|
+
end
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
# Start regional processors
|
315
|
+
us_east = RegionalProcessor.new('us_east')
|
316
|
+
us_west = RegionalProcessor.new('us_west')
|
317
|
+
eu = RegionalProcessor.new('eu')
|
318
|
+
```
|
319
|
+
|
320
|
+
### 6. Content-Based Routing
|
321
|
+
|
322
|
+
```ruby
|
323
|
+
# Route messages based on content analysis
|
324
|
+
class ContentRouter
|
325
|
+
def self.start
|
326
|
+
transport = SmartMessage::Transport::RedisQueueTransport.new
|
327
|
+
|
328
|
+
# Analyze incoming messages and route appropriately
|
329
|
+
transport.subscribe_pattern("content.#.*.*") do |msg_class, data|
|
330
|
+
message = JSON.parse(data)
|
331
|
+
content = message['content']
|
332
|
+
|
333
|
+
# Determine routing based on content
|
334
|
+
routes = analyze_content(content)
|
335
|
+
|
336
|
+
routes.each do |service|
|
337
|
+
RoutedMessage.new(
|
338
|
+
content: content,
|
339
|
+
analysis_result: routes,
|
340
|
+
_sm_header: {
|
341
|
+
from: 'content_router',
|
342
|
+
to: service
|
343
|
+
}
|
344
|
+
).publish
|
345
|
+
end
|
346
|
+
end
|
347
|
+
end
|
348
|
+
|
349
|
+
private
|
350
|
+
|
351
|
+
def self.analyze_content(content)
|
352
|
+
routes = []
|
353
|
+
|
354
|
+
# Text analysis routing
|
355
|
+
if content.match?(/urgent|emergency|critical/i)
|
356
|
+
routes << 'alert_service'
|
357
|
+
end
|
358
|
+
|
359
|
+
if content.match?(/order|purchase|buy/i)
|
360
|
+
routes << 'sales_service'
|
361
|
+
end
|
362
|
+
|
363
|
+
if content.match?(/bug|error|issue/i)
|
364
|
+
routes << 'support_service'
|
365
|
+
end
|
366
|
+
|
367
|
+
if content.match?/@\w+/) # Contains mentions
|
368
|
+
routes << 'notification_service'
|
369
|
+
end
|
370
|
+
|
371
|
+
routes << 'archive_service' # Always archive
|
372
|
+
routes.uniq
|
373
|
+
end
|
374
|
+
end
|
375
|
+
```
|
376
|
+
|
377
|
+
### 7. Workflow Routing
|
378
|
+
|
379
|
+
```ruby
|
380
|
+
# Multi-step workflow routing
|
381
|
+
class WorkflowStep < SmartMessage::Base
|
382
|
+
transport :redis_queue
|
383
|
+
property :workflow_id, required: true
|
384
|
+
property :step_number, required: true
|
385
|
+
property :data, required: true
|
386
|
+
property :next_step
|
387
|
+
end
|
388
|
+
|
389
|
+
class WorkflowEngine
|
390
|
+
WORKFLOWS = {
|
391
|
+
'order_processing' => [
|
392
|
+
'validate_order',
|
393
|
+
'check_inventory',
|
394
|
+
'process_payment',
|
395
|
+
'ship_order',
|
396
|
+
'send_confirmation'
|
397
|
+
],
|
398
|
+
'user_onboarding' => [
|
399
|
+
'verify_email',
|
400
|
+
'setup_profile',
|
401
|
+
'send_welcome',
|
402
|
+
'assign_trial'
|
403
|
+
]
|
404
|
+
}.freeze
|
405
|
+
|
406
|
+
def self.start
|
407
|
+
transport = SmartMessage::Transport::RedisQueueTransport.new
|
408
|
+
|
409
|
+
# Listen for workflow completions
|
410
|
+
transport.subscribe_pattern("workflow.#.*.*") do |msg_class, data|
|
411
|
+
step = JSON.parse(data)
|
412
|
+
advance_workflow(step)
|
413
|
+
end
|
414
|
+
end
|
415
|
+
|
416
|
+
private
|
417
|
+
|
418
|
+
def self.advance_workflow(completed_step)
|
419
|
+
workflow_type = determine_workflow_type(completed_step['workflow_id'])
|
420
|
+
steps = WORKFLOWS[workflow_type]
|
421
|
+
current_index = steps.index(completed_step['step_type'])
|
422
|
+
|
423
|
+
if current_index && current_index < steps.length - 1
|
424
|
+
next_step = steps[current_index + 1]
|
425
|
+
|
426
|
+
WorkflowStep.new(
|
427
|
+
workflow_id: completed_step['workflow_id'],
|
428
|
+
step_number: current_index + 2,
|
429
|
+
data: completed_step['data'],
|
430
|
+
next_step: next_step,
|
431
|
+
_sm_header: {
|
432
|
+
from: 'workflow_engine',
|
433
|
+
to: "#{next_step}_service"
|
434
|
+
}
|
435
|
+
).publish
|
436
|
+
else
|
437
|
+
# Workflow complete
|
438
|
+
WorkflowComplete.new(
|
439
|
+
workflow_id: completed_step['workflow_id'],
|
440
|
+
_sm_header: {
|
441
|
+
from: 'workflow_engine',
|
442
|
+
to: 'workflow_monitor'
|
443
|
+
}
|
444
|
+
).publish
|
445
|
+
end
|
446
|
+
end
|
447
|
+
end
|
448
|
+
```
|
449
|
+
|
450
|
+
## Complex Pattern Combinations
|
451
|
+
|
452
|
+
### 8. Multi-Criteria Routing
|
453
|
+
|
454
|
+
```ruby
|
455
|
+
# Complex routing with multiple criteria
|
456
|
+
class ComplexRouter
|
457
|
+
def self.start
|
458
|
+
transport = SmartMessage::Transport::RedisQueueTransport.new
|
459
|
+
|
460
|
+
# Route based on multiple message attributes
|
461
|
+
setup_routing_rules(transport)
|
462
|
+
end
|
463
|
+
|
464
|
+
private
|
465
|
+
|
466
|
+
def self.setup_routing_rules(transport)
|
467
|
+
# Rule 1: Critical messages from admin go to operations
|
468
|
+
transport.subscribe_pattern("#.admin.*.#") do |msg_class, data|
|
469
|
+
message = JSON.parse(data)
|
470
|
+
|
471
|
+
if message['severity'] == 'critical'
|
472
|
+
route_to_operations(message)
|
473
|
+
end
|
474
|
+
end
|
475
|
+
|
476
|
+
# Rule 2: Payment messages go to compliance if amount > $10,000
|
477
|
+
transport.subscribe_pattern("payment.#.*.*") do |msg_class, data|
|
478
|
+
message = JSON.parse(data)
|
479
|
+
|
480
|
+
if message['amount'] && message['amount'] > 10000
|
481
|
+
route_to_compliance(message)
|
482
|
+
end
|
483
|
+
end
|
484
|
+
|
485
|
+
# Rule 3: Messages from EU customers go to GDPR processor
|
486
|
+
transport.subscribe_pattern("#.*.#") do |msg_class, data|
|
487
|
+
message = JSON.parse(data)
|
488
|
+
|
489
|
+
if eu_customer?(message['customer_data'])
|
490
|
+
route_to_gdpr_processor(message)
|
491
|
+
end
|
492
|
+
end
|
493
|
+
|
494
|
+
# Rule 4: Time-sensitive messages during business hours
|
495
|
+
transport.subscribe_pattern("#.*.#") do |msg_class, data|
|
496
|
+
message = JSON.parse(data)
|
497
|
+
|
498
|
+
if message['time_sensitive'] && business_hours?
|
499
|
+
route_to_priority_processor(message)
|
500
|
+
elsif message['time_sensitive']
|
501
|
+
route_to_delayed_processor(message)
|
502
|
+
end
|
503
|
+
end
|
504
|
+
end
|
505
|
+
end
|
506
|
+
```
|
507
|
+
|
508
|
+
### 9. Fan-out and Aggregation
|
509
|
+
|
510
|
+
```ruby
|
511
|
+
# Fan-out pattern: One message to many processors
|
512
|
+
class FanOutMessage < SmartMessage::Base
|
513
|
+
transport :redis_queue
|
514
|
+
property :data, required: true
|
515
|
+
property :processors, required: true # Array of target processors
|
516
|
+
end
|
517
|
+
|
518
|
+
class FanOutRouter
|
519
|
+
def self.start
|
520
|
+
transport = SmartMessage::Transport::RedisQueueTransport.new
|
521
|
+
|
522
|
+
transport.subscribe_pattern("fanout.#.*.*") do |msg_class, data|
|
523
|
+
message = JSON.parse(data)
|
524
|
+
|
525
|
+
# Send to each specified processor
|
526
|
+
message['processors'].each do |processor|
|
527
|
+
ProcessingTask.new(
|
528
|
+
data: message['data'],
|
529
|
+
original_message_id: message['message_id'],
|
530
|
+
_sm_header: {
|
531
|
+
from: 'fanout_router',
|
532
|
+
to: processor
|
533
|
+
}
|
534
|
+
).publish
|
535
|
+
end
|
536
|
+
end
|
537
|
+
end
|
538
|
+
end
|
539
|
+
|
540
|
+
# Aggregation pattern: Many results back to one
|
541
|
+
class AggregationCollector
|
542
|
+
def initialize
|
543
|
+
@results = {}
|
544
|
+
@transport = SmartMessage::Transport::RedisQueueTransport.new
|
545
|
+
setup_subscriptions
|
546
|
+
end
|
547
|
+
|
548
|
+
private
|
549
|
+
|
550
|
+
def setup_subscriptions
|
551
|
+
@transport.subscribe_pattern("#.*.aggregation_collector") do |msg_class, data|
|
552
|
+
result = JSON.parse(data)
|
553
|
+
collect_result(result)
|
554
|
+
end
|
555
|
+
end
|
556
|
+
|
557
|
+
def collect_result(result)
|
558
|
+
message_id = result['original_message_id']
|
559
|
+
@results[message_id] ||= []
|
560
|
+
@results[message_id] << result
|
561
|
+
|
562
|
+
# Check if we have all expected results
|
563
|
+
if all_results_collected?(message_id)
|
564
|
+
publish_aggregated_result(message_id, @results[message_id])
|
565
|
+
@results.delete(message_id)
|
566
|
+
end
|
567
|
+
end
|
568
|
+
end
|
569
|
+
```
|
570
|
+
|
571
|
+
### 10. Circuit Breaker Pattern with Routing
|
572
|
+
|
573
|
+
```ruby
|
574
|
+
# Route around failing services
|
575
|
+
class CircuitBreakerRouter
|
576
|
+
def initialize
|
577
|
+
@circuit_states = {} # service_name => :closed | :open | :half_open
|
578
|
+
@failure_counts = {}
|
579
|
+
@last_failure_time = {}
|
580
|
+
@transport = SmartMessage::Transport::RedisQueueTransport.new
|
581
|
+
setup_routing
|
582
|
+
end
|
583
|
+
|
584
|
+
private
|
585
|
+
|
586
|
+
def setup_routing
|
587
|
+
# Monitor service health
|
588
|
+
@transport.subscribe_pattern("health.#.*.*") do |msg_class, data|
|
589
|
+
health_report = JSON.parse(data)
|
590
|
+
update_circuit_state(health_report['service'], health_report['status'])
|
591
|
+
end
|
592
|
+
|
593
|
+
# Route requests based on circuit state
|
594
|
+
@transport.subscribe_pattern("request.#.*.*") do |msg_class, data|
|
595
|
+
request = JSON.parse(data)
|
596
|
+
service = request['target_service']
|
597
|
+
|
598
|
+
case @circuit_states[service]
|
599
|
+
when :open
|
600
|
+
# Route to fallback or reject
|
601
|
+
route_to_fallback(request, service)
|
602
|
+
when :half_open
|
603
|
+
# Allow limited requests
|
604
|
+
if should_allow_request?(service)
|
605
|
+
route_to_service(request, service)
|
606
|
+
else
|
607
|
+
route_to_fallback(request, service)
|
608
|
+
end
|
609
|
+
else # :closed (healthy)
|
610
|
+
route_to_service(request, service)
|
611
|
+
end
|
612
|
+
end
|
613
|
+
end
|
614
|
+
|
615
|
+
def route_to_fallback(request, failed_service)
|
616
|
+
fallback_service = determine_fallback(failed_service)
|
617
|
+
|
618
|
+
if fallback_service
|
619
|
+
FallbackRequest.new(
|
620
|
+
original_service: failed_service,
|
621
|
+
request_data: request,
|
622
|
+
_sm_header: {
|
623
|
+
from: 'circuit_breaker_router',
|
624
|
+
to: fallback_service
|
625
|
+
}
|
626
|
+
).publish
|
627
|
+
else
|
628
|
+
# No fallback available
|
629
|
+
ErrorResponse.new(
|
630
|
+
error: "Service #{failed_service} unavailable",
|
631
|
+
_sm_header: {
|
632
|
+
from: 'circuit_breaker_router',
|
633
|
+
to: request['callback_service']
|
634
|
+
}
|
635
|
+
).publish
|
636
|
+
end
|
637
|
+
end
|
638
|
+
end
|
639
|
+
```
|
640
|
+
|
641
|
+
## Pattern Testing and Debugging
|
642
|
+
|
643
|
+
### Pattern Validation
|
644
|
+
|
645
|
+
```ruby
|
646
|
+
# Test pattern matching
|
647
|
+
class PatternTester
|
648
|
+
def self.test_pattern(pattern, test_keys)
|
649
|
+
transport = SmartMessage::Transport::RedisQueueTransport.new
|
650
|
+
|
651
|
+
puts "Testing pattern: #{pattern}"
|
652
|
+
puts "-" * 40
|
653
|
+
|
654
|
+
test_keys.each do |key|
|
655
|
+
matches = transport.send(:routing_key_matches_pattern?, key, pattern)
|
656
|
+
status = matches ? "✅ MATCH" : "❌ NO MATCH"
|
657
|
+
puts "#{status}: #{key}"
|
658
|
+
end
|
659
|
+
|
660
|
+
transport.disconnect
|
661
|
+
end
|
662
|
+
end
|
663
|
+
|
664
|
+
# Test cases
|
665
|
+
test_keys = [
|
666
|
+
'order.ordermessage.api_gateway.payment_service',
|
667
|
+
'user.usercreated.signup_service.notification_service',
|
668
|
+
'alert.systemalert.monitoring.broadcast',
|
669
|
+
'payment.paymentprocessed.payment_service.order_service'
|
670
|
+
]
|
671
|
+
|
672
|
+
PatternTester.test_pattern("#.*.payment_service", test_keys)
|
673
|
+
PatternTester.test_pattern("order.#.*.*", test_keys)
|
674
|
+
PatternTester.test_pattern("#.#.#.broadcast", test_keys)
|
675
|
+
```
|
676
|
+
|
677
|
+
### Pattern Performance Analysis
|
678
|
+
|
679
|
+
```ruby
|
680
|
+
# Analyze pattern matching performance
|
681
|
+
class PatternPerformance
|
682
|
+
def self.benchmark_patterns(patterns, test_keys, iterations = 1000)
|
683
|
+
transport = SmartMessage::Transport::RedisQueueTransport.new
|
684
|
+
|
685
|
+
patterns.each do |pattern|
|
686
|
+
start_time = Time.now
|
687
|
+
|
688
|
+
iterations.times do
|
689
|
+
test_keys.each do |key|
|
690
|
+
transport.send(:routing_key_matches_pattern?, key, pattern)
|
691
|
+
end
|
692
|
+
end
|
693
|
+
|
694
|
+
duration = Time.now - start_time
|
695
|
+
total_tests = iterations * test_keys.size
|
696
|
+
rate = total_tests / duration
|
697
|
+
|
698
|
+
puts "Pattern: #{pattern}"
|
699
|
+
puts " Tests: #{total_tests}"
|
700
|
+
puts " Time: #{duration.round(4)}s"
|
701
|
+
puts " Rate: #{rate.round(0)} matches/sec"
|
702
|
+
puts ""
|
703
|
+
end
|
704
|
+
|
705
|
+
transport.disconnect
|
706
|
+
end
|
707
|
+
end
|
708
|
+
|
709
|
+
patterns = [
|
710
|
+
"#.*.payment_service",
|
711
|
+
"order.#.*.*",
|
712
|
+
"#.api_gateway.*",
|
713
|
+
"#.#.#.broadcast"
|
714
|
+
]
|
715
|
+
|
716
|
+
PatternPerformance.benchmark_patterns(patterns, test_keys)
|
717
|
+
```
|
718
|
+
|
719
|
+
### Routing Table Analysis
|
720
|
+
|
721
|
+
```ruby
|
722
|
+
# Analyze routing efficiency
|
723
|
+
class RoutingAnalyzer
|
724
|
+
def self.analyze(transport)
|
725
|
+
routing_table = transport.routing_table
|
726
|
+
|
727
|
+
puts "📊 Routing Table Analysis"
|
728
|
+
puts "=" * 30
|
729
|
+
|
730
|
+
# Pattern complexity analysis
|
731
|
+
simple_patterns = 0
|
732
|
+
wildcard_patterns = 0
|
733
|
+
complex_patterns = 0
|
734
|
+
|
735
|
+
routing_table.each do |pattern, queues|
|
736
|
+
if pattern.include?('#') || pattern.include?('*')
|
737
|
+
if pattern.count('#') + pattern.count('*') > 2
|
738
|
+
complex_patterns += 1
|
739
|
+
else
|
740
|
+
wildcard_patterns += 1
|
741
|
+
end
|
742
|
+
else
|
743
|
+
simple_patterns += 1
|
744
|
+
end
|
745
|
+
end
|
746
|
+
|
747
|
+
puts "Pattern Types:"
|
748
|
+
puts " Simple: #{simple_patterns}"
|
749
|
+
puts " Wildcard: #{wildcard_patterns}"
|
750
|
+
puts " Complex: #{complex_patterns}"
|
751
|
+
puts ""
|
752
|
+
|
753
|
+
# Queue distribution
|
754
|
+
queue_counts = routing_table.values.map(&:size)
|
755
|
+
avg_queues = queue_counts.sum.to_f / queue_counts.size
|
756
|
+
|
757
|
+
puts "Queue Distribution:"
|
758
|
+
puts " Total patterns: #{routing_table.size}"
|
759
|
+
puts " Total queues: #{queue_counts.sum}"
|
760
|
+
puts " Avg queues/pattern: #{avg_queues.round(2)}"
|
761
|
+
puts " Max queues/pattern: #{queue_counts.max}"
|
762
|
+
puts ""
|
763
|
+
|
764
|
+
# Potential overlaps
|
765
|
+
overlapping_patterns = find_overlapping_patterns(routing_table.keys)
|
766
|
+
if overlapping_patterns.any?
|
767
|
+
puts "⚠️ Potentially overlapping patterns:"
|
768
|
+
overlapping_patterns.each do |pair|
|
769
|
+
puts " #{pair[0]} ↔ #{pair[1]}"
|
770
|
+
end
|
771
|
+
else
|
772
|
+
puts "✅ No overlapping patterns detected"
|
773
|
+
end
|
774
|
+
end
|
775
|
+
|
776
|
+
private
|
777
|
+
|
778
|
+
def self.find_overlapping_patterns(patterns)
|
779
|
+
overlaps = []
|
780
|
+
|
781
|
+
patterns.combination(2) do |p1, p2|
|
782
|
+
if patterns_might_overlap?(p1, p2)
|
783
|
+
overlaps << [p1, p2]
|
784
|
+
end
|
785
|
+
end
|
786
|
+
|
787
|
+
overlaps
|
788
|
+
end
|
789
|
+
|
790
|
+
def self.patterns_might_overlap?(p1, p2)
|
791
|
+
# Simple heuristic - both have wildcards in same positions
|
792
|
+
p1_parts = p1.split('.')
|
793
|
+
p2_parts = p2.split('.')
|
794
|
+
|
795
|
+
return false if p1_parts.size != p2_parts.size
|
796
|
+
|
797
|
+
p1_parts.zip(p2_parts).any? do |part1, part2|
|
798
|
+
(part1 == '#' || part1 == '*') && (part2 == '#' || part2 == '*')
|
799
|
+
end
|
800
|
+
end
|
801
|
+
end
|
802
|
+
|
803
|
+
# Analyze current routing
|
804
|
+
transport = SmartMessage::Transport::RedisQueueTransport.new
|
805
|
+
RoutingAnalyzer.analyze(transport)
|
806
|
+
```
|
807
|
+
|
808
|
+
## Best Practices for Pattern Design
|
809
|
+
|
810
|
+
### 1. Pattern Hierarchy
|
811
|
+
|
812
|
+
```ruby
|
813
|
+
# Organize patterns from specific to general
|
814
|
+
patterns = [
|
815
|
+
"emergency.alert.security.critical", # Most specific
|
816
|
+
"emergency.alert.security.*", # Department-specific
|
817
|
+
"emergency.alert.*.*", # Alert type-specific
|
818
|
+
"emergency.#.*.*", # Emergency namespace
|
819
|
+
"#.#.#.critical", # Priority-specific
|
820
|
+
"#.#.#.broadcast" # Broadcast messages
|
821
|
+
]
|
822
|
+
```
|
823
|
+
|
824
|
+
### 2. Naming Conventions
|
825
|
+
|
826
|
+
```ruby
|
827
|
+
# Use consistent naming patterns
|
828
|
+
PATTERN_CONVENTIONS = {
|
829
|
+
# Service routing
|
830
|
+
service_inbound: "#.*.{service_name}",
|
831
|
+
service_outbound: "#{service_name}.#.*.*",
|
832
|
+
|
833
|
+
# Event routing
|
834
|
+
domain_events: "#{domain}.#.*.*",
|
835
|
+
global_events: "#.#.#.broadcast",
|
836
|
+
|
837
|
+
# Priority routing
|
838
|
+
critical_messages: "#.#.#.critical",
|
839
|
+
urgent_messages: "#.#.#.urgent",
|
840
|
+
|
841
|
+
# Geographic routing
|
842
|
+
regional_messages: "#.*.{region}_*",
|
843
|
+
global_messages: "#.*.global"
|
844
|
+
}.freeze
|
845
|
+
```
|
846
|
+
|
847
|
+
### 3. Pattern Documentation
|
848
|
+
|
849
|
+
```ruby
|
850
|
+
# Document your routing patterns
|
851
|
+
class RoutingDocumentation
|
852
|
+
PATTERNS = {
|
853
|
+
"#.*.payment_service" => {
|
854
|
+
description: "All messages directed to payment service",
|
855
|
+
use_case: "Payment processing requests from any source",
|
856
|
+
examples: [
|
857
|
+
"order.paymentrequest.api_gateway.payment_service",
|
858
|
+
"refund.refundrequest.customer_service.payment_service"
|
859
|
+
],
|
860
|
+
performance: "High volume - ensure adequate consumers"
|
861
|
+
},
|
862
|
+
|
863
|
+
"emergency.#.*.*" => {
|
864
|
+
description: "All emergency messages regardless of routing",
|
865
|
+
use_case: "Emergency monitoring and logging",
|
866
|
+
examples: [
|
867
|
+
"emergency.fire.building_sensor.fire_department",
|
868
|
+
"emergency.medical.mobile_app.ambulance_service"
|
869
|
+
],
|
870
|
+
performance: "Critical - requires immediate processing"
|
871
|
+
}
|
872
|
+
}.freeze
|
873
|
+
|
874
|
+
def self.document_pattern(pattern)
|
875
|
+
info = PATTERNS[pattern]
|
876
|
+
return unless info
|
877
|
+
|
878
|
+
puts "Pattern: #{pattern}"
|
879
|
+
puts "Description: #{info[:description]}"
|
880
|
+
puts "Use Case: #{info[:use_case]}"
|
881
|
+
puts "Examples:"
|
882
|
+
info[:examples].each { |ex| puts " - #{ex}" }
|
883
|
+
puts "Performance Notes: #{info[:performance]}"
|
884
|
+
puts ""
|
885
|
+
end
|
886
|
+
end
|
887
|
+
```
|
888
|
+
|
889
|
+
Advanced routing patterns enable sophisticated message architectures that can adapt to complex business requirements while maintaining high performance and reliability. Use these patterns as building blocks to create messaging systems that scale with your application's needs.
|