smart_message 0.0.8 → 0.0.10

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 (87) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.irbrc +24 -0
  4. data/CHANGELOG.md +119 -0
  5. data/Gemfile.lock +6 -1
  6. data/README.md +389 -17
  7. data/docs/README.md +3 -1
  8. data/docs/addressing.md +119 -13
  9. data/docs/architecture.md +184 -46
  10. data/docs/dead_letter_queue.md +673 -0
  11. data/docs/dispatcher.md +87 -0
  12. data/docs/examples.md +59 -1
  13. data/docs/getting-started.md +8 -1
  14. data/docs/logging.md +382 -326
  15. data/docs/message_deduplication.md +488 -0
  16. data/docs/message_filtering.md +451 -0
  17. data/examples/01_point_to_point_orders.rb +54 -53
  18. data/examples/02_publish_subscribe_events.rb +14 -10
  19. data/examples/03_many_to_many_chat.rb +16 -8
  20. data/examples/04_redis_smart_home_iot.rb +20 -10
  21. data/examples/05_proc_handlers.rb +12 -11
  22. data/examples/06_custom_logger_example.rb +95 -100
  23. data/examples/07_error_handling_scenarios.rb +4 -2
  24. data/examples/08_entity_addressing_basic.rb +18 -6
  25. data/examples/08_entity_addressing_with_filtering.rb +27 -9
  26. data/examples/09_dead_letter_queue_demo.rb +559 -0
  27. data/examples/09_regex_filtering_microservices.rb +407 -0
  28. data/examples/10_header_block_configuration.rb +263 -0
  29. data/examples/10_message_deduplication.rb +209 -0
  30. data/examples/11_global_configuration_example.rb +219 -0
  31. data/examples/README.md +102 -0
  32. data/examples/dead_letters.jsonl +12 -0
  33. data/examples/performance_metrics/benchmark_results_ractor_20250818_205603.json +135 -0
  34. data/examples/performance_metrics/benchmark_results_ractor_20250818_205831.json +135 -0
  35. data/examples/performance_metrics/benchmark_results_test_20250818_204942.json +130 -0
  36. data/examples/performance_metrics/benchmark_results_threadpool_20250818_204942.json +130 -0
  37. data/examples/performance_metrics/benchmark_results_threadpool_20250818_204959.json +130 -0
  38. data/examples/performance_metrics/benchmark_results_threadpool_20250818_205044.json +130 -0
  39. data/examples/performance_metrics/benchmark_results_threadpool_20250818_205109.json +130 -0
  40. data/examples/performance_metrics/benchmark_results_threadpool_20250818_205252.json +130 -0
  41. data/examples/performance_metrics/benchmark_results_unknown_20250819_172852.json +130 -0
  42. data/examples/performance_metrics/compare_benchmarks.rb +519 -0
  43. data/examples/performance_metrics/dead_letters.jsonl +3100 -0
  44. data/examples/performance_metrics/performance_benchmark.rb +344 -0
  45. data/examples/show_logger.rb +367 -0
  46. data/examples/show_me.rb +145 -0
  47. data/examples/temp.txt +94 -0
  48. data/examples/tmux_chat/bot_agent.rb +4 -2
  49. data/examples/tmux_chat/human_agent.rb +4 -2
  50. data/examples/tmux_chat/room_monitor.rb +4 -2
  51. data/examples/tmux_chat/shared_chat_system.rb +6 -3
  52. data/lib/smart_message/addressing.rb +259 -0
  53. data/lib/smart_message/base.rb +123 -599
  54. data/lib/smart_message/circuit_breaker.rb +2 -1
  55. data/lib/smart_message/configuration.rb +199 -0
  56. data/lib/smart_message/ddq/base.rb +71 -0
  57. data/lib/smart_message/ddq/memory.rb +109 -0
  58. data/lib/smart_message/ddq/redis.rb +168 -0
  59. data/lib/smart_message/ddq.rb +31 -0
  60. data/lib/smart_message/dead_letter_queue.rb +27 -10
  61. data/lib/smart_message/deduplication.rb +174 -0
  62. data/lib/smart_message/dispatcher.rb +259 -61
  63. data/lib/smart_message/header.rb +5 -0
  64. data/lib/smart_message/logger/base.rb +21 -1
  65. data/lib/smart_message/logger/default.rb +88 -138
  66. data/lib/smart_message/logger/lumberjack.rb +324 -0
  67. data/lib/smart_message/logger/null.rb +81 -0
  68. data/lib/smart_message/logger.rb +17 -9
  69. data/lib/smart_message/messaging.rb +100 -0
  70. data/lib/smart_message/plugins.rb +132 -0
  71. data/lib/smart_message/serializer/base.rb +25 -8
  72. data/lib/smart_message/serializer/json.rb +5 -4
  73. data/lib/smart_message/subscription.rb +196 -0
  74. data/lib/smart_message/transport/base.rb +72 -41
  75. data/lib/smart_message/transport/memory_transport.rb +7 -5
  76. data/lib/smart_message/transport/redis_transport.rb +15 -45
  77. data/lib/smart_message/transport/stdout_transport.rb +18 -8
  78. data/lib/smart_message/transport.rb +1 -34
  79. data/lib/smart_message/utilities.rb +142 -0
  80. data/lib/smart_message/version.rb +1 -1
  81. data/lib/smart_message/versioning.rb +85 -0
  82. data/lib/smart_message/wrapper.rb.bak +132 -0
  83. data/lib/smart_message.rb +74 -28
  84. data/smart_message.gemspec +3 -0
  85. metadata +83 -3
  86. data/lib/smart_message/serializer.rb +0 -10
  87. data/lib/smart_message/wrapper.rb +0 -43
