smart_message 0.0.7 → 0.0.9

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 (80) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.irbrc +24 -0
  4. data/CHANGELOG.md +143 -0
  5. data/Gemfile.lock +6 -1
  6. data/README.md +289 -15
  7. data/docs/README.md +3 -1
  8. data/docs/addressing.md +119 -13
  9. data/docs/architecture.md +68 -0
  10. data/docs/dead_letter_queue.md +673 -0
  11. data/docs/dispatcher.md +87 -0
  12. data/docs/examples.md +59 -1
  13. data/docs/getting-started.md +8 -1
  14. data/docs/logging.md +382 -326
  15. data/docs/message_filtering.md +451 -0
  16. data/examples/01_point_to_point_orders.rb +54 -53
  17. data/examples/02_publish_subscribe_events.rb +14 -10
  18. data/examples/03_many_to_many_chat.rb +16 -8
  19. data/examples/04_redis_smart_home_iot.rb +20 -10
  20. data/examples/05_proc_handlers.rb +12 -11
  21. data/examples/06_custom_logger_example.rb +95 -100
  22. data/examples/07_error_handling_scenarios.rb +4 -2
  23. data/examples/08_entity_addressing_basic.rb +18 -6
  24. data/examples/08_entity_addressing_with_filtering.rb +27 -9
  25. data/examples/09_dead_letter_queue_demo.rb +559 -0
  26. data/examples/09_regex_filtering_microservices.rb +407 -0
  27. data/examples/10_header_block_configuration.rb +263 -0
  28. data/examples/11_global_configuration_example.rb +219 -0
  29. data/examples/README.md +102 -0
  30. data/examples/dead_letters.jsonl +12 -0
  31. data/examples/performance_metrics/benchmark_results_ractor_20250818_205603.json +135 -0
  32. data/examples/performance_metrics/benchmark_results_ractor_20250818_205831.json +135 -0
  33. data/examples/performance_metrics/benchmark_results_test_20250818_204942.json +130 -0
  34. data/examples/performance_metrics/benchmark_results_threadpool_20250818_204942.json +130 -0
  35. data/examples/performance_metrics/benchmark_results_threadpool_20250818_204959.json +130 -0
  36. data/examples/performance_metrics/benchmark_results_threadpool_20250818_205044.json +130 -0
  37. data/examples/performance_metrics/benchmark_results_threadpool_20250818_205109.json +130 -0
  38. data/examples/performance_metrics/benchmark_results_threadpool_20250818_205252.json +130 -0
  39. data/examples/performance_metrics/benchmark_results_unknown_20250819_172852.json +130 -0
  40. data/examples/performance_metrics/compare_benchmarks.rb +519 -0
  41. data/examples/performance_metrics/dead_letters.jsonl +3100 -0
  42. data/examples/performance_metrics/performance_benchmark.rb +344 -0
  43. data/examples/show_logger.rb +367 -0
  44. data/examples/show_me.rb +145 -0
  45. data/examples/temp.txt +94 -0
  46. data/examples/tmux_chat/bot_agent.rb +4 -2
  47. data/examples/tmux_chat/human_agent.rb +4 -2
  48. data/examples/tmux_chat/room_monitor.rb +4 -2
  49. data/examples/tmux_chat/shared_chat_system.rb +6 -3
  50. data/lib/smart_message/addressing.rb +259 -0
  51. data/lib/smart_message/base.rb +121 -599
  52. data/lib/smart_message/circuit_breaker.rb +23 -6
  53. data/lib/smart_message/configuration.rb +199 -0
  54. data/lib/smart_message/dead_letter_queue.rb +361 -0
  55. data/lib/smart_message/dispatcher.rb +90 -49
  56. data/lib/smart_message/header.rb +5 -0
  57. data/lib/smart_message/logger/base.rb +21 -1
  58. data/lib/smart_message/logger/default.rb +88 -138
  59. data/lib/smart_message/logger/lumberjack.rb +324 -0
  60. data/lib/smart_message/logger/null.rb +81 -0
  61. data/lib/smart_message/logger.rb +17 -9
  62. data/lib/smart_message/messaging.rb +100 -0
  63. data/lib/smart_message/plugins.rb +132 -0
  64. data/lib/smart_message/serializer/base.rb +25 -8
  65. data/lib/smart_message/serializer/json.rb +5 -4
  66. data/lib/smart_message/subscription.rb +193 -0
  67. data/lib/smart_message/transport/base.rb +84 -53
  68. data/lib/smart_message/transport/memory_transport.rb +7 -5
  69. data/lib/smart_message/transport/redis_transport.rb +15 -45
  70. data/lib/smart_message/transport/stdout_transport.rb +18 -8
  71. data/lib/smart_message/transport.rb +1 -34
  72. data/lib/smart_message/utilities.rb +142 -0
  73. data/lib/smart_message/version.rb +1 -1
  74. data/lib/smart_message/versioning.rb +85 -0
  75. data/lib/smart_message/wrapper.rb.bak +132 -0
  76. data/lib/smart_message.rb +74 -27
  77. data/smart_message.gemspec +3 -0
  78. metadata +77 -3
  79. data/lib/smart_message/serializer.rb +0 -10
  80. data/lib/smart_message/wrapper.rb +0 -43
