smart_message 0.0.10 → 0.0.13

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 (149) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/deploy-github-pages.yml +38 -0
  3. data/.gitignore +5 -0
  4. data/CHANGELOG.md +64 -0
  5. data/Gemfile.lock +35 -4
  6. data/README.md +169 -71
  7. data/Rakefile +29 -4
  8. data/docs/assets/images/ddq_architecture.svg +130 -0
  9. data/docs/assets/images/dlq_architecture.svg +115 -0
  10. data/docs/assets/images/enhanced-dual-publishing.svg +136 -0
  11. data/docs/assets/images/enhanced-fluent-api.svg +149 -0
  12. data/docs/assets/images/enhanced-microservices-routing.svg +115 -0
  13. data/docs/assets/images/enhanced-pattern-matching.svg +107 -0
  14. data/docs/assets/images/fluent-api-demo.svg +59 -0
  15. data/docs/assets/images/performance-comparison.svg +161 -0
  16. data/docs/assets/images/redis-basic-architecture.svg +53 -0
  17. data/docs/assets/images/redis-enhanced-architecture.svg +88 -0
  18. data/docs/assets/images/redis-queue-architecture.svg +101 -0
  19. data/docs/assets/images/smart_message.jpg +0 -0
  20. data/docs/assets/images/smart_message_walking.jpg +0 -0
  21. data/docs/assets/images/smartmessage_architecture_overview.svg +173 -0
  22. data/docs/assets/images/transport-comparison-matrix.svg +171 -0
  23. data/docs/assets/javascripts/mathjax.js +17 -0
  24. data/docs/assets/stylesheets/extra.css +51 -0
  25. data/docs/{addressing.md → core-concepts/addressing.md} +5 -7
  26. data/docs/{architecture.md → core-concepts/architecture.md} +80 -145
  27. data/docs/{dispatcher.md → core-concepts/dispatcher.md} +21 -21
  28. data/docs/{message_filtering.md → core-concepts/message-filtering.md} +2 -3
  29. data/docs/{message_processing.md → core-concepts/message-processing.md} +17 -17
  30. data/docs/{troubleshooting.md → development/troubleshooting.md} +7 -7
  31. data/docs/{examples.md → getting-started/examples.md} +103 -89
  32. data/docs/{getting-started.md → getting-started/quick-start.md} +47 -23
  33. data/docs/index.md +64 -0
  34. data/docs/{dead_letter_queue.md → reference/dead-letter-queue.md} +2 -3
  35. data/docs/{logging.md → reference/logging.md} +1 -1
  36. data/docs/{message_deduplication.md → reference/message-deduplication.md} +1 -0
  37. data/docs/{proc_handlers_summary.md → reference/proc-handlers.md} +7 -6
  38. data/docs/reference/serializers.md +245 -0
  39. data/docs/{transports.md → reference/transports.md} +9 -11
  40. data/docs/transports/memory-transport.md +374 -0
  41. data/docs/transports/redis-transport-comparison.md +361 -0
  42. data/docs/transports/redis-transport.md +490 -0
  43. data/examples/README.md +104 -14
  44. data/examples/city_scenario/911_emergency_call_flow.svg +99 -0
  45. data/examples/city_scenario/README.md +515 -0
  46. data/examples/city_scenario/ai_visitor_intelligence_flow.svg +108 -0
  47. data/examples/city_scenario/citizen.rb +195 -0
  48. data/examples/city_scenario/city_diagram.svg +125 -0
  49. data/examples/city_scenario/common/health_monitor.rb +80 -0
  50. data/examples/city_scenario/common/logger.rb +30 -0
  51. data/examples/city_scenario/emergency_dispatch_center.rb +270 -0
  52. data/examples/city_scenario/fire_department.rb +446 -0
  53. data/examples/city_scenario/fire_emergency_flow.svg +95 -0
  54. data/examples/city_scenario/health_department.rb +100 -0
  55. data/examples/city_scenario/health_monitoring_system.svg +130 -0
  56. data/examples/city_scenario/house.rb +244 -0
  57. data/examples/city_scenario/local_bank.rb +217 -0
  58. data/examples/city_scenario/messages/emergency_911_message.rb +80 -0
  59. data/examples/city_scenario/messages/emergency_resolved_message.rb +42 -0
  60. data/examples/city_scenario/messages/fire_dispatch_message.rb +42 -0
  61. data/examples/city_scenario/messages/fire_emergency_message.rb +44 -0
  62. data/examples/city_scenario/messages/health_check_message.rb +21 -0
  63. data/examples/city_scenario/messages/health_status_message.rb +34 -0
  64. data/examples/city_scenario/messages/police_dispatch_message.rb +45 -0
  65. data/examples/city_scenario/messages/silent_alarm_message.rb +37 -0
  66. data/examples/city_scenario/police_department.rb +316 -0
  67. data/examples/city_scenario/redis_monitor.rb +129 -0
  68. data/examples/city_scenario/redis_stats.rb +743 -0
  69. data/examples/city_scenario/room_for_improvement.md +240 -0
  70. data/examples/city_scenario/security_emergency_flow.svg +95 -0
  71. data/examples/city_scenario/service_internal_architecture.svg +154 -0
  72. data/examples/city_scenario/smart_message_ai_agent.rb +364 -0
  73. data/examples/city_scenario/start_demo.sh +236 -0
  74. data/examples/city_scenario/stop_demo.sh +106 -0
  75. data/examples/city_scenario/visitor.rb +631 -0
  76. data/examples/{10_message_deduplication.rb → memory/01_message_deduplication_demo.rb} +1 -3
  77. data/examples/{09_dead_letter_queue_demo.rb → memory/02_dead_letter_queue_demo.rb} +10 -40
  78. data/examples/{01_point_to_point_orders.rb → memory/03_point_to_point_orders.rb} +1 -3
  79. data/examples/{02_publish_subscribe_events.rb → memory/04_publish_subscribe_events.rb} +1 -2
  80. data/examples/{03_many_to_many_chat.rb → memory/05_many_to_many_chat.rb} +1 -4
  81. data/examples/{show_me.rb → memory/06_pretty_print_demo.rb} +1 -1
  82. data/examples/{05_proc_handlers.rb → memory/07_proc_handlers_demo.rb} +1 -2
  83. data/examples/{06_custom_logger_example.rb → memory/08_custom_logger_demo.rb} +13 -14
  84. data/examples/{07_error_handling_scenarios.rb → memory/09_error_handling_demo.rb} +1 -4
  85. data/examples/{08_entity_addressing_basic.rb → memory/10_entity_addressing_basic.rb} +2 -8
  86. data/examples/{08_entity_addressing_with_filtering.rb → memory/11_entity_addressing_with_filtering.rb} +2 -6
  87. data/examples/{09_regex_filtering_microservices.rb → memory/12_regex_filtering_microservices.rb} +1 -2
  88. data/examples/{10_header_block_configuration.rb → memory/13_header_block_configuration.rb} +1 -6
  89. data/examples/{11_global_configuration_example.rb → memory/14_global_configuration_demo.rb} +17 -8
  90. data/examples/{show_logger.rb → memory/15_logger_demo.rb} +1 -2
  91. data/examples/memory/README.md +163 -0
  92. data/examples/memory/memory_transport_architecture.svg +90 -0
  93. data/examples/memory/point_to_point_pattern.svg +94 -0
  94. data/examples/memory/publish_subscribe_pattern.svg +125 -0
  95. data/examples/{04_redis_smart_home_iot.rb → redis/01_smart_home_iot_demo.rb} +1 -5
  96. data/examples/redis/README.md +228 -0
  97. data/examples/redis/alert_system_flow.svg +127 -0
  98. data/examples/redis/dashboard_status_flow.svg +107 -0
  99. data/examples/redis/device_command_flow.svg +113 -0
  100. data/examples/redis/redis_transport_architecture.svg +115 -0
  101. data/examples/{smart_home_iot_dataflow.md → redis/smart_home_iot_dataflow.md} +4 -116
  102. data/examples/redis/smart_home_system_architecture.svg +133 -0
  103. data/ideas/README.md +41 -0
  104. data/ideas/agents.md +1001 -0
  105. data/ideas/database_transport.md +980 -0
  106. data/ideas/improvement.md +359 -0
  107. data/ideas/meshage.md +1788 -0
  108. data/ideas/message_discovery.md +178 -0
  109. data/ideas/message_schema.md +1381 -0
  110. data/lib/smart_message/.idea/.gitignore +8 -0
  111. data/lib/smart_message/.idea/markdown.xml +6 -0
  112. data/lib/smart_message/.idea/misc.xml +4 -0
  113. data/lib/smart_message/.idea/modules.xml +8 -0
  114. data/lib/smart_message/.idea/smart_message.iml +16 -0
  115. data/lib/smart_message/.idea/vcs.xml +6 -0
  116. data/lib/smart_message/addressing.rb +15 -0
  117. data/lib/smart_message/base.rb +19 -12
  118. data/lib/smart_message/configuration.rb +2 -23
  119. data/lib/smart_message/dead_letter_queue.rb +1 -1
  120. data/lib/smart_message/logger.rb +15 -4
  121. data/lib/smart_message/messaging.rb +3 -62
  122. data/lib/smart_message/plugins.rb +6 -44
  123. data/lib/smart_message/serializer.rb +14 -0
  124. data/lib/smart_message/transport/base.rb +42 -8
  125. data/lib/smart_message/transport/memory_transport.rb +23 -4
  126. data/lib/smart_message/transport/redis_transport.rb +11 -0
  127. data/lib/smart_message/transport/stdout_transport.rb +28 -10
  128. data/lib/smart_message/transport.rb +33 -1
  129. data/lib/smart_message/version.rb +1 -1
  130. data/lib/smart_message.rb +5 -52
  131. data/mkdocs.yml +184 -0
  132. data/p2p_plan.md +326 -0
  133. data/p2p_roadmap.md +287 -0
  134. data/smart_message.gemspec +2 -0
  135. data/smart_message.svg +51 -0
  136. metadata +145 -45
  137. data/docs/README.md +0 -57
  138. data/docs/serializers.md +0 -575
  139. data/examples/dead_letters.jsonl +0 -12
  140. data/examples/temp.txt +0 -94
  141. data/examples/tmux_chat/README.md +0 -283
  142. data/examples/tmux_chat/bot_agent.rb +0 -278
  143. data/examples/tmux_chat/human_agent.rb +0 -199
  144. data/examples/tmux_chat/room_monitor.rb +0 -160
  145. data/examples/tmux_chat/shared_chat_system.rb +0 -328
  146. data/examples/tmux_chat/start_chat_demo.sh +0 -190
  147. data/examples/tmux_chat/stop_chat_demo.sh +0 -22
  148. /data/docs/{properties.md → core-concepts/properties.md} +0 -0
  149. /data/docs/{ideas_to_think_about.md → development/ideas.md} +0 -0