@@ -2,14 +2,22 @@
2
2
  # encoding: utf-8
3
3
  # frozen_string_literal: true
4
4
 
5
- module SmartMessage::Logger
6
- # Logger module provides logging capabilities for SmartMessage
7
- # The Default logger automatically uses Rails.logger if available,
8
- # otherwise falls back to a standard Ruby Logger
9
- end # module SmartMessage::Logger
10
-
11
- # Load the base class first
12
5
  require_relative 'logger/base'
13
-
14
- # Load the default logger implementation
15
6
  require_relative 'logger/default'
7
+ require_relative 'logger/lumberjack'
8
+
9
+ module SmartMessage
10
+ module Logger
11
+ class << self
12
+ # Global default logger instance
13
+ def default
14
+ @default ||= Lumberjack.new
15
+ end
16
+
17
+ # Set the default logger
18
+ def default=(logger)
19
+ @default = logger
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,100 @@
1
+ # lib/smart_message/messaging.rb
2
+ # encoding: utf-8
3
+ # frozen_string_literal: true
4
+
5
+ module SmartMessage
6
+ # Messaging module for SmartMessage::Base
7
+ # Handles message encoding and publishing operations
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
+
36
+
37
+ # NOTE: you publish instances; but, you subscribe/unsubscribe at
38
+ # the class-level
39
+ def publish
40
+ begin
41
+ # Validate the complete message before publishing (now uses overridden validate!)
42
+ validate!
43
+
44
+ # Update header with current publication info
45
+ _sm_header.published_at = Time.now
46
+ _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
+
52
+ raise Errors::TransportNotConfigured if transport_missing?
53
+
54
+ # Transport receives the message class name (for channel routing) and serialized message
55
+ (self.class.logger || SmartMessage::Logger.default).debug { "[SmartMessage::Messaging] About to call transport.publish" }
56
+ transport.publish(_sm_header.message_class, serialized_message)
57
+ (self.class.logger || SmartMessage::Logger.default).debug { "[SmartMessage::Messaging] transport.publish completed" }
58
+
59
+ # Log the message publish
60
+ (self.class.logger || SmartMessage::Logger.default).info { "[SmartMessage] Published: #{self.class.name} via #{transport.class.name.split('::').last}" }
61
+
62
+ SS.add(_sm_header.message_class, 'publish')
63
+ SS.get(_sm_header.message_class, 'publish')
64
+ rescue => e
65
+ (self.class.logger || SmartMessage::Logger.default).error { "[SmartMessage] Error in message publishing: #{e.class.name} - #{e.message}" }
66
+ raise
67
+ end
68
+ end # def publish
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
+ end
100
+ end
@@ -0,0 +1,132 @@
1
+ # lib/smart_message/plugins.rb
2
+ # encoding: utf-8
3
+ # frozen_string_literal: true
4
+
5
+ module SmartMessage
6
+ # Plugin configuration module for SmartMessage::Base
7
+ # Handles transport, serializer, and logger configuration at both
8
+ # class and instance levels
9
+ module Plugins
10
+ def self.included(base)
11
+ base.extend(ClassMethods)
12
+ base.class_eval do
13
+ # Class-level plugin storage
14
+ class_variable_set(:@@transport, nil) unless class_variable_defined?(:@@transport)
15
+ class_variable_set(:@@serializer, nil) unless class_variable_defined?(:@@serializer)
16
+ class_variable_set(:@@logger, nil) unless class_variable_defined?(:@@logger)
17
+ end
18
+ end
19
+
20
+ #########################################################
21
+ ## instance-level configuration
22
+
23
+ # Configure the plugins for transport, serializer and logger
24
+ def config(&block)
25
+ instance_eval(&block) if block_given?
26
+ end
27
+
28
+ #########################################################
29
+ ## instance-level transport configuration
30
+
31
+ def transport(klass_or_instance = nil)
32
+ if klass_or_instance.nil?
33
+ # Return instance transport, class transport, or global configuration
34
+ @transport || self.class.class_variable_get(:@@transport) || SmartMessage::Transport.default
35
+ else
36
+ @transport = klass_or_instance
37
+ end
38
+ end
39
+
40
+ def transport_configured?; !transport_missing?; end
41
+ def transport_missing?
42
+ # Check if transport is explicitly configured (without fallback to defaults)
43
+ @transport.nil? &&
44
+ (self.class.class_variable_get(:@@transport) rescue nil).nil?
45
+ end
46
+ def reset_transport; @transport = nil; end
47
+
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
+ module ClassMethods
70
+ #########################################################
71
+ ## class-level configuration
72
+
73
+ def config(&block)
74
+ class_eval(&block) if block_given?
75
+ end
76
+
77
+ #########################################################
78
+ ## class-level transport configuration
79
+
80
+ def transport(klass_or_instance = nil)
81
+ if klass_or_instance.nil?
82
+ # Return class-level transport or fall back to global configuration
83
+ class_variable_get(:@@transport) || SmartMessage::Transport.default
84
+ else
85
+ class_variable_set(:@@transport, klass_or_instance)
86
+ end
87
+ end
88
+
89
+ def transport_configured?; !transport_missing?; end
90
+ def transport_missing?
91
+ # Check if class-level transport is explicitly configured (without fallback to defaults)
92
+ (class_variable_get(:@@transport) rescue nil).nil?
93
+ end
94
+ def reset_transport; class_variable_set(:@@transport, nil); end
95
+
96
+ #########################################################
97
+ ## class-level logger configuration
98
+
99
+ def logger(klass_or_instance = nil)
100
+ if klass_or_instance.nil?
101
+ # Return class-level logger or fall back to global configuration
102
+ class_variable_get(:@@logger) || SmartMessage::Logger.default
103
+ else
104
+ class_variable_set(:@@logger, klass_or_instance)
105
+ end
106
+ end
107
+
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
122
+ 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
130
+ end
131
+ end
132
+ end
@@ -2,6 +2,7 @@
2
2
  # encoding: utf-8
