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.
Files changed (53) hide show
  1. checksums.yaml +7 -0
  2. data/.envrc +3 -0
  3. data/.gitignore +8 -0
  4. data/.travis.yml +7 -0
  5. data/CHANGELOG.md +100 -0
  6. data/COMMITS.md +196 -0
  7. data/Gemfile +6 -0
  8. data/Gemfile.lock +71 -0
  9. data/README.md +303 -0
  10. data/Rakefile +10 -0
  11. data/bin/console +14 -0
  12. data/bin/setup +8 -0
  13. data/docs/README.md +52 -0
  14. data/docs/architecture.md +370 -0
  15. data/docs/dispatcher.md +593 -0
  16. data/docs/examples.md +808 -0
  17. data/docs/getting-started.md +235 -0
  18. data/docs/ideas_to_think_about.md +329 -0
  19. data/docs/serializers.md +575 -0
  20. data/docs/transports.md +501 -0
  21. data/docs/troubleshooting.md +582 -0
  22. data/examples/01_point_to_point_orders.rb +200 -0
  23. data/examples/02_publish_subscribe_events.rb +364 -0
  24. data/examples/03_many_to_many_chat.rb +608 -0
  25. data/examples/README.md +335 -0
  26. data/examples/tmux_chat/README.md +283 -0
  27. data/examples/tmux_chat/bot_agent.rb +272 -0
  28. data/examples/tmux_chat/human_agent.rb +197 -0
  29. data/examples/tmux_chat/room_monitor.rb +158 -0
  30. data/examples/tmux_chat/shared_chat_system.rb +295 -0
  31. data/examples/tmux_chat/start_chat_demo.sh +190 -0
  32. data/examples/tmux_chat/stop_chat_demo.sh +22 -0
  33. data/lib/simple_stats.rb +57 -0
  34. data/lib/smart_message/base.rb +284 -0
  35. data/lib/smart_message/dispatcher/.keep +0 -0
  36. data/lib/smart_message/dispatcher.rb +146 -0
  37. data/lib/smart_message/errors.rb +29 -0
  38. data/lib/smart_message/header.rb +20 -0
  39. data/lib/smart_message/logger/base.rb +8 -0
  40. data/lib/smart_message/logger.rb +7 -0
  41. data/lib/smart_message/serializer/base.rb +23 -0
  42. data/lib/smart_message/serializer/json.rb +22 -0
  43. data/lib/smart_message/serializer.rb +10 -0
  44. data/lib/smart_message/transport/base.rb +85 -0
  45. data/lib/smart_message/transport/memory_transport.rb +69 -0
  46. data/lib/smart_message/transport/registry.rb +59 -0
  47. data/lib/smart_message/transport/stdout_transport.rb +62 -0
  48. data/lib/smart_message/transport.rb +41 -0
  49. data/lib/smart_message/version.rb +7 -0
  50. data/lib/smart_message/wrapper.rb +43 -0
  51. data/lib/smart_message.rb +54 -0
  52. data/smart_message.gemspec +53 -0
  53. metadata +252 -0