@@ -2,6 +2,7 @@
2
2
  # encoding: utf-8
3
3
  # frozen_string_literal: true
4
4
 
5
+ require 'json' # STDLIB
5
6
  require_relative '../circuit_breaker'
6
7
 
7
8
  module SmartMessage::Serializer
@@ -12,7 +13,20 @@ module SmartMessage::Serializer
12
13
  # provide basic configuration
13
14
  def initialize
14
15
  configure_serializer_circuit_breakers
16
+
17
+ logger.debug { "[SmartMessage::Serializer::#{self.class.name.split('::').last}] Initialized" }
18
+ rescue => e
19
+ logger&.error { "[SmartMessage] Error in serializer initialization: #{e.class.name} - #{e.message}" }
20
+ raise
21
+ end
22
+
23
+ private
24
+
25
+ def logger
26
+ @logger ||= SmartMessage::Logger.default
15
27
  end
28
+
29
+ public
16
30
 
17
31
  def encode(message_instance)
18
32
  circuit(:serializer).wrap do
@@ -39,10 +53,16 @@ module SmartMessage::Serializer
39
53
  raise
40
54
  end
41
55
  end
56
+
57
+ private
42
58
 
43
59
  # Template methods for actual serialization (implement in subclasses)
44
60
  def do_encode(message_instance)
45
- raise ::SmartMessage::Errors::NotImplemented
61
+ # Default implementation: serialize only the payload portion for wrapper architecture
62
+ # Subclasses can override this for specific serialization formats
63
+ message_hash = message_instance.to_h
64
+ payload_portion = message_hash[:_sm_payload]
65
+ ::JSON.generate(payload_portion)
46
66
  end
47
67
 
48
68
  def do_decode(payload)
@@ -81,13 +101,10 @@ module SmartMessage::Serializer
81
101
 
82
102
  # Handle serializer circuit breaker fallback
83
103
  def handle_serializer_fallback(fallback_result, operation, data)
84
- if $DEBUG
85
- puts "Serializer circuit breaker activated: #{self.class.name}"
86
- puts "Operation: #{operation}"
87
- puts "Error: #{fallback_result[:circuit_breaker][:error]}"
88
- end
89
-
90
- # TODO: Integrate with structured logging when implemented
104
+ # Integrate with structured logging
105
+ logger.error { "[SmartMessage::Serializer] Circuit breaker activated: #{self.class.name}" }
106
+ logger.error { "[SmartMessage::Serializer] Operation: #{operation}" }
107
+ logger.error { "[SmartMessage::Serializer] Error: #{fallback_result[:circuit_breaker][:error]}" }
91
108
 
92
109
  # Return the fallback result
93
110
  fallback_result
@@ -5,11 +5,12 @@
5
5
  require 'json' # STDLIB
6
6
 
7
7
  module SmartMessage::Serializer
