smart_message 0.0.6 → 0.0.7

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.
@@ -87,7 +87,8 @@ puts "\nšŸ“¤ Publishing test messages..."
87
87
  # Broadcast message - should only be received by broadcast handler
88
88
  broadcast_msg = ServiceMessage.new(
89
89
  message_type: 'system_announcement',
90
- data: 'System maintenance at 2 AM'
90
+ data: 'System maintenance at 2 AM',
91
+ from: 'sender-service'
91
92
  )
92
93
  broadcast_msg.to(nil) # Explicitly set as broadcast
93
94
  puts "\n1. Publishing broadcast message (no 'to' field)..."
@@ -97,7 +98,8 @@ sleep(0.2) # Allow time for handlers to process
97
98
  # Directed message to 'my-service' - should only be received by directed handler
98
99
  directed_msg = ServiceMessage.new(
99
100
  message_type: 'service_update',
100
- data: 'Update your configuration'
101
+ data: 'Update your configuration',
102
+ from: 'sender-service'
101
103
  )
102
104
  directed_msg.to('my-service')
103
105
  puts "\n2. Publishing message to 'my-service'..."
@@ -107,7 +109,8 @@ sleep(0.2) # Allow time for handlers to process
107
109
  # Directed message to different service - should NOT be received
108
110
  other_msg = ServiceMessage.new(
109
111
  message_type: 'other_update',
110
- data: 'This is for another service'
112
+ data: 'This is for another service',
113
+ from: 'sender-service'
111
114
  )
112
115
  other_msg.to('other-service')
113
116
  puts "\n3. Publishing message to 'other-service' (should not be received)..."
@@ -117,7 +120,8 @@ sleep(0.2) # Allow time to confirm no handlers process this
117
120
  # Message from admin - should only be received by admin handler
118
121
  admin_msg = ServiceMessage.new(
119
122
  message_type: 'admin_command',
120
- data: 'Restart all services'
123
+ data: 'Restart all services',
124
+ from: 'admin-service'
121
125
  )
122
126
  admin_msg.from('admin-service')
123
127
  admin_msg.to('my-service')
@@ -191,7 +195,8 @@ puts "\nšŸ“¤ Publishing alert messages..."
191
195
  broadcast_alert = AlertMessage.new(
192
196
  severity: 'warning',
193
197
  alert_text: 'CPU usage high across cluster',
194
- source_system: 'cluster-monitor'
198
+ source_system: 'cluster-monitor',
199
+ from: 'monitoring-system-1'
195
200
  )
196
201
  broadcast_alert.from('monitoring-system-1')
197
202
  broadcast_alert.to(nil) # Broadcast
@@ -203,7 +208,8 @@ sleep(0.2) # Allow time for handlers to process
203
208
  directed_alert = AlertMessage.new(
204
209
  severity: 'critical',
205
210
  alert_text: 'Database connection lost',
206
- source_system: 'db-monitor'
211
+ source_system: 'db-monitor',
212
+ from: 'monitoring-system-2'
207
213
  )
208
214
  directed_alert.from('monitoring-system-2')
209
215
  directed_alert.to('alert-service')
@@ -215,7 +221,8 @@ sleep(0.2) # Allow time for handlers to process
215
221
  other_alert = AlertMessage.new(
216
222
  severity: 'info',
217
223
  alert_text: 'Backup completed successfully',
218
- source_system: 'backup-system'
224
+ source_system: 'backup-system',
225
+ from: 'monitoring-system-1'
219
226
  )
220
227
  other_alert.from('monitoring-system-1')
221
228
  other_alert.to('backup-service')
@@ -285,7 +292,8 @@ normal_order = OrderMessage.new(
285
292
  order_id: "ORD-001",
286
293
  priority: 'normal',
287
294
  items: ["Widget A", "Widget B"],
288
- total_amount: 99.99
295
+ total_amount: 99.99,
296
+ from: 'order-service'
289
297
  )
290
298
  puts "\n1. Publishing normal order to fulfillment..."
291
299
  normal_order.publish
@@ -296,7 +304,8 @@ high_priority_order = OrderMessage.new(
296
304
  order_id: "ORD-002",
297
305
  priority: 'high',
298
306
  items: ["Premium Widget", "Express Gadget"],
299
- total_amount: 999.99
307
+ total_amount: 999.99,
308
+ from: 'order-service'
300
309
  )
301
310
  puts "\n2. Publishing high-priority order..."
302
311
  high_priority_order.publish