@@ -0,0 +1,8 @@
1
+ # Default ignored files
2
+ /shelf/
3
+ /workspace.xml
4
+ # Editor-based HTTP Client requests
5
+ /httpRequests/
6
+ # Datasource local storage ignored files
7
+ /dataSources/
8
+ /dataSources.local.xml
@@ -0,0 +1,6 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="MarkdownSettings">
4
+ <option name="fileGroupingEnabled" value="true" />
5
+ </component>
6
+ </project>
@@ -0,0 +1,4 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="ProjectRootManager" version="2" project-jdk-name="ruby-2.6.10-p210" project-jdk-type="RUBY_SDK" />
4
+ </project>
@@ -0,0 +1,8 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="ProjectModuleManager">
4
+ <modules>
5
+ <module fileurl="file://$PROJECT_DIR$/.idea/smart_message.iml" filepath="$PROJECT_DIR$/.idea/smart_message.iml" />
6
+ </modules>
7
+ </component>
8
+ </project>
@@ -0,0 +1,16 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <module type="RUBY_MODULE" version="4">
3
+ <component name="ModuleRunConfigurationManager">
4
+ <shared />
5
+ </component>
6
+ <component name="NewModuleRootManager">
7
+ <content url="file://$MODULE_DIR$">
8
+ <sourceFolder url="file://$MODULE_DIR$/features" isTestSource="true" />
9
+ <sourceFolder url="file://$MODULE_DIR$/spec" isTestSource="true" />
10
+ <sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
11
+ </content>
12
+ <orderEntry type="inheritedJdk" />
13
+ <orderEntry type="sourceFolder" forTests="false" />
14
+ <orderEntry type="library" scope="PROVIDED" name="bundler (v1.17.2, ruby-2.6.10-p210) [gem]" level="application" />
15
+ </component>
16
+ </module>
@@ -0,0 +1,6 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="VcsDirectoryMappings">
4
+ <mapping directory="$PROJECT_DIR$/../.." vcs="Git" />
5
+ </component>
6
+ </project>
@@ -202,6 +202,11 @@ module SmartMessage
202
202
  end