8
- class JSON < Base
8
+ class Json < Base
9
9
  def do_encode(message_instance)
10
- # TODO: is this the right place to insert an automated-invisible
11
- # message header?
12
- message_instance.to_json
10
+ # Single-tier serialization: serialize the complete message structure
11
+ # This includes both header and payload for full message reconstruction
12
+ message_hash = message_instance.to_h
13
+ ::JSON.generate(message_hash)
13
14
  end
14
15
 
15
16
  def do_decode(payload)
@@ -0,0 +1,193 @@
1
+ # lib/smart_message/subscription.rb
2
+ # encoding: utf-8
3
+ # frozen_string_literal: true
4
+
5
+ require 'securerandom' # STDLIB
6
+
7
+ module SmartMessage
8
+ # Subscription management module for SmartMessage::Base
9
+ # Handles subscribe/unsubscribe operations and proc handler management
10
+ module Subscription
11
+ def self.included(base)
12
+ base.extend(ClassMethods)
13
+ base.class_eval do
14
+ # Registry for proc-based message handlers
15
+ class_variable_set(:@@proc_handlers, {}) unless class_variable_defined?(:@@proc_handlers)
16
+ end
17
+ end
18
+
19
+ module ClassMethods
20
+ #########################################################
21
+ ## proc handler management
22
+
23
+ # Register a proc handler and return a unique identifier for it
24
+ # @param message_class [String] The message class name
25
+ # @param handler_proc [Proc] The proc to register
26
+ # @return [String] Unique identifier for this handler
27
+ def register_proc_handler(message_class, handler_proc)
28
+ handler_id = "#{message_class}.proc_#{SecureRandom.hex(8)}"
29
+ class_variable_get(:@@proc_handlers)[handler_id] = handler_proc
30
+ handler_id
31
+ end
32
+
33
+ # Call a registered proc handler
34
+ # @param handler_id [String] The handler identifier
35
+ # @param wrapper [SmartMessage::Wrapper::Base] The message wrapper
36
+ def call_proc_handler(handler_id, wrapper)
37
+ handler_proc = class_variable_get(:@@proc_handlers)[handler_id]
38
+ return unless handler_proc
39
+
40
+ handler_proc.call(wrapper)
41
+ end
42
+
43
+ # Remove a proc handler from the registry
44
+ # @param handler_id [String] The handler identifier to remove
45
+ def unregister_proc_handler(handler_id)
46
+ class_variable_get(:@@proc_handlers).delete(handler_id)
47
+ end
48
+
49
+ # Check if a handler ID refers to a proc handler
50
+ # @param handler_id [String] The handler identifier
51
+ # @return [Boolean] True if this is a proc handler
52
+ def proc_handler?(handler_id)
53
+ class_variable_get(:@@proc_handlers).key?(handler_id)
54
+ end
55
+
56
+ #########################################################
57
+ ## class-level subscription management via the transport
58
+
59
+ # Add this message class to the transport's catalog of
60
+ # subscribed messages. If the transport is missing, raise
61
+ # an exception.
62
+ #
63
+ # @param process_method [String, Proc, nil] The processing method:
64
+ # - String: Method name like "MyService.handle_message"
65
+ # - Proc: A proc/lambda that accepts (message_header, message_payload)
66
+ # - nil: Uses default "MessageClass.process" method
67
+ # @param broadcast [Boolean, nil] Filter for broadcast messages (to: nil)
68
+ # @param to [String, Array, nil] Filter for messages directed to specific entities
69
+ # @param from [String, Array, nil] Filter for messages from specific entities
70
+ # @param block [Proc] Alternative way to pass a processing block
71
+ # @return [String] The identifier used for this subscription
72
+ #
73
+ # @example Using default handler (all messages)
74
+ # MyMessage.subscribe
75
+ #
76
+ # @example Using custom method name with filtering
77
+ # MyMessage.subscribe("MyService.handle_message", to: 'my-service')
78
+ #
79
+ # @example Using a block with broadcast filtering
80
+ # MyMessage.subscribe(broadcast: true) do |header, payload|
81
+ # data = JSON.parse(payload)
82
+ # puts "Received broadcast: #{data}"
83
+ # end
84
+ #
85
+ # @example Entity-specific filtering
86
+ # MyMessage.subscribe(to: 'order-service', from: ['payment', 'user'])
87
+ #
88
+ # @example Broadcast + directed messages
89
+ # MyMessage.subscribe(to: 'my-service', broadcast: true)
90
+ def subscribe(process_method = nil, broadcast: nil, to: nil, from: nil, &block)
91
+ message_class = whoami
92
+
93
+ # Handle different parameter types
94
+ if block_given?
95
+ # Block was passed - use it as the handler
96
+ handler_proc = block
97
+ process_method = register_proc_handler(message_class, handler_proc)
98
+ elsif process_method.respond_to?(:call)
99
+ # Proc/lambda was passed as first parameter
100
+ handler_proc = process_method
101
+ process_method = register_proc_handler(message_class, handler_proc)
102
+ elsif process_method.nil?
103
+ # Use default handler
104
+ process_method = message_class + '.process'
105
+ end
106
+ # If process_method is a String, use it as-is
107
+
108
+ # Normalize string filters to arrays
109
+ to_filter = normalize_filter_value(to)
110
+ from_filter = normalize_filter_value(from)
111
+
112
+ # Create filter options
113
+ filter_options = {
114
+ broadcast: broadcast,
115
+ to: to_filter,
116
+ from: from_filter
117
+ }
118
+
119
+ # Add proper logging
120
+ logger = SmartMessage::Logger.default
121
+
122
+ begin
123
+ raise Errors::TransportNotConfigured if transport_missing?
124
+ transport.subscribe(message_class, process_method, filter_options)
125
+
126
+ # Log successful subscription
127
+ handler_desc = block_given? || process_method.respond_to?(:call) ? " with block/proc handler" : ""
128
+ logger.info { "[SmartMessage] Subscribed: #{self.name}#{handler_desc}" }
129
+ logger.debug { "[SmartMessage::Subscription] Subscribed #{message_class} with filters: #{filter_options}" }
130
+
131
+ process_method
132
+ rescue => e
133
+ logger.error { "[SmartMessage] Error in message subscription: #{e.class.name} - #{e.message}" }
134
+ raise
135
+ end
136
+ end
137
+
138
+ # Remove this process_method for this message class from the
139
+ # subscribers list.
140
+ # @param process_method [String, nil] The processing method identifier to remove
141
+ # - String: Method name like "MyService.handle_message" or proc handler ID
142
+ # - nil: Uses default "MessageClass.process" method
143
+ def unsubscribe(process_method = nil)
144
+ message_class = whoami
145
+ process_method = message_class + '.process' if process_method.nil?
146
+ # Add proper logging
147
+ logger = SmartMessage::Logger.default
148
+
149
+ begin
150
+ if transport_configured?
151
+ transport.unsubscribe(message_class, process_method)
152
+
153
+ # If this was a proc handler, clean it up from the registry
154
+ if proc_handler?(process_method)
155
+ unregister_proc_handler(process_method)
156
+ end
157
+
158
+ # Log successful unsubscription
159
+ logger.info { "[SmartMessage] Unsubscribed: #{self.name}" }
160
+ logger.debug { "[SmartMessage::Subscription] Unsubscribed #{message_class} from #{process_method}" }
161
+ end
162
+ rescue => e
163
+ logger.error { "[SmartMessage] Error in message unsubscription: #{e.class.name} - #{e.message}" }
164
+ raise
165
+ end
166
+ end
167
+
168
+ # Remove this message class and all of its processing methods
169
+ # from the subscribers list.
170
+ def unsubscribe!
171
+ message_class = whoami
172
+
173
+ # TODO: Add proper logging here
174
+
175
+ transport.unsubscribe!(message_class) if transport_configured?
176
+ end
177
+
178
+ ###################################################
179
+ ## Business Logic resides in the #process method.
180
+
181
+ # When a transport receives a subscribed to message it
182
+ # creates an instance of the message and then calls
183
+ # the process method on that instance.
184
+ #
185
+ # It is expected that SmartMessage classes over ride
186
+ # the SmartMessage::Base#process method with appropriate
187
+ # business logic to handle the received message content.
188
+ def process(message_instance)
189
+ raise Errors::NotImplemented
190
+ end
191
+ end
192
+ end
193
+ end
@@ -10,7 +10,7 @@ module SmartMessage
10
10
  # This defines the standard interface that all transports must implement