@@ -307,7 +316,8 @@ misrouted_order = OrderMessage.new(
307
316
  order_id: "ORD-003",
308
317
  priority: 'normal',
309
318
  items: ["Test Item"],
310
- total_amount: 50.00
319
+ total_amount: 50.00,
320
+ from: 'order-service'
311
321
  )
312
322
  misrouted_order.to('wrong-service')
313
323
  puts "\n3. Publishing order to 'wrong-service' (should not be received)..."
@@ -371,7 +381,8 @@ puts "\nšŸ“¤ Publishing service requests..."
371
381
  api_request = ServiceRequest.new(
372
382
  request_id: SecureRandom.uuid,
373
383
  request_type: 'user_lookup',
374
- data: { user_id: 'USER-123' }
384
+ data: { user_id: 'USER-123' },
385
+ from: 'web-frontend'
375
386
  )
376
387
  api_request.from('web-frontend')
377
388
  api_request.to('api-service')
@@ -384,7 +395,8 @@ sleep(0.2) # Allow time for handlers to process
384
395
  data_request = ServiceRequest.new(
385
396
  request_id: SecureRandom.uuid,
386
397
  request_type: 'query',
387
- data: { table: 'orders', limit: 100 }
398
+ data: { table: 'orders', limit: 100 },
399
+ from: 'analytics-service'
388
400
  )
389
401
  data_request.from('analytics-service')
390
402
  data_request.to('data-service')
@@ -110,7 +110,8 @@ class BotChatAgent < BaseAgent
110
110
  user_name: chat_data['sender_name'],
111
111
  command: command,
112
112
  parameters: parameters,
113
- timestamp: Time.now.iso8601
113
+ timestamp: Time.now.iso8601,
114
+ from: chat_data['sender_id']
114
115
  )
115
116
 
116
117
  bot_command.publish
@@ -244,7 +244,8 @@ class BaseAgent
244
244
  message_type: message_type,
245
245
  timestamp: Time.now.iso8601,
246
246
  mentions: extract_mentions(content),
247
- metadata: { agent_type: @agent_type }
247
+ metadata: { agent_type: @agent_type },
248
+ from: @agent_id
248
249
  )
249
250
 
250
251
  log_display("šŸ’¬ [#{room_id}] #{@name}: #{content}")
@@ -291,7 +292,8 @@ class BaseAgent
291
292
  notification_type: notification_type,
292
293
  content: content,
293
294
  timestamp: Time.now.iso8601,
294
- metadata: { triggered_by: @agent_id }
295
+ metadata: { triggered_by: @agent_id },
296
+ from: @agent_id
295
297
  )
296
298
 
297
299
  notification.publish
@@ -73,10 +73,13 @@ module SmartMessage
73
73
  @serializer = nil
74
74
  @logger = nil
75
75
 
76
+ # Extract addressing information from props before creating header
77
+ addressing_props = props.extract!(:from, :to, :reply_to)
78
+
76
79
  # instance-level over ride of class addressing
77
- @from = nil
78
- @to = nil
79
- @reply_to = nil
80
+ @from = addressing_props[:from]
81
+ @to = addressing_props[:to]
82
+ @reply_to = addressing_props[:reply_to]
80
83
 
81
84
  # Create header with version validation specific to this message class