203
203
  end
204
204
 
205
+ # Setter method for from - allows ClassName.from = 'value' syntax
206
+ def from=(entity_id)
207
+ from(entity_id)
208
+ end
209
+
205
210
  def from_configured?; !from.nil?; end
206
211
  def from_missing?; from.nil?; end
207
212
  def reset_from;
@@ -224,6 +229,11 @@ module SmartMessage
224
229
  end
225
230
  end
226
231
 
232
+ # Setter method for to - allows ClassName.to = 'value' syntax
233
+ def to=(entity_id)
234
+ to(entity_id)
235
+ end
236
+
227
237
  def to_configured?; !to.nil?; end
228
238
  def to_missing?; to.nil?; end
229
239
  def reset_to;
@@ -246,6 +256,11 @@ module SmartMessage
246
256
  end
247
257
  end
248
258
 
259
+ # Setter method for reply_to - allows ClassName.reply_to = 'value' syntax
260
+ def reply_to=(entity_id)
261
+ reply_to(entity_id)
262
+ end
263
+
249
264
  def reply_to_configured?; !reply_to.nil?; end
250
265
  def reply_to_missing?; reply_to.nil?; end
251
266
  def reset_reply_to;
@@ -50,7 +50,6 @@ module SmartMessage
50
50
  # instance-level override of class plugins