11
11
  class Base
12
12
  include BreakerMachines::DSL
13
-
13
+
14
14
  attr_reader :options, :dispatcher
15
15
 
16
16
  def initialize(**options)
@@ -18,7 +18,20 @@ module SmartMessage
18
18
  @dispatcher = options[:dispatcher] || SmartMessage::Dispatcher.new
19
19
  configure
20
20
  configure_transport_circuit_breakers
21
+
22
+ logger.debug { "[SmartMessage::Transport::#{self.class.name.split('::').last}] Initialized with options: #{@options}" }
23
+ rescue => e
24
+ logger&.error { "[SmartMessage] Error in transport initialization: #{e.class.name} - #{e.message}" }
25
+ raise
26
+ end
27
+
28
+ private
29
+
30
+ def logger
31
+ @logger ||= SmartMessage::Logger.default
21
32
  end
33
+
34
+ public
22
35
 
23
36
  # Transport-specific configuration
24
37
  def configure
@@ -31,24 +44,27 @@ module SmartMessage
31
44
  end
32
45
 
33
46
  # Publish a message with circuit breaker protection
34
- # @param message_header [SmartMessage::Header] Message routing information
35
- # @param message_payload [String] Serialized message content
36
- def publish(message_header, message_payload)
47
+ # @param message_class [String] The message class name (used for channel routing)
48
+ # @param serialized_message [String] Complete serialized message content
49
+ def publish(message_class, serialized_message)
37
50
  circuit(:transport_publish).wrap do