3
3
  # frozen_string_literal: true
4
4
 
5
+ require 'json' # STDLIB
5
6
  require_relative '../circuit_breaker'
6
7
 
7
8
  module SmartMessage::Serializer
@@ -12,7 +13,20 @@ module SmartMessage::Serializer
12
13
  # provide basic configuration
13
14
  def initialize
14
15
  configure_serializer_circuit_breakers
16
+
17
+ logger.debug { "[SmartMessage::Serializer::#{self.class.name.split('::').last}] Initialized" }
18
+ rescue => e
19
+ logger&.error { "[SmartMessage] Error in serializer initialization: #{e.class.name} - #{e.message}" }
20
+ raise
21
+ end
22
+
23
+ private
24
+
25
+ def logger
26
+ @logger ||= SmartMessage::Logger.default
15
27
  end
28
+
29
+ public
16
30
 
17
31
  def encode(message_instance)
18
32
  circuit(:serializer).wrap do
@@ -39,10 +53,16 @@ module SmartMessage::Serializer
39
53
  raise
40
54
  end
41
55
  end
56
+
57
+ private
42
58
 
43
59
  # Template methods for actual serialization (implement in subclasses)
44
60
  def do_encode(message_instance)
45
- raise ::SmartMessage::Errors::NotImplemented
61
+ # Default implementation: serialize only the payload portion for wrapper architecture
62
+ # Subclasses can override this for specific serialization formats
63
+ message_hash = message_instance.to_h
64
+ payload_portion = message_hash[:_sm_payload]
65
+ ::JSON.generate(payload_portion)
46
66
  end
47
67
 
48
68
  def do_decode(payload)
@@ -81,13 +101,10 @@ module SmartMessage::Serializer
81
101
 
82
102
  # Handle serializer circuit breaker fallback
83
103
  def handle_serializer_fallback(fallback_result, operation, data)