51
51
  # Don't use fallback defaults here - let the methods handle fallbacks when actually used
52
52
  @transport = (self.class.class_variable_get(:@@transport) rescue nil)
53
- @serializer = (self.class.class_variable_get(:@@serializer) rescue nil)
54
53
 
55
54
  # Check if we're reconstructing from serialized data (complete header provided)
56
55
  if props[:_sm_header]
@@ -71,8 +70,8 @@ module SmartMessage
71
70
  @to = header.to
72
71
  @reply_to = header.reply_to
73
72
 
74
- # Extract payload properties
75
- payload_props = props[:_sm_payload] || {}
73
+ # Extract payload properties directly from props (flat structure)
74
+ payload_props = props.except(:_sm_header)
76
75
 
77
76
  attributes = {
78
77
  _sm_header: header
@@ -132,7 +131,7 @@ module SmartMessage
132
131
  # Extract payload properties (non-header properties)
133
132
  payload_props = self.class.properties.each_with_object({}) do |prop, hash|
134
133
  next if prop == :_sm_header
135
- hash[prop.to_s] = self[prop] # Use string keys to match old format
134
+ hash[prop.to_s] = self[prop] # Access property value
136
135
  end
137
136
 
138
137
  JSON.generate(payload_props)
@@ -154,31 +153,39 @@ module SmartMessage
154
153
 
155
154
 
156
155
 
156
+ # Convert message to hash for serialization
157
+ def to_hash
158
+ # Get all properties and their values
159
+ hash = {}
160
+ self.class.properties.each do |prop|
161
+ hash[prop] = self[prop]
162
+ end
163
+ hash
164
+ end
165
+
157
166
  ###########################################################
158
167
  ## class methods
159
168
 
160
169
  class << self
161
170
  # Decode a complete serialized message back to a message instance
171
+ # Note: This method is no longer used with transport-based serialization
172
+ # Transports handle decoding and create message instances directly
162
173
  # @param serialized_message [String] The serialized message content
163
174
  # @return [SmartMessage::Base] The decoded message instance
164
175
  def decode(serialized_message)
165
- logger = SmartMessage::Logger.default
166
-
167
176
  begin
168
177
  (self.logger || SmartMessage::Logger.default).info { "[SmartMessage] Received: #{self.name} (#{serialized_message.bytesize} bytes)" }
169
178
 
170
- # Use the class's configured serializer to decode the message
171
- serializer = self.serializer || SmartMessage::Serializer.default
172
-
173
- # Deserialize the complete message
174
- deserialized_data = serializer.decode(serialized_message)
179
+ # This method is deprecated - transports now handle serialization
180
+ # For backward compatibility, try to decode as JSON
181
+ require 'json'
182
+ deserialized_data = JSON.parse(serialized_message)
175
183
 
176
184
  # Create new message instance with the complete deserialized data
177
185
  if deserialized_data.is_a?(Hash)
178
186
  # Convert string keys to symbols for compatibility with keyword arguments
179
187
  symbol_props = deserialized_data.transform_keys(&:to_sym)
180
188
 
181
- # With single-tier serialization, use the complete deserialized message structure
182
189
  message = self.new(**symbol_props)
183
190
 
184
191
  (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 => e
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,
@@ -9,15 +9,26 @@ require_relative 'logger/lumberjack'
9
9
  module SmartMessage
10
10
  module Logger
11
11
  class << self
12
- # Global default logger instance
13
- def default
14
- @default ||= Lumberjack.new
12
+ # Global default logger instance - uses configuration if available
13
+ def default(options = {})
14
+ # Always check current configuration first (don't cache when config is available)
15
+ if defined?(SmartMessage.configuration) && SmartMessage.configuration.logger_configured?
16
+ SmartMessage.configuration.default_logger
17
+ else
18
+ # Cache the framework default logger only when no configuration
19
+ @default ||= Lumberjack.new(**options)
20
+ end
15
21
  end
16
22
 
17
23
  # Set the default logger
18
24
  def default=(logger)
19
25
  @default = logger
20
26
  end
27
+
28
+ # Reset the cached default logger
29
+ def reset!
30
+ @default = nil
31
+ end
21
32
  end
22
33
  end
23
- end
34
+ end
@@ -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,16 +18,12 @@ 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
24
+ # Transport now handles serialization - just pass the message instance
55
25
  (self.class.logger || SmartMessage::Logger.default).debug { "[SmartMessage::Messaging] About to call transport.publish" }
56
- transport.publish(_sm_header.message_class, serialized_message)
26
+ transport.publish(self)
57
27
  (self.class.logger || SmartMessage::Logger.default).debug { "[SmartMessage::Messaging] transport.publish completed" }
58
28
 
59
29
  # Log the message publish
@@ -67,34 +37,5 @@ module SmartMessage
67
37
  end
68
38
  end # def publish
69
39
 
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
40
  end
100
41
  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
@@ -45,27 +44,6 @@ module SmartMessage
45
44
  end
46
45
  def reset_transport; @transport = nil; end
47
46
 
48
-
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
59
- end
60
-
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?
66
- end
67
- def reset_serializer; @serializer = nil; end
68
-
69
47
  module ClassMethods
70
48
  #########################################################
71
49
  ## class-level configuration
@@ -105,28 +83,12 @@ module SmartMessage
105
83
  end
106
84
  end
107
85
 
108
- def logger_configured?; !logger.nil?; end
109
- def logger_missing?; logger.nil?; end
110
- def reset_logger; class_variable_set(:@@logger, nil); end
111
-
112
- #########################################################
113
- ## class-level serializer configuration
114
-
115
- def serializer(klass_or_instance = nil)
116
- if klass_or_instance.nil?
117
- # Return class-level serializer or fall back to global configuration
118
- class_variable_get(:@@serializer) || SmartMessage::Serializer.default
119
- else
120
- class_variable_set(:@@serializer, klass_or_instance)
121
- end
86
+ def logger_configured?; !logger_missing?; end
87
+ def logger_missing?
88
+ # Check if class-level logger is explicitly configured (without fallback to defaults)
89
+ (class_variable_get(:@@logger) rescue nil).nil?
122
90
  end
123
-
124
- def serializer_configured?; !serializer_missing?; end
125
- def serializer_missing?
126
- # Check if class-level serializer is explicitly configured (without fallback to defaults)
127
- (class_variable_get(:@@serializer) rescue nil).nil?
128
- end
129
- def reset_serializer; class_variable_set(:@@serializer, nil); end
91
+ def reset_logger; class_variable_set(:@@logger, nil); end
130
92
  end
131
93
  end
132
94
  end
@@ -0,0 +1,14 @@
1
+ # lib/smart_message/serializer.rb
2
+ # encoding: utf-8
3
+ # frozen_string_literal: true
4
+
5
+ module SmartMessage
6
+ module Serializer
7
+ class << self
8
+ def default
9
+ # Check global configuration first, then fall back to framework default
10
+ SmartMessage.configuration.default_serializer
11
+ end
12
+ end
13
+ end
14
+ end
@@ -11,15 +11,17 @@ module SmartMessage
11
11
  class Base
12
12
  include BreakerMachines::DSL
13
13
 
14
- attr_reader :options, :dispatcher
14
+ attr_reader :options, :dispatcher, :serializer
15
15
 
16
16
  def initialize(**options)
17
17
  @options = default_options.merge(options)
18
18
  @dispatcher = options[:dispatcher] || SmartMessage::Dispatcher.new
19
+ @serializer = options[:serializer] || default_serializer
19
20
  configure
20
21
  configure_transport_circuit_breakers
21
22
 
22
23
  logger.debug { "[SmartMessage::Transport::#{self.class.name.split('::').last}] Initialized with options: #{@options}" }
24
+ logger.debug { "[SmartMessage::Transport::#{self.class.name.split('::').last}] Using serializer: #{@serializer.class.name}" }
23
25
  rescue => e
24
26
  logger&.error { "[SmartMessage] Error in transport initialization: #{e.class.name} - #{e.message}" }
25
27
  raise
@@ -43,10 +45,20 @@ module SmartMessage
43
45
  {}
44
46
  end
45
47
 
48
+ # Default serializer for this transport (override in subclasses)
49
+ def default_serializer
50
+ SmartMessage::Serializer::Json.new
51
+ end
52
+
46
53
  # Publish a message with circuit breaker protection
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)
54
+ # @param message [SmartMessage::Base] The message instance to publish
55
+ def publish(message)
56
+ # Extract routing info from message header
57
+ message_class = message._sm_header.message_class
58
+
59
+ # Serialize the entire message (flat structure with _sm_header)
60
+ serialized_message = encode_message(message)
61
+
50
62
  circuit(:transport_publish).wrap do
51
63
  do_publish(message_class, serialized_message)
52
64
  end
@@ -58,7 +70,7 @@ module SmartMessage
58
70
  raise unless e.is_a?(Hash) && e[:circuit_breaker]
59
71
 
60
72
  # Handle circuit breaker fallback
61
- handle_publish_fallback(e, message_class, serialized_message)
73
+ handle_publish_fallback(e, message._sm_header.message_class, serialized_message)
62
74
  end
63
75
 
64
76
  # Template method for actual publishing (implement in subclasses)
@@ -150,13 +162,32 @@ module SmartMessage
150
162
  end
151
163
  end
152
164
 
165
+ # Encode a message using the transport's serializer
166
+ # @param message [SmartMessage::Base] The message to encode
167
+ # @return [String] The serialized message
168
+ def encode_message(message)
169
+ # Update header with serializer info
170
+ message._sm_header.serializer = @serializer.class.to_s
171
+
172
+ # Serialize the entire message as a flat structure
173
+ @serializer.encode(message.to_hash)
174
+ end
175
+
176
+ # Decode a message using the transport's serializer
177
+ # @param serialized_message [String] The serialized message
178
+ # @return [Hash] The decoded message data
179
+ def decode_message(serialized_message)
180
+ @serializer.decode(serialized_message)
181
+ end
182
+
153
183
  # Receive and route a message (called by transport implementations)
154
184
  # @param message_class [String] The message class name
155
185
  # @param serialized_message [String] The serialized message content
156
186
  protected
157
187
 
158
188
  def receive(message_class, serialized_message)
159
- # Decode the message using the class's configured serializer
189
+ # Decode the message using transport's serializer
190
+ decoded_data = decode_message(serialized_message)
160
191
 
161
192
  # Add defensive check for message_class type
162
193
  unless message_class.respond_to?(:constantize)
@@ -165,10 +196,13 @@ module SmartMessage
165
196
  raise ArgumentError, "message_class must be a String, got #{message_class.class.name}"
166
197
  end
167
198
 
199
+ # Reconstruct the message instance from flat structure
168
200
  message_class_obj = message_class.constantize
169
- decoded_message = message_class_obj.decode(serialized_message)
201
+ # Convert string keys to symbols for keyword arguments
202
+ symbol_data = decoded_data.transform_keys(&:to_sym)
203
+ message = message_class_obj.new(**symbol_data)
170
204
 
171
- @dispatcher.route(decoded_message)
205
+ @dispatcher.route(message)
172
206
  rescue => e
173
207
  logger.error { "[SmartMessage] Error in transport receive: #{e.class.name} - #{e.message}" }
174
208
  logger.error { "[SmartMessage] message_class: #{message_class.inspect} (#{message_class.class.name})" }
@@ -16,30 +16,49 @@ module SmartMessage
16
16
  }
