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.
- checksums.yaml +4 -4
- data/.github/workflows/deploy-github-pages.yml +38 -0
- data/.gitignore +5 -0
- data/CHANGELOG.md +64 -0
- data/Gemfile.lock +35 -4
- data/README.md +169 -71
- data/Rakefile +29 -4
- data/docs/assets/images/ddq_architecture.svg +130 -0
- data/docs/assets/images/dlq_architecture.svg +115 -0
- data/docs/assets/images/enhanced-dual-publishing.svg +136 -0
- data/docs/assets/images/enhanced-fluent-api.svg +149 -0
- data/docs/assets/images/enhanced-microservices-routing.svg +115 -0
- data/docs/assets/images/enhanced-pattern-matching.svg +107 -0
- data/docs/assets/images/fluent-api-demo.svg +59 -0
- data/docs/assets/images/performance-comparison.svg +161 -0
- data/docs/assets/images/redis-basic-architecture.svg +53 -0
- data/docs/assets/images/redis-enhanced-architecture.svg +88 -0
- data/docs/assets/images/redis-queue-architecture.svg +101 -0
- data/docs/assets/images/smart_message.jpg +0 -0
- data/docs/assets/images/smart_message_walking.jpg +0 -0
- data/docs/assets/images/smartmessage_architecture_overview.svg +173 -0
- data/docs/assets/images/transport-comparison-matrix.svg +171 -0
- data/docs/assets/javascripts/mathjax.js +17 -0
- data/docs/assets/stylesheets/extra.css +51 -0
- data/docs/{addressing.md → core-concepts/addressing.md} +5 -7
- data/docs/{architecture.md → core-concepts/architecture.md} +80 -145
- data/docs/{dispatcher.md → core-concepts/dispatcher.md} +21 -21
- data/docs/{message_filtering.md → core-concepts/message-filtering.md} +2 -3
- data/docs/{message_processing.md → core-concepts/message-processing.md} +17 -17
- data/docs/{troubleshooting.md → development/troubleshooting.md} +7 -7
- data/docs/{examples.md → getting-started/examples.md} +103 -89
- data/docs/{getting-started.md → getting-started/quick-start.md} +47 -23
- data/docs/index.md +64 -0
- data/docs/{dead_letter_queue.md → reference/dead-letter-queue.md} +2 -3
- data/docs/{logging.md → reference/logging.md} +1 -1
- data/docs/{message_deduplication.md → reference/message-deduplication.md} +1 -0
- data/docs/{proc_handlers_summary.md → reference/proc-handlers.md} +7 -6
- data/docs/reference/serializers.md +245 -0
- data/docs/{transports.md → reference/transports.md} +9 -11
- data/docs/transports/memory-transport.md +374 -0
- data/docs/transports/redis-transport-comparison.md +361 -0
- data/docs/transports/redis-transport.md +490 -0
- data/examples/README.md +104 -14
- data/examples/city_scenario/911_emergency_call_flow.svg +99 -0
- data/examples/city_scenario/README.md +515 -0
- data/examples/city_scenario/ai_visitor_intelligence_flow.svg +108 -0
- data/examples/city_scenario/citizen.rb +195 -0
- data/examples/city_scenario/city_diagram.svg +125 -0
- data/examples/city_scenario/common/health_monitor.rb +80 -0
- data/examples/city_scenario/common/logger.rb +30 -0
- data/examples/city_scenario/emergency_dispatch_center.rb +270 -0
- data/examples/city_scenario/fire_department.rb +446 -0
- data/examples/city_scenario/fire_emergency_flow.svg +95 -0
- data/examples/city_scenario/health_department.rb +100 -0
- data/examples/city_scenario/health_monitoring_system.svg +130 -0
- data/examples/city_scenario/house.rb +244 -0
- data/examples/city_scenario/local_bank.rb +217 -0
- data/examples/city_scenario/messages/emergency_911_message.rb +80 -0
- data/examples/city_scenario/messages/emergency_resolved_message.rb +42 -0
- data/examples/city_scenario/messages/fire_dispatch_message.rb +42 -0
- data/examples/city_scenario/messages/fire_emergency_message.rb +44 -0
- data/examples/city_scenario/messages/health_check_message.rb +21 -0
- data/examples/city_scenario/messages/health_status_message.rb +34 -0
- data/examples/city_scenario/messages/police_dispatch_message.rb +45 -0
- data/examples/city_scenario/messages/silent_alarm_message.rb +37 -0
- data/examples/city_scenario/police_department.rb +316 -0
- data/examples/city_scenario/redis_monitor.rb +129 -0
- data/examples/city_scenario/redis_stats.rb +743 -0
- data/examples/city_scenario/room_for_improvement.md +240 -0
- data/examples/city_scenario/security_emergency_flow.svg +95 -0
- data/examples/city_scenario/service_internal_architecture.svg +154 -0
- data/examples/city_scenario/smart_message_ai_agent.rb +364 -0
- data/examples/city_scenario/start_demo.sh +236 -0
- data/examples/city_scenario/stop_demo.sh +106 -0
- data/examples/city_scenario/visitor.rb +631 -0
- data/examples/{10_message_deduplication.rb → memory/01_message_deduplication_demo.rb} +1 -3
- data/examples/{09_dead_letter_queue_demo.rb → memory/02_dead_letter_queue_demo.rb} +10 -40
- data/examples/{01_point_to_point_orders.rb → memory/03_point_to_point_orders.rb} +1 -3
- data/examples/{02_publish_subscribe_events.rb → memory/04_publish_subscribe_events.rb} +1 -2
- data/examples/{03_many_to_many_chat.rb → memory/05_many_to_many_chat.rb} +1 -4
- data/examples/{show_me.rb → memory/06_pretty_print_demo.rb} +1 -1
- data/examples/{05_proc_handlers.rb → memory/07_proc_handlers_demo.rb} +1 -2
- data/examples/{06_custom_logger_example.rb → memory/08_custom_logger_demo.rb} +13 -14
- data/examples/{07_error_handling_scenarios.rb → memory/09_error_handling_demo.rb} +1 -4
- data/examples/{08_entity_addressing_basic.rb → memory/10_entity_addressing_basic.rb} +2 -8
- data/examples/{08_entity_addressing_with_filtering.rb → memory/11_entity_addressing_with_filtering.rb} +2 -6
- data/examples/{09_regex_filtering_microservices.rb → memory/12_regex_filtering_microservices.rb} +1 -2
- data/examples/{10_header_block_configuration.rb → memory/13_header_block_configuration.rb} +1 -6
- data/examples/{11_global_configuration_example.rb → memory/14_global_configuration_demo.rb} +17 -8
- data/examples/{show_logger.rb → memory/15_logger_demo.rb} +1 -2
- data/examples/memory/README.md +163 -0
- data/examples/memory/memory_transport_architecture.svg +90 -0
- data/examples/memory/point_to_point_pattern.svg +94 -0
- data/examples/memory/publish_subscribe_pattern.svg +125 -0
- data/examples/{04_redis_smart_home_iot.rb → redis/01_smart_home_iot_demo.rb} +1 -5
- data/examples/redis/README.md +228 -0
- data/examples/redis/alert_system_flow.svg +127 -0
- data/examples/redis/dashboard_status_flow.svg +107 -0
- data/examples/redis/device_command_flow.svg +113 -0
- data/examples/redis/redis_transport_architecture.svg +115 -0
- data/examples/{smart_home_iot_dataflow.md → redis/smart_home_iot_dataflow.md} +4 -116
- data/examples/redis/smart_home_system_architecture.svg +133 -0
- data/ideas/README.md +41 -0
- data/ideas/agents.md +1001 -0
- data/ideas/database_transport.md +980 -0
- data/ideas/improvement.md +359 -0
- data/ideas/meshage.md +1788 -0
- data/ideas/message_discovery.md +178 -0
- data/ideas/message_schema.md +1381 -0
- data/lib/smart_message/.idea/.gitignore +8 -0
- data/lib/smart_message/.idea/markdown.xml +6 -0
- data/lib/smart_message/.idea/misc.xml +4 -0
- data/lib/smart_message/.idea/modules.xml +8 -0
- data/lib/smart_message/.idea/smart_message.iml +16 -0
- data/lib/smart_message/.idea/vcs.xml +6 -0
- data/lib/smart_message/addressing.rb +15 -0
- data/lib/smart_message/base.rb +19 -12
- data/lib/smart_message/configuration.rb +2 -23
- data/lib/smart_message/dead_letter_queue.rb +1 -1
- data/lib/smart_message/logger.rb +15 -4
- data/lib/smart_message/messaging.rb +3 -62
- data/lib/smart_message/plugins.rb +6 -44
- data/lib/smart_message/serializer.rb +14 -0
- data/lib/smart_message/transport/base.rb +42 -8
- data/lib/smart_message/transport/memory_transport.rb +23 -4
- data/lib/smart_message/transport/redis_transport.rb +11 -0
- data/lib/smart_message/transport/stdout_transport.rb +28 -10
- data/lib/smart_message/transport.rb +33 -1
- data/lib/smart_message/version.rb +1 -1
- data/lib/smart_message.rb +5 -52
- data/mkdocs.yml +184 -0
- data/p2p_plan.md +326 -0
- data/p2p_roadmap.md +287 -0
- data/smart_message.gemspec +2 -0
- data/smart_message.svg +51 -0
- metadata +145 -45
- data/docs/README.md +0 -57
- data/docs/serializers.md +0 -575
- data/examples/dead_letters.jsonl +0 -12
- data/examples/temp.txt +0 -94
- data/examples/tmux_chat/README.md +0 -283
- data/examples/tmux_chat/bot_agent.rb +0 -278
- data/examples/tmux_chat/human_agent.rb +0 -199
- data/examples/tmux_chat/room_monitor.rb +0 -160
- data/examples/tmux_chat/shared_chat_system.rb +0 -328
- data/examples/tmux_chat/start_chat_demo.sh +0 -190
- data/examples/tmux_chat/stop_chat_demo.sh +0 -22
- /data/docs/{properties.md → core-concepts/properties.md} +0 -0
- /data/docs/{ideas_to_think_about.md → development/ideas.md} +0 -0
@@ -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>
|
@@ -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;
|
data/lib/smart_message/base.rb
CHANGED
@@ -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
|
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] #
|
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
|
-
#
|
171
|
-
|
172
|
-
|
173
|
-
|
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
|
73
|
+
# # transport still uses global defaults
|
75
74
|
# end
|
76
75
|
# end
|
77
76
|
class Configuration
|
78
|
-
attr_accessor :transport, :
|
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.
|
51
|
+
message_payload = JSON.generate(message.to_hash)
|
52
52
|
|
53
53
|
entry = {
|
54
54
|
timestamp: Time.now.iso8601,
|
data/lib/smart_message/logger.rb
CHANGED
@@ -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
|
-
|
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
|
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
|
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(
|
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
|
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?; !
|
109
|
-
def logger_missing
|
110
|
-
|
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
|
48
|
-
|
49
|
-
|
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
|
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
|
-
|
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(
|
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
|
-
#
|
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
|
-
|
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
|
-
|
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
|
-
|
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])
|