84
- if $DEBUG
85
- puts "Serializer circuit breaker activated: #{self.class.name}"
86
- puts "Operation: #{operation}"
87
- puts "Error: #{fallback_result[:circuit_breaker][:error]}"
88
- end
89
-
90
- # TODO: Integrate with structured logging when implemented
104
+ # Integrate with structured logging
105
+ logger.error { "[SmartMessage::Serializer] Circuit breaker activated: #{self.class.name}" }
106
+ logger.error { "[SmartMessage::Serializer] Operation: #{operation}" }
107
+ logger.error { "[SmartMessage::Serializer] Error: #{fallback_result[:circuit_breaker][:error]}" }
91
108
 
92
109
  # Return the fallback result
93
110
  fallback_result
@@ -5,11 +5,12 @@
5
5
  require 'json' # STDLIB
6
6
 
7
7
  module SmartMessage::Serializer
8
- class JSON < Base
8
+ class Json < Base
9
9
  def do_encode(message_instance)
10
- # TODO: is this the right place to insert an automated-invisible
11
- # message header?
12
- message_instance.to_json
10
+ # Single-tier serialization: serialize the complete message structure
11
+ # This includes both header and payload for full message reconstruction
12
+ message_hash = message_instance.to_h
13
+ ::JSON.generate(message_hash)
13
14
  end
14
15
 
15
16
  def do_decode(payload)
