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,373 @@
1
+ #!/usr/bin/env ruby
2
+ # examples/08_entity_addressing.rb
3
+ #
4
+ # Demonstrates SmartMessage entity addressing capabilities including:
5
+ # - Point-to-point messaging with FROM/TO fields
6
+ # - Broadcast messaging (no TO field)
7
+ # - Request-reply patterns with REPLY_TO
8
+ # - Instance-level addressing overrides
9
+ # - Gateway patterns
10
+
11
+ require_relative '../lib/smart_message'
12
+
13
+ puts "šŸŽÆ SmartMessage Entity Addressing Demo"
14
+ puts "=" * 50
15
+
16
+ # Configure transport for demo
17
+ transport = SmartMessage::Transport.create(:stdout, loopback: true)
18
+ serializer = SmartMessage::Serializer::JSON.new
19
+
20
+ # =============================================================================
21
+ # Example 1: Point-to-Point Messaging
22
+ # =============================================================================
23
+
24
+ puts "\nšŸ“” Example 1: Point-to-Point Messaging"
25
+ puts "-" * 40
26
+
27
+ class OrderMessage < SmartMessage::Base
28
+ version 1
29
+ description "Direct order processing between order service and fulfillment"
30
+
31
+ # Point-to-point addressing
32
+ from 'order-service'
33
+ to 'fulfillment-service'
34
+ reply_to 'order-service'
35
+
36
+ property :order_id, required: true
37
+ property :customer_id, required: true
38
+ property :items, required: true
39
+ property :total_amount, required: true
40
+
41
+ config do
42
+ transport SmartMessage::Transport.create(:stdout, loopback: true)
43
+ serializer SmartMessage::Serializer::JSON.new
44
+ end
45
+
46
+ def self.process(header, payload)
47
+ data = JSON.parse(payload)
48
+ puts " šŸŽÆ FULFILLMENT SERVICE received order:"
49
+ puts " Order ID: #{data['order_id']}"
50
+ puts " From: #{header.from} → To: #{header.to}"
51
+ puts " Reply to: #{header.reply_to}"
52
+ puts " Customer: #{data['customer_id']}"
53
+ puts " Items: #{data['items'].join(', ')}"
54
+ puts " Total: $#{data['total_amount']}"
55
+ end
56
+ end
57
+
58
+ # Subscribe and publish point-to-point message
59
+ OrderMessage.subscribe
60
+
61
+ order = OrderMessage.new(
62
+ order_id: "ORD-2024-001",
63
+ customer_id: "CUST-12345",
64
+ items: ["Widget A", "Widget B", "Gadget C"],
65
+ total_amount: 299.99,
66
+ from: 'order-service'
67
+ )
68
+
69
+ puts "\nšŸ“¤ Publishing point-to-point order message..."
70
+ order.publish
71
+ sleep(0.2) # Allow time for handlers to process
72
+
73
+ # =============================================================================
74
+ # Example 2: Broadcast Messaging
75
+ # =============================================================================
76
+
77
+ puts "\nšŸ“» Example 2: Broadcast Messaging"
78
+ puts "-" * 40
79
+
80
+ class SystemAnnouncementMessage < SmartMessage::Base
81
+ version 1
82
+ description "System-wide announcements to all services"
83
+
84
+ # Broadcast addressing (no 'to' field)
85
+ from 'admin-service'
86
+ # No 'to' field = broadcast to all subscribers
87
+
88
+ property :message, required: true
89
+ property :priority, default: 'normal'
90
+ property :effective_time, required: true
91
+
92
+ config do
93
+ transport SmartMessage::Transport.create(:stdout, loopback: true)
94
+ serializer SmartMessage::Serializer::JSON.new
95
+ end
96
+
97
+ def self.process(header, payload)
98
+ data = JSON.parse(payload)
99
+ priority_icon = data['priority'] == 'high' ? '🚨' : 'šŸ“¢'
100
+ puts " #{priority_icon} ALL SERVICES received announcement:"
101
+ puts " From: #{header.from}"
102
+ puts " To: #{header.to.nil? ? 'ALL (broadcast)' : header.to}"
103
+ puts " Priority: #{data['priority'].upcase}"
104
+ puts " Message: #{data['message']}"
105
+ puts " Effective: #{data['effective_time']}"
106
+ end
107
+ end
108
+
109
+ # Subscribe and publish broadcast message
110
+ SystemAnnouncementMessage.subscribe
111
+
112
+ announcement = SystemAnnouncementMessage.new(
113
+ message: "System maintenance scheduled for tonight at 2:00 AM EST",
114
+ priority: 'high',
115
+ effective_time: '2024-12-20 02:00:00 EST',
116
+ from: 'admin-service'
117
+ )
118
+
119
+ puts "\nšŸ“¤ Publishing broadcast announcement..."
120
+ announcement.publish
121
+ sleep(0.2) # Allow time for handlers to process
122
+
123
+ # =============================================================================
124
+ # Example 3: Request-Reply Pattern
125
+ # =============================================================================
126
+
127
+ puts "\nšŸ”„ Example 3: Request-Reply Pattern"
128
+ puts "-" * 40
129
+
130
+ class UserLookupRequest < SmartMessage::Base
131
+ version 1
132
+ description "Request user information from user service"
133
+
134
+ from 'web-service'
135
+ to 'user-service'
136
+ reply_to 'web-service'
137
+
138
+ property :user_id, required: true
139
+ property :request_id, required: true
140
+ property :requested_fields, default: ['name', 'email']
141
+
142
+ config do
143
+ transport SmartMessage::Transport.create(:stdout, loopback: true)
144
+ serializer SmartMessage::Serializer::JSON.new
145
+ end
146
+
147
+ def self.process(header, payload)
148
+ data = JSON.parse(payload)
149
+ puts " šŸ” USER SERVICE received lookup request:"
150
+ puts " Request ID: #{data['request_id']}"
151
+ puts " User ID: #{data['user_id']}"
152
+ puts " From: #{header.from} → To: #{header.to}"
153
+ puts " Reply to: #{header.reply_to}"
154
+ puts " Fields: #{data['requested_fields'].join(', ')}"
155
+
156
+ # Simulate response (in real system, this would be a separate response message)
157
+ puts " ā†©ļø Simulated response would go to: #{header.reply_to}"
158
+ end
159
+ end
160
+
161
+ class UserLookupResponse < SmartMessage::Base
162
+ version 1
163
+ description "Response with user information"
164
+
165
+ from 'user-service'
166
+ # 'to' will be set to the original request's 'reply_to'
167
+
168
+ property :user_id, required: true
169
+ property :request_id, required: true
170
+ property :user_data
171
+ property :success, default: true
172
+ property :error_message
173
+
174
+ config do
175
+ transport SmartMessage::Transport.create(:stdout, loopback: true)
176
+ serializer SmartMessage::Serializer::JSON.new
177
+ end
178
+
179
+ def self.process(header, payload)
180
+ data = JSON.parse(payload)
181
+ puts " āœ… WEB SERVICE received lookup response:"
182
+ puts " Request ID: #{data['request_id']}"
183
+ puts " Success: #{data['success']}"
184
+ puts " User Data: #{data['user_data'] ? 'Present' : 'None'}"
185
+ puts " From: #{header.from} → To: #{header.to}"
186
+ end
187
+ end
188
+
189
+ # Subscribe to both request and response
190
+ UserLookupRequest.subscribe
191
+ UserLookupResponse.subscribe
192
+
193
+ # Send request
194
+ request = UserLookupRequest.new(
195
+ user_id: "USER-789",
196
+ request_id: SecureRandom.uuid,
197
+ requested_fields: ['name', 'email', 'last_login'],
198
+ from: 'web-service'
199
+ )
200
+
201
+ puts "\nšŸ“¤ Publishing user lookup request..."
202
+ request.publish
203
+ sleep(0.2) # Allow time for handlers to process
204
+
205
+ # Send simulated response
206
+ response = UserLookupResponse.new(
207
+ user_id: "USER-789",
208
+ request_id: request.request_id,
209
+ user_data: {
210
+ name: "Alice Johnson",
211
+ email: "alice@example.com",
212
+ last_login: "2024-12-19 14:30:00"
213
+ },
214
+ success: true,
215
+ from: 'user-service'
216
+ )
217
+ response.to('web-service') # Set reply destination
218
+
219
+ puts "\nšŸ“¤ Publishing user lookup response..."
220
+ response.publish
221
+ sleep(0.2) # Allow time for handlers to process
222
+
223
+ # =============================================================================
224
+ # Example 4: Instance-Level Addressing Override
225
+ # =============================================================================
226
+
227
+ puts "\nšŸ”§ Example 4: Instance-Level Addressing Override"
228
+ puts "-" * 40
229
+
230
+ class PaymentMessage < SmartMessage::Base
231
+ version 1
232
+ description "Payment processing with configurable routing"
233
+
234
+ # Default addressing
235
+ from 'payment-service'
236
+ to 'primary-bank-gateway'
237
+ reply_to 'payment-service'
238
+
239
+ property :payment_id, required: true
240
+ property :amount, required: true
241
+ property :account_id, required: true
242
+ property :payment_method, default: 'credit_card'
243
+
244
+ config do
245
+ transport SmartMessage::Transport.create(:stdout, loopback: true)
246
+ serializer SmartMessage::Serializer::JSON.new
247
+ end
248
+
249
+ def self.process(header, payload)
250
+ data = JSON.parse(payload)
251
+ gateway_icon = header.to.include?('backup') ? 'šŸ”„' : 'šŸ¦'
252
+ puts " #{gateway_icon} #{header.to.upcase} received payment:"
253
+ puts " Payment ID: #{data['payment_id']}"
254
+ puts " From: #{header.from} → To: #{header.to}"
255
+ puts " Amount: $#{data['amount']}"
256
+ puts " Account: #{data['account_id']}"
257
+ puts " Method: #{data['payment_method']}"
258
+ end
259
+ end
260
+
261
+ PaymentMessage.subscribe
262
+
263
+ # Normal payment using class defaults
264
+ normal_payment = PaymentMessage.new(
265
+ payment_id: "PAY-001",
266
+ amount: 150.00,
267
+ account_id: "ACCT-12345",
268
+ payment_method: 'credit_card',
269
+ from: 'payment-service'
270
+ )
271
+
272
+ puts "\nšŸ“¤ Publishing normal payment (using class defaults)..."
273
+ puts " Class FROM: #{PaymentMessage.from}"
274
+ puts " Class TO: #{PaymentMessage.to}"
275
+ normal_payment.publish
276
+ sleep(0.2) # Allow time for handlers to process
277
+
278
+ # Override addressing for backup gateway
279
+ backup_payment = PaymentMessage.new(
280
+ payment_id: "PAY-002",
281
+ amount: 75.50,
282
+ account_id: "ACCT-67890",
283
+ payment_method: 'debit_card',
284
+ from: 'payment-service'
285
+ )
286
+
287
+ # Override instance addressing
288
+ backup_payment.to('backup-bank-gateway')
289
+ backup_payment.reply_to('payment-backup-service')
290
+
291
+ puts "\nšŸ“¤ Publishing backup payment (with overrides)..."
292
+ puts " Instance FROM: #{backup_payment.from}"
293
+ puts " Instance TO: #{backup_payment.to}"
294
+ puts " Instance REPLY_TO: #{backup_payment.reply_to}"
295
+ backup_payment.publish
296
+ sleep(0.2) # Allow time for handlers to process
297
+
298
+ # =============================================================================
299
+ # Example 5: Gateway Pattern
300
+ # =============================================================================
301
+
302
+ puts "\nšŸŒ‰ Example 5: Gateway Pattern"
303
+ puts "-" * 40
304
+
305
+ class ExternalAPIMessage < SmartMessage::Base
306
+ version 1
307
+ description "External API integration message"
308
+
309
+ from 'api-gateway'
310
+ to 'external-partner-service'
311
+
312
+ property :api_call, required: true
313
+ property :payload_data, required: true
314
+ property :authentication_token
315
+ property :partner_id, required: true
316
+
317
+ config do
318
+ transport SmartMessage::Transport.create(:stdout, loopback: true)
319
+ serializer SmartMessage::Serializer::JSON.new
320
+ end
321
+
322
+ def self.process(header, payload)
323
+ data = JSON.parse(payload)
324
+ puts " 🌐 EXTERNAL PARTNER received API call:"
325
+ puts " API Call: #{data['api_call']}"
326
+ puts " From: #{header.from} → To: #{header.to}"
327
+ puts " Partner ID: #{data['partner_id']}"
328
+ puts " Has Auth Token: #{data['authentication_token'] ? 'Yes' : 'No'}"
329
+ puts " Payload Size: #{data['payload_data'].to_s.length} characters"
330
+ end
331
+ end
332
+
333
+ ExternalAPIMessage.subscribe
334
+
335
+ # Create internal message
336
+ internal_data = {
337
+ user_id: "USER-123",
338
+ action: "update_profile",
339
+ changes: { email: "newemail@example.com" }
340
+ }
341
+
342
+ # Transform for external API via gateway
343
+ external_message = ExternalAPIMessage.new(
344
+ api_call: "PUT /api/v1/users/USER-123",
345
+ payload_data: internal_data,
346
+ authentication_token: "Bearer abc123xyz789",
347
+ partner_id: "PARTNER-ALPHA",
348
+ from: 'api-gateway'
349
+ )
350
+
351
+ # Gateway can override destination based on routing rules
352
+ if internal_data[:action] == "update_profile"
353
+ external_message.to('partner-alpha-profile-service')
354
+ else
355
+ external_message.to('partner-alpha-general-service')
356
+ end
357
+
358
+ puts "\nšŸ“¤ Publishing external API message via gateway..."
359
+ external_message.publish
360
+ sleep(0.2) # Allow time for handlers to process
361
+
362
+ # =============================================================================
363
+ # Summary
364
+ # =============================================================================
365
+
366
+ puts "\nšŸŽÆ Entity Addressing Summary"
367
+ puts "=" * 50
368
+ puts "āœ… Point-to-Point: FROM/TO specified for direct routing"
369
+ puts "āœ… Broadcast: FROM only, TO=nil for all subscribers"
370
+ puts "āœ… Request-Reply: REPLY_TO for response routing"
371
+ puts "āœ… Instance Override: Runtime addressing changes"
372
+ puts "āœ… Gateway Pattern: Message transformation and routing"
373
+ puts "\nFor more details, see docs/addressing.md"