@@ -0,0 +1,200 @@
1
+ #!/usr/bin/env ruby
2
+ # examples/01_point_to_point_orders.rb
3
+ #
4
+ # 1-to-1 Messaging Example: Order Processing System
5
+ #
6
+ # This example demonstrates point-to-point messaging between an OrderService
7
+ # and a PaymentService. Each order gets processed by exactly one payment processor.
8
+
9
+ require_relative '../lib/smart_message'
10
+
11
+ puts "=== SmartMessage Example: Point-to-Point Order Processing ==="
12
+ puts
13
+
14
+ # Define the Order Message
15
+ class OrderMessage < SmartMessage::Base
16
+ property :order_id
17
+ property :customer_id
18
+ property :amount
19
+ property :currency, default: 'USD'
20
+ property :payment_method
21
+ property :items
22
+
23
+ # Configure to use memory transport for this example
24
+ config do
25
+ transport SmartMessage::Transport::StdoutTransport.new(loopback: true)
26
+ serializer SmartMessage::Serializer::JSON.new
27
+ end
28
+
29
+ # Default processing - just logs the order
30
+ def self.process(message_header, message_payload)
31
+ order_data = JSON.parse(message_payload)
32
+ puts "šŸ“‹ Order received: #{order_data['order_id']} for $#{order_data['amount']}"
33
+ end
34
+ end
35
+
36
+ # Define the Payment Response Message
37
+ class PaymentResponseMessage < SmartMessage::Base
38
+ property :order_id
39
+ property :payment_id
40
+ property :status # 'success', 'failed', 'pending'
41
+ property :message
42
+ property :processed_at
43
+
44
+ config do
45
+ transport SmartMessage::Transport::StdoutTransport.new(loopback: true)
46
+ serializer SmartMessage::Serializer::JSON.new
47
+ end
48
+
49
+ def self.process(message_header, message_payload)
50
+ response_data = JSON.parse(message_payload)
51
+ status_emoji = case response_data['status']
52
+ when 'success' then 'āœ…'
53
+ when 'failed' then 'āŒ'
54
+ when 'pending' then 'ā³'
55
+ else 'ā“'
56
+ end
57
+
58
+ puts "#{status_emoji} Payment #{response_data['status']}: Order #{response_data['order_id']} - #{response_data['message']}"
59
+ end
60
+ end
61
+
62
+ # Order Service - Creates and sends orders
63
+ class OrderService
64
+ def initialize
65
+ puts "šŸŖ OrderService: Starting up..."
66
+ @order_counter = 1000
67
+ end
68
+
69
+ def create_order(customer_id:, amount:, payment_method:, items:)
70
+ order_id = "ORD-#{@order_counter += 1}"
71
+
72
+ puts "\nšŸŖ OrderService: Creating order #{order_id}"
73
+
74
+ order = OrderMessage.new(
75
+ order_id: order_id,
76
+ customer_id: customer_id,
77
+ amount: amount,
78
+ payment_method: payment_method,
79
+ items: items
80
+ )
81
+
82
+ puts "šŸŖ OrderService: Sending order to payment processing..."
83
+ order.publish
84
+
85
+ order_id
86
+ end
87
+ end
88
+
89
+ # Payment Service - Processes orders and sends responses
90
+ class PaymentService
91
+ def initialize
92
+ puts "šŸ’³ PaymentService: Starting up..."
93
+ @payment_counter = 5000
94
+
95
+ # Subscribe to order messages with custom processor
96
+ OrderMessage.subscribe('PaymentService.process_order')
97
+ end
98
+
99
+ def self.process_order(message_header, message_payload)
100
+ processor = new
101
+ processor.handle_order(message_header, message_payload)
102
+ end
103
+
104
+ def handle_order(message_header, message_payload)
105
+ order_data = JSON.parse(message_payload)
106
+ payment_id = "PAY-#{@payment_counter += 1}"
107
+
108
+ puts "šŸ’³ PaymentService: Processing payment for order #{order_data['order_id']}"
109
+
110
+ # Simulate payment processing logic
111
+ success = simulate_payment_processing(order_data)
112
+
113
+ # Send response back
114
+ response = PaymentResponseMessage.new(
115
+ order_id: order_data['order_id'],
116
+ payment_id: payment_id,
117
+ status: success ? 'success' : 'failed',
118
+ message: success ? 'Payment processed successfully' : 'Insufficient funds',
119
+ processed_at: Time.now.iso8601
120
+ )
121
+
122
+ puts "šŸ’³ PaymentService: Sending payment response..."
123
+ response.publish
124
+ end
125
+
126
+ private
127
+
128
+ def simulate_payment_processing(order_data)
129
+ # Simulate processing time
130
+ sleep(0.1)
131
+
132
+ # Simulate success/failure based on amount (fail large orders for demo)
133
+ order_data['amount'] < 1000
134
+ end
135
+ end
136
+
137
+ # Demo Runner
138
+ class OrderProcessingDemo
139
+ def run
140
+ puts "šŸš€ Starting Order Processing Demo\n"
141
+
142
+ # Start services
143
+ order_service = OrderService.new
144
+ payment_service = PaymentService.new
145
+
146
+ # Subscribe to payment responses
147
+ PaymentResponseMessage.subscribe
148
+
149
+ puts "\n" + "="*60
150
+ puts "Processing Sample Orders"
151
+ puts "="*60
152
+
153
+ # Create some sample orders
154
+ orders = [
155
+ {
156
+ customer_id: "CUST-001",
157
+ amount: 99.99,
158
+ payment_method: "credit_card",
159
+ items: ["Widget A", "Widget B"]
160
+ },
161
+ {
162
+ customer_id: "CUST-002",
163
+ amount: 1299.99, # This will fail (too large)
164
+ payment_method: "debit_card",
165
+ items: ["Premium Widget", "Extended Warranty"]
166
+ },
167
+ {
168
+ customer_id: "CUST-003",
169
+ amount: 45.50,
170
+ payment_method: "paypal",
171
+ items: ["Small Widget"]
172
+ }
173
+ ]
174
+
175
+ orders.each_with_index do |order_params, index|
176
+ puts "\n--- Order #{index + 1} ---"
177
+ order_id = order_service.create_order(**order_params)
178
+
179
+ # Brief pause between orders for clarity
180
+ sleep(0.5)
181
+ end
182
+
183
+ # Give time for all async processing to complete
184
+ puts "\nā³ Waiting for all payments to process..."
185
+ sleep(2)
186
+
187
+ puts "\n✨ Demo completed!"
188
+ puts "\nThis example demonstrated:"
189
+ puts "• Point-to-point messaging between OrderService and PaymentService"
190
+ puts "• Bidirectional communication with request/response pattern"
191
+ puts "• JSON serialization of complex message data"
192
+ puts "• STDOUT transport with loopback for local demonstration"
193
+ end
194
+ end
195
+
196
+ # Run the demo if this file is executed directly
197
+ if __FILE__ == $0
198
+ demo = OrderProcessingDemo.new
199
+ demo.run
200
+ end
@@ -0,0 +1,364 @@
1
+ #!/usr/bin/env ruby
2
+ # examples/02_publish_subscribe_events.rb
3
+ #
4
+ # 1-to-Many Messaging Example: Event Notification System
5
+ #
6
+ # This example demonstrates publish-subscribe messaging where one event publisher
7
+ # sends notifications to multiple subscribers (email service, SMS service, audit logger).
8
+
9
+ require_relative '../lib/smart_message'
10
+
11
+ puts "=== SmartMessage Example: Publish-Subscribe Event Notifications ==="
12
+ puts
13
+
14
+ # Define the User Event Message
15
+ class UserEventMessage < SmartMessage::Base
16
+ property :event_id
17
+ property :event_type # 'user_registered', 'user_login', 'password_changed', etc.
18
+ property :user_id
19
+ property :user_email
20
+ property :user_name
21
+ property :timestamp
22
+ property :metadata # Additional event-specific data
23
+
24
+ config do
25
+ transport SmartMessage::Transport::StdoutTransport.new(loopback: true)
26
+ serializer SmartMessage::Serializer::JSON.new
27
+ end
28
+
29
+ # Default processor - just logs the event
30
+ def self.process(message_header, message_payload)
31
+ event_data = JSON.parse(message_payload)
32
+ puts "šŸ“” Event broadcasted: #{event_data['event_type']} for user #{event_data['user_id']}"
33
+ end
34
+ end
35
+
36
+ # Email Notification Service
37
+ class EmailService
38
+ def initialize
39
+ puts "šŸ“§ EmailService: Starting up..."
40
+ # Subscribe to user events with custom processor
41
+ UserEventMessage.subscribe('EmailService.handle_user_event')
42
+ end
43
+
44
+ def self.handle_user_event(message_header, message_payload)
45
+ service = new
46
+ service.process_event(message_header, message_payload)
47
+ end
48
+
49
+ def process_event(message_header, message_payload)
50
+ event_data = JSON.parse(message_payload)
51
+
52
+ case event_data['event_type']
53
+ when 'user_registered'
54
+ send_welcome_email(event_data)
55
+ when 'password_changed'
56
+ send_security_alert(event_data)
57
+ when 'user_login'
58
+ # Could send login notifications for suspicious activity
59
+ log_email_activity("Login notification skipped for #{event_data['user_email']}")
60
+ else
61
+ log_email_activity("No email action for event: #{event_data['event_type']}")
62
+ end
63
+ end
64
+
65
+ private
66
+
67
+ def send_welcome_email(event_data)
68
+ puts "šŸ“§ EmailService: Sending welcome email to #{event_data['user_email']}"
69
+ puts " Subject: Welcome to our platform, #{event_data['user_name']}!"
70
+ puts " Content: Thank you for registering..."
71
+ simulate_email_delivery
72
+ end
73
+
74
+ def send_security_alert(event_data)
75
+ puts "šŸ“§ EmailService: Sending security alert to #{event_data['user_email']}"
76
+ puts " Subject: Your password was changed"
77
+ puts " Content: If this wasn't you, please contact support..."
78
+ simulate_email_delivery
79
+ end
80
+
81
+ def log_email_activity(message)
82
+ puts "šŸ“§ EmailService: #{message}"
83
+ end
84
+
85
+ def simulate_email_delivery
86
+ # Simulate email sending delay
87
+ sleep(0.1)
88
+ puts " āœ‰ļø Email queued for delivery"
89
+ end
90
+ end
91
+
92
+ # SMS Notification Service
93
+ class SMSService
94
+ def initialize
95
+ puts "šŸ“± SMSService: Starting up..."
96
+ UserEventMessage.subscribe('SMSService.handle_user_event')
97
+ end
98
+
99
+ def self.handle_user_event(message_header, message_payload)
100
+ service = new
101
+ service.process_event(message_header, message_payload)
102
+ end
103
+
104
+ def process_event(message_header, message_payload)
105
+ event_data = JSON.parse(message_payload)
106
+
107
+ case event_data['event_type']
108
+ when 'password_changed'
109
+ send_security_sms(event_data)
110
+ when 'user_login'
111
+ if suspicious_login?(event_data)
112
+ send_login_alert(event_data)
113
+ else
114
+ log_sms_activity("Normal login, no SMS sent for #{event_data['user_id']}")
115
+ end
116
+ else
117
+ log_sms_activity("No SMS action for event: #{event_data['event_type']}")
118
+ end
119
+ end
120
+
121
+ private
122
+
123
+ def send_security_sms(event_data)
124
+ phone = get_user_phone(event_data['user_id'])
125
+ puts "šŸ“± SMSService: Sending security SMS to #{phone}"
126
+ puts " Message: Your password was changed. Contact support if this wasn't you."
127
+ simulate_sms_delivery
128
+ end
129
+
130
+ def send_login_alert(event_data)
131
+ phone = get_user_phone(event_data['user_id'])
132
+ puts "šŸ“± SMSService: Sending login alert SMS to #{phone}"
133
+ puts " Message: Suspicious login detected from new location."
134
+ simulate_sms_delivery
135
+ end
136
+
137
+ def suspicious_login?(event_data)
138
+ # Simulate detection logic - mark logins from 'unknown' locations as suspicious
139
+ event_data.dig('metadata', 'location') == 'unknown'
140
+ end
141
+
142
+ def get_user_phone(user_id)
143
+ "+1-555-0#{user_id.split('-').last}"
144
+ end
145
+
146
+ def log_sms_activity(message)
147
+ puts "šŸ“± SMSService: #{message}"
148
+ end
149
+
150
+ def simulate_sms_delivery
151
+ sleep(0.05)
152
+ puts " šŸ’¬ SMS sent"
153
+ end
154
+ end
155
+
156
+ # Audit Logging Service
157
+ class AuditService
158
+ def initialize
159
+ puts "šŸ“Š AuditService: Starting up..."
160
+ @audit_log = []
161
+ UserEventMessage.subscribe('AuditService.handle_user_event')
162
+ end
163
+
164
+ def self.handle_user_event(message_header, message_payload)
165
+ # Use a singleton pattern for persistent audit log
166
+ @@instance ||= new
167
+ @@instance.process_event(message_header, message_payload)
168
+ end
169
+
170
+ def self.get_summary
171
+ @@instance&.get_audit_summary || {}
172
+ end
173
+
174
+ def process_event(message_header, message_payload)
175
+ event_data = JSON.parse(message_payload)
176
+
177
+ audit_entry = {
178
+ timestamp: Time.now.iso8601,
179
+ event_id: event_data['event_id'],
180
+ event_type: event_data['event_type'],
181
+ user_id: event_data['user_id'],
182
+ processed_at: message_header.published_at
183
+ }
184
+
185
+ @audit_log << audit_entry
186
+ puts "šŸ“Š AuditService: Logged event #{event_data['event_id']} (#{event_data['event_type']})"
187
+ puts " Total events logged: #{@audit_log.size}"
188
+ end
189
+
190
+ def get_audit_summary
191
+ @audit_log.group_by { |entry| entry[:event_type] }
192
+ .transform_values(&:count)
193
+ end
194
+ end
195
+
196
+ # User Management System (Event Publisher)
197
+ class UserManager
198
+ def initialize
199
+ puts "šŸ‘¤ UserManager: Starting up..."
200
+ @user_counter = 100
201
+ @event_counter = 1000
202
+ end
203
+
204
+ def register_user(name:, email:)
205
+ user_id = "USER-#{@user_counter += 1}"
206
+
207
+ puts "\nšŸ‘¤ UserManager: Registering new user #{name} (#{user_id})"
208
+
209
+ # Simulate user creation in database
210
+ create_user_record(user_id, name, email)
211
+
212
+ # Publish user registration event
213
+ publish_event(
214
+ event_type: 'user_registered',
215
+ user_id: user_id,
216
+ user_email: email,
217
+ user_name: name,
218
+ metadata: { source: 'web_registration' }
219
+ )
220
+
221
+ user_id
222
+ end
223
+
224
+ def user_login(user_id:, email:, location: 'known')
225
+ puts "\nšŸ‘¤ UserManager: User #{user_id} logging in from #{location}"
226
+
227
+ publish_event(
228
+ event_type: 'user_login',
229
+ user_id: user_id,
230
+ user_email: email,
231
+ user_name: get_user_name(user_id),
232
+ metadata: { location: location, ip: generate_fake_ip }
233
+ )
234
+ end
235
+
236
+ def change_password(user_id:, email:)
237
+ puts "\nšŸ‘¤ UserManager: User #{user_id} changed password"
238
+
239
+ publish_event(
240
+ event_type: 'password_changed',
241
+ user_id: user_id,
242
+ user_email: email,
243
+ user_name: get_user_name(user_id),
244
+ metadata: { method: 'self_service' }
245
+ )
246
+ end
247
+
248
+ private
249
+
250
+ def create_user_record(user_id, name, email)
251
+ # Simulate database insertion
252
+ sleep(0.05)
253
+ puts "šŸ‘¤ UserManager: User record created in database"
254
+ end
255
+
256
+ def publish_event(event_type:, user_id:, user_email:, user_name:, metadata: {})
257
+ event = UserEventMessage.new(
258
+ event_id: "EVT-#{@event_counter += 1}",
259
+ event_type: event_type,
260
+ user_id: user_id,
261
+ user_email: user_email,
262
+ user_name: user_name,
263
+ timestamp: Time.now.iso8601,
264
+ metadata: metadata
265
+ )
266
+
267
+ puts "šŸ‘¤ UserManager: Publishing #{event_type} event..."
268
+ event.publish
269
+ end
270
+
271
+ def get_user_name(user_id)
272
+ # Simulate database lookup
273
+ case user_id
274
+ when /101/ then "Alice Johnson"
275
+ when /102/ then "Bob Smith"
276
+ when /103/ then "Carol Williams"
277
+ else "Unknown User"
278
+ end
279
+ end
280
+
281
+ def generate_fake_ip
282
+ "192.168.#{rand(1..254)}.#{rand(1..254)}"
283
+ end
284
+ end
285
+
286
+ # Demo Runner
287
+ class EventNotificationDemo
288
+ def run
289
+ puts "šŸš€ Starting Event Notification Demo\n"
290
+
291
+ # Start all services (these become subscribers)
292
+ email_service = EmailService.new
293
+ sms_service = SMSService.new
294
+ audit_service = AuditService.new
295
+
296
+ # Start the publisher
297
+ user_manager = UserManager.new
298
+
299
+ puts "\n" + "="*70
300
+ puts "Simulating User Activities"
301
+ puts "="*70
302
+
303
+ # Simulate various user activities
304
+ puts "\n--- Scenario 1: New User Registration ---"
305
+ user_id_1 = user_manager.register_user(
306
+ name: "Alice Johnson",
307
+ email: "alice@example.com"
308
+ )
309
+ sleep(0.8) # Let all services process
310
+
311
+ puts "\n--- Scenario 2: Normal User Login ---"
312
+ user_manager.user_login(
313
+ user_id: user_id_1,
314
+ email: "alice@example.com",
315
+ location: "known"
316
+ )
317
+ sleep(0.8)
318
+
319
+ puts "\n--- Scenario 3: Another User Registration ---"
320
+ user_id_2 = user_manager.register_user(
321
+ name: "Bob Smith",
322
+ email: "bob@example.com"
323
+ )
324
+ sleep(0.8)
325
+
326
+ puts "\n--- Scenario 4: Suspicious Login ---"
327
+ user_manager.user_login(
328
+ user_id: user_id_2,
329
+ email: "bob@example.com",
330
+ location: "unknown"
331
+ )
332
+ sleep(0.8)
333
+
334
+ puts "\n--- Scenario 5: Password Change ---"
335
+ user_manager.change_password(
336
+ user_id: user_id_1,
337
+ email: "alice@example.com"
338
+ )
339
+ sleep(0.8)
340
+
341
+ # Show audit summary
342
+ puts "\n" + "="*70
343
+ puts "šŸ“Š Final Audit Summary"
344
+ puts "="*70
345
+ summary = AuditService.get_summary
346
+ summary.each do |event_type, count|
347
+ puts "#{event_type}: #{count} events"
348
+ end
349
+
350
+ puts "\n✨ Demo completed!"
351
+ puts "\nThis example demonstrated:"
352
+ puts "• One-to-many publish-subscribe messaging pattern"
353
+ puts "• Multiple services subscribing to the same event stream"
354
+ puts "• Different services handling events in their own specific ways"
355
+ puts "• Decoupled architecture where services can be added/removed independently"
356
+ puts "• Event-driven architecture with audit logging"
357
+ end
358
+ end
359
+
360
+ # Run the demo if this file is executed directly
361
+ if __FILE__ == $0
362
+ demo = EventNotificationDemo.new
363
+ demo.run
364
+ end