smart_message 0.0.16 โ†’ 0.0.17

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,535 @@
1
+ # File Transport
2
+
3
+ The **File Transport** is a base class for file-based message transports in SmartMessage. It provides the foundation for writing messages to files with automatic directory creation, message serialization, and thread-safe operations.
4
+
5
+ ## Overview
6
+
7
+ File Transport serves as the base class for:
8
+ - **STDOUT Transport** - Console and file output with formatting
9
+ - **Custom File Transports** - Application-specific file-based messaging
10
+ - **Log Transport Extensions** - Specialized logging implementations
11
+ - **Message Persistence** - File-based message storage and archiving
12
+
13
+ ## Key Features
14
+
15
+ - ๐Ÿ“ **Automatic Directory Creation** - Creates parent directories as needed
16
+ - ๐Ÿงต **Thread-Safe Operations** - Safe for concurrent message publishing
17
+ - ๐Ÿ”„ **Message Serialization** - Handles SmartMessage object encoding
18
+ - ๐Ÿ“ **File Append Operations** - Messages appended to existing files
19
+ - โš™๏ธ **Extensible Architecture** - Base class for specialized file transports
20
+ - ๐Ÿ›ก๏ธ **Error Handling** - Graceful handling of file system errors
21
+
22
+ ## Architecture
23
+
24
+ ```
25
+ Message โ†’ FileTransport โ†’ encode_message() โ†’ do_publish() โ†’ File System
26
+ (base class) (serialization) (file write) (thread-safe)
27
+ ```
28
+
29
+ File Transport provides the core infrastructure that derived classes like STDOUT Transport build upon.
30
+
31
+ ## Class Hierarchy
32
+
33
+ ```
34
+ SmartMessage::Transport::BaseTransport
35
+ โ””โ”€โ”€ SmartMessage::Transport::FileTransport
36
+ โ””โ”€โ”€ SmartMessage::Transport::StdoutTransport
37
+ ```
38
+
39
+ ## Configuration
40
+
41
+ ### Basic Setup
42
+
43
+ ```ruby
44
+ # Direct usage (rarely used directly)
45
+ transport = SmartMessage::Transport::FileTransport.new(
46
+ file_path: '/var/log/messages.log'
47
+ )
48
+
49
+ # With options
50
+ transport = SmartMessage::Transport::FileTransport.new(
51
+ file_path: '/var/log/app/events.log',
52
+ auto_create_dirs: true
53
+ )
54
+ ```
55
+
56
+ ### Inheritance Pattern
57
+
58
+ ```ruby
59
+ # Custom transport inheriting from FileTransport
60
+ class CustomFileTransport < SmartMessage::Transport::FileTransport
61
+ def initialize(file_path:, custom_option: nil, **options)
62
+ @custom_option = custom_option
63
+ super(file_path: file_path, **options)
64
+ end
65
+
66
+ private
67
+
68
+ def do_publish(message_class, serialized_message)
69
+ # Custom formatting before file write
70
+ formatted_content = format_for_custom_system(serialized_message)
71
+
72
+ # Use parent's file writing capability
73
+ super(message_class, formatted_content)
74
+ end
75
+
76
+ def format_for_custom_system(message)
77
+ # Custom formatting logic
78
+ "#{Time.now.iso8601}: #{message}\n"
79
+ end
80
+ end
81
+ ```
82
+
83
+ ## Configuration Options
84
+
85
+ | Option | Type | Default | Description |
86
+ |--------|------|---------|-------------|
87
+ | `file_path` | String | **Required** | Path to output file |
88
+ | `auto_create_dirs` | Boolean | `true` | Automatically create parent directories |
89
+
90
+ ## Core Methods
91
+
92
+ ### Public Interface
93
+
94
+ #### `#publish(message)`
95
+ Publishes a SmartMessage object to the configured file.
96
+
97
+ ```ruby
98
+ transport = SmartMessage::Transport::FileTransport.new(
99
+ file_path: '/var/log/messages.log'
100
+ )
101
+
102
+ message = MyMessage.new(data: "example")
103
+ transport.publish(message)
104
+ ```
105
+
106
+ #### `#file_path`
107
+ Returns the configured file path.
108
+
109
+ ```ruby
110
+ puts transport.file_path # => '/var/log/messages.log'
111
+ ```
112
+
113
+ #### `#connected?`
114
+ Always returns `true` for file system availability.
115
+
116
+ ```ruby
117
+ puts transport.connected? # => true
118
+ ```
119
+
120
+ ### Protected Interface (for Subclasses)
121
+
122
+ #### `#encode_message(message)`
123
+ Serializes a SmartMessage object using the configured serializer.
124
+
125
+ ```ruby
126
+ class MyFileTransport < SmartMessage::Transport::FileTransport
127
+ private
128
+
129
+ def do_publish(message_class, serialized_message)
130
+ # serialized_message comes from encode_message(message)
131
+ File.write(file_path, "#{serialized_message}\n", mode: 'a')
132
+ end
133
+ end
134
+ ```
135
+
136
+ #### `#do_publish(message_class, serialized_message)`
137
+ Template method for subclasses to implement file writing logic.
138
+
139
+ ```ruby
140
+ # Base implementation in FileTransport
141
+ def do_publish(message_class, serialized_message)
142
+ File.write(file_path, "#{serialized_message}\n", mode: 'a')
143
+ end
144
+ ```
145
+
146
+ ## Implementation Details
147
+
148
+ ### Message Processing Pipeline
149
+
150
+ 1. **Message Receipt**: `publish(message)` called with SmartMessage object
151
+ 2. **Class Extraction**: Extract message class name from `message._sm_header.message_class`
152
+ 3. **Serialization**: Convert message to string via `encode_message(message)`
153
+ 4. **File Writing**: Call `do_publish(message_class, serialized_message)`
154
+ 5. **Directory Creation**: Create parent directories if needed
155
+ 6. **Thread Safety**: File operations protected for concurrent access
156
+
157
+ ### Source Code Structure
158
+
159
+ ```ruby
160
+ class FileTransport < BaseTransport
161
+ def initialize(file_path:, auto_create_dirs: true, **options)
162
+ @file_path = file_path
163
+ @auto_create_dirs = auto_create_dirs
164
+ super(**options)
165
+ end
166
+
167
+ def publish(message)
168
+ # Extract message class and serialize the message
169
+ message_class = message._sm_header.message_class
170
+ serialized_message = encode_message(message)
171
+ do_publish(message_class, serialized_message)
172
+ end
173
+
174
+ private
175
+
176
+ def do_publish(message_class, serialized_message)
177
+ ensure_directory_exists
178
+ File.write(file_path, "#{serialized_message}\n", mode: 'a')
179
+ end
180
+
181
+ def ensure_directory_exists
182
+ return unless auto_create_dirs
183
+
184
+ dir = File.dirname(file_path)
185
+ FileUtils.mkdir_p(dir) unless Dir.exist?(dir)
186
+ end
187
+ end
188
+ ```
189
+
190
+ ## Usage Examples
191
+
192
+ ### Basic File Logging
193
+
194
+ ```ruby
195
+ class LogMessage < SmartMessage::Base
196
+ property :level, required: true
197
+ property :message, required: true
198
+ property :timestamp, default: -> { Time.now.iso8601 }
199
+
200
+ transport SmartMessage::Transport::FileTransport.new(
201
+ file_path: '/var/log/application.log'
202
+ )
203
+ end
204
+
205
+ LogMessage.new(
206
+ level: "INFO",
207
+ message: "Application started"
208
+ ).publish
209
+
210
+ # File contains JSON-serialized message
211
+ ```
212
+
213
+ ### Custom File Transport
214
+
215
+ ```ruby
216
+ class AuditFileTransport < SmartMessage::Transport::FileTransport
217
+ def initialize(file_path:, include_headers: true, **options)
218
+ @include_headers = include_headers
219
+ super(file_path: file_path, **options)
220
+ end
221
+
222
+ private
223
+
224
+ def do_publish(message_class, serialized_message)
225
+ ensure_directory_exists
226
+
227
+ content = if @include_headers
228
+ "#{Time.now.iso8601} [#{message_class}] #{serialized_message}\n"
229
+ else
230
+ "#{serialized_message}\n"
231
+ end
232
+
233
+ File.write(file_path, content, mode: 'a')
234
+ end
235
+ end
236
+
237
+ # Usage
238
+ class AuditMessage < SmartMessage::Base
239
+ property :action, required: true
240
+ property :user_id, required: true
241
+
242
+ transport AuditFileTransport.new(
243
+ file_path: '/var/log/audit.log',
244
+ include_headers: true
245
+ )
246
+ end
247
+
248
+ AuditMessage.new(action: "login", user_id: 123).publish
249
+ ```
250
+
251
+ ### Rotated Log Files
252
+
253
+ ```ruby
254
+ class RotatedFileTransport < SmartMessage::Transport::FileTransport
255
+ def initialize(base_path:, **options)
256
+ @base_path = base_path
257
+ super(file_path: current_log_file, **options)
258
+ end
259
+
260
+ private
261
+
262
+ def current_log_file
263
+ date_str = Time.now.strftime("%Y-%m-%d")
264
+ "#{@base_path}/#{date_str}.log"
265
+ end
266
+
267
+ def do_publish(message_class, serialized_message)
268
+ # Update file path for current date
269
+ @file_path = current_log_file
270
+ super(message_class, serialized_message)
271
+ end
272
+ end
273
+
274
+ # Usage
275
+ class DailyMessage < SmartMessage::Base
276
+ property :event, required: true
277
+
278
+ transport RotatedFileTransport.new(
279
+ base_path: '/var/log/daily'
280
+ )
281
+ end
282
+
283
+ # Messages automatically go to /var/log/daily/2024-01-15.log
284
+ DailyMessage.new(event: "user_action").publish
285
+ ```
286
+
287
+ ## Directory Management
288
+
289
+ ### Automatic Directory Creation
290
+
291
+ ```ruby
292
+ # Creates /var/log/app/subsystem/ if it doesn't exist
293
+ transport = SmartMessage::Transport::FileTransport.new(
294
+ file_path: '/var/log/app/subsystem/events.log',
295
+ auto_create_dirs: true # default
296
+ )
297
+ ```
298
+
299
+ ### Manual Directory Management
300
+
301
+ ```ruby
302
+ # Disable automatic creation
303
+ transport = SmartMessage::Transport::FileTransport.new(
304
+ file_path: '/existing/path/events.log',
305
+ auto_create_dirs: false
306
+ )
307
+
308
+ # Create directories manually
309
+ FileUtils.mkdir_p('/var/log/custom')
310
+ transport = SmartMessage::Transport::FileTransport.new(
311
+ file_path: '/var/log/custom/events.log'
312
+ )
313
+ ```
314
+
315
+ ## Thread Safety
316
+
317
+ File Transport is fully thread-safe:
318
+ - File append operations are atomic
319
+ - Directory creation is protected
320
+ - Multiple threads can publish concurrently
321
+
322
+ ```ruby
323
+ transport = SmartMessage::Transport::FileTransport.new(
324
+ file_path: '/tmp/concurrent.log'
325
+ )
326
+
327
+ class TestMessage < SmartMessage::Base
328
+ property :thread_id
329
+ property :sequence
330
+ transport transport
331
+ end
332
+
333
+ # Thread-safe concurrent publishing
334
+ threads = []
335
+ 5.times do |thread_id|
336
+ threads << Thread.new do
337
+ 10.times do |sequence|
338
+ TestMessage.new(
339
+ thread_id: thread_id,
340
+ sequence: sequence
341
+ ).publish
342
+ end
343
+ end
344
+ end
345
+ threads.each(&:join)
346
+
347
+ # All 50 messages safely written to file
348
+ ```
349
+
350
+ ## Error Handling
351
+
352
+ ### File System Errors
353
+
354
+ ```ruby
355
+ begin
356
+ message.publish
357
+ rescue Errno::ENOENT => e
358
+ puts "Directory doesn't exist: #{e.message}"
359
+ rescue Errno::EACCES => e
360
+ puts "Permission denied: #{e.message}"
361
+ rescue Errno::ENOSPC => e
362
+ puts "No space left on device: #{e.message}"
363
+ end
364
+ ```
365
+
366
+ ### Custom Error Handling
367
+
368
+ ```ruby
369
+ class SafeFileTransport < SmartMessage::Transport::FileTransport
370
+ private
371
+
372
+ def do_publish(message_class, serialized_message)
373
+ super(message_class, serialized_message)
374
+ rescue => e
375
+ # Log error and fall back to alternate location
376
+ fallback_path = "/tmp/fallback_#{File.basename(file_path)}"
377
+ File.write(fallback_path, "#{serialized_message}\n", mode: 'a')
378
+ warn "File transport error: #{e.message}, using fallback: #{fallback_path}"
379
+ end
380
+ end
381
+ ```
382
+
383
+ ## Performance Characteristics
384
+
385
+ - **Latency**: ~1-5ms (filesystem dependent)
386
+ - **Throughput**: Limited by I/O operations
387
+ - **Memory Usage**: Minimal (immediate write)
388
+ - **Concurrency**: Thread-safe with file locking
389
+ - **Disk Usage**: Grows with message volume
390
+
391
+ ## Extension Patterns
392
+
393
+ ### Formatted Output Transport
394
+
395
+ ```ruby
396
+ class FormattedFileTransport < SmartMessage::Transport::FileTransport
397
+ def initialize(file_path:, format: :json, **options)
398
+ @format = format
399
+ super(file_path: file_path, **options)
400
+ end
401
+
402
+ private
403
+
404
+ def do_publish(message_class, serialized_message)
405
+ content = case @format
406
+ when :csv
407
+ to_csv(message_class, serialized_message)
408
+ when :xml
409
+ to_xml(message_class, serialized_message)
410
+ else
411
+ serialized_message
412
+ end
413
+
414
+ File.write(file_path, "#{content}\n", mode: 'a')
415
+ end
416
+
417
+ def to_csv(message_class, data)
418
+ # Convert JSON to CSV format
419
+ parsed = JSON.parse(data)
420
+ parsed.values.join(',')
421
+ end
422
+
423
+ def to_xml(message_class, data)
424
+ # Convert JSON to XML format
425
+ "<message class=\"#{message_class}\">#{data}</message>"
426
+ end
427
+ end
428
+ ```
429
+
430
+ ### Buffered File Transport
431
+
432
+ ```ruby
433
+ class BufferedFileTransport < SmartMessage::Transport::FileTransport
434
+ def initialize(file_path:, buffer_size: 100, **options)
435
+ @buffer_size = buffer_size
436
+ @buffer = []
437
+ @buffer_mutex = Mutex.new
438
+ super(file_path: file_path, **options)
439
+ end
440
+
441
+ private
442
+
443
+ def do_publish(message_class, serialized_message)
444
+ @buffer_mutex.synchronize do
445
+ @buffer << serialized_message
446
+
447
+ if @buffer.size >= @buffer_size
448
+ flush_buffer
449
+ end
450
+ end
451
+ end
452
+
453
+ def flush_buffer
454
+ return if @buffer.empty?
455
+
456
+ content = @buffer.join("\n") + "\n"
457
+ File.write(file_path, content, mode: 'a')
458
+ @buffer.clear
459
+ end
460
+
461
+ public
462
+
463
+ def close
464
+ @buffer_mutex.synchronize { flush_buffer }
465
+ end
466
+ end
467
+ ```
468
+
469
+ ## Best Practices
470
+
471
+ ### Configuration
472
+ - Use absolute paths for file_path
473
+ - Enable auto_create_dirs for robustness
474
+ - Consider log rotation for long-running applications
475
+
476
+ ### Performance
477
+ - Use buffered writes for high-volume scenarios
478
+ - Monitor disk space usage
479
+ - Consider asynchronous variants for critical paths
480
+
481
+ ### Error Handling
482
+ - Implement fallback locations for critical messages
483
+ - Monitor file system permissions
484
+ - Handle disk full scenarios gracefully
485
+
486
+ ### Testing
487
+ - Use temporary directories in tests
488
+ - Clean up test files in teardown
489
+ - Mock file operations for unit tests
490
+
491
+ ## Testing Support
492
+
493
+ ### Test Helpers
494
+
495
+ ```ruby
496
+ class TestFileTransport < SmartMessage::Transport::FileTransport
497
+ attr_reader :written_messages
498
+
499
+ def initialize(**options)
500
+ @written_messages = []
501
+ super(file_path: '/dev/null', **options)
502
+ end
503
+
504
+ private
505
+
506
+ def do_publish(message_class, serialized_message)
507
+ @written_messages << {
508
+ message_class: message_class,
509
+ content: serialized_message,
510
+ timestamp: Time.now
511
+ }
512
+ end
513
+ end
514
+
515
+ # Usage in tests
516
+ RSpec.describe "Message Publishing" do
517
+ let(:transport) { TestFileTransport.new }
518
+
519
+ it "publishes messages" do
520
+ MyMessage.transport = transport
521
+ MyMessage.new(data: "test").publish
522
+
523
+ expect(transport.written_messages).to have(1).item
524
+ expect(transport.written_messages.first[:message_class]).to eq("MyMessage")
525
+ end
526
+ end
527
+ ```
528
+
529
+ ## Related Documentation
530
+
531
+ - [STDOUT Transport](stdout-transport.md) - File Transport implementation with formatting
532
+ - [Transport Overview](../reference/transports.md) - All available transports
533
+ - [Redis Transport](redis-transport.md) - Distributed messaging transport
534
+ - [Memory Transport](memory-transport.md) - In-memory development transport
535
+ - [Troubleshooting Guide](../development/troubleshooting.md) - Testing and debugging strategies
@@ -481,4 +481,4 @@ end
481
481
  - [Redis Queue Transport](redis-transport.md)
482
482
  - [Memory Transport](memory-transport.md)
483
483
  - [Error Handling and Dead Letter Queues](../reference/dead-letter-queue.md)
484
- - [Performance Optimization](../development/performance.md)
484
+ - [Troubleshooting Guide](../development/troubleshooting.md)
@@ -480,7 +480,7 @@ Each example includes comprehensive logging and demonstrates production-ready pa
480
480
  ### Additional Resources
481
481
 
482
482
  For more Redis Transport examples and patterns, also see:
483
- - **[Memory Transport Examples](../../examples/memory/)** - Can be adapted to Redis Transport by changing configuration
483
+ - **[Memory Transport Examples](https://github.com/MadBomber/smart_message/tree/main/examples/memory)** - Can be adapted to Redis Transport by changing configuration
484
484
  - **[Complete Documentation](https://github.com/MadBomber/smart_message/blob/main/examples/redis/smart_home_iot_dataflow.md)** - Detailed data flow analysis with SVG diagrams
485
485
 
486
486
  ## Related Documentation