82
85
  header = SmartMessage::Header.new(
@@ -0,0 +1,227 @@
1
+ # lib/smart_message/circuit_breaker.rb
2
+ # encoding: utf-8
3
+ # frozen_string_literal: true
4
+
5
+ require 'breaker_machines'
6
+
7
+ module SmartMessage
8
+ # Circuit breaker configuration and management for SmartMessage
9
+ # Provides production-grade reliability patterns using BreakerMachines gem
10
+ module CircuitBreaker
11
+ extend self
12
+
13
+ # Default circuit breaker configurations
14
+ DEFAULT_CONFIGS = {
15
+ message_processor: {
16
+ threshold: { failures: 3, within: 60 }, # 3 failures within 1 minute
17
+ reset_after: 30, # Reset after 30 seconds
18
+ storage: :memory # Use memory storage by default
19
+ },
20
+ transport_publish: {
21
+ threshold: { failures: 5, within: 30 }, # 5 failures within 30 seconds
22
+ reset_after: 15, # Reset after 15 seconds
23
+ storage: :memory
24
+ },
25
+ transport_subscribe: {
26
+ threshold: { failures: 3, within: 60 }, # 3 failures within 1 minute
27
+ reset_after: 45, # Reset after 45 seconds
28
+ storage: :memory
29
+ },
30
+ serializer: {
31
+ threshold: { failures: 5, within: 30 }, # 5 failures within 30 seconds
32
+ reset_after: 10, # Reset after 10 seconds
33
+ storage: :memory
34
+ },
35
+ dispatcher_shutdown: {
36
+ threshold: { failures: 2, within: 10 }, # 2 failures within 10 seconds
37
+ reset_after: 5, # Reset after 5 seconds
38
+ storage: :memory
39
+ }
40
+ }.freeze
41
+
42
+ # Configure circuit breakers for a class
43
+ # @param target_class [Class] The class to add circuit breakers to
44
+ # @param options [Hash] Configuration options
45
+ def configure_for(target_class, options = {})
46
+ target_class.include BreakerMachines::DSL
47
+
48
+ # Configure each circuit breaker type
49
+ DEFAULT_CONFIGS.each do |circuit_name, config|
50
+ final_config = config.merge(options[circuit_name] || {})
51
+
52
+ target_class.circuit circuit_name do
53
+ threshold failures: final_config[:threshold][:failures],
54
+ within: final_config[:threshold][:within].seconds
55
+ reset_after final_config[:reset_after].seconds
56
+
57
+ # Configure storage backend
58
+ case final_config[:storage]
59
+ when :redis
60
+ # Use Redis storage if configured
61
+ storage BreakerMachines::Storage::Redis.new(
62
+ redis: SmartMessage::Transport::RedisTransport.new.redis_pub
63
+ )
64
+ else
65
+ # Default to memory storage
66
+ storage BreakerMachines::Storage::Memory.new
67
+ end
68
+
69
+ # Default fallback that logs the failure
70
+ fallback do |exception|
71
+ {
72
+ circuit_breaker: {
73
+ circuit: circuit_name,
74
+ state: 'open',
75
+ error: exception.message,
76
+ timestamp: Time.now.iso8601,
77
+ fallback_triggered: true
78
+ }
79
+ }
80
+ end
81
+ end
82
+ end
83
+ end
84
+
85
+ # Create a specialized circuit breaker for entity-specific processing
86
+ # @param target_class [Class] The class to add the circuit to
87
+ # @param entity_id [String] The entity identifier
88
+ # @param options [Hash] Configuration options
89
+ def configure_entity_circuit(target_class, entity_id, options = {})
90
+ circuit_name = "entity_#{entity_id}".to_sym
91
+ config = DEFAULT_CONFIGS[:message_processor].merge(options)
92
+
93
+ target_class.circuit circuit_name do
94
+ threshold failures: config[:threshold][:failures],
95
+ within: config[:threshold][:within].seconds
96
+ reset_after config[:reset_after].seconds
97
+
98
+ # Configure storage
99
+ case config[:storage]
100
+ when :redis
101
+ storage BreakerMachines::Storage::Redis.new(
102
+ redis: SmartMessage::Transport::RedisTransport.new.redis_pub
103
+ )
104
+ else
105
+ storage BreakerMachines::Storage::Memory.new
106
+ end
107
+
108
+ # Entity-specific fallback
109
+ fallback do |exception|
110
+ {
111
+ circuit_breaker: {
112
+ circuit: circuit_name,
113
+ entity_id: entity_id,
114
+ state: 'open',
115
+ error: exception.message,
116
+ timestamp: Time.now.iso8601,
117
+ fallback_triggered: true
118
+ }
119
+ }
120
+ end
121
+ end
122
+
123
+ circuit_name
124
+ end
125
+
126
+ # Get circuit breaker statistics
127
+ # @param circuit_instance [Object] Instance with circuit breakers
128
+ # @param circuit_name [Symbol] Name of the circuit
129
+ def stats(circuit_instance, circuit_name)
130
+ breaker = circuit_instance.circuit(circuit_name)
131
+ return nil unless breaker
132
+
133
+ {
134
+ name: circuit_name,
135
+ state: breaker.state,
136
+ failure_count: breaker.failure_count,
137
+ last_failure_time: breaker.last_failure_time,
138
+ next_attempt_time: breaker.next_attempt_time
139
+ }
140
+ end
141
+
142
+ # Check if circuit breaker is available (closed or half-open)
143
+ # @param circuit_instance [Object] Instance with circuit breakers
144
+ # @param circuit_name [Symbol] Name of the circuit
145
+ def available?(circuit_instance, circuit_name)
146
+ breaker = circuit_instance.circuit(circuit_name)
147
+ return true unless breaker # No circuit breaker means always available
148
+
149
+ breaker.state != :open
150
+ end
151
+
152
+ # Manually reset a circuit breaker
153
+ # @param circuit_instance [Object] Instance with circuit breakers
154
+ # @param circuit_name [Symbol] Name of the circuit
155
+ def reset!(circuit_instance, circuit_name)
156
+ breaker = circuit_instance.circuit(circuit_name)
157
+ breaker&.reset!
158
+ end
159
+
160
+ # Configure fallback handlers for different scenarios
161
+ module Fallbacks
162
+ # Dead letter queue fallback
163
+ def self.dead_letter_queue(dlq_transport = nil)
164
+ proc do |exception, *args|
165
+ # Extract message details from args if available
166
+ message_header = args[0] if args[0].is_a?(SmartMessage::Header)
167
+ message_payload = args[1] if args.length > 1
168
+
169
+ # Log to dead letter queue if transport provided
170
+ if dlq_transport && message_header && message_payload
171
+ dlq_transport.publish(message_header, message_payload)
172
+ end
173
+
174
+ {
175
+ circuit_breaker: {
176
+ state: 'open',
177
+ error: exception.message,
178
+ sent_to_dlq: !!(dlq_transport && message_header && message_payload),
179
+ timestamp: Time.now.iso8601
180
+ }
181
+ }
182
+ end
183
+ end
184
+
185
+ # Retry with exponential backoff fallback
186
+ def self.retry_with_backoff(max_retries: 3, base_delay: 1)
187
+ proc do |exception, *args|
188
+ retry_count = Thread.current[:circuit_retry_count] ||= 0
189
+
190
+ if retry_count < max_retries
191
+ Thread.current[:circuit_retry_count] += 1
192
+ delay = base_delay * (2 ** retry_count)
193
+ sleep(delay)
194
+
195
+ # Re-raise to trigger retry
196
+ raise exception
197
+ else
198
+ Thread.current[:circuit_retry_count] = nil
199
+
200
+ {
201
+ circuit_breaker: {
202
+ state: 'open',
203
+ error: exception.message,
204
+ max_retries_exceeded: true,
205
+ timestamp: Time.now.iso8601
206
+ }
207
+ }
208
+ end
209
+ end
210
+ end
211
+
212
+ # Graceful degradation fallback
213
+ def self.graceful_degradation(degraded_response)
214
+ proc do |exception|
215
+ {
216
+ circuit_breaker: {
217
+ state: 'open',
218
+ error: exception.message,
219
+ degraded_response: degraded_response,
220
+ timestamp: Time.now.iso8601
221
+ }
222
+ }
223
+ end
224
+ end
225
+ end
226
+ end
227
+ end
@@ -3,26 +3,25 @@
3
3
  # frozen_string_literal: true
4
4
 
5
5
  require 'concurrent'
6
+ require_relative 'circuit_breaker'
6
7
 
7
8
  module SmartMessage
8
9
 
9
10
  # The disoatcher routes incoming messages to all of the methods that
10
11
  # have been subscribed to the message.
11
12
  class Dispatcher
13
+ include BreakerMachines::DSL
12
14
 
13
15
  # TODO: setup forwardable for some @router_pool methods
14
16
 
15
- def initialize
17
+ def initialize(circuit_breaker_options = {})
16
18
  @subscribers = Hash.new(Array.new)
17
19
  @router_pool = Concurrent::CachedThreadPool.new
20
+
21
+ # Configure circuit breakers
22
+ configure_circuit_breakers(circuit_breaker_options)
18
23
  at_exit do
19
- print "Shuttingdown down the dispatcher's @router_pool ..."
20
- @router_pool.shutdown
21
- while @router_pool.shuttingdown?
22
- print '.'
23
- sleep 1
24
- end
25
- puts " done."
24
+ shutdown_pool
26
25
  end
27
26
  end
28
27
 
@@ -139,7 +138,8 @@ module SmartMessage
139
138
 
140
139
  SS.add(message_klass, message_processor, 'routed' )
141
140
  @router_pool.post do
142
- begin
141
+ # Use circuit breaker to protect message processing
142
+ circuit_result = circuit(:message_processor).wrap do
143
143
  # Check if this is a proc handler or a regular method call
144
144
  if proc_handler?(message_processor)
145
145
  # Call the proc handler via SmartMessage::Base
@@ -153,17 +153,63 @@ module SmartMessage
153
153
  .method(class_method)
154
154
  .call(message_header, message_payload)
155
155
  end
156
- rescue Exception => e
157
- # TODO: Add proper exception logging
158
- # Exception details: #{e.message}
159
- # Processor: #{message_processor}
160
- puts "Error processing message: #{e.message}" if $DEBUG
156
+ end
157
+
158
+ # Handle circuit breaker fallback responses
159
+ if circuit_result.is_a?(Hash) && circuit_result[:circuit_breaker]
160
+ handle_circuit_breaker_fallback(circuit_result, message_header, message_payload, message_processor)
161
+ end
162
+ end
163
+ end
164
+ end
165
+
166
+ # Get circuit breaker statistics
167
+ # @return [Hash] Circuit breaker statistics
168
+ def circuit_breaker_stats
169
+ stats = {}
170
+
171
+ begin
172
+ if respond_to?(:circuit)
173
+ breaker = circuit(:message_processor)
174
+ if breaker
175
+ stats[:message_processor] = {
176
+ status: breaker.status,
177
+ closed: breaker.closed?,
178
+ open: breaker.open?,
179
+ half_open: breaker.half_open?,
180
+ last_error: breaker.last_error,
181
+ opened_at: breaker.opened_at,
182
+ stats: breaker.stats
183
+ }
161
184
  end
162
185
  end
186
+ rescue => e
187
+ stats[:error] = "Failed to get circuit breaker stats: #{e.message}"
163
188
  end
189
+
190
+ stats
164
191
  end
165
192
 
166
- private
193
+ # Reset circuit breakers
194
+ # @param circuit_name [Symbol] Optional specific circuit to reset
195
+ def reset_circuit_breakers!(circuit_name = nil)
196
+ if circuit_name
197
+ circuit(circuit_name)&.reset!
198
+ else
199
+ # Reset all known circuits
200
+ circuit(:message_processor)&.reset!
201
+ end
202
+ end
203
+
204
+ # Shutdown the router pool with timeout and fallback
205
+ def shutdown_pool
206
+ @router_pool.shutdown
207
+
208
+ # Wait for graceful shutdown, force kill if timeout
209
+ unless @router_pool.wait_for_termination(3)
210
+ @router_pool.kill
211
+ end
212
+ end
167
213
 
168
214
  # Check if a message matches the subscription filters
169
215
  # @param message_header [SmartMessage::Header] The message header
@@ -199,6 +245,81 @@ module SmartMessage
199
245
  SmartMessage::Base.proc_handler?(message_processor)
200
246
  end
201
247
 
248
+ # Configure circuit breakers for the dispatcher
249
+ # @param options [Hash] Circuit breaker configuration options
250
+ def configure_circuit_breakers(options = {})
251
+ # Ensure CircuitBreaker module is available
252
+ return unless defined?(SmartMessage::CircuitBreaker::DEFAULT_CONFIGS)
253
+
254
+ # Configure message processor circuit breaker
255
+ default_config = SmartMessage::CircuitBreaker::DEFAULT_CONFIGS[:message_processor]
256
+ return unless default_config
257
+
258
+ processor_config = default_config.merge(options[:message_processor] || {})
259
+
260
+ # Define the circuit using the class-level DSL
261
+ self.class.circuit :message_processor do
262
+ threshold failures: processor_config[:threshold][:failures],
263
+ within: processor_config[:threshold][:within].seconds
264
+ reset_after processor_config[:reset_after].seconds
265
+
266
+ # Configure storage backend
267
+ case processor_config[:storage]
268
+ when :redis
269
+ # Use Redis storage if configured and available
270
+ if defined?(SmartMessage::Transport::RedisTransport)
271
+ begin
272
+ redis_transport = SmartMessage::Transport::RedisTransport.new
273
+ storage BreakerMachines::Storage::Redis.new(redis: redis_transport.redis_pub)
274
+ rescue
275
+ # Fall back to memory storage if Redis not available
276
+ storage BreakerMachines::Storage::Memory.new
277
+ end
278
+ else
279
+ storage BreakerMachines::Storage::Memory.new
280
+ end
281
+ else
282
+ storage BreakerMachines::Storage::Memory.new
283
+ end
284
+
285
+ # Default fallback for message processing failures
286
+ fallback do |exception|
287
+ {
288
+ circuit_breaker: {
289
+ circuit: :message_processor,
290
+ state: 'open',
291
+ error: exception.message,
292
+ error_class: exception.class.name,
293
+ timestamp: Time.now.iso8601,
294
+ fallback_triggered: true
295
+ }
296
+ }
297
+ end
298
+ end
299
+
300
+ end
301
+
302
+ # Handle circuit breaker fallback responses
303
+ # @param circuit_result [Hash] The circuit breaker fallback result
304
+ # @param message_header [SmartMessage::Header] The message header
305
+ # @param message_payload [String] The message payload
306
+ # @param message_processor [String] The processor that failed
307
+ def handle_circuit_breaker_fallback(circuit_result, message_header, message_payload, message_processor)
308
+ # Log circuit breaker activation
309
+ if $DEBUG
310
+ puts "Circuit breaker activated for processor: #{message_processor}"
311
+ puts "Error: #{circuit_result[:circuit_breaker][:error]}"
312
+ puts "Message: #{message_header.message_class} from #{message_header.from}"
313
+ end
314
+
315
+ # TODO: Integrate with structured logging when implemented
316
+ # TODO: Send to dead letter queue when implemented
317
+ # TODO: Emit metrics/events for monitoring
318
+
319
+ # For now, record the failure in simple stats
320
+ SS.add(message_header.message_class, message_processor, 'circuit_breaker_fallback')
321
+ end
322
+
202
323
 
203
324
  #######################################################
204
325
  ## Class methods
@@ -2,22 +2,95 @@
2
2
  # encoding: utf-8
3
3
  # frozen_string_literal: true
4
4
 
5
+ require_relative '../circuit_breaker'
6
+
5
7
  module SmartMessage::Serializer
6
8
  # the standard super class
7
9
  class Base
10
+ include BreakerMachines::DSL
11
+
8
12
  # provide basic configuration
9
13
  def initialize
10
- # TODO: write this
14
+ configure_serializer_circuit_breakers
11
15
  end
12
16
 
13
17
  def encode(message_instance)
14
- # TODO: Add proper logging here
15
- raise ::SmartMessage::Errors::NotImplemented
18
+ circuit(:serializer).wrap do
19
+ do_encode(message_instance)
20
+ end
21
+ rescue => e
22
+ # Handle circuit breaker fallback
23
+ if e.is_a?(Hash) && e[:circuit_breaker]
24
+ handle_serializer_fallback(e, :encode, message_instance)
25
+ else
26
+ raise
27
+ end
16
28
  end
17
29
 
18
30
  def decode(payload)
19
- # TODO: Add proper logging here
31
+ circuit(:serializer).wrap do
32
+ do_decode(payload)
33
+ end
34
+ rescue => e
35
+ # Handle circuit breaker fallback
36
+ if e.is_a?(Hash) && e[:circuit_breaker]
37
+ handle_serializer_fallback(e, :decode, payload)
38
+ else
39
+ raise
40
+ end
41
+ end
42
+
43
+ # Template methods for actual serialization (implement in subclasses)
44
+ def do_encode(message_instance)
45
+ raise ::SmartMessage::Errors::NotImplemented
46
+ end
47
+
48
+ def do_decode(payload)
20
49
  raise ::SmartMessage::Errors::NotImplemented
21
50
  end
51
+
52
+ private
53
+
54
+ # Configure circuit breaker for serializer operations
55
+ def configure_serializer_circuit_breakers
56
+ serializer_config = SmartMessage::CircuitBreaker::DEFAULT_CONFIGS[:serializer]
57
+
58
+ self.class.circuit :serializer do
59
+ threshold failures: serializer_config[:threshold][:failures],
60
+ within: serializer_config[:threshold][:within].seconds
61
+ reset_after serializer_config[:reset_after].seconds
62
+
63
+ storage BreakerMachines::Storage::Memory.new
64
+
65
+ # Fallback for serializer failures
66
+ fallback do |exception|
67
+ {
68
+ circuit_breaker: {
69
+ circuit: :serializer,
70
+ serializer_type: self.class.name,
71
+ state: 'open',
72
+ error: exception.message,
73
+ error_class: exception.class.name,
74
+ timestamp: Time.now.iso8601,
75
+ fallback_triggered: true
76
+ }
77
+ }
78
+ end
79
+ end
80
+ end
81
+
82
+ # Handle serializer circuit breaker fallback
83
+ 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
91
+
92
+ # Return the fallback result
93
+ fallback_result
94
+ end
22
95
  end # class Base
23
96
  end # module SmartMessage::Serializer