smart_message 0.0.12 → 0.0.16

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 (117) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/CHANGELOG.md +155 -1
  4. data/Gemfile.lock +6 -6
  5. data/README.md +71 -25
  6. data/docs/core-concepts/architecture.md +5 -10
  7. data/docs/getting-started/examples.md +0 -12
  8. data/docs/getting-started/quick-start.md +4 -9
  9. data/docs/index.md +6 -4
  10. data/docs/reference/serializers.md +160 -488
  11. data/docs/reference/transports.md +47 -146
  12. data/docs/transports/memory-transport.md +2 -1
  13. data/docs/transports/multi-transport.md +484 -0
  14. data/docs/transports/redis-transport-comparison.md +215 -350
  15. data/docs/transports/redis-transport.md +3 -22
  16. data/examples/README.md +6 -9
  17. data/examples/city_scenario/README.md +1 -1
  18. data/examples/city_scenario/messages/emergency_911_message.rb +0 -1
  19. data/examples/city_scenario/messages/emergency_resolved_message.rb +0 -1
  20. data/examples/city_scenario/messages/fire_dispatch_message.rb +0 -1
  21. data/examples/city_scenario/messages/fire_emergency_message.rb +0 -1
  22. data/examples/city_scenario/messages/health_check_message.rb +0 -1
  23. data/examples/city_scenario/messages/health_status_message.rb +0 -1
  24. data/examples/city_scenario/messages/police_dispatch_message.rb +0 -1
  25. data/examples/city_scenario/messages/silent_alarm_message.rb +0 -1
  26. data/examples/file/00_run_all_file_demos.rb +260 -0
  27. data/examples/file/01_basic_file_transport_demo.rb +237 -0
  28. data/examples/file/02_fifo_transport_demo.rb +289 -0
  29. data/examples/file/03_file_watching_demo.rb +332 -0
  30. data/examples/file/04_multi_transport_file_demo.rb +432 -0
  31. data/examples/file/README.md +257 -0
  32. data/examples/memory/00_run_all_demos.rb +317 -0
  33. data/examples/memory/01_message_deduplication_demo.rb +18 -32
  34. data/examples/memory/02_dead_letter_queue_demo.rb +9 -12
  35. data/examples/memory/03_point_to_point_orders.rb +3 -5
  36. data/examples/memory/04_publish_subscribe_events.rb +15 -16
  37. data/examples/memory/05_many_to_many_chat.rb +19 -22
  38. data/examples/memory/06_stdout_publish_only.rb +145 -0
  39. data/examples/memory/07_proc_handlers_demo.rb +13 -14
  40. data/examples/memory/08_custom_logger_demo.rb +136 -140
  41. data/examples/memory/09_error_handling_demo.rb +7 -10
  42. data/examples/memory/10_entity_addressing_basic.rb +25 -31
  43. data/examples/memory/11_entity_addressing_with_filtering.rb +32 -36
  44. data/examples/memory/12_regex_filtering_microservices.rb +10 -11
  45. data/examples/memory/13_header_block_configuration.rb +0 -5
  46. data/examples/memory/14_global_configuration_demo.rb +12 -14
  47. data/examples/memory/15_logger_demo.rb +0 -1
  48. data/examples/memory/README.md +37 -20
  49. data/examples/memory/log/demo_app.log.1 +100 -0
  50. data/examples/memory/log/demo_app.log.2 +100 -0
  51. data/examples/multi_transport_example.rb +114 -0
  52. data/examples/redis/01_smart_home_iot_demo.rb +20 -24
  53. data/examples/redis/README.md +0 -2
  54. data/examples/utilities/box_it.rb +12 -0
  55. data/examples/utilities/doing.rb +19 -0
  56. data/examples/utilities/temp.md +28 -0
  57. data/lib/smart_message/base.rb +24 -17
  58. data/lib/smart_message/configuration.rb +2 -23
  59. data/lib/smart_message/dead_letter_queue.rb +1 -1
  60. data/lib/smart_message/errors.rb +3 -0
  61. data/lib/smart_message/header.rb +1 -1
  62. data/lib/smart_message/logger/default.rb +1 -1
  63. data/lib/smart_message/messaging.rb +37 -66
  64. data/lib/smart_message/plugins.rb +42 -41
  65. data/lib/smart_message/serializer/base.rb +1 -1
  66. data/lib/smart_message/serializer.rb +3 -2
  67. data/lib/smart_message/subscription.rb +18 -20
  68. data/lib/smart_message/transport/async_publish_queue.rb +284 -0
  69. data/lib/smart_message/transport/base.rb +42 -8
  70. data/lib/smart_message/transport/fifo_operations.rb +264 -0
  71. data/lib/smart_message/transport/file_operations.rb +200 -0
  72. data/lib/smart_message/transport/file_transport.rb +149 -0
  73. data/lib/smart_message/transport/file_watching.rb +72 -0
  74. data/lib/smart_message/transport/memory_transport.rb +23 -4
  75. data/lib/smart_message/transport/partitioned_files.rb +46 -0
  76. data/lib/smart_message/transport/redis_transport.rb +11 -0
  77. data/lib/smart_message/transport/registry.rb +0 -1
  78. data/lib/smart_message/transport/stdout_transport.rb +73 -41
  79. data/lib/smart_message/transport/stdout_transport.rb.backup +88 -0
  80. data/lib/smart_message/transport.rb +0 -1
  81. data/lib/smart_message/version.rb +1 -1
  82. metadata +25 -37
  83. data/docs/guides/redis-queue-getting-started.md +0 -697
  84. data/docs/guides/redis-queue-patterns.md +0 -889
  85. data/docs/guides/redis-queue-production.md +0 -1091
  86. data/docs/transports/redis-enhanced-transport.md +0 -524
  87. data/docs/transports/redis-queue-transport.md +0 -1304
  88. data/examples/redis_enhanced/README.md +0 -319
  89. data/examples/redis_enhanced/enhanced_01_basic_patterns.rb +0 -233
  90. data/examples/redis_enhanced/enhanced_02_fluent_api.rb +0 -331
  91. data/examples/redis_enhanced/enhanced_03_dual_publishing.rb +0 -281
  92. data/examples/redis_enhanced/enhanced_04_advanced_routing.rb +0 -419
  93. data/examples/redis_queue/01_basic_messaging.rb +0 -221
  94. data/examples/redis_queue/01_comprehensive_examples.rb +0 -508
  95. data/examples/redis_queue/02_pattern_routing.rb +0 -405
  96. data/examples/redis_queue/03_fluent_api.rb +0 -422
  97. data/examples/redis_queue/04_load_balancing.rb +0 -486
  98. data/examples/redis_queue/05_microservices.rb +0 -735
  99. data/examples/redis_queue/06_emergency_alerts.rb +0 -777
  100. data/examples/redis_queue/07_queue_management.rb +0 -587
  101. data/examples/redis_queue/README.md +0 -366
  102. data/examples/redis_queue/enhanced_01_basic_patterns.rb +0 -233
  103. data/examples/redis_queue/enhanced_02_fluent_api.rb +0 -331
  104. data/examples/redis_queue/enhanced_03_dual_publishing.rb +0 -281
  105. data/examples/redis_queue/enhanced_04_advanced_routing.rb +0 -419
  106. data/examples/redis_queue/redis_queue_architecture.svg +0 -148
  107. data/ideas/README.md +0 -41
  108. data/ideas/agents.md +0 -1001
  109. data/ideas/database_transport.md +0 -980
  110. data/ideas/improvement.md +0 -359
  111. data/ideas/meshage.md +0 -1788
  112. data/ideas/message_discovery.md +0 -178
  113. data/ideas/message_schema.md +0 -1381
  114. data/lib/smart_message/transport/redis_enhanced_transport.rb +0 -399
  115. data/lib/smart_message/transport/redis_queue_transport.rb +0 -555
  116. data/lib/smart_message/wrapper.rb.bak +0 -132
  117. /data/examples/memory/{06_pretty_print_demo.rb → 16_pretty_print_demo.rb} +0 -0
