smart_message 0.0.8 → 0.0.9

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 (80) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.irbrc +24 -0
  4. data/CHANGELOG.md +96 -0
  5. data/Gemfile.lock +6 -1
  6. data/README.md +289 -15
  7. data/docs/README.md +3 -1
  8. data/docs/addressing.md +119 -13
  9. data/docs/architecture.md +68 -0
  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_filtering.md +451 -0
  16. data/examples/01_point_to_point_orders.rb +54 -53
  17. data/examples/02_publish_subscribe_events.rb +14 -10
  18. data/examples/03_many_to_many_chat.rb +16 -8
  19. data/examples/04_redis_smart_home_iot.rb +20 -10
  20. data/examples/05_proc_handlers.rb +12 -11
  21. data/examples/06_custom_logger_example.rb +95 -100
  22. data/examples/07_error_handling_scenarios.rb +4 -2
  23. data/examples/08_entity_addressing_basic.rb +18 -6
  24. data/examples/08_entity_addressing_with_filtering.rb +27 -9
  25. data/examples/09_dead_letter_queue_demo.rb +559 -0
  26. data/examples/09_regex_filtering_microservices.rb +407 -0
  27. data/examples/10_header_block_configuration.rb +263 -0
  28. data/examples/11_global_configuration_example.rb +219 -0
  29. data/examples/README.md +102 -0
  30. data/examples/dead_letters.jsonl +12 -0
  31. data/examples/performance_metrics/benchmark_results_ractor_20250818_205603.json +135 -0
  32. data/examples/performance_metrics/benchmark_results_ractor_20250818_205831.json +135 -0
  33. data/examples/performance_metrics/benchmark_results_test_20250818_204942.json +130 -0
  34. data/examples/performance_metrics/benchmark_results_threadpool_20250818_204942.json +130 -0
  35. data/examples/performance_metrics/benchmark_results_threadpool_20250818_204959.json +130 -0
  36. data/examples/performance_metrics/benchmark_results_threadpool_20250818_205044.json +130 -0
  37. data/examples/performance_metrics/benchmark_results_threadpool_20250818_205109.json +130 -0
  38. data/examples/performance_metrics/benchmark_results_threadpool_20250818_205252.json +130 -0
  39. data/examples/performance_metrics/benchmark_results_unknown_20250819_172852.json +130 -0
  40. data/examples/performance_metrics/compare_benchmarks.rb +519 -0
  41. data/examples/performance_metrics/dead_letters.jsonl +3100 -0
  42. data/examples/performance_metrics/performance_benchmark.rb +344 -0
  43. data/examples/show_logger.rb +367 -0
  44. data/examples/show_me.rb +145 -0
  45. data/examples/temp.txt +94 -0
  46. data/examples/tmux_chat/bot_agent.rb +4 -2
  47. data/examples/tmux_chat/human_agent.rb +4 -2
  48. data/examples/tmux_chat/room_monitor.rb +4 -2
  49. data/examples/tmux_chat/shared_chat_system.rb +6 -3
  50. data/lib/smart_message/addressing.rb +259 -0
  51. data/lib/smart_message/base.rb +121 -599
  52. data/lib/smart_message/circuit_breaker.rb +2 -1
  53. data/lib/smart_message/configuration.rb +199 -0
  54. data/lib/smart_message/dead_letter_queue.rb +27 -10
  55. data/lib/smart_message/dispatcher.rb +90 -49
  56. data/lib/smart_message/header.rb +5 -0
  57. data/lib/smart_message/logger/base.rb +21 -1
  58. data/lib/smart_message/logger/default.rb +88 -138
  59. data/lib/smart_message/logger/lumberjack.rb +324 -0
  60. data/lib/smart_message/logger/null.rb +81 -0
  61. data/lib/smart_message/logger.rb +17 -9
  62. data/lib/smart_message/messaging.rb +100 -0
  63. data/lib/smart_message/plugins.rb +132 -0
  64. data/lib/smart_message/serializer/base.rb +25 -8
  65. data/lib/smart_message/serializer/json.rb +5 -4
  66. data/lib/smart_message/subscription.rb +193 -0
  67. data/lib/smart_message/transport/base.rb +72 -41
  68. data/lib/smart_message/transport/memory_transport.rb +7 -5
  69. data/lib/smart_message/transport/redis_transport.rb +15 -45
  70. data/lib/smart_message/transport/stdout_transport.rb +18 -8
  71. data/lib/smart_message/transport.rb +1 -34
  72. data/lib/smart_message/utilities.rb +142 -0
  73. data/lib/smart_message/version.rb +1 -1
  74. data/lib/smart_message/versioning.rb +85 -0
  75. data/lib/smart_message/wrapper.rb.bak +132 -0
  76. data/lib/smart_message.rb +74 -28
  77. data/smart_message.gemspec +3 -0
  78. metadata +76 -3
  79. data/lib/smart_message/serializer.rb +0 -10
  80. 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,193 @@
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 (all messages)
74
+ # MyMessage.subscribe
75
+ #
76
+ # @example Using custom method name with filtering
77
+ # MyMessage.subscribe("MyService.handle_message", to: 'my-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
86
+ # MyMessage.subscribe(to: 'order-service', from: ['payment', 'user'])
87
+ #
88
+ # @example Broadcast + directed messages
89
+ # MyMessage.subscribe(to: 'my-service', broadcast: true)
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
+ # Normalize string filters to arrays
109
+ to_filter = normalize_filter_value(to)
110
+ from_filter = normalize_filter_value(from)
111
+
112
+ # Create filter options
113
+ filter_options = {
114
+ broadcast: broadcast,
115
+ to: to_filter,
116
+ from: from_filter
117
+ }
118
+
119
+ # Add proper logging
120
+ logger = SmartMessage::Logger.default
121
+
122
+ begin
123
+ raise Errors::TransportNotConfigured if transport_missing?
124
+ transport.subscribe(message_class, process_method, filter_options)
125
+
126
+ # Log successful subscription
127
+ handler_desc = block_given? || process_method.respond_to?(:call) ? " with block/proc handler" : ""
128
+ logger.info { "[SmartMessage] Subscribed: #{self.name}#{handler_desc}" }
129
+ logger.debug { "[SmartMessage::Subscription] Subscribed #{message_class} with filters: #{filter_options}" }
130
+
131
+ process_method
132
+ rescue => e
133
+ logger.error { "[SmartMessage] Error in message subscription: #{e.class.name} - #{e.message}" }
134
+ raise
135
+ end
136
+ end
137
+
138
+ # Remove this process_method for this message class from the
139
+ # subscribers list.
140
+ # @param process_method [String, nil] The processing method identifier to remove
141
+ # - String: Method name like "MyService.handle_message" or proc handler ID
142
+ # - nil: Uses default "MessageClass.process" method
143
+ def unsubscribe(process_method = nil)
144
+ message_class = whoami
145
+ process_method = message_class + '.process' if process_method.nil?
146
+ # Add proper logging
147
+ logger = SmartMessage::Logger.default
148
+
149
+ begin
150
+ if transport_configured?
151
+ transport.unsubscribe(message_class, process_method)
152
+
153
+ # If this was a proc handler, clean it up from the registry
154
+ if proc_handler?(process_method)
155
+ unregister_proc_handler(process_method)
156
+ end
157
+
158
+ # Log successful unsubscription
159
+ logger.info { "[SmartMessage] Unsubscribed: #{self.name}" }
160
+ logger.debug { "[SmartMessage::Subscription] Unsubscribed #{message_class} from #{process_method}" }
161
+ end
162
+ rescue => e
163
+ logger.error { "[SmartMessage] Error in message unsubscription: #{e.class.name} - #{e.message}" }
164
+ raise
165
+ end
166
+ end
167
+
168
+ # Remove this message class and all of its processing methods
169
+ # from the subscribers list.
170
+ def unsubscribe!
171
+ message_class = whoami
172
+
173
+ # TODO: Add proper logging here
174
+
175
+ transport.unsubscribe!(message_class) if transport_configured?
176
+ end
177
+
178
+ ###################################################
179
+ ## Business Logic resides in the #process method.
180
+
181
+ # When a transport receives a subscribed to message it
182
+ # creates an instance of the message and then calls
183
+ # the process method on that instance.
184
+ #
185
+ # It is expected that SmartMessage classes over ride
186
+ # the SmartMessage::Base#process method with appropriate
187
+ # business logic to handle the received message content.
188
+ def process(message_instance)
189
+ raise Errors::NotImplemented
190
+ end
191
+ end
192
+ end
193
+ end