@@ -0,0 +1,196 @@
1
+ # lib/smart_message/subscription.rb
2
+ # encoding: utf-8
3
+ # frozen_string_literal: true
4
+
5
+ require 'securerandom' # STDLIB
6
+
7
+ module SmartMessage
8
+ # Subscription management module for SmartMessage::Base
9
+ # Handles subscribe/unsubscribe operations and proc handler management
10
+ module Subscription
11
+ def self.included(base)
12
+ base.extend(ClassMethods)
13
+ base.class_eval do
14
+ # Registry for proc-based message handlers
15
+ class_variable_set(:@@proc_handlers, {}) unless class_variable_defined?(:@@proc_handlers)
16
+ end
17
+ end
18
+
19
+ module ClassMethods
20
+ #########################################################
21
+ ## proc handler management
22
+
23
+ # Register a proc handler and return a unique identifier for it
24
+ # @param message_class [String] The message class name
25
+ # @param handler_proc [Proc] The proc to register
26
+ # @return [String] Unique identifier for this handler
27
+ def register_proc_handler(message_class, handler_proc)
28
+ handler_id = "#{message_class}.proc_#{SecureRandom.hex(8)}"
29
+ class_variable_get(:@@proc_handlers)[handler_id] = handler_proc
30
+ handler_id
31
+ end
32
+
33
+ # Call a registered proc handler
34
+ # @param handler_id [String] The handler identifier
35
+ # @param wrapper [SmartMessage::Wrapper::Base] The message wrapper
36
+ def call_proc_handler(handler_id, wrapper)
37
+ handler_proc = class_variable_get(:@@proc_handlers)[handler_id]
38
+ return unless handler_proc
39
+
40
+ handler_proc.call(wrapper)
41
+ end
42
+
43
+ # Remove a proc handler from the registry
44
+ # @param handler_id [String] The handler identifier to remove
45
+ def unregister_proc_handler(handler_id)
46
+ class_variable_get(:@@proc_handlers).delete(handler_id)
47
+ end
48
+
49
+ # Check if a handler ID refers to a proc handler
50
+ # @param handler_id [String] The handler identifier
51
+ # @return [Boolean] True if this is a proc handler
52
+ def proc_handler?(handler_id)
53
+ class_variable_get(:@@proc_handlers).key?(handler_id)
54
+ end
55
+
56
+ #########################################################
57
+ ## class-level subscription management via the transport
58
+
59
+ # Add this message class to the transport's catalog of
60
+ # subscribed messages. If the transport is missing, raise
61
+ # an exception.
62
+ #
63
+ # @param process_method [String, Proc, nil] The processing method:
64
+ # - String: Method name like "MyService.handle_message"
65
+ # - Proc: A proc/lambda that accepts (message_header, message_payload)
66
+ # - nil: Uses default "MessageClass.process" method
67
+ # @param broadcast [Boolean, nil] Filter for broadcast messages (to: nil)
68
+ # @param to [String, Array, nil] Filter for messages directed to specific entities
69
+ # @param from [String, Array, nil] Filter for messages from specific entities
70
+ # @param block [Proc] Alternative way to pass a processing block
71
+ # @return [String] The identifier used for this subscription
72
+ #
73
+ # @example Using default handler
74
+ # MyMessage.subscribe
75
+ #
76
+ # @example Using custom method name with filtering
77
+ # MyMessage.subscribe("MyService.handle_message", from: ['order-service'])
78
+ #
79
+ # @example Using a block with broadcast filtering
80
+ # MyMessage.subscribe(broadcast: true) do |header, payload|
81
+ # data = JSON.parse(payload)
82
+ # puts "Received broadcast: #{data}"
83
+ # end
84
+ #
85
+ # @example Entity-specific filtering (receives only messages from payment service)
86
+ # MyMessage.subscribe("OrderService.process", from: ['payment'])
87
+ #
88
+ # @example Explicit to filter
89
+ # MyMessage.subscribe("AdminService.handle", to: 'admin', broadcast: false)
90
+ def subscribe(process_method = nil, broadcast: nil, to: nil, from: nil, &block)
91
+ message_class = whoami
92
+
93
+ # Handle different parameter types
94
+ if block_given?
95
+ # Block was passed - use it as the handler
96
+ handler_proc = block
97
+ process_method = register_proc_handler(message_class, handler_proc)
98
+ elsif process_method.respond_to?(:call)
99
+ # Proc/lambda was passed as first parameter
100
+ handler_proc = process_method
101
+ process_method = register_proc_handler(message_class, handler_proc)
102
+ elsif process_method.nil?
103
+ # Use default handler
104
+ process_method = message_class + '.process'
105
+ end
106
+ # If process_method is a String, use it as-is
107
+
108
+ # Subscriber identity is derived from the process method (handler)
109
+ # This ensures each handler gets its own DDQ scope per message class
110
+
111
+ # Normalize string filters to arrays
112
+ to_filter = normalize_filter_value(to)
113
+ from_filter = normalize_filter_value(from)
114
+
115
+ # Create filter options (no explicit subscriber identity needed)
116
+ filter_options = {
117
+ broadcast: broadcast,
118
+ to: to_filter,
119
+ from: from_filter
120
+ }
121
+
122
+ # Add proper logging
123
+ logger = SmartMessage::Logger.default
124
+
125
+ begin
126
+ raise Errors::TransportNotConfigured if transport_missing?
127
+ transport.subscribe(message_class, process_method, filter_options)
128
+
129
+ # Log successful subscription
130
+ handler_desc = block_given? || process_method.respond_to?(:call) ? " with block/proc handler" : ""
131
+ logger.info { "[SmartMessage] Subscribed: #{self.name}#{handler_desc}" }
132
+ logger.debug { "[SmartMessage::Subscription] Subscribed #{message_class} with filters: #{filter_options}" }
133
+
134
+ process_method
135
+ rescue => e
136
+ logger.error { "[SmartMessage] Error in message subscription: #{e.class.name} - #{e.message}" }
137
+ raise
138
+ end
139
+ end
140
+
141
+ # Remove this process_method for this message class from the
142
+ # subscribers list.
143
+ # @param process_method [String, nil] The processing method identifier to remove
144
+ # - String: Method name like "MyService.handle_message" or proc handler ID
145
+ # - nil: Uses default "MessageClass.process" method
146
+ def unsubscribe(process_method = nil)
147
+ message_class = whoami
148
+ process_method = message_class + '.process' if process_method.nil?
149
+ # Add proper logging
150
+ logger = SmartMessage::Logger.default
151
+
152
+ begin
153
+ if transport_configured?
154
+ transport.unsubscribe(message_class, process_method)
155
+
156
+ # If this was a proc handler, clean it up from the registry
157
+ if proc_handler?(process_method)
158
+ unregister_proc_handler(process_method)
159
+ end
160
+
161
+ # Log successful unsubscription
162
+ logger.info { "[SmartMessage] Unsubscribed: #{self.name}" }
163
+ logger.debug { "[SmartMessage::Subscription] Unsubscribed #{message_class} from #{process_method}" }
164
+ end
165
+ rescue => e
166
+ logger.error { "[SmartMessage] Error in message unsubscription: #{e.class.name} - #{e.message}" }
167
+ raise
168
+ end
169
+ end
170
+
171
+ # Remove this message class and all of its processing methods
172
+ # from the subscribers list.
173
+ def unsubscribe!
174
+ message_class = whoami
175
+
176
+ # TODO: Add proper logging here
177
+
178
+ transport.unsubscribe!(message_class) if transport_configured?
179
+ end
180
+
181
+ ###################################################
182
+ ## Business Logic resides in the #process method.
183
+
184
+ # When a transport receives a subscribed to message it
185
+ # creates an instance of the message and then calls
186
+ # the process method on that instance.
187
+ #
188
+ # It is expected that SmartMessage classes over ride
189
+ # the SmartMessage::Base#process method with appropriate
190
+ # business logic to handle the received message content.
191
+ def process(message_instance)
192
+ raise Errors::NotImplemented
193
+ end
194
+ end
195
+ end
196
+ end