38
- do_publish(message_header, message_payload)
51
+ do_publish(message_class, serialized_message)
39
52
  end
40
53
  rescue => e
54
+ # Log the exception for debugging
55
+ logger.error { "[SmartMessage] Error in transport publish: #{e.class.name} - #{e.message}" }
56
+
41
57
  # Re-raise if it's not a circuit breaker fallback
42
58
  raise unless e.is_a?(Hash) && e[:circuit_breaker]
43
-
59
+
44
60
  # Handle circuit breaker fallback
45
- handle_publish_fallback(e, message_header, message_payload)
61
+ handle_publish_fallback(e, message_class, serialized_message)
46
62
  end
47
63
 
48
64
  # Template method for actual publishing (implement in subclasses)
49
- # @param message_header [SmartMessage::Header] Message routing information
50
- # @param message_payload [String] Serialized message content
51
- def do_publish(message_header, message_payload)
65
+ # @param message_class [String] The message class name (used for channel routing)
66
+ # @param serialized_message [String] Complete serialized message content
67
+ def do_publish(message_class, serialized_message)
52
68
  raise NotImplementedError, 'Transport must implement #do_publish'
53
69
  end
54
70
 
@@ -61,7 +77,7 @@ module SmartMessage
61
77
  end
62
78
 
63
79
  # Unsubscribe from a specific message class and process method
64
- # @param message_class [String] The message class name
80
+ # @param message_class [String] The message class name
65
81
  # @param process_method [String] The processing method identifier
66
82
  def unsubscribe(message_class, process_method)
67
83
  @dispatcher.drop(message_class, process_method)
@@ -88,7 +104,7 @@ module SmartMessage
88
104
  # Override in subclasses if connection setup is needed
89
105
  end
90
106
 
91
- # Disconnect from transport (if applicable)
107
+ # Disconnect from transport (if applicable)
92
108
  def disconnect
93
109
  # Override in subclasses if cleanup is needed
94
110
  end
@@ -97,7 +113,7 @@ module SmartMessage
97
113
  # @return [Hash] Circuit breaker statistics
