smart_message 0.0.5 → 0.0.6

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,418 @@
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
+ )
92
+ broadcast_msg.to(nil) # Explicitly set as broadcast
93
+ puts "\n1. Publishing broadcast message (no 'to' field)..."
94
+ broadcast_msg.publish
95
+ sleep(0.2) # Allow time for handlers to process
96
+
97
+ # Directed message to 'my-service' - should only be received by directed handler
98
+ directed_msg = ServiceMessage.new(
99
+ message_type: 'service_update',
100
+ data: 'Update your configuration'
101
+ )
102
+ directed_msg.to('my-service')
103
+ puts "\n2. Publishing message to 'my-service'..."
104
+ directed_msg.publish
105
+ sleep(0.2) # Allow time for handlers to process
106
+
107
+ # Directed message to different service - should NOT be received
108
+ other_msg = ServiceMessage.new(
109
+ message_type: 'other_update',
110
+ data: 'This is for another service'
111
+ )
112
+ other_msg.to('other-service')
113
+ puts "\n3. Publishing message to 'other-service' (should not be received)..."
114
+ other_msg.publish
115
+ sleep(0.2) # Allow time to confirm no handlers process this
116
+
117
+ # Message from admin - should only be received by admin handler
118
+ admin_msg = ServiceMessage.new(
119
+ message_type: 'admin_command',
120
+ data: 'Restart all services'
121
+ )
122
+ admin_msg.from('admin-service')
123
+ admin_msg.to('my-service')
124
+ puts "\n4. Publishing message from 'admin-service'..."
125
+ admin_msg.publish
126
+ sleep(0.2) # Allow time for handlers to process
127
+
128
+ # =============================================================================
129
+ # Example 2: Combined Filters
130
+ # =============================================================================
131
+
132
+ puts "\nšŸ”— Example 2: Combined Subscription Filters"
133
+ puts "-" * 40
134
+
135
+ class AlertMessage < SmartMessage::Base
136
+ version 1
137
+ description "Alert messages with combined filtering"
138
+
139
+ from 'alert-service' # Default from field
140
+
141
+ property :severity, required: true
142
+ property :alert_text, required: true
143
+ property :source_system
144
+
145
+ config do
146
+ transport SmartMessage::Transport.create(:stdout, loopback: true)
147
+ serializer SmartMessage::Serializer::JSON.new
148
+ end
149
+
150
+ def self.process_critical_or_broadcast(header, payload)
151
+ data = JSON.parse(payload)
152
+ icon = data['severity'] == 'critical' ? '🚨' : 'šŸ“¢'
153
+ puts " #{icon} ALERT MONITOR received:"
154
+ puts " Severity: #{data['severity'].upcase}"
155
+ puts " From: #{header.from}, To: #{header.to || 'ALL'}"
156
+ puts " Alert: #{data['alert_text']}"
157
+ end
158
+
159
+ def self.process_from_monitoring(header, payload)
160
+ data = JSON.parse(payload)
161
+ puts " šŸ“Š MONITORING TEAM received:"
162
+ puts " From: #{header.from} (monitoring system)"
163
+ puts " Severity: #{data['severity']}"
164
+ puts " Alert: #{data['alert_text']}"
165
+ end
166
+ end
167
+
168
+ # Clear previous subscriptions
169
+ AlertMessage.unsubscribe!
170
+
171
+ # Subscribe to broadcasts OR messages to 'alert-service'
172
+ puts "\nšŸ“Œ Setting up combined filter subscriptions:"
173
+ puts " 1. Subscribe to broadcasts OR messages to 'alert-service'..."
174
+ AlertMessage.subscribe(
175
+ 'AlertMessage.process_critical_or_broadcast',
176
+ broadcast: true,
177
+ to: 'alert-service'
178
+ )
179
+
180
+ # Subscribe to messages from specific monitoring systems
181
+ puts " 2. Subscribe to messages from monitoring systems..."
182
+ AlertMessage.subscribe(
183
+ 'AlertMessage.process_from_monitoring',
184
+ from: ['monitoring-system-1', 'monitoring-system-2']
185
+ )
186
+
187
+ # Test combined filters
188
+ puts "\nšŸ“¤ Publishing alert messages..."
189
+
190
+ # Broadcast alert - should be received by first handler
191
+ broadcast_alert = AlertMessage.new(
192
+ severity: 'warning',
193
+ alert_text: 'CPU usage high across cluster',
194
+ source_system: 'cluster-monitor'
195
+ )
196
+ broadcast_alert.from('monitoring-system-1')
197
+ broadcast_alert.to(nil) # Broadcast
198
+ puts "\n1. Broadcasting alert..."
199
+ broadcast_alert.publish
200
+ sleep(0.2) # Allow time for handlers to process
201
+
202
+ # Directed alert to 'alert-service' - should be received by first handler
203
+ directed_alert = AlertMessage.new(
204
+ severity: 'critical',
205
+ alert_text: 'Database connection lost',
206
+ source_system: 'db-monitor'
207
+ )
208
+ directed_alert.from('monitoring-system-2')
209
+ directed_alert.to('alert-service')
210
+ puts "\n2. Sending critical alert to 'alert-service'..."
211
+ directed_alert.publish
212
+ sleep(0.2) # Allow time for handlers to process
213
+
214
+ # Alert to different service - should only be received by monitoring handler
215
+ other_alert = AlertMessage.new(
216
+ severity: 'info',
217
+ alert_text: 'Backup completed successfully',
218
+ source_system: 'backup-system'
219
+ )
220
+ other_alert.from('monitoring-system-1')
221
+ other_alert.to('backup-service')
222
+ puts "\n3. Sending info alert to 'backup-service'..."
223
+ other_alert.publish
224
+ sleep(0.2) # Allow time for handlers to process
225
+
226
+ # =============================================================================
227
+ # Example 3: Point-to-Point with Filtering
228
+ # =============================================================================
229
+
230
+ puts "\nšŸ“” Example 3: Point-to-Point Messaging with Filtering"
231
+ puts "-" * 40
232
+
233
+ class OrderMessage < SmartMessage::Base
234
+ version 1
235
+ description "Order processing with selective subscription"
236
+
237
+ from 'order-service'
238
+ to 'fulfillment-service'
239
+ reply_to 'order-service'
240
+
241
+ property :order_id, required: true
242
+ property :priority, default: 'normal'
243
+ property :items, required: true
244
+ property :total_amount, required: true
245
+
246
+ config do
247
+ transport SmartMessage::Transport.create(:stdout, loopback: true)
248
+ serializer SmartMessage::Serializer::JSON.new
249
+ end
250
+
251
+ def self.process_high_priority(header, payload)
252
+ data = JSON.parse(payload)
253
+ puts " šŸš€ HIGH PRIORITY ORDER HANDLER:"
254
+ puts " Order ID: #{data['order_id']} (PRIORITY: #{data['priority'].upcase})"
255
+ puts " From: #{header.from} → To: #{header.to}"
256
+ puts " Total: $#{data['total_amount']}"
257
+ end
258
+
259
+ def self.process_normal(header, payload)
260
+ data = JSON.parse(payload)
261
+ puts " šŸ“¦ NORMAL ORDER HANDLER:"
262
+ puts " Order ID: #{data['order_id']}"
263
+ puts " From: #{header.from} → To: #{header.to}"
264
+ puts " Total: $#{data['total_amount']}"
265
+ end
266
+ end
267
+
268
+ # Clear and set up filtered subscriptions
269
+ OrderMessage.unsubscribe!
270
+
271
+ puts "\nšŸ“Œ Setting up order processing subscriptions:"
272
+ # Only the fulfillment service subscribes
273
+ puts " 1. Fulfillment service subscribes to orders..."
274
+ OrderMessage.subscribe('OrderMessage.process_normal', to: 'fulfillment-service')
275
+
276
+ # High-priority team also monitors high-value orders
277
+ puts " 2. Priority team subscribes to high-value orders..."
278
+ OrderMessage.subscribe('OrderMessage.process_high_priority', to: 'fulfillment-service')
279
+
280
+ # Send different types of orders
281
+ puts "\nšŸ“¤ Publishing orders..."
282
+
283
+ # Normal order to fulfillment
284
+ normal_order = OrderMessage.new(
285
+ order_id: "ORD-001",
286
+ priority: 'normal',
287
+ items: ["Widget A", "Widget B"],
288
+ total_amount: 99.99
289
+ )
290
+ puts "\n1. Publishing normal order to fulfillment..."
291
+ normal_order.publish
292
+ sleep(0.2) # Allow time for handlers to process
293
+
294
+ # High priority order
295
+ high_priority_order = OrderMessage.new(
296
+ order_id: "ORD-002",
297
+ priority: 'high',
298
+ items: ["Premium Widget", "Express Gadget"],
299
+ total_amount: 999.99
300
+ )
301
+ puts "\n2. Publishing high-priority order..."
302
+ high_priority_order.publish
303
+ sleep(0.2) # Allow time for handlers to process
304
+
305
+ # Order to different service (should not be received)
306
+ misrouted_order = OrderMessage.new(
307
+ order_id: "ORD-003",
308
+ priority: 'normal',
309
+ items: ["Test Item"],
310
+ total_amount: 50.00
311
+ )
312
+ misrouted_order.to('wrong-service')
313
+ puts "\n3. Publishing order to 'wrong-service' (should not be received)..."
314
+ misrouted_order.publish
315
+ sleep(0.2) # Allow time to confirm no handlers process this
316
+
317
+ # =============================================================================
318
+ # Example 4: Request-Reply with Filtering
319
+ # =============================================================================
320
+
321
+ puts "\nšŸ”„ Example 4: Request-Reply Pattern with Filtering"
322
+ puts "-" * 40
323
+
324
+ class ServiceRequest < SmartMessage::Base
325
+ version 1
326
+ description "Service requests with filtered responses"
327
+
328
+ from 'request-service' # Default from field
329
+
330
+ property :request_id, required: true
331
+ property :request_type, required: true
332
+ property :data
333
+
334
+ config do
335
+ transport SmartMessage::Transport.create(:stdout, loopback: true)
336
+ serializer SmartMessage::Serializer::JSON.new
337
+ end
338
+
339
+ def self.process_api_requests(header, payload)
340
+ data = JSON.parse(payload)
341
+ puts " 🌐 API SERVICE received request:"
342
+ puts " Request ID: #{data['request_id']}"
343
+ puts " Type: #{data['request_type']}"
344
+ puts " From: #{header.from} → To: #{header.to}"
345
+ puts " Reply To: #{header.reply_to}"
346
+ end
347
+
348
+ def self.process_data_requests(header, payload)
349
+ data = JSON.parse(payload)
350
+ puts " šŸ’¾ DATA SERVICE received request:"
351
+ puts " Request ID: #{data['request_id']}"
352
+ puts " Type: #{data['request_type']}"
353
+ puts " From: #{header.from} → To: #{header.to}"
354
+ end
355
+ end
356
+
357
+ ServiceRequest.unsubscribe!
358
+
359
+ puts "\nšŸ“Œ Setting up service request routing:"
360
+ # API service only handles requests directed to it
361
+ puts " 1. API service subscribes to its requests..."
362
+ ServiceRequest.subscribe('ServiceRequest.process_api_requests', to: 'api-service')
363
+
364
+ # Data service handles data requests
365
+ puts " 2. Data service subscribes to its requests..."
366
+ ServiceRequest.subscribe('ServiceRequest.process_data_requests', to: 'data-service')
367
+
368
+ puts "\nšŸ“¤ Publishing service requests..."
369
+
370
+ # API request
371
+ api_request = ServiceRequest.new(
372
+ request_id: SecureRandom.uuid,
373
+ request_type: 'user_lookup',
374
+ data: { user_id: 'USER-123' }
375
+ )
376
+ api_request.from('web-frontend')
377
+ api_request.to('api-service')
378
+ api_request.reply_to('web-frontend')
379
+ puts "\n1. Publishing API request..."
380
+ api_request.publish
381
+ sleep(0.2) # Allow time for handlers to process
382
+
383
+ # Data request
384
+ data_request = ServiceRequest.new(
385
+ request_id: SecureRandom.uuid,
386
+ request_type: 'query',
387
+ data: { table: 'orders', limit: 100 }
388
+ )
389
+ data_request.from('analytics-service')
390
+ data_request.to('data-service')
391
+ data_request.reply_to('analytics-service')
392
+ puts "\n2. Publishing data request..."
393
+ data_request.publish
394
+ sleep(0.2) # Allow time for handlers to process
395
+
396
+ # =============================================================================
397
+ # Summary
398
+ # =============================================================================
399
+
400
+ puts "\nšŸŽÆ Entity Addressing & Filtering Summary"
401
+ puts "=" * 50
402
+ puts "āœ… Point-to-Point: FROM/TO specified for direct routing"
403
+ puts "āœ… Broadcast: FROM only, TO=nil for all broadcast subscribers"
404
+ puts "āœ… Filtered Subscriptions: Subscribe to specific message patterns:"
405
+ puts " • broadcast: true - Only receive broadcast messages"
406
+ puts " • to: 'service-name' - Only receive messages directed to you"
407
+ puts " • from: 'sender' - Only receive from specific senders"
408
+ puts " • from: ['sender1', 'sender2'] - Receive from multiple senders"
409
+ puts " • Combined filters work with OR logic for broadcast/to"
410
+ puts "āœ… Request-Reply: REPLY_TO for response routing"
411
+ puts "āœ… Instance Override: Runtime addressing changes"
412
+ puts "āœ… Gateway Pattern: Message transformation and routing"
413
+ puts "\nšŸ’” Filtering enables microservices to:"
414
+ puts " • Ignore messages not meant for them"
415
+ puts " • Handle broadcasts separately from directed messages"
416
+ puts " • Route messages to appropriate handlers based on sender"
417
+ puts " • Reduce processing overhead by filtering at subscription level"
418
+ 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