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,501 @@
1
+ # Transport Layer
2
+
3
+ The transport layer is responsible for moving messages between systems. SmartMessage provides a pluggable transport architecture that supports various backend systems.
4
+
5
+ ## Overview
6
+
7
+ Transports handle:
8
+ - **Publishing**: Sending messages to a destination
9
+ - **Subscribing**: Registering interest in message types
10
+ - **Routing**: Directing incoming messages to the dispatcher
11
+ - **Connection Management**: Handling connections to external systems
12
+
13
+ ## Built-in Transports
14
+
15
+ ### STDOUT Transport
16
+
17
+ Perfect for development, debugging, and logging scenarios.
18
+
19
+ **Features:**
20
+ - Outputs messages to console or file
21
+ - Optional loopback for testing subscriptions
22
+ - Human-readable message formatting
23
+ - No external dependencies
24
+
25
+ **Usage:**
26
+
27
+ ```ruby
28
+ # Basic STDOUT output
29
+ transport = SmartMessage::Transport.create(:stdout)
30
+
31
+ # With loopback enabled (messages get processed locally)
32
+ transport = SmartMessage::Transport.create(:stdout, loopback: true)
33
+
34
+ # Output to file instead of console
35
+ transport = SmartMessage::Transport.create(:stdout, output: "messages.log")
36
+
37
+ # Configure in message class
38
+ class LogMessage < SmartMessage::Base
39
+ property :level
40
+ property :message
41
+
42
+ config do
43
+ transport SmartMessage::Transport.create(:stdout,
44
+ output: "app.log",
45
+ loopback: false
46
+ )
47
+ end
48
+ end
49
+ ```
50
+
51
+ **Options:**
52
+ - `loopback` (Boolean): Whether to process published messages locally (default: false)
53
+ - `output` (String|IO): Output destination - filename string or IO object (default: $stdout)
54
+
55
+ **Example Output:**
56
+ ```
57
+ ===================================================
58
+ == SmartMessage Published via STDOUT Transport
59
+ == Header: #<SmartMessage::Header:0x... @uuid="abc-123", @message_class="MyMessage", ...>
60
+ == Payload: {"user_id":123,"action":"login","timestamp":"2025-08-17T10:30:00Z"}
61
+ ===================================================
62
+ ```
63
+
64
+ ### Memory Transport
65
+
66
+ Ideal for testing and in-memory message queuing.
67
+
68
+ **Features:**
69
+ - Stores messages in memory
70
+ - Thread-safe operations
71
+ - Optional auto-processing
72
+ - Message inspection capabilities
73
+ - Memory overflow protection
74
+
75
+ **Usage:**
76
+
77
+ ```ruby
78
+ # Auto-process messages as they're published
79
+ transport = SmartMessage::Transport.create(:memory, auto_process: true)
80
+
81
+ # Store messages without processing (manual control)
82
+ transport = SmartMessage::Transport.create(:memory, auto_process: false)
83
+
84
+ # Configure maximum message storage
85
+ transport = SmartMessage::Transport.create(:memory,
86
+ auto_process: false,
87
+ max_messages: 500
88
+ )
89
+
90
+ # Use in message class
91
+ class TestMessage < SmartMessage::Base
92
+ property :data
93
+
94
+ config do
95
+ transport SmartMessage::Transport.create(:memory, auto_process: true)
96
+ end
97
+ end
98
+ ```
99
+
100
+ **Options:**
101
+ - `auto_process` (Boolean): Automatically route messages to dispatcher (default: true)
102
+ - `max_messages` (Integer): Maximum messages to store in memory (default: 1000)
103
+
104
+ **Message Management:**
105
+
106
+ ```ruby
107
+ transport = SmartMessage::Transport.create(:memory)
108
+
109
+ # Check stored messages
110
+ puts transport.message_count
111
+ puts transport.all_messages
112
+
113
+ # Process all pending messages manually
114
+ transport.process_all
115
+
116
+ # Clear all stored messages
117
+ transport.clear_messages
118
+
119
+ # Access individual messages
120
+ messages = transport.all_messages
121
+ messages.each do |msg|
122
+ puts "Published at: #{msg[:published_at]}"
123
+ puts "Header: #{msg[:header]}"
124
+ puts "Payload: #{msg[:payload]}"
125
+ end
126
+ ```
127
+
128
+ ## Transport Interface
129
+
130
+ All transports must implement the `SmartMessage::Transport::Base` interface:
131
+
132
+ ### Required Methods
133
+
134
+ ```ruby
135
+ class CustomTransport < SmartMessage::Transport::Base
136
+ # Publish a message
137
+ def publish(message_header, message_payload)
138
+ # Send the message via your transport mechanism
139
+ end
140
+
141
+ # Optional: Override default options
142
+ def default_options
143
+ {
144
+ connection_timeout: 30,
145
+ retry_attempts: 3
146
+ }
147
+ end
148
+
149
+ # Optional: Custom configuration setup
150
+ def configure
151
+ @connection = establish_connection(@options)
152
+ end
153
+
154
+ # Optional: Connection status checking
155
+ def connected?
156
+ @connection&.connected?
157
+ end
158
+
159
+ # Optional: Cleanup resources
160
+ def disconnect
161
+ @connection&.close
162
+ end
163
+ end
164
+ ```
165
+
166
+ ### Inherited Methods
167
+
168
+ Transports automatically inherit these methods from `SmartMessage::Transport::Base`:
169
+
170
+ ```ruby
171
+ # Subscription management (uses dispatcher)
172
+ transport.subscribe(message_class, process_method)
173
+ transport.unsubscribe(message_class, process_method)
174
+ transport.unsubscribe!(message_class)
175
+ transport.subscribers
176
+
177
+ # Connection management
178
+ transport.connect
179
+ transport.disconnect
180
+ transport.connected?
181
+
182
+ # Message receiving (call this from your transport)
183
+ transport.receive(message_header, message_payload) # protected method
184
+ ```
185
+
186
+ ## Transport Registration
187
+
188
+ Register custom transports for easy creation:
189
+
190
+ ```ruby
191
+ # Register a transport class
192
+ SmartMessage::Transport.register(:redis, RedisTransport)
193
+ SmartMessage::Transport.register(:kafka, KafkaTransport)
194
+
195
+ # List all registered transports
196
+ puts SmartMessage::Transport.available
197
+ # => [:stdout, :memory, :redis, :kafka]
198
+
199
+ # Create instances
200
+ redis_transport = SmartMessage::Transport.create(:redis,
201
+ url: "redis://localhost:6379"
202
+ )
203
+
204
+ kafka_transport = SmartMessage::Transport.create(:kafka,
205
+ servers: ["localhost:9092"]
206
+ )
207
+ ```
208
+
209
+ ## Configuration Patterns
210
+
211
+ ### Class-Level Configuration
212
+
213
+ ```ruby
214
+ class OrderMessage < SmartMessage::Base
215
+ property :order_id
216
+ property :amount
217
+
218
+ # All instances use this transport by default
219
+ config do
220
+ transport SmartMessage::Transport.create(:memory, auto_process: true)
221
+ serializer SmartMessage::Serializer::JSON.new
222
+ end
223
+ end
224
+ ```
225
+
226
+ ### Instance-Level Override
227
+
228
+ ```ruby
229
+ # Override transport for specific instances
230
+ order = OrderMessage.new(order_id: "123", amount: 99.99)
231
+
232
+ order.config do
233
+ # This instance will use STDOUT instead of memory
234
+ transport SmartMessage::Transport.create(:stdout, loopback: true)
235
+ end
236
+
237
+ order.publish # Uses STDOUT transport
238
+ ```
239
+
240
+ ### Runtime Transport Switching
241
+
242
+ ```ruby
243
+ class NotificationMessage < SmartMessage::Base
244
+ property :recipient
245
+ property :message
246
+
247
+ def self.send_via_email
248
+ config do
249
+ transport EmailTransport.new
250
+ end
251
+ end
252
+
253
+ def self.send_via_sms
254
+ config do
255
+ transport SMSTransport.new
256
+ end
257
+ end
258
+ end
259
+
260
+ # Switch transport at runtime
261
+ NotificationMessage.send_via_email
262
+ notification = NotificationMessage.new(
263
+ recipient: "user@example.com",
264
+ message: "Hello!"
265
+ )
266
+ notification.publish # Sent via email
267
+ ```
268
+
269
+ ## Transport Options
270
+
271
+ ### Common Options Pattern
272
+
273
+ Most transports support these common option patterns:
274
+
275
+ ```ruby
276
+ transport = SmartMessage::Transport.create(:custom,
277
+ # Connection options
278
+ host: "localhost",
279
+ port: 5672,
280
+ username: "guest",
281
+ password: "guest",
282
+
283
+ # Retry options
284
+ retry_attempts: 3,
285
+ retry_delay: 1.0,
286
+
287
+ # Timeout options
288
+ connection_timeout: 30,
289
+ read_timeout: 10,
290
+
291
+ # Behavior options
292
+ auto_reconnect: true,
293
+ persistent: true
294
+ )
295
+ ```
296
+
297
+ ### Transport-Specific Options
298
+
299
+ Each transport may have specific options:
300
+
301
+ ```ruby
302
+ # STDOUT specific
303
+ SmartMessage::Transport.create(:stdout,
304
+ loopback: true,
305
+ output: "/var/log/messages.log"
306
+ )
307
+
308
+ # Memory specific
309
+ SmartMessage::Transport.create(:memory,
310
+ auto_process: false,
311
+ max_messages: 1000
312
+ )
313
+
314
+ # Hypothetical Redis specific
315
+ SmartMessage::Transport.create(:redis,
316
+ url: "redis://localhost:6379",
317
+ db: 1,
318
+ namespace: "messages",
319
+ ttl: 3600
320
+ )
321
+ ```
322
+
323
+ ## Error Handling
324
+
325
+ ### Transport Errors
326
+
327
+ Transports should handle their own connection and transmission errors:
328
+
329
+ ```ruby
330
+ class RobustTransport < SmartMessage::Transport::Base
331
+ def publish(message_header, message_payload)
332
+ retry_count = 0
333
+ begin
334
+ send_message(message_header, message_payload)
335
+ rescue ConnectionError => e
336
+ retry_count += 1
337
+ if retry_count <= @options[:retry_attempts]
338
+ sleep(@options[:retry_delay])
339
+ retry
340
+ else
341
+ # Log error and potentially fallback
342
+ handle_publish_error(e, message_header, message_payload)
343
+ end
344
+ end
345
+ end
346
+
347
+ private
348
+
349
+ def handle_publish_error(error, header, payload)
350
+ # Log the error
351
+ puts "Failed to publish message: #{error.message}"
352
+
353
+ # Optional: Store for later retry
354
+ store_failed_message(header, payload)
355
+
356
+ # Optional: Use fallback transport
357
+ fallback_transport&.publish(header, payload)
358
+ end
359
+ end
360
+ ```
361
+
362
+ ### Connection Monitoring
363
+
364
+ ```ruby
365
+ class MonitoredTransport < SmartMessage::Transport::Base
366
+ def connected?
367
+ @connection&.ping rescue false
368
+ end
369
+
370
+ def publish(message_header, message_payload)
371
+ unless connected?
372
+ connect
373
+ end
374
+
375
+ super
376
+ end
377
+
378
+ def connect
379
+ @connection = establish_connection(@options)
380
+ puts "Connected to #{@options[:host]}:#{@options[:port]}"
381
+ rescue => e
382
+ puts "Failed to connect: #{e.message}"
383
+ raise
384
+ end
385
+ end
386
+ ```
387
+
388
+ ## Performance Considerations
389
+
390
+ ### Message Batching
391
+
392
+ For high-throughput scenarios, consider batching:
393
+
394
+ ```ruby
395
+ class BatchingTransport < SmartMessage::Transport::Base
396
+ def initialize(options = {})
397
+ super
398
+ @batch = []
399
+ @batch_mutex = Mutex.new
400
+ setup_batch_timer
401
+ end
402
+
403
+ def publish(message_header, message_payload)
404
+ @batch_mutex.synchronize do
405
+ @batch << [message_header, message_payload]
406
+
407
+ if @batch.size >= @options[:batch_size]
408
+ flush_batch
409
+ end
410
+ end
411
+ end
412
+
413
+ private
414
+
415
+ def flush_batch
416
+ return if @batch.empty?
417
+
418
+ batch_to_send = @batch.dup
419
+ @batch.clear
420
+
421
+ send_batch(batch_to_send)
422
+ end
423
+ end
424
+ ```
425
+
426
+ ### Connection Pooling
427
+
428
+ For database or network transports:
429
+
430
+ ```ruby
431
+ class PooledTransport < SmartMessage::Transport::Base
432
+ def initialize(options = {})
433
+ super
434
+ @connection_pool = ConnectionPool.new(
435
+ size: @options[:pool_size] || 5,
436
+ timeout: @options[:pool_timeout] || 5
437
+ ) { create_connection }
438
+ end
439
+
440
+ def publish(message_header, message_payload)
441
+ @connection_pool.with do |connection|
442
+ connection.send(message_header, message_payload)
443
+ end
444
+ end
445
+ end
446
+ ```
447
+
448
+ ## Testing Transports
449
+
450
+ ### Mock Transport for Testing
451
+
452
+ ```ruby
453
+ class MockTransport < SmartMessage::Transport::Base
454
+ attr_reader :published_messages
455
+
456
+ def initialize(options = {})
457
+ super
458
+ @published_messages = []
459
+ end
460
+
461
+ def publish(message_header, message_payload)
462
+ @published_messages << {
463
+ header: message_header,
464
+ payload: message_payload,
465
+ published_at: Time.now
466
+ }
467
+
468
+ # Optionally trigger processing
469
+ receive(message_header, message_payload) if @options[:auto_process]
470
+ end
471
+
472
+ def clear
473
+ @published_messages.clear
474
+ end
475
+ end
476
+
477
+ # Use in tests
478
+ RSpec.describe "Message Publishing" do
479
+ let(:transport) { MockTransport.new(auto_process: true) }
480
+
481
+ before do
482
+ MyMessage.config do
483
+ transport transport
484
+ end
485
+ end
486
+
487
+ it "publishes messages" do
488
+ MyMessage.new(data: "test").publish
489
+
490
+ expect(transport.published_messages).to have(1).message
491
+ expect(transport.published_messages.first[:payload]).to include("test")
492
+ end
493
+ end
494
+ ```
495
+
496
+ ## Next Steps
497
+
498
+ - [Custom Transports](custom-transports.md) - Build your own transport
499
+ - [Serializers](serializers.md) - Understanding message serialization
500
+ - [Dispatcher](dispatcher.md) - Message routing and processing
501
+ - [Examples](examples.md) - Real-world transport usage patterns