smart_message 0.0.1
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 +7 -0
- data/.envrc +3 -0
- data/.gitignore +8 -0
- data/.travis.yml +7 -0
- data/CHANGELOG.md +100 -0
- data/COMMITS.md +196 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +71 -0
- data/README.md +303 -0
- data/Rakefile +10 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/docs/README.md +52 -0
- data/docs/architecture.md +370 -0
- data/docs/dispatcher.md +593 -0
- data/docs/examples.md +808 -0
- data/docs/getting-started.md +235 -0
- data/docs/ideas_to_think_about.md +329 -0
- data/docs/serializers.md +575 -0
- data/docs/transports.md +501 -0
- data/docs/troubleshooting.md +582 -0
- data/examples/01_point_to_point_orders.rb +200 -0
- data/examples/02_publish_subscribe_events.rb +364 -0
- data/examples/03_many_to_many_chat.rb +608 -0
- data/examples/README.md +335 -0
- data/examples/tmux_chat/README.md +283 -0
- data/examples/tmux_chat/bot_agent.rb +272 -0
- data/examples/tmux_chat/human_agent.rb +197 -0
- data/examples/tmux_chat/room_monitor.rb +158 -0
- data/examples/tmux_chat/shared_chat_system.rb +295 -0
- data/examples/tmux_chat/start_chat_demo.sh +190 -0
- data/examples/tmux_chat/stop_chat_demo.sh +22 -0
- data/lib/simple_stats.rb +57 -0
- data/lib/smart_message/base.rb +284 -0
- data/lib/smart_message/dispatcher/.keep +0 -0
- data/lib/smart_message/dispatcher.rb +146 -0
- data/lib/smart_message/errors.rb +29 -0
- data/lib/smart_message/header.rb +20 -0
- data/lib/smart_message/logger/base.rb +8 -0
- data/lib/smart_message/logger.rb +7 -0
- data/lib/smart_message/serializer/base.rb +23 -0
- data/lib/smart_message/serializer/json.rb +22 -0
- data/lib/smart_message/serializer.rb +10 -0
- data/lib/smart_message/transport/base.rb +85 -0
- data/lib/smart_message/transport/memory_transport.rb +69 -0
- data/lib/smart_message/transport/registry.rb +59 -0
- data/lib/smart_message/transport/stdout_transport.rb +62 -0
- data/lib/smart_message/transport.rb +41 -0
- data/lib/smart_message/version.rb +7 -0
- data/lib/smart_message/wrapper.rb +43 -0
- data/lib/smart_message.rb +54 -0
- data/smart_message.gemspec +53 -0
- metadata +252 -0
data/docs/examples.md
ADDED
@@ -0,0 +1,808 @@
|
|
1
|
+
# Examples & Use Cases
|
2
|
+
|
3
|
+
This document provides practical examples of using SmartMessage in real-world scenarios.
|
4
|
+
|
5
|
+
## Basic Messaging Patterns
|
6
|
+
|
7
|
+
### Simple Notification System
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
require 'smart_message'
|
11
|
+
|
12
|
+
class NotificationMessage < SmartMessage::Base
|
13
|
+
property :recipient
|
14
|
+
property :subject
|
15
|
+
property :body
|
16
|
+
property :priority, default: 'normal'
|
17
|
+
property :channel, default: 'email'
|
18
|
+
|
19
|
+
config do
|
20
|
+
transport SmartMessage::Transport.create(:stdout, loopback: true)
|
21
|
+
serializer SmartMessage::Serializer::JSON.new
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.process(message_header, message_payload)
|
25
|
+
data = JSON.parse(message_payload)
|
26
|
+
notification = new(data)
|
27
|
+
|
28
|
+
case notification.channel
|
29
|
+
when 'email'
|
30
|
+
send_email(notification)
|
31
|
+
when 'sms'
|
32
|
+
send_sms(notification)
|
33
|
+
when 'push'
|
34
|
+
send_push_notification(notification)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def self.send_email(notification)
|
41
|
+
puts "📧 Sending email to #{notification.recipient}"
|
42
|
+
puts "Subject: #{notification.subject}"
|
43
|
+
puts "Priority: #{notification.priority}"
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.send_sms(notification)
|
47
|
+
puts "📱 Sending SMS to #{notification.recipient}"
|
48
|
+
puts "Message: #{notification.body}"
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.send_push_notification(notification)
|
52
|
+
puts "🔔 Sending push notification to #{notification.recipient}"
|
53
|
+
puts "Title: #{notification.subject}"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# Setup
|
58
|
+
NotificationMessage.subscribe
|
59
|
+
|
60
|
+
# Send notifications
|
61
|
+
NotificationMessage.new(
|
62
|
+
recipient: "user@example.com",
|
63
|
+
subject: "Welcome!",
|
64
|
+
body: "Thanks for signing up!",
|
65
|
+
priority: "high"
|
66
|
+
).publish
|
67
|
+
|
68
|
+
NotificationMessage.new(
|
69
|
+
recipient: "+1234567890",
|
70
|
+
subject: "Alert",
|
71
|
+
body: "Your order has shipped!",
|
72
|
+
channel: "sms"
|
73
|
+
).publish
|
74
|
+
```
|
75
|
+
|
76
|
+
### Event-Driven Architecture
|
77
|
+
|
78
|
+
```ruby
|
79
|
+
# User registration event
|
80
|
+
class UserRegisteredEvent < SmartMessage::Base
|
81
|
+
property :user_id
|
82
|
+
property :email
|
83
|
+
property :name
|
84
|
+
property :registration_source
|
85
|
+
property :timestamp, default: -> { Time.now.iso8601 }
|
86
|
+
|
87
|
+
config do
|
88
|
+
transport SmartMessage::Transport.create(:memory, auto_process: true)
|
89
|
+
serializer SmartMessage::Serializer::JSON.new
|
90
|
+
end
|
91
|
+
|
92
|
+
def self.process(message_header, message_payload)
|
93
|
+
data = JSON.parse(message_payload)
|
94
|
+
event = new(data)
|
95
|
+
|
96
|
+
# Fan out to multiple handlers
|
97
|
+
send_welcome_email(event)
|
98
|
+
create_user_profile(event)
|
99
|
+
track_analytics(event)
|
100
|
+
setup_default_preferences(event)
|
101
|
+
end
|
102
|
+
|
103
|
+
private
|
104
|
+
|
105
|
+
def self.send_welcome_email(event)
|
106
|
+
WelcomeEmailMessage.new(
|
107
|
+
user_id: event.user_id,
|
108
|
+
email: event.email,
|
109
|
+
name: event.name
|
110
|
+
).publish
|
111
|
+
end
|
112
|
+
|
113
|
+
def self.create_user_profile(event)
|
114
|
+
CreateProfileMessage.new(
|
115
|
+
user_id: event.user_id,
|
116
|
+
source: event.registration_source
|
117
|
+
).publish
|
118
|
+
end
|
119
|
+
|
120
|
+
def self.track_analytics(event)
|
121
|
+
AnalyticsMessage.new(
|
122
|
+
event_type: 'user_registration',
|
123
|
+
user_id: event.user_id,
|
124
|
+
properties: {
|
125
|
+
source: event.registration_source,
|
126
|
+
timestamp: event.timestamp
|
127
|
+
}
|
128
|
+
).publish
|
129
|
+
end
|
130
|
+
|
131
|
+
def self.setup_default_preferences(event)
|
132
|
+
PreferencesMessage.new(
|
133
|
+
user_id: event.user_id,
|
134
|
+
preferences: default_preferences
|
135
|
+
).publish
|
136
|
+
end
|
137
|
+
|
138
|
+
def self.default_preferences
|
139
|
+
{
|
140
|
+
email_notifications: true,
|
141
|
+
marketing_emails: false,
|
142
|
+
theme: 'light'
|
143
|
+
}
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
# Supporting message classes
|
148
|
+
class WelcomeEmailMessage < SmartMessage::Base
|
149
|
+
property :user_id
|
150
|
+
property :email
|
151
|
+
property :name
|
152
|
+
|
153
|
+
config do
|
154
|
+
transport SmartMessage::Transport.create(:stdout)
|
155
|
+
serializer SmartMessage::Serializer::JSON.new
|
156
|
+
end
|
157
|
+
|
158
|
+
def self.process(message_header, message_payload)
|
159
|
+
data = JSON.parse(message_payload)
|
160
|
+
message = new(data)
|
161
|
+
|
162
|
+
puts "📧 Sending welcome email to #{message.email} (#{message.name})"
|
163
|
+
# Email sending logic here
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
class AnalyticsMessage < SmartMessage::Base
|
168
|
+
property :event_type
|
169
|
+
property :user_id
|
170
|
+
property :properties
|
171
|
+
|
172
|
+
config do
|
173
|
+
transport SmartMessage::Transport.create(:stdout)
|
174
|
+
serializer SmartMessage::Serializer::JSON.new
|
175
|
+
end
|
176
|
+
|
177
|
+
def self.process(message_header, message_payload)
|
178
|
+
data = JSON.parse(message_payload)
|
179
|
+
event = new(data)
|
180
|
+
|
181
|
+
puts "📊 Tracking event: #{event.event_type} for user #{event.user_id}"
|
182
|
+
# Analytics tracking logic here
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
# Setup and trigger
|
187
|
+
[UserRegisteredEvent, WelcomeEmailMessage, AnalyticsMessage].each(&:subscribe)
|
188
|
+
|
189
|
+
# Simulate user registration
|
190
|
+
UserRegisteredEvent.new(
|
191
|
+
user_id: 12345,
|
192
|
+
email: "alice@example.com",
|
193
|
+
name: "Alice Johnson",
|
194
|
+
registration_source: "web_form"
|
195
|
+
).publish
|
196
|
+
```
|
197
|
+
|
198
|
+
## E-commerce Order Processing
|
199
|
+
|
200
|
+
```ruby
|
201
|
+
# Order lifecycle management
|
202
|
+
class OrderCreatedMessage < SmartMessage::Base
|
203
|
+
property :order_id
|
204
|
+
property :customer_id
|
205
|
+
property :items
|
206
|
+
property :total_amount
|
207
|
+
property :shipping_address
|
208
|
+
property :created_at, default: -> { Time.now.iso8601 }
|
209
|
+
|
210
|
+
config do
|
211
|
+
transport SmartMessage::Transport.create(:memory, auto_process: true)
|
212
|
+
serializer SmartMessage::Serializer::JSON.new
|
213
|
+
end
|
214
|
+
|
215
|
+
def self.process(message_header, message_payload)
|
216
|
+
data = JSON.parse(message_payload)
|
217
|
+
order = new(data)
|
218
|
+
|
219
|
+
# Validate order
|
220
|
+
if valid_order?(order)
|
221
|
+
# Reserve inventory
|
222
|
+
InventoryReservationMessage.new(
|
223
|
+
order_id: order.order_id,
|
224
|
+
items: order.items
|
225
|
+
).publish
|
226
|
+
|
227
|
+
# Process payment
|
228
|
+
PaymentProcessingMessage.new(
|
229
|
+
order_id: order.order_id,
|
230
|
+
customer_id: order.customer_id,
|
231
|
+
amount: order.total_amount
|
232
|
+
).publish
|
233
|
+
else
|
234
|
+
# Handle invalid order
|
235
|
+
OrderRejectedMessage.new(
|
236
|
+
order_id: order.order_id,
|
237
|
+
reason: "Invalid order data"
|
238
|
+
).publish
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
private
|
243
|
+
|
244
|
+
def self.valid_order?(order)
|
245
|
+
order.items&.any? && order.total_amount&.positive?
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
class InventoryReservationMessage < SmartMessage::Base
|
250
|
+
property :order_id
|
251
|
+
property :items
|
252
|
+
|
253
|
+
config do
|
254
|
+
transport SmartMessage::Transport.create(:memory, auto_process: true)
|
255
|
+
serializer SmartMessage::Serializer::JSON.new
|
256
|
+
end
|
257
|
+
|
258
|
+
def self.process(message_header, message_payload)
|
259
|
+
data = JSON.parse(message_payload)
|
260
|
+
reservation = new(data)
|
261
|
+
|
262
|
+
success = reserve_inventory(reservation.items)
|
263
|
+
|
264
|
+
if success
|
265
|
+
InventoryReservedMessage.new(
|
266
|
+
order_id: reservation.order_id
|
267
|
+
).publish
|
268
|
+
else
|
269
|
+
InventoryFailedMessage.new(
|
270
|
+
order_id: reservation.order_id,
|
271
|
+
reason: "Insufficient stock"
|
272
|
+
).publish
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
private
|
277
|
+
|
278
|
+
def self.reserve_inventory(items)
|
279
|
+
# Inventory reservation logic
|
280
|
+
puts "🏪 Reserving inventory for #{items.length} items"
|
281
|
+
true # Simulate success
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
class PaymentProcessingMessage < SmartMessage::Base
|
286
|
+
property :order_id
|
287
|
+
property :customer_id
|
288
|
+
property :amount
|
289
|
+
|
290
|
+
config do
|
291
|
+
transport SmartMessage::Transport.create(:memory, auto_process: true)
|
292
|
+
serializer SmartMessage::Serializer::JSON.new
|
293
|
+
end
|
294
|
+
|
295
|
+
def self.process(message_header, message_payload)
|
296
|
+
data = JSON.parse(message_payload)
|
297
|
+
payment = new(data)
|
298
|
+
|
299
|
+
success = process_payment(payment)
|
300
|
+
|
301
|
+
if success
|
302
|
+
PaymentSuccessMessage.new(
|
303
|
+
order_id: payment.order_id,
|
304
|
+
transaction_id: generate_transaction_id
|
305
|
+
).publish
|
306
|
+
else
|
307
|
+
PaymentFailedMessage.new(
|
308
|
+
order_id: payment.order_id,
|
309
|
+
reason: "Payment declined"
|
310
|
+
).publish
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
private
|
315
|
+
|
316
|
+
def self.process_payment(payment)
|
317
|
+
puts "💳 Processing payment of $#{payment.amount} for order #{payment.order_id}"
|
318
|
+
true # Simulate success
|
319
|
+
end
|
320
|
+
|
321
|
+
def self.generate_transaction_id
|
322
|
+
"txn_#{SecureRandom.hex(8)}"
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
326
|
+
# Setup
|
327
|
+
[
|
328
|
+
OrderCreatedMessage,
|
329
|
+
InventoryReservationMessage,
|
330
|
+
PaymentProcessingMessage
|
331
|
+
].each(&:subscribe)
|
332
|
+
|
333
|
+
# Create an order
|
334
|
+
OrderCreatedMessage.new(
|
335
|
+
order_id: "ORD-001",
|
336
|
+
customer_id: "CUST-123",
|
337
|
+
items: [
|
338
|
+
{ sku: "WIDGET-A", quantity: 2, price: 19.99 },
|
339
|
+
{ sku: "GADGET-B", quantity: 1, price: 49.99 }
|
340
|
+
],
|
341
|
+
total_amount: 89.97,
|
342
|
+
shipping_address: {
|
343
|
+
street: "123 Main St",
|
344
|
+
city: "Anytown",
|
345
|
+
state: "CA",
|
346
|
+
zip: "12345"
|
347
|
+
}
|
348
|
+
).publish
|
349
|
+
```
|
350
|
+
|
351
|
+
## Logging and Monitoring
|
352
|
+
|
353
|
+
```ruby
|
354
|
+
# Centralized logging system
|
355
|
+
class LogMessage < SmartMessage::Base
|
356
|
+
property :level
|
357
|
+
property :service
|
358
|
+
property :message
|
359
|
+
property :context
|
360
|
+
property :timestamp, default: -> { Time.now.iso8601 }
|
361
|
+
property :correlation_id
|
362
|
+
|
363
|
+
config do
|
364
|
+
transport SmartMessage::Transport.create(:stdout, output: "application.log")
|
365
|
+
serializer SmartMessage::Serializer::JSON.new
|
366
|
+
end
|
367
|
+
|
368
|
+
def self.process(message_header, message_payload)
|
369
|
+
data = JSON.parse(message_payload)
|
370
|
+
log_entry = new(data)
|
371
|
+
|
372
|
+
formatted_message = format_log_entry(log_entry)
|
373
|
+
|
374
|
+
case log_entry.level
|
375
|
+
when 'ERROR', 'FATAL'
|
376
|
+
send_alert(log_entry)
|
377
|
+
when 'WARN'
|
378
|
+
track_warning(log_entry)
|
379
|
+
end
|
380
|
+
|
381
|
+
puts formatted_message
|
382
|
+
end
|
383
|
+
|
384
|
+
private
|
385
|
+
|
386
|
+
def self.format_log_entry(log_entry)
|
387
|
+
"[#{log_entry.timestamp}] #{log_entry.level} #{log_entry.service}: #{log_entry.message}" +
|
388
|
+
(log_entry.correlation_id ? " (#{log_entry.correlation_id})" : "") +
|
389
|
+
(log_entry.context ? " | #{log_entry.context.to_json}" : "")
|
390
|
+
end
|
391
|
+
|
392
|
+
def self.send_alert(log_entry)
|
393
|
+
if log_entry.level == 'FATAL'
|
394
|
+
puts "🚨 FATAL ERROR ALERT: #{log_entry.message}"
|
395
|
+
else
|
396
|
+
puts "⚠️ ERROR ALERT: #{log_entry.message}"
|
397
|
+
end
|
398
|
+
end
|
399
|
+
|
400
|
+
def self.track_warning(log_entry)
|
401
|
+
puts "📝 Warning tracked: #{log_entry.message}"
|
402
|
+
end
|
403
|
+
end
|
404
|
+
|
405
|
+
# Application performance monitoring
|
406
|
+
class MetricMessage < SmartMessage::Base
|
407
|
+
property :metric_name
|
408
|
+
property :value
|
409
|
+
property :unit
|
410
|
+
property :tags
|
411
|
+
property :timestamp, default: -> { Time.now.to_f }
|
412
|
+
|
413
|
+
config do
|
414
|
+
transport SmartMessage::Transport.create(:memory, auto_process: true)
|
415
|
+
serializer SmartMessage::Serializer::JSON.new
|
416
|
+
end
|
417
|
+
|
418
|
+
def self.process(message_header, message_payload)
|
419
|
+
data = JSON.parse(message_payload)
|
420
|
+
metric = new(data)
|
421
|
+
|
422
|
+
# Store metric (would typically go to monitoring system)
|
423
|
+
store_metric(metric)
|
424
|
+
|
425
|
+
# Check for alerts
|
426
|
+
check_thresholds(metric)
|
427
|
+
end
|
428
|
+
|
429
|
+
private
|
430
|
+
|
431
|
+
def self.store_metric(metric)
|
432
|
+
puts "📊 Metric: #{metric.metric_name} = #{metric.value} #{metric.unit} #{metric.tags}"
|
433
|
+
end
|
434
|
+
|
435
|
+
def self.check_thresholds(metric)
|
436
|
+
case metric.metric_name
|
437
|
+
when 'response_time'
|
438
|
+
if metric.value > 1000 # More than 1 second
|
439
|
+
puts "⚠️ High response time alert: #{metric.value}ms"
|
440
|
+
end
|
441
|
+
when 'error_rate'
|
442
|
+
if metric.value > 0.05 # More than 5% error rate
|
443
|
+
puts "🚨 High error rate alert: #{(metric.value * 100).round(2)}%"
|
444
|
+
end
|
445
|
+
end
|
446
|
+
end
|
447
|
+
end
|
448
|
+
|
449
|
+
# Setup
|
450
|
+
LogMessage.subscribe
|
451
|
+
MetricMessage.subscribe
|
452
|
+
|
453
|
+
# Log some events
|
454
|
+
LogMessage.new(
|
455
|
+
level: "INFO",
|
456
|
+
service: "user-service",
|
457
|
+
message: "User login successful",
|
458
|
+
context: { user_id: 123, ip: "192.168.1.1" },
|
459
|
+
correlation_id: "req-abc123"
|
460
|
+
).publish
|
461
|
+
|
462
|
+
LogMessage.new(
|
463
|
+
level: "ERROR",
|
464
|
+
service: "payment-service",
|
465
|
+
message: "Payment gateway timeout",
|
466
|
+
context: { order_id: "ORD-001", gateway: "stripe" },
|
467
|
+
correlation_id: "req-def456"
|
468
|
+
).publish
|
469
|
+
|
470
|
+
# Send some metrics
|
471
|
+
MetricMessage.new(
|
472
|
+
metric_name: "response_time",
|
473
|
+
value: 1250,
|
474
|
+
unit: "ms",
|
475
|
+
tags: { service: "api", endpoint: "/users" }
|
476
|
+
).publish
|
477
|
+
|
478
|
+
MetricMessage.new(
|
479
|
+
metric_name: "error_rate",
|
480
|
+
value: 0.08,
|
481
|
+
unit: "percentage",
|
482
|
+
tags: { service: "payment-service" }
|
483
|
+
).publish
|
484
|
+
```
|
485
|
+
|
486
|
+
## Gateway Pattern
|
487
|
+
|
488
|
+
```ruby
|
489
|
+
# Bridge between different message systems
|
490
|
+
class MessageGateway < SmartMessage::Base
|
491
|
+
property :source_system
|
492
|
+
property :destination_system
|
493
|
+
property :message_type
|
494
|
+
property :payload
|
495
|
+
|
496
|
+
# Receive from one transport
|
497
|
+
config do
|
498
|
+
transport SmartMessage::Transport.create(:memory, auto_process: true)
|
499
|
+
serializer SmartMessage::Serializer::JSON.new
|
500
|
+
end
|
501
|
+
|
502
|
+
def self.process(message_header, message_payload)
|
503
|
+
data = JSON.parse(message_payload)
|
504
|
+
gateway_message = new(data)
|
505
|
+
|
506
|
+
# Transform and forward to destination system
|
507
|
+
case gateway_message.destination_system
|
508
|
+
when 'email_system'
|
509
|
+
forward_to_email_system(gateway_message)
|
510
|
+
when 'sms_system'
|
511
|
+
forward_to_sms_system(gateway_message)
|
512
|
+
when 'audit_system'
|
513
|
+
forward_to_audit_system(gateway_message)
|
514
|
+
end
|
515
|
+
end
|
516
|
+
|
517
|
+
private
|
518
|
+
|
519
|
+
def self.forward_to_email_system(gateway_message)
|
520
|
+
# Create a new message instance with different transport
|
521
|
+
email_message = EmailSystemMessage.new(
|
522
|
+
original_payload: gateway_message.payload,
|
523
|
+
source: gateway_message.source_system
|
524
|
+
)
|
525
|
+
|
526
|
+
# Override transport for this instance
|
527
|
+
email_message.config do
|
528
|
+
transport SmartMessage::Transport.create(:stdout, output: "email_system.log")
|
529
|
+
end
|
530
|
+
|
531
|
+
email_message.publish
|
532
|
+
end
|
533
|
+
|
534
|
+
def self.forward_to_sms_system(gateway_message)
|
535
|
+
sms_message = SMSSystemMessage.new(
|
536
|
+
original_payload: gateway_message.payload,
|
537
|
+
source: gateway_message.source_system
|
538
|
+
)
|
539
|
+
|
540
|
+
sms_message.config do
|
541
|
+
transport SmartMessage::Transport.create(:stdout, output: "sms_system.log")
|
542
|
+
end
|
543
|
+
|
544
|
+
sms_message.publish
|
545
|
+
end
|
546
|
+
|
547
|
+
def self.forward_to_audit_system(gateway_message)
|
548
|
+
audit_message = AuditSystemMessage.new(
|
549
|
+
event_type: gateway_message.message_type,
|
550
|
+
data: gateway_message.payload,
|
551
|
+
source_system: gateway_message.source_system,
|
552
|
+
processed_at: Time.now.iso8601
|
553
|
+
)
|
554
|
+
|
555
|
+
audit_message.config do
|
556
|
+
transport SmartMessage::Transport.create(:stdout, output: "audit_system.log")
|
557
|
+
end
|
558
|
+
|
559
|
+
audit_message.publish
|
560
|
+
end
|
561
|
+
end
|
562
|
+
|
563
|
+
# Destination system message classes
|
564
|
+
class EmailSystemMessage < SmartMessage::Base
|
565
|
+
property :original_payload
|
566
|
+
property :source
|
567
|
+
|
568
|
+
def self.process(message_header, message_payload)
|
569
|
+
puts "📧 Email system processed message from #{JSON.parse(message_payload)['source']}"
|
570
|
+
end
|
571
|
+
end
|
572
|
+
|
573
|
+
class SMSSystemMessage < SmartMessage::Base
|
574
|
+
property :original_payload
|
575
|
+
property :source
|
576
|
+
|
577
|
+
def self.process(message_header, message_payload)
|
578
|
+
puts "📱 SMS system processed message from #{JSON.parse(message_payload)['source']}"
|
579
|
+
end
|
580
|
+
end
|
581
|
+
|
582
|
+
class AuditSystemMessage < SmartMessage::Base
|
583
|
+
property :event_type
|
584
|
+
property :data
|
585
|
+
property :source_system
|
586
|
+
property :processed_at
|
587
|
+
|
588
|
+
def self.process(message_header, message_payload)
|
589
|
+
puts "📋 Audit system logged event from #{JSON.parse(message_payload)['source_system']}"
|
590
|
+
end
|
591
|
+
end
|
592
|
+
|
593
|
+
# Setup
|
594
|
+
[MessageGateway, EmailSystemMessage, SMSSystemMessage, AuditSystemMessage].each(&:subscribe)
|
595
|
+
|
596
|
+
# Route messages through gateway
|
597
|
+
MessageGateway.new(
|
598
|
+
source_system: "web_app",
|
599
|
+
destination_system: "email_system",
|
600
|
+
message_type: "notification",
|
601
|
+
payload: { recipient: "user@example.com", subject: "Hello!" }
|
602
|
+
).publish
|
603
|
+
|
604
|
+
MessageGateway.new(
|
605
|
+
source_system: "mobile_app",
|
606
|
+
destination_system: "audit_system",
|
607
|
+
message_type: "user_action",
|
608
|
+
payload: { action: "login", user_id: 123 }
|
609
|
+
).publish
|
610
|
+
```
|
611
|
+
|
612
|
+
## Error Handling and Retry Patterns
|
613
|
+
|
614
|
+
```ruby
|
615
|
+
# Resilient message processing with retries
|
616
|
+
class ResilientMessage < SmartMessage::Base
|
617
|
+
property :data
|
618
|
+
property :retry_count, default: 0
|
619
|
+
property :max_retries, default: 3
|
620
|
+
property :original_error
|
621
|
+
|
622
|
+
config do
|
623
|
+
transport SmartMessage::Transport.create(:memory, auto_process: true)
|
624
|
+
serializer SmartMessage::Serializer::JSON.new
|
625
|
+
end
|
626
|
+
|
627
|
+
def self.process(message_header, message_payload)
|
628
|
+
data = JSON.parse(message_payload)
|
629
|
+
message = new(data)
|
630
|
+
|
631
|
+
begin
|
632
|
+
# Simulate potentially failing operation
|
633
|
+
if should_fail?(message)
|
634
|
+
raise StandardError, "Simulated failure"
|
635
|
+
end
|
636
|
+
|
637
|
+
puts "✅ Successfully processed message: #{message.data}"
|
638
|
+
|
639
|
+
rescue => e
|
640
|
+
handle_error(message, e)
|
641
|
+
end
|
642
|
+
end
|
643
|
+
|
644
|
+
private
|
645
|
+
|
646
|
+
def self.should_fail?(message)
|
647
|
+
# Simulate 30% failure rate
|
648
|
+
rand < 0.3
|
649
|
+
end
|
650
|
+
|
651
|
+
def self.handle_error(message, error)
|
652
|
+
puts "❌ Error processing message: #{error.message}"
|
653
|
+
|
654
|
+
if message.retry_count < message.max_retries
|
655
|
+
# Retry with exponential backoff
|
656
|
+
delay = 2 ** message.retry_count
|
657
|
+
puts "🔄 Retrying in #{delay} seconds (attempt #{message.retry_count + 1})"
|
658
|
+
|
659
|
+
# In a real system, you'd use a delayed job or similar
|
660
|
+
Thread.new do
|
661
|
+
sleep(delay)
|
662
|
+
|
663
|
+
retry_message = new(
|
664
|
+
data: message.data,
|
665
|
+
retry_count: message.retry_count + 1,
|
666
|
+
max_retries: message.max_retries,
|
667
|
+
original_error: error.message
|
668
|
+
)
|
669
|
+
|
670
|
+
retry_message.publish
|
671
|
+
end
|
672
|
+
else
|
673
|
+
# Max retries exceeded, send to dead letter queue
|
674
|
+
DeadLetterMessage.new(
|
675
|
+
original_message: message.to_h,
|
676
|
+
final_error: error.message,
|
677
|
+
retry_attempts: message.retry_count,
|
678
|
+
failed_at: Time.now.iso8601
|
679
|
+
).publish
|
680
|
+
end
|
681
|
+
end
|
682
|
+
end
|
683
|
+
|
684
|
+
class DeadLetterMessage < SmartMessage::Base
|
685
|
+
property :original_message
|
686
|
+
property :final_error
|
687
|
+
property :retry_attempts
|
688
|
+
property :failed_at
|
689
|
+
|
690
|
+
config do
|
691
|
+
transport SmartMessage::Transport.create(:stdout, output: "dead_letter_queue.log")
|
692
|
+
serializer SmartMessage::Serializer::JSON.new
|
693
|
+
end
|
694
|
+
|
695
|
+
def self.process(message_header, message_payload)
|
696
|
+
data = JSON.parse(message_payload)
|
697
|
+
dead_letter = new(data)
|
698
|
+
|
699
|
+
puts "💀 Message sent to dead letter queue:"
|
700
|
+
puts " Original: #{dead_letter.original_message}"
|
701
|
+
puts " Error: #{dead_letter.final_error}"
|
702
|
+
puts " Attempts: #{dead_letter.retry_attempts}"
|
703
|
+
puts " Failed at: #{dead_letter.failed_at}"
|
704
|
+
|
705
|
+
# Could trigger alerts, save to database, etc.
|
706
|
+
end
|
707
|
+
end
|
708
|
+
|
709
|
+
# Setup
|
710
|
+
ResilientMessage.subscribe
|
711
|
+
DeadLetterMessage.subscribe
|
712
|
+
|
713
|
+
# Send messages that might fail
|
714
|
+
5.times do |i|
|
715
|
+
ResilientMessage.new(
|
716
|
+
data: "Test message #{i + 1}"
|
717
|
+
).publish
|
718
|
+
|
719
|
+
sleep(0.1) # Small delay between messages
|
720
|
+
end
|
721
|
+
```
|
722
|
+
|
723
|
+
## Testing Helpers
|
724
|
+
|
725
|
+
```ruby
|
726
|
+
# Test utilities for SmartMessage
|
727
|
+
module SmartMessageTestHelpers
|
728
|
+
def self.with_test_transport
|
729
|
+
original_transports = {}
|
730
|
+
|
731
|
+
# Store original transports
|
732
|
+
SmartMessage::Base.descendants.each do |klass|
|
733
|
+
original_transports[klass] = klass.transport
|
734
|
+
end
|
735
|
+
|
736
|
+
# Set up test transport
|
737
|
+
test_transport = SmartMessage::Transport.create(:memory, auto_process: true)
|
738
|
+
|
739
|
+
SmartMessage::Base.descendants.each do |klass|
|
740
|
+
klass.config do
|
741
|
+
transport test_transport
|
742
|
+
end
|
743
|
+
end
|
744
|
+
|
745
|
+
yield test_transport
|
746
|
+
|
747
|
+
ensure
|
748
|
+
# Restore original transports
|
749
|
+
original_transports.each do |klass, transport|
|
750
|
+
klass.config do
|
751
|
+
transport transport
|
752
|
+
end
|
753
|
+
end
|
754
|
+
end
|
755
|
+
|
756
|
+
def self.clear_statistics
|
757
|
+
SS.reset
|
758
|
+
end
|
759
|
+
|
760
|
+
def self.wait_for_processing(timeout: 1.0)
|
761
|
+
start_time = Time.now
|
762
|
+
|
763
|
+
while Time.now - start_time < timeout
|
764
|
+
# Check if any messages are still being processed
|
765
|
+
# This is a simplified check
|
766
|
+
sleep(0.01)
|
767
|
+
end
|
768
|
+
end
|
769
|
+
end
|
770
|
+
|
771
|
+
# Example test usage
|
772
|
+
def test_message_processing
|
773
|
+
SmartMessageTestHelpers.with_test_transport do |transport|
|
774
|
+
# Clear any existing messages
|
775
|
+
transport.clear_messages
|
776
|
+
SmartMessageTestHelpers.clear_statistics
|
777
|
+
|
778
|
+
# Set up subscriptions
|
779
|
+
TestMessage.subscribe
|
780
|
+
|
781
|
+
# Send test message
|
782
|
+
TestMessage.new(data: "test").publish
|
783
|
+
|
784
|
+
# Wait for processing
|
785
|
+
SmartMessageTestHelpers.wait_for_processing
|
786
|
+
|
787
|
+
# Check results
|
788
|
+
puts "Messages in transport: #{transport.message_count}"
|
789
|
+
puts "Published count: #{SS.get('TestMessage', 'publish')}"
|
790
|
+
puts "Processed count: #{SS.get('TestMessage', 'TestMessage.process', 'routed')}"
|
791
|
+
end
|
792
|
+
end
|
793
|
+
|
794
|
+
class TestMessage < SmartMessage::Base
|
795
|
+
property :data
|
796
|
+
|
797
|
+
def self.process(message_header, message_payload)
|
798
|
+
data = JSON.parse(message_payload)
|
799
|
+
message = new(data)
|
800
|
+
puts "Processed test message: #{message.data}"
|
801
|
+
end
|
802
|
+
end
|
803
|
+
|
804
|
+
# Run the test
|
805
|
+
test_message_processing
|
806
|
+
```
|
807
|
+
|
808
|
+
These examples demonstrate the flexibility and power of SmartMessage for building robust, scalable messaging systems. Each pattern can be adapted to your specific needs and combined with other patterns for more complex workflows.
|