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