smart_message 0.0.5 → 0.0.7

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.
@@ -0,0 +1,430 @@
1
+ #!/usr/bin/env ruby
2
+ # examples/08_entity_addressing_with_filtering.rb
3
+ #
4
+ # Demonstrates SmartMessage entity addressing and filtering capabilities including:
5
+ # - Point-to-point messaging with FROM/TO fields
6
+ # - Broadcast messaging (no TO field)
7
+ # - Entity-aware subscription filtering
8
+ # - Request-reply patterns with REPLY_TO
9
+ # - Instance-level addressing overrides
10
+ # - Gateway patterns
11
+
12
+ require_relative '../lib/smart_message'
13
+
14
+ puts "šŸŽÆ SmartMessage Entity Addressing & Filtering Demo"
15
+ puts "=" * 50
16
+
17
+ # Configure transport for demo
18
+ transport = SmartMessage::Transport.create(:stdout, loopback: true)
19
+ serializer = SmartMessage::Serializer::JSON.new
20
+
21
+ # =============================================================================
22
+ # Example 1: Entity-Aware Message Filtering
23
+ # =============================================================================
24
+
25
+ puts "\nšŸ” Example 1: Entity-Aware Message Filtering"
26
+ puts "-" * 40
27
+
28
+ class ServiceMessage < SmartMessage::Base
29
+ version 1
30
+ description "Messages between microservices with filtering"
31
+
32
+ from 'sender-service'
33
+
34
+ property :message_type, required: true
35
+ property :data, required: true
36
+ property :timestamp, default: -> { Time.now.to_s }
37
+
38
+ config do
39
+ transport SmartMessage::Transport.create(:stdout, loopback: true)
40
+ serializer SmartMessage::Serializer::JSON.new
41
+ end
42
+
43
+ # Different handlers for different subscription filters
44
+ def self.process_broadcast(header, payload)
45
+ data = JSON.parse(payload)
46
+ puts " šŸ“» BROADCAST HANDLER received:"
47
+ puts " Type: #{data['message_type']}"
48
+ puts " From: #{header.from}, To: #{header.to || 'ALL'}"
49
+ puts " Data: #{data['data']}"
50
+ end
51
+
52
+ def self.process_directed(header, payload)
53
+ data = JSON.parse(payload)
54
+ puts " šŸŽÆ DIRECTED HANDLER received:"
55
+ puts " Type: #{data['message_type']}"
56
+ puts " From: #{header.from} → To: #{header.to}"
57
+ puts " Data: #{data['data']}"
58
+ end
59
+
60
+ def self.process_from_admin(header, payload)
61
+ data = JSON.parse(payload)
62
+ puts " šŸ‘® ADMIN HANDLER received:"
63
+ puts " Type: #{data['message_type']}"
64
+ puts " From: #{header.from} (ADMIN)"
65
+ puts " Data: #{data['data']}"
66
+ end
67
+ end
68
+
69
+ # Subscribe with different filters
70
+ puts "\nšŸ“Œ Setting up filtered subscriptions:"
71
+
72
+ # Subscribe to broadcast messages only
73
+ puts " 1. Subscribing to broadcast messages only..."
74
+ ServiceMessage.subscribe('ServiceMessage.process_broadcast', broadcast: true)
75
+
76
+ # Subscribe to messages directed to 'my-service'
77
+ puts " 2. Subscribing to messages for 'my-service' only..."
78
+ ServiceMessage.subscribe('ServiceMessage.process_directed', to: 'my-service')
79
+
80
+ # Subscribe to messages from 'admin-service'
81
+ puts " 3. Subscribing to messages from 'admin-service'..."
82
+ ServiceMessage.subscribe('ServiceMessage.process_from_admin', from: 'admin-service')
83
+
84
+ # Test different message types
85
+ puts "\nšŸ“¤ Publishing test messages..."
86
+
87
+ # Broadcast message - should only be received by broadcast handler
88
+ broadcast_msg = ServiceMessage.new(
89
+ message_type: 'system_announcement',
90
+ data: 'System maintenance at 2 AM',
91
+ from: 'sender-service'
92
+ )
93
+ broadcast_msg.to(nil) # Explicitly set as broadcast
94
+ puts "\n1. Publishing broadcast message (no 'to' field)..."
95
+ broadcast_msg.publish
96
+ sleep(0.2) # Allow time for handlers to process
97
+
98
+ # Directed message to 'my-service' - should only be received by directed handler
99
+ directed_msg = ServiceMessage.new(
100
+ message_type: 'service_update',
101
+ data: 'Update your configuration',
102
+ from: 'sender-service'
103
+ )
104
+ directed_msg.to('my-service')
105
+ puts "\n2. Publishing message to 'my-service'..."
106
+ directed_msg.publish
107
+ sleep(0.2) # Allow time for handlers to process
108
+
109
+ # Directed message to different service - should NOT be received
110
+ other_msg = ServiceMessage.new(
111
+ message_type: 'other_update',
112
+ data: 'This is for another service',
113
+ from: 'sender-service'
114
+ )
115
+ other_msg.to('other-service')
116
+ puts "\n3. Publishing message to 'other-service' (should not be received)..."
117
+ other_msg.publish
118
+ sleep(0.2) # Allow time to confirm no handlers process this
119
+
120
+ # Message from admin - should only be received by admin handler
121
+ admin_msg = ServiceMessage.new(
122
+ message_type: 'admin_command',
123
+ data: 'Restart all services',
124
+ from: 'admin-service'
125
+ )
126
+ admin_msg.from('admin-service')
127
+ admin_msg.to('my-service')
128
+ puts "\n4. Publishing message from 'admin-service'..."
129
+ admin_msg.publish
130
+ sleep(0.2) # Allow time for handlers to process
131
+
132
+ # =============================================================================
133
+ # Example 2: Combined Filters
134
+ # =============================================================================
135
+
136
+ puts "\nšŸ”— Example 2: Combined Subscription Filters"
137
+ puts "-" * 40
138
+
139
+ class AlertMessage < SmartMessage::Base
140
+ version 1
141
+ description "Alert messages with combined filtering"
142
+
143
+ from 'alert-service' # Default from field
144
+
145
+ property :severity, required: true
146
+ property :alert_text, required: true
147
+ property :source_system
148
+
149
+ config do
150
+ transport SmartMessage::Transport.create(:stdout, loopback: true)
151
+ serializer SmartMessage::Serializer::JSON.new
152
+ end
153
+
154
+ def self.process_critical_or_broadcast(header, payload)
155
+ data = JSON.parse(payload)
156
+ icon = data['severity'] == 'critical' ? '🚨' : 'šŸ“¢'
157
+ puts " #{icon} ALERT MONITOR received:"
158
+ puts " Severity: #{data['severity'].upcase}"
159
+ puts " From: #{header.from}, To: #{header.to || 'ALL'}"
160
+ puts " Alert: #{data['alert_text']}"
161
+ end
162
+
163
+ def self.process_from_monitoring(header, payload)
164
+ data = JSON.parse(payload)
165
+ puts " šŸ“Š MONITORING TEAM received:"
166
+ puts " From: #{header.from} (monitoring system)"
167
+ puts " Severity: #{data['severity']}"
168
+ puts " Alert: #{data['alert_text']}"
169
+ end
170
+ end
171
+
172
+ # Clear previous subscriptions
173
+ AlertMessage.unsubscribe!
174
+
175
+ # Subscribe to broadcasts OR messages to 'alert-service'
176
+ puts "\nšŸ“Œ Setting up combined filter subscriptions:"
177
+ puts " 1. Subscribe to broadcasts OR messages to 'alert-service'..."
178
+ AlertMessage.subscribe(
179
+ 'AlertMessage.process_critical_or_broadcast',
180
+ broadcast: true,
181
+ to: 'alert-service'
182
+ )
183
+
184
+ # Subscribe to messages from specific monitoring systems
185
+ puts " 2. Subscribe to messages from monitoring systems..."
186
+ AlertMessage.subscribe(
187
+ 'AlertMessage.process_from_monitoring',
188
+ from: ['monitoring-system-1', 'monitoring-system-2']
189
+ )
190
+
191
+ # Test combined filters
192
+ puts "\nšŸ“¤ Publishing alert messages..."
193
+
194
+ # Broadcast alert - should be received by first handler
195
+ broadcast_alert = AlertMessage.new(
196
+ severity: 'warning',
197
+ alert_text: 'CPU usage high across cluster',
198
+ source_system: 'cluster-monitor',
199
+ from: 'monitoring-system-1'
200
+ )
201
+ broadcast_alert.from('monitoring-system-1')
202
+ broadcast_alert.to(nil) # Broadcast
203
+ puts "\n1. Broadcasting alert..."
204
+ broadcast_alert.publish
205
+ sleep(0.2) # Allow time for handlers to process
206
+
207
+ # Directed alert to 'alert-service' - should be received by first handler
208
+ directed_alert = AlertMessage.new(
209
+ severity: 'critical',
210
+ alert_text: 'Database connection lost',
211
+ source_system: 'db-monitor',
212
+ from: 'monitoring-system-2'
213
+ )
214
+ directed_alert.from('monitoring-system-2')
215
+ directed_alert.to('alert-service')
216
+ puts "\n2. Sending critical alert to 'alert-service'..."
217
+ directed_alert.publish
218
+ sleep(0.2) # Allow time for handlers to process
219
+
220
+ # Alert to different service - should only be received by monitoring handler
221
+ other_alert = AlertMessage.new(
222
+ severity: 'info',
223
+ alert_text: 'Backup completed successfully',
224
+ source_system: 'backup-system',
225
+ from: 'monitoring-system-1'
226
+ )
227
+ other_alert.from('monitoring-system-1')
228
+ other_alert.to('backup-service')
229
+ puts "\n3. Sending info alert to 'backup-service'..."
230
+ other_alert.publish
231
+ sleep(0.2) # Allow time for handlers to process
232
+
233
+ # =============================================================================
234
+ # Example 3: Point-to-Point with Filtering
235
+ # =============================================================================
236
+
237
+ puts "\nšŸ“” Example 3: Point-to-Point Messaging with Filtering"
238
+ puts "-" * 40
239
+
240
+ class OrderMessage < SmartMessage::Base
241
+ version 1
242
+ description "Order processing with selective subscription"
243
+
244
+ from 'order-service'
245
+ to 'fulfillment-service'
246
+ reply_to 'order-service'
247
+
248
+ property :order_id, required: true
249
+ property :priority, default: 'normal'
250
+ property :items, required: true
251
+ property :total_amount, required: true
252
+
253
+ config do
254
+ transport SmartMessage::Transport.create(:stdout, loopback: true)
255
+ serializer SmartMessage::Serializer::JSON.new
256
+ end
257
+
258
+ def self.process_high_priority(header, payload)
259
+ data = JSON.parse(payload)
260
+ puts " šŸš€ HIGH PRIORITY ORDER HANDLER:"
261
+ puts " Order ID: #{data['order_id']} (PRIORITY: #{data['priority'].upcase})"
262
+ puts " From: #{header.from} → To: #{header.to}"
263
+ puts " Total: $#{data['total_amount']}"
264
+ end
265
+
266
+ def self.process_normal(header, payload)
267
+ data = JSON.parse(payload)
268
+ puts " šŸ“¦ NORMAL ORDER HANDLER:"
269
+ puts " Order ID: #{data['order_id']}"
270
+ puts " From: #{header.from} → To: #{header.to}"
271
+ puts " Total: $#{data['total_amount']}"
272
+ end
273
+ end
274
+
275
+ # Clear and set up filtered subscriptions
276
+ OrderMessage.unsubscribe!
277
+
278
+ puts "\nšŸ“Œ Setting up order processing subscriptions:"
279
+ # Only the fulfillment service subscribes
280
+ puts " 1. Fulfillment service subscribes to orders..."
281
+ OrderMessage.subscribe('OrderMessage.process_normal', to: 'fulfillment-service')
282
+
283
+ # High-priority team also monitors high-value orders
284
+ puts " 2. Priority team subscribes to high-value orders..."
285
+ OrderMessage.subscribe('OrderMessage.process_high_priority', to: 'fulfillment-service')
286
+
287
+ # Send different types of orders
288
+ puts "\nšŸ“¤ Publishing orders..."
289
+
290
+ # Normal order to fulfillment
291
+ normal_order = OrderMessage.new(
292
+ order_id: "ORD-001",
293
+ priority: 'normal',
294
+ items: ["Widget A", "Widget B"],
295
+ total_amount: 99.99,
296
+ from: 'order-service'
297
+ )
298
+ puts "\n1. Publishing normal order to fulfillment..."
299
+ normal_order.publish
300
+ sleep(0.2) # Allow time for handlers to process
301
+
302
+ # High priority order
303
+ high_priority_order = OrderMessage.new(
304
+ order_id: "ORD-002",
305
+ priority: 'high',
306
+ items: ["Premium Widget", "Express Gadget"],
307
+ total_amount: 999.99,
308
+ from: 'order-service'
309
+ )
310
+ puts "\n2. Publishing high-priority order..."
311
+ high_priority_order.publish
312
+ sleep(0.2) # Allow time for handlers to process
313
+
314
+ # Order to different service (should not be received)
315
+ misrouted_order = OrderMessage.new(
316
+ order_id: "ORD-003",
317
+ priority: 'normal',
318
+ items: ["Test Item"],
319
+ total_amount: 50.00,
320
+ from: 'order-service'
321
+ )
322
+ misrouted_order.to('wrong-service')
323
+ puts "\n3. Publishing order to 'wrong-service' (should not be received)..."
324
+ misrouted_order.publish
325
+ sleep(0.2) # Allow time to confirm no handlers process this
326
+
327
+ # =============================================================================
328
+ # Example 4: Request-Reply with Filtering
329
+ # =============================================================================
330
+
331
+ puts "\nšŸ”„ Example 4: Request-Reply Pattern with Filtering"
332
+ puts "-" * 40
333
+
334
+ class ServiceRequest < SmartMessage::Base
335
+ version 1
336
+ description "Service requests with filtered responses"
337
+
338
+ from 'request-service' # Default from field
339
+
340
+ property :request_id, required: true
341
+ property :request_type, required: true
342
+ property :data
343
+
344
+ config do
345
+ transport SmartMessage::Transport.create(:stdout, loopback: true)
346
+ serializer SmartMessage::Serializer::JSON.new
347
+ end
348
+
349
+ def self.process_api_requests(header, payload)
350
+ data = JSON.parse(payload)
351
+ puts " 🌐 API SERVICE received request:"
352
+ puts " Request ID: #{data['request_id']}"
353
+ puts " Type: #{data['request_type']}"
354
+ puts " From: #{header.from} → To: #{header.to}"
355
+ puts " Reply To: #{header.reply_to}"
356
+ end
357
+
358
+ def self.process_data_requests(header, payload)
359
+ data = JSON.parse(payload)
360
+ puts " šŸ’¾ DATA SERVICE received request:"
361
+ puts " Request ID: #{data['request_id']}"
362
+ puts " Type: #{data['request_type']}"
363
+ puts " From: #{header.from} → To: #{header.to}"
364
+ end
365
+ end
366
+
367
+ ServiceRequest.unsubscribe!
368
+
369
+ puts "\nšŸ“Œ Setting up service request routing:"
370
+ # API service only handles requests directed to it
371
+ puts " 1. API service subscribes to its requests..."
372
+ ServiceRequest.subscribe('ServiceRequest.process_api_requests', to: 'api-service')
373
+
374
+ # Data service handles data requests
375
+ puts " 2. Data service subscribes to its requests..."
376
+ ServiceRequest.subscribe('ServiceRequest.process_data_requests', to: 'data-service')
377
+
378
+ puts "\nšŸ“¤ Publishing service requests..."
379
+
380
+ # API request
381
+ api_request = ServiceRequest.new(
382
+ request_id: SecureRandom.uuid,
383
+ request_type: 'user_lookup',
384
+ data: { user_id: 'USER-123' },
385
+ from: 'web-frontend'
386
+ )
387
+ api_request.from('web-frontend')
388
+ api_request.to('api-service')
389
+ api_request.reply_to('web-frontend')
390
+ puts "\n1. Publishing API request..."
391
+ api_request.publish
392
+ sleep(0.2) # Allow time for handlers to process
393
+
394
+ # Data request
395
+ data_request = ServiceRequest.new(
396
+ request_id: SecureRandom.uuid,
397
+ request_type: 'query',
398
+ data: { table: 'orders', limit: 100 },
399
+ from: 'analytics-service'
400
+ )
401
+ data_request.from('analytics-service')
402
+ data_request.to('data-service')
403
+ data_request.reply_to('analytics-service')
404
+ puts "\n2. Publishing data request..."
405
+ data_request.publish
406
+ sleep(0.2) # Allow time for handlers to process
407
+
408
+ # =============================================================================
409
+ # Summary
410
+ # =============================================================================
411
+
412
+ puts "\nšŸŽÆ Entity Addressing & Filtering Summary"
413
+ puts "=" * 50
414
+ puts "āœ… Point-to-Point: FROM/TO specified for direct routing"
415
+ puts "āœ… Broadcast: FROM only, TO=nil for all broadcast subscribers"
416
+ puts "āœ… Filtered Subscriptions: Subscribe to specific message patterns:"
417
+ puts " • broadcast: true - Only receive broadcast messages"
418
+ puts " • to: 'service-name' - Only receive messages directed to you"
419
+ puts " • from: 'sender' - Only receive from specific senders"
420
+ puts " • from: ['sender1', 'sender2'] - Receive from multiple senders"
421
+ puts " • Combined filters work with OR logic for broadcast/to"
422
+ puts "āœ… Request-Reply: REPLY_TO for response routing"
423
+ puts "āœ… Instance Override: Runtime addressing changes"
424
+ puts "āœ… Gateway Pattern: Message transformation and routing"
425
+ puts "\nšŸ’” Filtering enables microservices to:"
426
+ puts " • Ignore messages not meant for them"
427
+ puts " • Handle broadcasts separately from directed messages"
428
+ puts " • Route messages to appropriate handlers based on sender"
429
+ puts " • Reduce processing overhead by filtering at subscription level"
430
+ puts "\nFor more details, see docs/addressing.md"
data/examples/README.md CHANGED
@@ -13,6 +13,9 @@ ruby 02_publish_subscribe_events.rb
13
13
  ruby 03_many_to_many_chat.rb
