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.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/CHANGELOG.md +155 -1
- data/Gemfile.lock +6 -6
- data/README.md +71 -25
- data/docs/core-concepts/architecture.md +5 -10
- data/docs/getting-started/examples.md +0 -12
- data/docs/getting-started/quick-start.md +4 -9
- data/docs/index.md +6 -4
- data/docs/reference/serializers.md +160 -488
- data/docs/reference/transports.md +47 -146
- data/docs/transports/memory-transport.md +2 -1
- data/docs/transports/multi-transport.md +484 -0
- data/docs/transports/redis-transport-comparison.md +215 -350
- data/docs/transports/redis-transport.md +3 -22
- data/examples/README.md +6 -9
- data/examples/city_scenario/README.md +1 -1
- data/examples/city_scenario/messages/emergency_911_message.rb +0 -1
- data/examples/city_scenario/messages/emergency_resolved_message.rb +0 -1
- data/examples/city_scenario/messages/fire_dispatch_message.rb +0 -1
- data/examples/city_scenario/messages/fire_emergency_message.rb +0 -1
- data/examples/city_scenario/messages/health_check_message.rb +0 -1
- data/examples/city_scenario/messages/health_status_message.rb +0 -1
- data/examples/city_scenario/messages/police_dispatch_message.rb +0 -1
- data/examples/city_scenario/messages/silent_alarm_message.rb +0 -1
- data/examples/file/00_run_all_file_demos.rb +260 -0
- data/examples/file/01_basic_file_transport_demo.rb +237 -0
- data/examples/file/02_fifo_transport_demo.rb +289 -0
- data/examples/file/03_file_watching_demo.rb +332 -0
- data/examples/file/04_multi_transport_file_demo.rb +432 -0
- data/examples/file/README.md +257 -0
- data/examples/memory/00_run_all_demos.rb +317 -0
- data/examples/memory/01_message_deduplication_demo.rb +18 -32
- data/examples/memory/02_dead_letter_queue_demo.rb +9 -12
- data/examples/memory/03_point_to_point_orders.rb +3 -5
- data/examples/memory/04_publish_subscribe_events.rb +15 -16
- data/examples/memory/05_many_to_many_chat.rb +19 -22
- data/examples/memory/06_stdout_publish_only.rb +145 -0
- data/examples/memory/07_proc_handlers_demo.rb +13 -14
- data/examples/memory/08_custom_logger_demo.rb +136 -140
- data/examples/memory/09_error_handling_demo.rb +7 -10
- data/examples/memory/10_entity_addressing_basic.rb +25 -31
- data/examples/memory/11_entity_addressing_with_filtering.rb +32 -36
- data/examples/memory/12_regex_filtering_microservices.rb +10 -11
- data/examples/memory/13_header_block_configuration.rb +0 -5
- data/examples/memory/14_global_configuration_demo.rb +12 -14
- data/examples/memory/15_logger_demo.rb +0 -1
- data/examples/memory/README.md +37 -20
- data/examples/memory/log/demo_app.log.1 +100 -0
- data/examples/memory/log/demo_app.log.2 +100 -0
- data/examples/multi_transport_example.rb +114 -0
- data/examples/redis/01_smart_home_iot_demo.rb +20 -24
- data/examples/redis/README.md +0 -2
- data/examples/utilities/box_it.rb +12 -0
- data/examples/utilities/doing.rb +19 -0
- data/examples/utilities/temp.md +28 -0
- data/lib/smart_message/base.rb +24 -17
- data/lib/smart_message/configuration.rb +2 -23
- data/lib/smart_message/dead_letter_queue.rb +1 -1
- data/lib/smart_message/errors.rb +3 -0
- data/lib/smart_message/header.rb +1 -1
- data/lib/smart_message/logger/default.rb +1 -1
- data/lib/smart_message/messaging.rb +37 -66
- data/lib/smart_message/plugins.rb +42 -41
- data/lib/smart_message/serializer/base.rb +1 -1
- data/lib/smart_message/serializer.rb +3 -2
- data/lib/smart_message/subscription.rb +18 -20
- data/lib/smart_message/transport/async_publish_queue.rb +284 -0
- data/lib/smart_message/transport/base.rb +42 -8
- data/lib/smart_message/transport/fifo_operations.rb +264 -0
- data/lib/smart_message/transport/file_operations.rb +200 -0
- data/lib/smart_message/transport/file_transport.rb +149 -0
- data/lib/smart_message/transport/file_watching.rb +72 -0
- data/lib/smart_message/transport/memory_transport.rb +23 -4
- data/lib/smart_message/transport/partitioned_files.rb +46 -0
- data/lib/smart_message/transport/redis_transport.rb +11 -0
- data/lib/smart_message/transport/registry.rb +0 -1
- data/lib/smart_message/transport/stdout_transport.rb +73 -41
- data/lib/smart_message/transport/stdout_transport.rb.backup +88 -0
- data/lib/smart_message/transport.rb +0 -1
- data/lib/smart_message/version.rb +1 -1
- metadata +25 -37
- data/docs/guides/redis-queue-getting-started.md +0 -697
- data/docs/guides/redis-queue-patterns.md +0 -889
- data/docs/guides/redis-queue-production.md +0 -1091
- data/docs/transports/redis-enhanced-transport.md +0 -524
- data/docs/transports/redis-queue-transport.md +0 -1304
- data/examples/redis_enhanced/README.md +0 -319
- data/examples/redis_enhanced/enhanced_01_basic_patterns.rb +0 -233
- data/examples/redis_enhanced/enhanced_02_fluent_api.rb +0 -331
- data/examples/redis_enhanced/enhanced_03_dual_publishing.rb +0 -281
- data/examples/redis_enhanced/enhanced_04_advanced_routing.rb +0 -419
- data/examples/redis_queue/01_basic_messaging.rb +0 -221
- data/examples/redis_queue/01_comprehensive_examples.rb +0 -508
- data/examples/redis_queue/02_pattern_routing.rb +0 -405
- data/examples/redis_queue/03_fluent_api.rb +0 -422
- data/examples/redis_queue/04_load_balancing.rb +0 -486
- data/examples/redis_queue/05_microservices.rb +0 -735
- data/examples/redis_queue/06_emergency_alerts.rb +0 -777
- data/examples/redis_queue/07_queue_management.rb +0 -587
- data/examples/redis_queue/README.md +0 -366
- data/examples/redis_queue/enhanced_01_basic_patterns.rb +0 -233
- data/examples/redis_queue/enhanced_02_fluent_api.rb +0 -331
- data/examples/redis_queue/enhanced_03_dual_publishing.rb +0 -281
- data/examples/redis_queue/enhanced_04_advanced_routing.rb +0 -419
- data/examples/redis_queue/redis_queue_architecture.svg +0 -148
- data/ideas/README.md +0 -41
- data/ideas/agents.md +0 -1001
- data/ideas/database_transport.md +0 -980
- data/ideas/improvement.md +0 -359
- data/ideas/meshage.md +0 -1788
- data/ideas/message_discovery.md +0 -178
- data/ideas/message_schema.md +0 -1381
- data/lib/smart_message/transport/redis_enhanced_transport.rb +0 -399
- data/lib/smart_message/transport/redis_queue_transport.rb +0 -555
- data/lib/smart_message/wrapper.rb.bak +0 -132
- /data/examples/memory/{06_pretty_print_demo.rb → 16_pretty_print_demo.rb} +0 -0
data/lib/smart_message/base.rb
CHANGED
@@ -37,11 +37,9 @@ module SmartMessage
|
|
37
37
|
include Hashie::Extensions::MethodAccess
|
38
38
|
|
39
39
|
# Common attrubutes for all messages
|
40
|
-
# TODO:
|
41
|
-
#
|
42
|
-
#
|
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
|
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] #
|
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
|
142
|
-
# Returns [header, payload_json] in the old
|
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
|
-
#
|
169
|
-
|
170
|
-
|
171
|
-
|
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
|
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
|
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/errors.rb
CHANGED
data/lib/smart_message/header.rb
CHANGED
@@ -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
|
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
|
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
|
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
|
-
#
|
55
|
-
|
56
|
-
|
57
|
-
|
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
|
-
|
60
|
-
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
62
|
-
|
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
|
-
|
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
|
-
|
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
|
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
|
-
#
|
10
|
-
|
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
|
-
|
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(
|
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 (
|
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 |
|
81
|
-
#
|
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
|