@@ -37,11 +37,9 @@ module SmartMessage
37
37
  include Hashie::Extensions::MethodAccess
38
38
 
39
39
  # Common attrubutes for all messages
40
- # TODO: Need to change the SmartMessage::Header into a
41
- # smartMessage::Wrapper concept where the message
42
- # content is serialized into an element in the wrapper
43
- # where the wrapper contains header/routing information
44
- # in addition to the serialized message data.
40
+ # TODO: This comment is now obsolete - the flat message structure
41
+ # has been implemented where header and message properties
42
+ # exist at the same level in a flat structure.
45
43
  property :_sm_header
46
44
 
47
45
  # Constructor for a messsage definition that allows the
@@ -50,7 +48,6 @@ module SmartMessage
50
48
  # instance-level override of class plugins
51
49
  # Don't use fallback defaults here - let the methods handle fallbacks when actually used
52
50
  @transport = (self.class.class_variable_get(:@@transport) rescue nil)
53
- @serializer = (self.class.class_variable_get(:@@serializer) rescue nil)
54
51
 
55
52
  # Check if we're reconstructing from serialized data (complete header provided)
56
53
  if props[:_sm_header]
@@ -71,8 +68,8 @@ module SmartMessage
71
68
  @to = header.to
72
69
  @reply_to = header.reply_to