98
114
  def transport_circuit_stats
99
115
  stats = {}
100
-
116
+
101
117
  [:transport_publish, :transport_subscribe].each do |circuit_name|
102
118
  begin
103
119
  if respond_to?(:circuit)
@@ -118,7 +134,7 @@ module SmartMessage
118
134
  stats[circuit_name] = { error: "Failed to get stats: #{e.message}" }
119
135
  end
120
136
  end
121
-
137
+
122
138
  stats
123
139
  end
124
140
 
@@ -135,54 +151,59 @@ module SmartMessage
135
151
  end
136
152
 
137
153
  # Receive and route a message (called by transport implementations)
138
- # @param message_header [SmartMessage::Header] Message routing information
139
- # @param message_payload [String] Serialized message content
154
+ # @param message_class [String] The message class name
155
+ # @param serialized_message [String] The serialized message content
140
156
  protected
141
157
 
142
- def receive(message_header, message_payload)
143
- @dispatcher.route(message_header, message_payload)
158
+ def receive(message_class, serialized_message)
159
+ # Decode the message using the class's configured serializer
160
+
161
+ # Add defensive check for message_class type
162
+ unless message_class.respond_to?(:constantize)
163
+ logger.error { "[SmartMessage] Invalid message_class type: #{message_class.class.name} - #{message_class.inspect}" }
164
+ logger.error { "[SmartMessage] Expected String, got: #{message_class.class.name}" }
165
+ raise ArgumentError, "message_class must be a String, got #{message_class.class.name}"
166
+ end
167
+
168
+ message_class_obj = message_class.constantize
169
+ decoded_message = message_class_obj.decode(serialized_message)
170
+
171
+ @dispatcher.route(decoded_message)
172
+ rescue => e
173
+ logger.error { "[SmartMessage] Error in transport receive: #{e.class.name} - #{e.message}" }
174
+ logger.error { "[SmartMessage] message_class: #{message_class.inspect} (#{message_class.class.name})" }
175
+ logger.error { "[SmartMessage] serialized_message length: #{serialized_message&.length}" }
176
+ raise
144
177
  end
145
178
 
146
179
  # Configure circuit breakers for transport operations
147
180
  def configure_transport_circuit_breakers
148
181
  # Configure publish circuit breaker
149
182
  publish_config = SmartMessage::CircuitBreaker::DEFAULT_CONFIGS[:transport_publish]
150
-
183
+
151
184
  self.class.circuit :transport_publish do
152
- threshold failures: publish_config[:threshold][:failures],
185
+ threshold failures: publish_config[:threshold][:failures],
153
186
  within: publish_config[:threshold][:within].seconds
154
187
  reset_after publish_config[:reset_after].seconds
155
-
188
+
156
189
  # Use memory storage by default for transport circuits
157
190
  storage BreakerMachines::Storage::Memory.new
158
-
159
- # Fallback for publish failures
160
- fallback do |exception|
161
- {
162
- circuit_breaker: {
163
- circuit: :transport_publish,
164
- transport_type: self.class.name,
165
- state: 'open',
166
- error: exception.message,
167
- error_class: exception.class.name,
168
- timestamp: Time.now.iso8601,
169
- fallback_triggered: true
170
- }
171
- }
172
- end
191
+
192
+ # Fallback for publish failures - use DLQ fallback
193
+ fallback SmartMessage::CircuitBreaker::Fallbacks.dead_letter_queue
173
194
  end
174
195
 
175
196
  # Configure subscribe circuit breaker
176
197
  subscribe_config = SmartMessage::CircuitBreaker::DEFAULT_CONFIGS[:transport_subscribe]
177
-
198
+
178
199
  self.class.circuit :transport_subscribe do
179
- threshold failures: subscribe_config[:threshold][:failures],
200
+ threshold failures: subscribe_config[:threshold][:failures],
180
201
  within: subscribe_config[:threshold][:within].seconds
181
202
  reset_after subscribe_config[:reset_after].seconds
182
-
203
+
183
204
  storage BreakerMachines::Storage::Memory.new