14
14
  ruby 04_redis_smart_home_iot.rb
15
15
  ruby 05_proc_handlers.rb
16
+ ruby 06_custom_logger_example.rb
17
+ ruby 07_error_handling_scenarios.rb
18
+ ruby 08_entity_addressing.rb
16
19
  ```
17
20
 
18
21
  ## Examples Overview
@@ -198,6 +201,71 @@ NotificationMessage.subscribe("NotificationService.handle")
198
201
  - **Maintainability**: Choose the right abstraction level for each need
199
202
  - **Performance**: Understand overhead of different handler approaches
200
203
 
204
+ ---
205
+
206
+ ### 8. Entity Addressing System (Advanced Routing)
207
+ **File:** `08_entity_addressing.rb`
208
+
209
+ **Scenario:** Comprehensive demonstration of SmartMessage's entity addressing system showing point-to-point messaging, broadcast patterns, request-reply workflows, and gateway patterns.
210
+
211
+ **Key Features:**
212
+ - FROM/TO/REPLY_TO addressing fields for sophisticated routing
213
+ - Point-to-point messaging with specific entity targeting
214
+ - Broadcast messaging to all subscribers
215
+ - Request-reply patterns with response routing
216
+ - Instance-level addressing overrides
217
+ - Gateway patterns for message transformation and routing
218
+
219
+ **Messages Used:**
220
+ - `OrderMessage` - Point-to-point order processing
221
+ - `SystemAnnouncementMessage` - Broadcast announcements
222
+ - `UserLookupRequest` & `UserLookupResponse` - Request-reply pattern
223
+ - `PaymentMessage` - Instance-level addressing override
224
+ - `ExternalAPIMessage` - Gateway pattern demonstration
225
+
226
+ **Addressing Patterns Shown:**
227
+ ```ruby
228
+ # Point-to-point messaging
229
+ class OrderMessage < SmartMessage::Base
230
+ from 'order-service' # Required: sender identity
231
+ to 'fulfillment-service' # Optional: specific recipient
232
+ reply_to 'order-service' # Optional: response routing
233
+ end
234
+
235
+ # Broadcast messaging
236
+ class AnnouncementMessage < SmartMessage::Base
237
+ from 'admin-service' # Required sender
238
+ # No 'to' field = broadcast to all subscribers
239
+ end
240
+
241
+ # Instance-level override
242
+ payment = PaymentMessage.new(amount: 100.00)
243
+ payment.to('backup-gateway') # Override destination
244
+ payment.publish
245
+ ```
246
+
247
+ **What You'll Learn:**
248
+ - Entity-to-entity communication patterns
249
+ - Point-to-point vs broadcast messaging
250
+ - Request-reply workflows with proper response routing
251
+ - Runtime addressing configuration and overrides
252
+ - Gateway patterns for cross-system integration
253
+ - How addressing enables sophisticated routing logic
254
+
255
+ **Routing Patterns Demonstrated:**
256
+ - **Point-to-Point**: Direct entity targeting with FROM/TO
257
+ - **Broadcast**: FROM only, TO=nil for all subscribers
258
+ - **Request-Reply**: REPLY_TO for response routing
259
+ - **Gateway**: Dynamic addressing for message transformation
260
+ - **Override**: Instance-level addressing changes
261
+
262
+ **Benefits:**
263
+ - **Flexible Routing**: Support multiple messaging patterns
264
+ - **Entity Identification**: Clear sender/recipient tracking
265
+ - **Response Management**: Structured request-reply workflows
266
+ - **Runtime Configuration**: Dynamic addressing based on conditions
267
+ - **Integration Patterns**: Gateway support for external systems
268
+
201
269
  ## Message Patterns Demonstrated
202
270
 
203
271
  ### Request-Response Pattern
@@ -110,7 +110,8 @@ class BotChatAgent < BaseAgent
110
110
  user_name: chat_data['sender_name'],
111
111
  command: command,
112
112
  parameters: parameters,
113
- timestamp: Time.now.iso8601
113
+ timestamp: Time.now.iso8601,
114
+ from: chat_data['sender_id']
114
115
  )
115
116
 
116
117
  bot_command.publish
@@ -244,7 +244,8 @@ class BaseAgent
244
244
  message_type: message_type,
245
245
  timestamp: Time.now.iso8601,
246
246
  mentions: extract_mentions(content),
247
- metadata: { agent_type: @agent_type }
247
+ metadata: { agent_type: @agent_type },
248
+ from: @agent_id
248
249
  )
249
250
 
250
251
  log_display("šŸ’¬ [#{room_id}] #{@name}: #{content}")
@@ -291,7 +292,8 @@ class BaseAgent
291
292
  notification_type: notification_type,
292
293
  content: content,
293
294
  timestamp: Time.now.iso8601,
294
- metadata: { triggered_by: @agent_id }
295
+ metadata: { triggered_by: @agent_id },
296
+ from: @agent_id
295
297
  )
296
298
 
297
299
  notification.publish