73
70
 
74
- # Extract payload properties
75
- payload_props = props[:_sm_payload] || {}
71
+ # Extract payload properties directly from props (flat structure)
72
+ payload_props = props.except(:_sm_header)
76
73
 
77
74
  attributes = {
78
75
  _sm_header: header
@@ -132,14 +129,14 @@ module SmartMessage
132
129
  # Extract payload properties (non-header properties)
133
130
  payload_props = self.class.properties.each_with_object({}) do |prop, hash|
134
131
  next if prop == :_sm_header
135
- hash[prop.to_s] = self[prop] # Use string keys to match old format
132
+ hash[prop.to_s] = self[prop] # Access property value
136
133
  end
137
134
 
138
135
  JSON.generate(payload_props)
139
136
  end
140
137
 
141
- # Backward compatibility method for handlers that expect wrapper.split
142
- # Returns [header, payload_json] in the old wrapper format
138
+ # Backward compatibility method for handlers that expect message.split
139
+ # Returns [header, payload_json] in the old two-tier format
143
140
  def split
144
141
  [_sm_header, _sm_payload]
145
142
  end
@@ -154,29 +151,39 @@ module SmartMessage
154
151
 
155
152
 
156
153
 
154
+ # Convert message to hash for serialization
155
+ def to_hash
156
+ # Get all properties and their values
157
+ hash = {}
158
+ self.class.properties.each do |prop|
159
+ hash[prop] = self[prop]
160
+ end
161
+ hash
162
+ end
163
+
157
164
  ###########################################################
158
165
  ## class methods
159
166
 
160
167
  class << self
161
168
  # Decode a complete serialized message back to a message instance
169
+ # Note: This method is no longer used with transport-based serialization
170
+ # Transports handle decoding and create message instances directly
162
171
  # @param serialized_message [String] The serialized message content
163
172
  # @return [SmartMessage::Base] The decoded message instance
164
173
  def decode(serialized_message)
165
174
  begin
166
175
  (self.logger || SmartMessage::Logger.default).info { "[SmartMessage] Received: #{self.name} (#{serialized_message.bytesize} bytes)" }
167
176
 
168
- # Use the class's configured serializer to decode the message
169
- serializer = self.serializer || SmartMessage::Serializer.default
170
-
171
- # Deserialize the complete message
172
- deserialized_data = serializer.decode(serialized_message)
177
+ # This method is deprecated - transports now handle serialization
178
+ # For backward compatibility, try to decode as JSON
179
+ require 'json'
180
+ deserialized_data = JSON.parse(serialized_message)
173
181
 
174
182
  # Create new message instance with the complete deserialized data
175
183
  if deserialized_data.is_a?(Hash)
176
184
  # Convert string keys to symbols for compatibility with keyword arguments
177
185
  symbol_props = deserialized_data.transform_keys(&:to_sym)
178
186
 
179
- # With single-tier serialization, use the complete deserialized message structure
180
187
  message = self.new(**symbol_props)
181
188
 
182
189
  (self.logger || SmartMessage::Logger.default).debug { "[SmartMessage] Deserialized message: #{self.name}" }
@@ -53,7 +53,6 @@ module SmartMessage
53
53
  # SmartMessage.configure do |config|
54
54
  # config.logger = MyApp::Logger.new # Custom logger object
55
55
  # config.transport = MyApp::Transport.new
56
- # config.serializer = MyApp::Serializer.new
57
56
  # end
58
57
  #
59
58
  # # Explicitly disable logging:
@@ -71,17 +70,16 @@ module SmartMessage
71
70
  # class SpecialMessage < SmartMessage::Base
72
71
  # config do
73
72
  # logger MyApp::SpecialLogger.new # Override just the logger
74
- # # transport and serializer still use global defaults
73
+ # # transport still uses global defaults
75
74
  # end
76
75
  # end
77
76
  class Configuration
78
- attr_accessor :transport, :serializer, :log_level, :log_format, :log_include_source, :log_structured_data, :log_colorize, :log_options
77
+ attr_accessor :transport, :log_level, :log_format, :log_include_source, :log_structured_data, :log_colorize, :log_options
79
78
  attr_reader :logger
80
79
 
81
80
  def initialize
82
81
  @logger = nil
83
82
  @transport = nil
84
- @serializer = nil
85
83
  @logger_explicitly_set_to_nil = false
86
84
  @log_level = nil
87
85
  @log_format = nil
@@ -101,7 +99,6 @@ module SmartMessage
101
99
  def reset!
102
100
  @logger = nil
103
101
  @transport = nil
104
- @serializer = nil
105
102
  @logger_explicitly_set_to_nil = false
106
103
  @log_level = nil
107
104
  @log_format = nil
@@ -121,11 +118,6 @@ module SmartMessage
121
118
  !@transport.nil?
122
119
  end
123
120
 
124
- # Check if serializer is configured
125
- def serializer_configured?
126
- !@serializer.nil?
127
- end
128
-
129
121
  # Get the configured logger or no logging
130
122
  def default_logger
131
123
  case @logger
@@ -156,11 +148,6 @@ module SmartMessage
156
148
  @transport || framework_default_transport
157
149
  end
158
150
 
159
- # Get the configured serializer or framework default
160
- def default_serializer
161
- @serializer || framework_default_serializer
162
- end
163
-
164
151
  private
165
152
 
166
153
  # Framework's built-in default logger (Lumberjack)
@@ -187,13 +174,5 @@ module SmartMessage
187
174
  def framework_default_transport
188
175
  SmartMessage::Transport::RedisTransport.new
189
176
  end
190
-
191
- # Framework's built-in default serializer (JSON)
192
- def framework_default_serializer
193
- SmartMessage::Serializer::Json.new
194
- rescue
195
- # Fallback if JSON serializer is not available
196
- nil
197
- end
198
177
  end
199
178
  end
@@ -48,7 +48,7 @@ module SmartMessage
48
48
  # @param error_info [Hash] Error details including :error, :retry_count, :transport, etc.
49
49
  def enqueue(message, error_info = {})
50
50
  message_header = message._sm_header
51
- message_payload = message.encode
51
+ message_payload = JSON.generate(message.to_hash)
52
52
 
53
53
  entry = {
54
54
  timestamp: Time.now.iso8601,
@@ -28,5 +28,8 @@ module SmartMessage
28
28
  # A property validation failed
29
29
  class ValidationError < RuntimeError; end
30
30
 
31
+ # Publishing failed on all configured transports
32
+ class PublishError < RuntimeError; end
33
+
31
34
  end
32
35
  end
@@ -58,7 +58,7 @@ module SmartMessage
58
58
  required: false,
59
59
  description: "Optional unique identifier of the entity that should receive replies to this message. Defaults to 'from' entity if not specified"
60
60
 
61
- # Serialization tracking for wrapper architecture
61
+ # Serialization tracking for message architecture
62
62
  property :serializer,
63
63
  required: false,
64
64
  description: "Class name of the serializer used to encode the payload (e.g., 'SmartMessage::Serializer::Json'). Used by DLQ and cross-serializer gateway patterns"
@@ -10,7 +10,7 @@ module SmartMessage
10
10
  module Logger
11
11
  # Default logger implementation for SmartMessage
12
12
  #
13
- # This logger provides a simple Ruby Logger wrapper with enhanced formatting.
13
+ # This logger provides a simple Ruby Logger message with enhanced formatting.
14
14
  # Applications can easily configure Rails.logger or other loggers through
15
15
  # the global configuration system instead.
16
16
  #
@@ -4,34 +4,8 @@
4
4
 
5
5
  module SmartMessage
6
6
  # Messaging module for SmartMessage::Base
7
- # Handles message encoding and publishing operations
7
+ # Handles message publishing operations
8
8
  module Messaging
9
- # SMELL: How does the transport know how to decode a message before
10
- # it knows the message class? We need a wrapper around
11
- # the entire message in a known serialization. That
12
- # wrapper would contain two properties: _sm_header and
13
- # _sm_payload
14
-
15
- # NOTE: to publish a message it must first be encoded using a
16
- # serializer. The receive a subscribed to message it must
17
- # be decoded via a serializer from the transport to be processed.
18
- def encode
19
- raise Errors::SerializerNotConfigured if serializer_missing?
20
-
21
- serializer.encode(self)
22
- end
23
-
24
- # Convert message to hash with _sm_header and _sm_payload structure
25
- # This is the foundation for wrapper architecture
26
- def to_h
27
- # Update header with serializer info before converting
28
- _sm_header.serializer = serializer.class.to_s if serializer_configured?
29
-
30
- {
31
- :_sm_header => header_hash_with_symbols,
32
- :_sm_payload => payload_hash_with_symbols
33
- }
34
- end
35
9
 
36
10
 
37
11
  # NOTE: you publish instances; but, you subscribe/unsubscribe at
@@ -44,20 +18,46 @@ module SmartMessage
44
18
  # Update header with current publication info
45
19
  _sm_header.published_at = Time.now
46
20
  _sm_header.publisher_pid = Process.pid
47
- _sm_header.serializer = serializer.class.to_s if serializer_configured?
48
-
49
- # Single-tier serialization: serialize entire message with designated serializer
50
- serialized_message = encode
51
21
 
52
22
  raise Errors::TransportNotConfigured if transport_missing?
53
23
 
54
- # Transport receives the message class name (for channel routing) and serialized message
55
- (self.class.logger || SmartMessage::Logger.default).debug { "[SmartMessage::Messaging] About to call transport.publish" }
56
- transport.publish(_sm_header.message_class, serialized_message)
57
- (self.class.logger || SmartMessage::Logger.default).debug { "[SmartMessage::Messaging] transport.publish completed" }
24
+ # Get all configured transports (supports both single and multiple)
25
+ transport_list = transports
26
+ logger = self.class.logger || SmartMessage::Logger.default
27
+
28
+ # Track publication results for each transport
29
+ successful_transports = []
30
+ failed_transports = []
31
+
32
+ # Publish to each configured transport
33
+ transport_list.each do |transport_instance|
34
+ begin
35
+ # Transport handles serialization - just pass the message instance
36
+ logger.debug { "[SmartMessage::Messaging] About to call transport.publish on #{transport_instance.class.name.split('::').last}" }
37
+ transport_instance.publish(self)
38
+ logger.debug { "[SmartMessage::Messaging] transport.publish completed on #{transport_instance.class.name.split('::').last}" }
39
+
40
+ successful_transports << transport_instance.class.name.split('::').last
41
+ rescue => transport_error
42
+ logger.error { "[SmartMessage] Transport #{transport_instance.class.name.split('::').last} failed: #{transport_error.class.name} - #{transport_error.message}" }
43
+ failed_transports << { transport: transport_instance.class.name.split('::').last, error: transport_error }
44
+ end
45
+ end
46
+
47
+ # Log overall publication results
48
+ if successful_transports.any?
49
+ logger.info { "[SmartMessage] Published: #{self.class.name} via #{successful_transports.join(', ')}" }
50
+ end
58
51
 
59
- # Log the message publish
60
- (self.class.logger || SmartMessage::Logger.default).info { "[SmartMessage] Published: #{self.class.name} via #{transport.class.name.split('::').last}" }
52
+ if failed_transports.any?
53
+ logger.warn { "[SmartMessage] Failed transports for #{self.class.name}: #{failed_transports.map { |ft| ft[:transport] }.join(', ')}" }
54
+ end
55
+
56
+ # Raise error only if ALL transports failed
57
+ if successful_transports.empty? && failed_transports.any?
58
+ error_messages = failed_transports.map { |ft| "#{ft[:transport]}: #{ft[:error].message}" }.join('; ')
59
+ raise Errors::PublishError, "All transports failed: #{error_messages}"
60
+ end
61
61
 
62
62
  SS.add(_sm_header.message_class, 'publish')
63
63
  SS.get(_sm_header.message_class, 'publish')
@@ -67,34 +67,5 @@ module SmartMessage
67
67
  end
68
68
  end # def publish
69
69
 
70
- private
71
-
72
- # Convert header to hash with symbol keys
73
- def header_hash_with_symbols
74
- _sm_header.to_hash.transform_keys(&:to_sym)
75
- end
76
-
77
- # Extract all non-header properties into a hash with symbol keys
78
- # Performs deep symbolization on nested structures
79
- def payload_hash_with_symbols
80
- self.class.properties.each_with_object({}) do |prop, hash|
81
- next if prop == :_sm_header
82
- hash[prop.to_sym] = deep_symbolize_keys(self[prop])
83
- end
84
- end
85
-
86
- # Recursively convert all string keys to symbols in nested hashes and arrays
87
- def deep_symbolize_keys(obj)
88
- case obj
89
- when Hash
90
- obj.each_with_object({}) do |(key, value), result|
91
- result[key.to_sym] = deep_symbolize_keys(value)
92
- end
93
- when Array
94
- obj.map { |item| deep_symbolize_keys(item) }
95
- else
96
- obj
97
- end
98
- end
99
70
  end
100
71
  end
@@ -4,7 +4,7 @@
4
4
 
5
5
  module SmartMessage
6
6
  # Plugin configuration module for SmartMessage::Base
7
- # Handles transport, serializer, and logger configuration at both
7
+ # Handles transport and logger configuration at both
8
8
  # class and instance levels
9
9
  module Plugins
10
10
  def self.included(base)
@@ -12,7 +12,6 @@ module SmartMessage
12
12
  base.class_eval do
13
13
  # Class-level plugin storage
14
14
  class_variable_set(:@@transport, nil) unless class_variable_defined?(:@@transport)
15
- class_variable_set(:@@serializer, nil) unless class_variable_defined?(:@@serializer)
16
15
  class_variable_set(:@@logger, nil) unless class_variable_defined?(:@@logger)
17
16
  end
18
17
  end
@@ -31,9 +30,14 @@ module SmartMessage
31
30
  def transport(klass_or_instance = nil)
32
31
  if klass_or_instance.nil?
33
32
  # Return instance transport, class transport, or global configuration
34
- @transport || self.class.class_variable_get(:@@transport) || SmartMessage::Transport.default
33
+ # For backward compatibility, return first transport if array, otherwise single transport
34
+ transport_value = @transport || self.class.class_variable_get(:@@transport) || SmartMessage::Transport.default
35
+ transport_value.is_a?(Array) ? transport_value.first : transport_value
35
36
  else
36
- @transport = klass_or_instance
37
+ # Normalize to array for internal consistent handling
38
+ @transport = Array(klass_or_instance)
39
+ # Return the original value for backward compatibility with method chaining
40
+ klass_or_instance
37
41
  end
38
42
  end
39
43
 
@@ -45,26 +49,21 @@ module SmartMessage
45
49
  end
46
50
  def reset_transport; @transport = nil; end
47
51
 
52
+ # Utility methods for working with transport collections
53
+ def transports
54
+ # Get the raw transport value (which is internally stored as array)
55
+ raw_transport = @transport || self.class.class_variable_get(:@@transport) || SmartMessage::Transport.default
56
+ # Always return as array for consistent handling
57
+ raw_transport.is_a?(Array) ? raw_transport : Array(raw_transport)
58
+ end
48
59
 
49
- #########################################################
50
- ## instance-level serializer configuration
51
-
52
- def serializer(klass_or_instance = nil)
53
- if klass_or_instance.nil?
54
- # Return instance serializer, class serializer, or global configuration
55
- @serializer || self.class.class_variable_get(:@@serializer) || SmartMessage::Serializer.default
56
- else
57
- @serializer = klass_or_instance
58
- end
60
+ def single_transport?
61
+ transports.length == 1
59
62
  end
60
63
 
61
- def serializer_configured?; !serializer_missing?; end
62
- def serializer_missing?
63
- # Check if serializer is explicitly configured (without fallback to defaults)
64
- @serializer.nil? &&
65
- (self.class.class_variable_get(:@@serializer) rescue nil).nil?
64
+ def multiple_transports?
65
+ transports.length > 1
66
66
  end
67
- def reset_serializer; @serializer = nil; end
68
67
 
69
68
  module ClassMethods
70
69
  #########################################################
@@ -80,9 +79,14 @@ module SmartMessage
80
79
  def transport(klass_or_instance = nil)
81
80
  if klass_or_instance.nil?
82
81
  # Return class-level transport or fall back to global configuration
83
- class_variable_get(:@@transport) || SmartMessage::Transport.default
82
+ # For backward compatibility, return first transport if array, otherwise single transport
83
+ transport_value = class_variable_get(:@@transport) || SmartMessage::Transport.default
84
+ transport_value.is_a?(Array) ? transport_value.first : transport_value
84
85
  else
85
- class_variable_set(:@@transport, klass_or_instance)
86
+ # Normalize to array for internal consistent handling
87
+ class_variable_set(:@@transport, Array(klass_or_instance))
88
+ # Return the original value for backward compatibility with method chaining
89
+ klass_or_instance
86
90
  end
87
91
  end
88
92
 
@@ -93,6 +97,22 @@ module SmartMessage
93
97
  end
94
98
  def reset_transport; class_variable_set(:@@transport, nil); end
95
99
 
100
+ # Utility methods for working with transport collections
101
+ def transports
102
+ # Get the raw transport value (which is internally stored as array)
103
+ raw_transport = class_variable_get(:@@transport) || SmartMessage::Transport.default
104
+ # Always return as array for consistent handling
105
+ raw_transport.is_a?(Array) ? raw_transport : Array(raw_transport)
106
+ end
107
+
108
+ def single_transport?
109
+ transports.length == 1
110
+ end
111
+
112
+ def multiple_transports?
113
+ transports.length > 1
114
+ end
115
+
96
116
  #########################################################
97
117
  ## class-level logger configuration
98
118
 
@@ -111,25 +131,6 @@ module SmartMessage
111
131
  (class_variable_get(:@@logger) rescue nil).nil?
112
132
  end
113
133
  def reset_logger; class_variable_set(:@@logger, nil); end
114
-
115
- #########################################################
116
- ## class-level serializer configuration
117
-
118
- def serializer(klass_or_instance = nil)
119
- if klass_or_instance.nil?
120
- # Return class-level serializer or fall back to global configuration
121
- class_variable_get(:@@serializer) || SmartMessage::Serializer.default
122
- else
123
- class_variable_set(:@@serializer, klass_or_instance)
124
- end
125
- end
126
-
127
- def serializer_configured?; !serializer_missing?; end
128
- def serializer_missing?
129
- # Check if class-level serializer is explicitly configured (without fallback to defaults)
130
- (class_variable_get(:@@serializer) rescue nil).nil?
131
- end
132
- def reset_serializer; class_variable_set(:@@serializer, nil); end
133
134
  end
134
135
  end
135
136
  end
@@ -58,7 +58,7 @@ module SmartMessage::Serializer
58
58
 
59
59
  # Template methods for actual serialization (implement in subclasses)
60
60
  def do_encode(message_instance)
61
- # Default implementation: serialize only the payload portion for wrapper architecture
61
+ # Default implementation: serialize only the payload portion for message architecture
62
62
  # Subclasses can override this for specific serialization formats
63
63
  message_hash = message_instance.to_h
64
64
  payload_portion = message_hash[:_sm_payload]
@@ -6,8 +6,9 @@ module SmartMessage
6
6
  module Serializer
7
7
  class << self
8
8
  def default
9
- # Check global configuration first, then fall back to framework default
10
- SmartMessage.configuration.default_serializer
9
+ # Return the framework's default serializer class
10
+ # Note: Serialization is handled by transports, not messages
11
+ SmartMessage::Serializer::Json
11
12
  end
12
13
  end
13
14
  end
@@ -32,12 +32,11 @@ module SmartMessage
32
32
 
33
33
  # Call a registered proc handler
34
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)
35
+ def call_proc_handler(handler_id, message)
37
36
  handler_proc = class_variable_get(:@@proc_handlers)[handler_id]
38
37
  return unless handler_proc
39
38
 
40
- handler_proc.call(wrapper)
39
+ handler_proc.call(message)
41
40
  end
42
41
 
43
42
  # Remove a proc handler from the registry
@@ -61,8 +60,8 @@ module SmartMessage
61
60
  # an exception.
62
61
  #
63
62
  # @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)
63
+ # - String: Method name like "MyService.handle_message"
64
+ # - Proc: A proc/lambda that accepts (message)
66
65
  # - nil: Uses default "MessageClass.process" method
67
66
  # @param broadcast [Boolean, nil] Filter for broadcast messages (to: nil)
68
67
  # @param to [String, Array, nil] Filter for messages directed to specific entities
@@ -70,26 +69,25 @@ module SmartMessage
70
69
  # @param block [Proc] Alternative way to pass a processing block
71
70
  # @return [String] The identifier used for this subscription
72
71
  #
73
- # @example Using default handler
72
+ # @example Using default handler
74
73
  # MyMessage.subscribe
75
74
  #
76
75
  # @example Using custom method name with filtering
77
76
  # MyMessage.subscribe("MyService.handle_message", from: ['order-service'])
78
77
  #
79
78
  # @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}"
79
+ # MyMessage.subscribe(broadcast: true) do |message|
80
+ # puts "Received broadcast: #{message.content}"
83
81
  # end
84
82
  #
85
83
  # @example Entity-specific filtering (receives only messages from payment service)
86
84
  # MyMessage.subscribe("OrderService.process", from: ['payment'])
87
85
  #
88
- # @example Explicit to filter
86
+ # @example Explicit to filter
89
87
  # MyMessage.subscribe("AdminService.handle", to: 'admin', broadcast: false)
90
88
  def subscribe(process_method = nil, broadcast: nil, to: nil, from: nil, &block)
91
89
  message_class = whoami
92
-
90
+
93
91
  # Handle different parameter types
94
92
  if block_given?
95
93
  # Block was passed - use it as the handler
@@ -107,11 +105,11 @@ module SmartMessage
107
105
 
108
106
  # Subscriber identity is derived from the process method (handler)
109
107
  # This ensures each handler gets its own DDQ scope per message class
110
-
108
+
111
109
  # Normalize string filters to arrays
112
110
  to_filter = normalize_filter_value(to)
113
111
  from_filter = normalize_filter_value(from)
114
-
112
+
115
113
  # Create filter options (no explicit subscriber identity needed)
116
114
  filter_options = {
117
115
  broadcast: broadcast,
@@ -121,16 +119,16 @@ module SmartMessage
121
119
 
122
120
  # Add proper logging
123
121
  logger = SmartMessage::Logger.default
124
-
122
+
125
123
  begin
126
124
  raise Errors::TransportNotConfigured if transport_missing?
127
125
  transport.subscribe(message_class, process_method, filter_options)
128
-
126
+
129
127
  # Log successful subscription
130
128
  handler_desc = block_given? || process_method.respond_to?(:call) ? " with block/proc handler" : ""
131
129
  logger.info { "[SmartMessage] Subscribed: #{self.name}#{handler_desc}" }
132
130
  logger.debug { "[SmartMessage::Subscription] Subscribed #{message_class} with filters: #{filter_options}" }
133
-
131
+
134
132
  process_method
135
133
  rescue => e
136
134
  logger.error { "[SmartMessage] Error in message subscription: #{e.class.name} - #{e.message}" }
@@ -148,16 +146,16 @@ module SmartMessage
148
146
  process_method = message_class + '.process' if process_method.nil?
149
147
  # Add proper logging
150
148
  logger = SmartMessage::Logger.default
151
-
149
+
152
150
  begin
153
151
  if transport_configured?
154
152
  transport.unsubscribe(message_class, process_method)
155
-
153
+
156
154
  # If this was a proc handler, clean it up from the registry
157
155
  if proc_handler?(process_method)
158
156
  unregister_proc_handler(process_method)
159
157
  end
160
-
158
+
161
159
  # Log successful unsubscription
162
160
  logger.info { "[SmartMessage] Unsubscribed: #{self.name}" }
163
161
  logger.debug { "[SmartMessage::Subscription] Unsubscribed #{message_class} from #{process_method}" }
@@ -193,4 +191,4 @@ module SmartMessage
193
191
  end
194
192
  end
195
193
  end
196
- end
194
+ end