184
-
185
- # Fallback for subscribe failures
205
+
206
+ # Fallback for subscribe failures - log and return error info
186
207
  fallback do |exception|
187
208
  {
188
209
  circuit_breaker: {
@@ -201,22 +222,32 @@ module SmartMessage
201
222
 
202
223
  # Handle publish circuit breaker fallback
203
224
  # @param fallback_result [Hash] The circuit breaker fallback result
204
- # @param message_header [SmartMessage::Header] The message header
205
- # @param message_payload [String] The message payload
206
- def handle_publish_fallback(fallback_result, message_header, message_payload)
225
+ # @param message_class [String] The message class name
226
+ # @param serialized_message [String] The serialized message
227
+ def handle_publish_fallback(fallback_result, message_class, serialized_message)
207
228
  # Log the circuit breaker activation
208
- if $DEBUG
209
- puts "Transport publish circuit breaker activated: #{self.class.name}"
210
- puts "Error: #{fallback_result[:circuit_breaker][:error]}"
211
- puts "Message: #{message_header.message_class}"
229
+ logger.error { "[SmartMessage::Transport] Circuit breaker activated: #{self.class.name}" }
230
+ logger.error { "[SmartMessage::Transport] Error: #{fallback_result[:circuit_breaker][:error]}" }
231
+ logger.error { "[SmartMessage::Transport] Message: #{message_class}" }
232
+ logger.info { "[SmartMessage::Transport] Sent to DLQ: #{fallback_result[:circuit_breaker][:sent_to_dlq]}" }
233
+
234
+ # If message wasn't sent to DLQ by circuit breaker, send it now
235
+ unless fallback_result.dig(:circuit_breaker, :sent_to_dlq)
236
+ begin
237
+ SmartMessage::DeadLetterQueue.default.enqueue(
238
+ message_class,
239
+ serialized_message,
240
+ error: fallback_result.dig(:circuit_breaker, :error) || 'Circuit breaker activated',
241
+ transport: self.class.name
242
+ )
243
+ rescue => dlq_error
244
+ logger.warn { "[SmartMessage] Warning: Failed to store message in DLQ: #{dlq_error.message}" }
245
+ end
212
246
  end
213
-
214
- # TODO: Integrate with structured logging when implemented
215
- # TODO: Queue for retry or send to dead letter queue
216
-
247
+
217
248
  # Return the fallback result to indicate failure
218
249
  fallback_result
219
250
  end
220
251
  end
221
252
  end
222
- end
253
+ end
@@ -22,20 +22,22 @@ module SmartMessage
22
22
  end
23
23
 
24
24
  # Publish message to memory queue
25
- def do_publish(message_header, message_payload)
25
+ def do_publish(message_class, serialized_message)
26
26
  @message_mutex.synchronize do
27
27
  # Prevent memory overflow
28
28
  @messages.shift if @messages.size >= @options[:max_messages]
29
29
 
30
30
  @messages << {
31
- header: message_header,
32
- payload: message_payload,
31
+ message_class: message_class,
32
+ serialized_message: serialized_message,
33
33
  published_at: Time.now
34
34
  }
35
35
  end
36
36
 
37
37
  # Auto-process if enabled
38
- receive(message_header, message_payload) if @options[:auto_process]
38
+ if @options[:auto_process]
39
+ receive(message_class, serialized_message)
40
+ end
39
41
  end
40
42
 
41
43
  # Get all stored messages
@@ -57,7 +59,7 @@ module SmartMessage
57
59
  def process_all
58
60
  messages_to_process = @message_mutex.synchronize { @messages.dup }
59
61
  messages_to_process.each do |msg|
60
- receive(msg[:header], msg[:payload])
62
+ receive(msg[:message_class], msg[:serialized_message])
61
63
  end
62
64
  end
63
65
 
@@ -36,20 +36,13 @@ module SmartMessage
36
36
  end
37
37
 
38
38
  # Publish message to Redis channel using message class name
39
- def do_publish(message_header, message_payload)
40
- channel = message_header.message_class
41
-
42
- # Combine header and payload for Redis transport
43
- # This ensures header information (from, to, reply_to, etc.) is preserved
44
- redis_message = {
45
- header: message_header.to_hash,
46
- payload: message_payload
47
- }.to_json
39
+ def do_publish(message_class, serialized_message)
40
+ channel = message_class
48
41
 
49
42
  begin
50
- @redis_pub.publish(channel, redis_message)
43
+ @redis_pub.publish(channel, serialized_message)
51
44
  rescue Redis::ConnectionError
52
- retry_with_reconnect('publish') { @redis_pub.publish(channel, redis_message) }
45
+ retry_with_reconnect('publish') { @redis_pub.publish(channel, serialized_message) }
53
46
  end
54
47
  end
55
48
 
@@ -117,7 +110,7 @@ module SmartMessage
117
110
  subscribe_to_channels
118
111
  rescue => e
119
112
  # Log error but don't crash the thread
120
- puts "Redis subscriber error: #{e.message}" if @options[:debug]
113
+ logger.error { "[SmartMessage] Error in redis subscriber: #{e.class.name} - #{e.message}" }
121
114
  retry_subscriber
122
115
  end
123
116
  end
@@ -143,51 +136,28 @@ module SmartMessage
143
136
 
144
137
  begin
145
138
  @redis_sub.subscribe(*@subscribed_channels) do |on|
146
- on.message do |channel, redis_message|
139
+ on.message do |channel, serialized_message|
147
140
  begin
148
- # Parse the Redis message to extract header and payload
149
- parsed_message = JSON.parse(redis_message)
150
-
151
- if parsed_message.is_a?(Hash) && parsed_message.has_key?('header') && parsed_message.has_key?('payload')
152
- # Reconstruct the original header from the parsed data
153
- header_data = parsed_message['header']
154
- message_header = SmartMessage::Header.new(header_data)
155
- message_payload = parsed_message['payload']
156
- else
157
- # Fallback for messages that don't have header/payload structure (legacy support)
158
- message_header = SmartMessage::Header.new(
159
- message_class: channel,
160
- uuid: SecureRandom.uuid,
161
- published_at: Time.now,
162
- publisher_pid: 'redis_subscriber'
163
- )
164
- message_payload = redis_message
165
- end
166
-
167
- receive(message_header, message_payload)
168
- rescue JSON::ParserError
169
- # Handle malformed JSON - fallback to legacy behavior
170
- message_header = SmartMessage::Header.new(
171
- message_class: channel,
172
- uuid: SecureRandom.uuid,
173
- published_at: Time.now,
174
- publisher_pid: 'redis_subscriber'
175
- )
176
- receive(message_header, redis_message)
141
+ # Channel name is the message class name
142
+ # Serialized message contains the complete message
143
+ receive(channel, serialized_message)
144
+ rescue => e
145
+ logger.error { "[SmartMessage] Error in redis message processing: #{e.class.name} - #{e.message}" }
146
+ # Continue processing other messages
177
147
  end
178
148
  end
179
149
 
180
150
  on.subscribe do |channel, subscriptions|
181
- puts "Subscribed to Redis channel: #{channel} (#{subscriptions} total)" if @options[:debug]
151
+ logger.debug { "[SmartMessage::RedisTransport] Subscribed to Redis channel: #{channel} (#{subscriptions} total)" }
182
152
  end
183
153
 
184
154
  on.unsubscribe do |channel, subscriptions|
185
- puts "Unsubscribed from Redis channel: #{channel} (#{subscriptions} total)" if @options[:debug]
155
+ logger.debug { "[SmartMessage::RedisTransport] Unsubscribed from Redis channel: #{channel} (#{subscriptions} total)" }
186
156
  end
187
157
  end
188
158
  rescue => e
189
159
  # Silently handle connection errors during subscription
190
- puts "Redis subscription error: #{e.class.name}" if @options[:debug]
160
+ logger.error { "[SmartMessage] Error in redis subscription: #{e.class.name} - #{e.message}" }
191
161
  retry_subscriber if @running
192
162
  end
193
163
  end