17
17
  end
18
18
 
19
+ # Memory transport doesn't need serialization
20
+ def default_serializer
21
+ nil
22
+ end
23
+
19
24
  def configure
20
25
  @messages = []
21
26
  @message_mutex = Mutex.new
22
27
  end
23
28
 
24
- # Publish message to memory queue
29
+ # Implement do_publish for memory transport (no serialization needed)
25
30
  def do_publish(message_class, serialized_message)
31
+ # For memory transport, serialized_message is actually the message object
32
+ message = serialized_message
33
+
26
34
  @message_mutex.synchronize do
27
35
  # Prevent memory overflow
28
36
  @messages.shift if @messages.size >= @options[:max_messages]
29
37
 
38
+ # Store the actual message object, no serialization needed
30
39
  @messages << {
31
40
  message_class: message_class,
32
- serialized_message: serialized_message,
41
+ message: message.dup, # Store a copy to prevent mutation
33
42
  published_at: Time.now
34
43
  }
35
44
  end
36
45
 
37
46
  # Auto-process if enabled
38
47
  if @options[:auto_process]
39
- receive(message_class, serialized_message)
48
+ # Route directly without serialization
49
+ @dispatcher.route(message)
40
50
  end
41
51
  end
42
52
 
53
+ # Override encode_message to return the message object directly
54
+ def encode_message(message)
55
+ # Update header with serializer info (even though we don't serialize)
56
+ message._sm_header.serializer = 'none'
57
+
58
+ # Return the message object itself (no encoding needed)
59
+ message
60
+ end
61
+
43
62
  # Get all stored messages
44
63
  def all_messages
45
64
  @message_mutex.synchronize { @messages.dup }
@@ -59,7 +78,7 @@ module SmartMessage
59
78
  def process_all
60
79
  messages_to_process = @message_mutex.synchronize { @messages.dup }
61
80
  messages_to_process.each do |msg|
62
- receive(msg[:message_class], msg[:serialized_message])
81
+ @dispatcher.route(msg[:message])
63
82
  end
64
83
  end
65
84
 
@@ -24,6 +24,17 @@ module SmartMessage
24
24
  }
25
25
  end
26
26
 
27
+ # Default to MessagePack for Redis (efficient binary format)
28
+ def default_serializer
29
+ # Try MessagePack first, fall back to JSON
30
+ begin
31
+ require 'smart_message/serializer/message_pack'
32
+ SmartMessage::Serializer::MessagePack.new
33
+ rescue LoadError
34
+ SmartMessage::Serializer::Json.new
35
+ end
36
+ end
37
+
27
38
  def configure
28
39
  @redis_pub = Redis.new(url: @options[:url], db: @options[:db])
29
40
  @redis_sub = Redis.new(url: @options[:url], db: @options[:db])