smart_message 0.0.7 โ 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.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.irbrc +24 -0
- data/CHANGELOG.md +143 -0
- data/Gemfile.lock +6 -1
- data/README.md +289 -15
- data/docs/README.md +3 -1
- data/docs/addressing.md +119 -13
- data/docs/architecture.md +68 -0
- data/docs/dead_letter_queue.md +673 -0
- data/docs/dispatcher.md +87 -0
- data/docs/examples.md +59 -1
- data/docs/getting-started.md +8 -1
- data/docs/logging.md +382 -326
- data/docs/message_filtering.md +451 -0
- data/examples/01_point_to_point_orders.rb +54 -53
- data/examples/02_publish_subscribe_events.rb +14 -10
- data/examples/03_many_to_many_chat.rb +16 -8
- data/examples/04_redis_smart_home_iot.rb +20 -10
- data/examples/05_proc_handlers.rb +12 -11
- data/examples/06_custom_logger_example.rb +95 -100
- data/examples/07_error_handling_scenarios.rb +4 -2
- data/examples/08_entity_addressing_basic.rb +18 -6
- data/examples/08_entity_addressing_with_filtering.rb +27 -9
- data/examples/09_dead_letter_queue_demo.rb +559 -0
- data/examples/09_regex_filtering_microservices.rb +407 -0
- data/examples/10_header_block_configuration.rb +263 -0
- data/examples/11_global_configuration_example.rb +219 -0
- data/examples/README.md +102 -0
- data/examples/dead_letters.jsonl +12 -0
- data/examples/performance_metrics/benchmark_results_ractor_20250818_205603.json +135 -0
- data/examples/performance_metrics/benchmark_results_ractor_20250818_205831.json +135 -0
- data/examples/performance_metrics/benchmark_results_test_20250818_204942.json +130 -0
- data/examples/performance_metrics/benchmark_results_threadpool_20250818_204942.json +130 -0
- data/examples/performance_metrics/benchmark_results_threadpool_20250818_204959.json +130 -0
- data/examples/performance_metrics/benchmark_results_threadpool_20250818_205044.json +130 -0
- data/examples/performance_metrics/benchmark_results_threadpool_20250818_205109.json +130 -0
- data/examples/performance_metrics/benchmark_results_threadpool_20250818_205252.json +130 -0
- data/examples/performance_metrics/benchmark_results_unknown_20250819_172852.json +130 -0
- data/examples/performance_metrics/compare_benchmarks.rb +519 -0
- data/examples/performance_metrics/dead_letters.jsonl +3100 -0
- data/examples/performance_metrics/performance_benchmark.rb +344 -0
- data/examples/show_logger.rb +367 -0
- data/examples/show_me.rb +145 -0
- data/examples/temp.txt +94 -0
- data/examples/tmux_chat/bot_agent.rb +4 -2
- data/examples/tmux_chat/human_agent.rb +4 -2
- data/examples/tmux_chat/room_monitor.rb +4 -2
- data/examples/tmux_chat/shared_chat_system.rb +6 -3
- data/lib/smart_message/addressing.rb +259 -0
- data/lib/smart_message/base.rb +121 -599
- data/lib/smart_message/circuit_breaker.rb +23 -6
- data/lib/smart_message/configuration.rb +199 -0
- data/lib/smart_message/dead_letter_queue.rb +361 -0
- data/lib/smart_message/dispatcher.rb +90 -49
- data/lib/smart_message/header.rb +5 -0
- data/lib/smart_message/logger/base.rb +21 -1
- data/lib/smart_message/logger/default.rb +88 -138
- data/lib/smart_message/logger/lumberjack.rb +324 -0
- data/lib/smart_message/logger/null.rb +81 -0
- data/lib/smart_message/logger.rb +17 -9
- data/lib/smart_message/messaging.rb +100 -0
- data/lib/smart_message/plugins.rb +132 -0
- data/lib/smart_message/serializer/base.rb +25 -8
- data/lib/smart_message/serializer/json.rb +5 -4
- data/lib/smart_message/subscription.rb +193 -0
- data/lib/smart_message/transport/base.rb +84 -53
- data/lib/smart_message/transport/memory_transport.rb +7 -5
- data/lib/smart_message/transport/redis_transport.rb +15 -45
- data/lib/smart_message/transport/stdout_transport.rb +18 -8
- data/lib/smart_message/transport.rb +1 -34
- data/lib/smart_message/utilities.rb +142 -0
- data/lib/smart_message/version.rb +1 -1
- data/lib/smart_message/versioning.rb +85 -0
- data/lib/smart_message/wrapper.rb.bak +132 -0
- data/lib/smart_message.rb +74 -27
- data/smart_message.gemspec +3 -0
- metadata +77 -3
- data/lib/smart_message/serializer.rb +0 -10
- data/lib/smart_message/wrapper.rb +0 -43
@@ -0,0 +1,559 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# encoding: utf-8
|
3
|
+
# frozen_string_literal: true
|
4
|
+
|
5
|
+
# examples/09_dead_letter_queue_demo.rb
|
6
|
+
#
|
7
|
+
# Demonstrates the Dead Letter Queue (DLQ) system with:
|
8
|
+
# - Automatic capture via circuit breakers
|
9
|
+
# - Manual capture of failed messages
|
10
|
+
# - Replay capabilities
|
11
|
+
# - Administrative functions
|
12
|
+
# - Monitoring and statistics
|
13
|
+
|
14
|
+
require_relative '../lib/smart_message'
|
15
|
+
require 'json'
|
16
|
+
require 'fileutils'
|
17
|
+
|
18
|
+
# Configure DLQ for this demo
|
19
|
+
DEMO_DLQ_PATH = '/tmp/smart_message_dlq_demo.jsonl'
|
20
|
+
SmartMessage::DeadLetterQueue.configure_default(DEMO_DLQ_PATH)
|
21
|
+
|
22
|
+
# Clean up any existing DLQ file from previous runs
|
23
|
+
FileUtils.rm_f(DEMO_DLQ_PATH)
|
24
|
+
|
25
|
+
puts "=" * 80
|
26
|
+
puts "SmartMessage Dead Letter Queue Demo"
|
27
|
+
puts "=" * 80
|
28
|
+
puts "\nDLQ Path: #{DEMO_DLQ_PATH}"
|
29
|
+
puts "=" * 80
|
30
|
+
|
31
|
+
# ==============================================================================
|
32
|
+
# 1. Define Message Classes
|
33
|
+
# ==============================================================================
|
34
|
+
|
35
|
+
class PaymentMessage < SmartMessage::Base
|
36
|
+
description "Payment processing message that might fail"
|
37
|
+
version 1
|
38
|
+
from 'payment-service'
|
39
|
+
to 'bank-gateway'
|
40
|
+
|
41
|
+
property :payment_id, required: true, description: "Unique payment identifier"
|
42
|
+
property :amount, required: true, description: "Payment amount"
|
43
|
+
property :customer_id, required: true, description: "Customer making payment"
|
44
|
+
property :card_last_four, description: "Last 4 digits of card"
|
45
|
+
|
46
|
+
config do
|
47
|
+
transport SmartMessage::Transport.create(:memory)
|
48
|
+
serializer SmartMessage::Serializer::JSON.new
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.process(wrapper)
|
52
|
+
header = wrapper._sm_header
|
53
|
+
payload = wrapper._sm_payload
|
54
|
+
data = JSON.parse(payload, symbolize_names: true)
|
55
|
+
puts " ๐ณ Processing payment #{data[:payment_id]} for $#{data[:amount]}"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
class OrderMessage < SmartMessage::Base
|
60
|
+
description "Order processing message"
|
61
|
+
version 1
|
62
|
+
from 'order-service'
|
63
|
+
to 'fulfillment-service'
|
64
|
+
|
65
|
+
property :order_id, required: true, description: "Order identifier"
|
66
|
+
property :items, default: [], description: "Order items"
|
67
|
+
property :total, required: true, description: "Order total"
|
68
|
+
|
69
|
+
config do
|
70
|
+
transport SmartMessage::Transport.create(:memory)
|
71
|
+
serializer SmartMessage::Serializer::JSON.new
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.process(wrapper)
|
75
|
+
header = wrapper._sm_header
|
76
|
+
payload = wrapper._sm_payload
|
77
|
+
data = JSON.parse(payload, symbolize_names: true)
|
78
|
+
puts " ๐ฆ Processing order #{data[:order_id]} with #{data[:items].size} items"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
class NotificationMessage < SmartMessage::Base
|
83
|
+
description "User notification message"
|
84
|
+
version 1
|
85
|
+
from 'notification-service'
|
86
|
+
|
87
|
+
property :user_id, required: true, description: "User to notify"
|
88
|
+
property :message, required: true, description: "Notification content"
|
89
|
+
property :channel, default: 'email', description: "Notification channel"
|
90
|
+
|
91
|
+
config do
|
92
|
+
transport SmartMessage::Transport.create(:memory)
|
93
|
+
serializer SmartMessage::Serializer::JSON.new
|
94
|
+
end
|
95
|
+
|
96
|
+
def self.process(wrapper)
|
97
|
+
header = wrapper._sm_header
|
98
|
+
payload = wrapper._sm_payload
|
99
|
+
data = JSON.parse(payload, symbolize_names: true)
|
100
|
+
puts " ๐ง Sending #{data[:channel]} to user #{data[:user_id]}: #{data[:message]}"
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# ==============================================================================
|
105
|
+
# 2. Simulate Transport Failures
|
106
|
+
# ==============================================================================
|
107
|
+
|
108
|
+
class FailingTransport < SmartMessage::Transport::Base
|
109
|
+
attr_accessor :failure_mode, :failure_count
|
110
|
+
|
111
|
+
def initialize(**options)
|
112
|
+
super
|
113
|
+
@failure_mode = options[:failure_mode] || :none
|
114
|
+
@failure_count = 0
|
115
|
+
@max_failures = options[:max_failures] || 3
|
116
|
+
end
|
117
|
+
|
118
|
+
def do_publish(message_header, message_payload)
|
119
|
+
case @failure_mode
|
120
|
+
when :connection_error
|
121
|
+
@failure_count += 1
|
122
|
+
if @failure_count <= @max_failures
|
123
|
+
raise "Connection refused to payment gateway"
|
124
|
+
end
|
125
|
+
puts " โ
Transport recovered, message sent"
|
126
|
+
when :timeout
|
127
|
+
@failure_count += 1
|
128
|
+
if @failure_count <= @max_failures
|
129
|
+
raise "Request timeout after 30 seconds"
|
130
|
+
end
|
131
|
+
puts " โ
Transport recovered, message sent"
|
132
|
+
when :intermittent
|
133
|
+
if rand < 0.5
|
134
|
+
raise "Intermittent network error"
|
135
|
+
end
|
136
|
+
puts " โ
Message sent successfully"
|
137
|
+
else
|
138
|
+
puts " โ
Message published successfully"
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
# ==============================================================================
|
144
|
+
# 3. Demo Functions
|
145
|
+
# ==============================================================================
|
146
|
+
|
147
|
+
def section_header(title)
|
148
|
+
puts "\n" + "=" * 80
|
149
|
+
puts "#{title}"
|
150
|
+
puts "=" * 80
|
151
|
+
end
|
152
|
+
|
153
|
+
def show_dlq_status
|
154
|
+
dlq = SmartMessage::DeadLetterQueue.default
|
155
|
+
puts "\n๐ DLQ Status:"
|
156
|
+
puts " - Queue size: #{dlq.size} messages"
|
157
|
+
|
158
|
+
if dlq.size > 0
|
159
|
+
stats = dlq.statistics
|
160
|
+
puts " - By class: #{stats[:by_class]}"
|
161
|
+
puts " - By error: #{stats[:by_error].keys.first(3).join(', ')}..."
|
162
|
+
|
163
|
+
# Show next message
|
164
|
+
next_msg = dlq.peek
|
165
|
+
if next_msg
|
166
|
+
puts " - Next message: #{next_msg[:header][:message_class]} (#{next_msg[:error]})"
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
# ==============================================================================
|
172
|
+
# 4. Demonstration Scenarios
|
173
|
+
# ==============================================================================
|
174
|
+
|
175
|
+
section_header("1. MANUAL DLQ CAPTURE")
|
176
|
+
puts "\nManually capturing failed messages in DLQ..."
|
177
|
+
|
178
|
+
dlq = SmartMessage::DeadLetterQueue.default
|
179
|
+
|
180
|
+
# Simulate validation failure
|
181
|
+
begin
|
182
|
+
payment = PaymentMessage.new(
|
183
|
+
payment_id: "PAY-001",
|
184
|
+
amount: -100, # Invalid amount
|
185
|
+
customer_id: "CUST-123"
|
186
|
+
)
|
187
|
+
|
188
|
+
# Business logic validation
|
189
|
+
if payment.amount < 0
|
190
|
+
raise "Invalid payment amount: cannot be negative"
|
191
|
+
end
|
192
|
+
rescue => e
|
193
|
+
puts "โ Validation failed: #{e.message}"
|
194
|
+
|
195
|
+
# Manually add to DLQ
|
196
|
+
wrapper = SmartMessage::Wrapper::Base.new(
|
197
|
+
header: payment._sm_header,
|
198
|
+
payload: payment.encode
|
199
|
+
)
|
200
|
+
dlq.enqueue(
|
201
|
+
wrapper,
|
202
|
+
error: e.message,
|
203
|
+
transport: "ValidationLayer",
|
204
|
+
retry_count: 0
|
205
|
+
)
|
206
|
+
puts "๐พ Message saved to DLQ"
|
207
|
+
end
|
208
|
+
|
209
|
+
show_dlq_status
|
210
|
+
|
211
|
+
# ==============================================================================
|
212
|
+
|
213
|
+
section_header("2. AUTOMATIC CAPTURE VIA TRANSPORT FAILURE")
|
214
|
+
puts "\nSimulating transport failures that trigger DLQ..."
|
215
|
+
|
216
|
+
# Create a payment with failing transport
|
217
|
+
payment = PaymentMessage.new(
|
218
|
+
payment_id: "PAY-002",
|
219
|
+
amount: 250.00,
|
220
|
+
customer_id: "CUST-456",
|
221
|
+
card_last_four: "1234"
|
222
|
+
)
|
223
|
+
|
224
|
+
# Override with failing transport
|
225
|
+
failing_transport = FailingTransport.new(
|
226
|
+
failure_mode: :connection_error,
|
227
|
+
max_failures: 2
|
228
|
+
)
|
229
|
+
payment.transport(failing_transport)
|
230
|
+
|
231
|
+
# First attempt - will fail
|
232
|
+
puts "\n๐ Attempt 1: Publishing payment message..."
|
233
|
+
begin
|
234
|
+
payment.publish
|
235
|
+
rescue => e
|
236
|
+
puts "โ Publish failed: #{e.message}"
|
237
|
+
|
238
|
+
# Circuit breaker would normally handle this, but we'll add manually for demo
|
239
|
+
wrapper = SmartMessage::Wrapper::Base.new(
|
240
|
+
header: payment._sm_header,
|
241
|
+
payload: payment.encode
|
242
|
+
)
|
243
|
+
dlq.enqueue(
|
244
|
+
wrapper,
|
245
|
+
error: e.message,
|
246
|
+
transport: failing_transport.class.name,
|
247
|
+
retry_count: 1
|
248
|
+
)
|
249
|
+
puts "๐พ Message automatically saved to DLQ"
|
250
|
+
end
|
251
|
+
|
252
|
+
show_dlq_status
|
253
|
+
|
254
|
+
# ==============================================================================
|
255
|
+
|
256
|
+
section_header("3. MULTIPLE FAILURE TYPES")
|
257
|
+
puts "\nAdding various types of failures to DLQ..."
|
258
|
+
|
259
|
+
# Order with timeout
|
260
|
+
order = OrderMessage.new(
|
261
|
+
order_id: "ORD-789",
|
262
|
+
items: ["SKU-001", "SKU-002"],
|
263
|
+
total: 99.99
|
264
|
+
)
|
265
|
+
|
266
|
+
wrapper = SmartMessage::Wrapper::Base.new(
|
267
|
+
header: order._sm_header,
|
268
|
+
payload: order.encode
|
269
|
+
)
|
270
|
+
dlq.enqueue(
|
271
|
+
wrapper,
|
272
|
+
error: "Gateway timeout after 30 seconds",
|
273
|
+
transport: "Redis",
|
274
|
+
retry_count: 3
|
275
|
+
)
|
276
|
+
puts "๐พ Added order with timeout error"
|
277
|
+
|
278
|
+
# Notification with rate limit
|
279
|
+
notification = NotificationMessage.new(
|
280
|
+
user_id: "USER-999",
|
281
|
+
message: "Your order has shipped!",
|
282
|
+
channel: "sms"
|
283
|
+
)
|
284
|
+
|
285
|
+
wrapper = SmartMessage::Wrapper::Base.new(
|
286
|
+
header: notification._sm_header,
|
287
|
+
payload: notification.encode
|
288
|
+
)
|
289
|
+
dlq.enqueue(
|
290
|
+
wrapper,
|
291
|
+
error: "Rate limit exceeded: 429 Too Many Requests",
|
292
|
+
transport: "SMSGateway",
|
293
|
+
retry_count: 1
|
294
|
+
)
|
295
|
+
puts "๐พ Added notification with rate limit error"
|
296
|
+
|
297
|
+
# Another payment with different error
|
298
|
+
payment3 = PaymentMessage.new(
|
299
|
+
payment_id: "PAY-003",
|
300
|
+
amount: 500.00,
|
301
|
+
customer_id: "CUST-789"
|
302
|
+
)
|
303
|
+
|
304
|
+
wrapper = SmartMessage::Wrapper::Base.new(
|
305
|
+
header: payment3._sm_header,
|
306
|
+
payload: payment3.encode
|
307
|
+
)
|
308
|
+
dlq.enqueue(
|
309
|
+
wrapper,
|
310
|
+
error: "Invalid merchant credentials",
|
311
|
+
transport: "StripeGateway",
|
312
|
+
retry_count: 0
|
313
|
+
)
|
314
|
+
puts "๐พ Added payment with auth error"
|
315
|
+
|
316
|
+
show_dlq_status
|
317
|
+
|
318
|
+
# ==============================================================================
|
319
|
+
|
320
|
+
section_header("4. FILTERING AND ANALYSIS")
|
321
|
+
puts "\nAnalyzing failed messages in DLQ..."
|
322
|
+
|
323
|
+
# Filter by message class
|
324
|
+
puts "\n๐ Filtering by message class:"
|
325
|
+
payment_failures = dlq.filter_by_class('PaymentMessage')
|
326
|
+
puts " - Found #{payment_failures.size} failed PaymentMessage(s)"
|
327
|
+
payment_failures.each do |entry|
|
328
|
+
puts " โข #{entry[:header][:uuid][0..7]}... - #{entry[:error]}"
|
329
|
+
end
|
330
|
+
|
331
|
+
order_failures = dlq.filter_by_class('OrderMessage')
|
332
|
+
puts " - Found #{order_failures.size} failed OrderMessage(s)"
|
333
|
+
|
334
|
+
# Filter by error pattern
|
335
|
+
puts "\n๐ Filtering by error pattern:"
|
336
|
+
timeout_errors = dlq.filter_by_error_pattern(/timeout/i)
|
337
|
+
puts " - Found #{timeout_errors.size} timeout error(s)"
|
338
|
+
timeout_errors.each do |entry|
|
339
|
+
puts " โข #{entry[:header][:message_class]} - #{entry[:error]}"
|
340
|
+
end
|
341
|
+
|
342
|
+
connection_errors = dlq.filter_by_error_pattern(/connection|refused/i)
|
343
|
+
puts " - Found #{connection_errors.size} connection error(s)"
|
344
|
+
|
345
|
+
# Get detailed statistics
|
346
|
+
puts "\n๐ Detailed Statistics:"
|
347
|
+
stats = dlq.statistics
|
348
|
+
puts " Total messages: #{stats[:total]}"
|
349
|
+
puts "\n By message class:"
|
350
|
+
stats[:by_class].each do |klass, count|
|
351
|
+
percentage = (count.to_f / stats[:total] * 100).round(1)
|
352
|
+
puts " โข #{klass}: #{count} (#{percentage}%)"
|
353
|
+
end
|
354
|
+
puts "\n By error type:"
|
355
|
+
stats[:by_error].sort_by { |_, count| -count }.each do |error, count|
|
356
|
+
puts " โข #{error[0..50]}#{'...' if error.length > 50}: #{count}"
|
357
|
+
end
|
358
|
+
|
359
|
+
# ==============================================================================
|
360
|
+
|
361
|
+
section_header("5. MESSAGE INSPECTION")
|
362
|
+
puts "\nInspecting messages without removing them..."
|
363
|
+
|
364
|
+
messages = dlq.inspect_messages(limit: 3)
|
365
|
+
messages.each_with_index do |msg, i|
|
366
|
+
puts "\n๐ Message #{i + 1}:"
|
367
|
+
puts " - Class: #{msg[:header][:message_class]}"
|
368
|
+
puts " - ID: #{msg[:header][:uuid][0..12]}..."
|
369
|
+
puts " - Error: #{msg[:error]}"
|
370
|
+
puts " - Retry count: #{msg[:retry_count]}"
|
371
|
+
puts " - Timestamp: #{msg[:timestamp]}"
|
372
|
+
end
|
373
|
+
|
374
|
+
# ==============================================================================
|
375
|
+
|
376
|
+
section_header("6. REPLAY CAPABILITIES")
|
377
|
+
puts "\nDemonstrating message replay functionality..."
|
378
|
+
|
379
|
+
puts "\n๐ Replaying oldest message:"
|
380
|
+
puts " Queue size before: #{dlq.size}"
|
381
|
+
|
382
|
+
# Create a working transport for replay
|
383
|
+
working_transport = SmartMessage::Transport.create(:memory)
|
384
|
+
|
385
|
+
# Replay the oldest message
|
386
|
+
result = dlq.replay_one(working_transport)
|
387
|
+
if result[:success]
|
388
|
+
puts " โ
Message replayed successfully!"
|
389
|
+
message = result[:message]
|
390
|
+
puts " โข Class: #{message.class.name}"
|
391
|
+
puts " โข Sent via: #{working_transport.class.name}"
|
392
|
+
else
|
393
|
+
puts " โ Replay failed: #{result[:error]}"
|
394
|
+
end
|
395
|
+
|
396
|
+
puts " Queue size after: #{dlq.size}"
|
397
|
+
|
398
|
+
# Batch replay
|
399
|
+
puts "\n๐ Replaying batch of 2 messages:"
|
400
|
+
results = dlq.replay_batch(2, working_transport)
|
401
|
+
puts " โ
Successfully replayed: #{results[:success]}"
|
402
|
+
puts " โ Failed to replay: #{results[:failed]}"
|
403
|
+
if results[:errors].any?
|
404
|
+
results[:errors].each do |error|
|
405
|
+
puts " โข Error: #{error}"
|
406
|
+
end
|
407
|
+
end
|
408
|
+
|
409
|
+
show_dlq_status
|
410
|
+
|
411
|
+
# ==============================================================================
|
412
|
+
|
413
|
+
section_header("7. TIME-BASED OPERATIONS")
|
414
|
+
puts "\nExporting messages by time range..."
|
415
|
+
|
416
|
+
# Get all messages from the last minute
|
417
|
+
one_minute_ago = Time.now - 60
|
418
|
+
now = Time.now
|
419
|
+
|
420
|
+
recent_messages = dlq.export_range(one_minute_ago, now)
|
421
|
+
puts "๐
Messages from last minute: #{recent_messages.size}"
|
422
|
+
|
423
|
+
# Group by time intervals
|
424
|
+
if recent_messages.any?
|
425
|
+
puts "\n Timeline:"
|
426
|
+
recent_messages.each do |msg|
|
427
|
+
time = Time.parse(msg[:timestamp])
|
428
|
+
seconds_ago = (now - time).to_i
|
429
|
+
puts " โข #{seconds_ago}s ago: #{msg[:header][:message_class]} - #{msg[:error][0..30]}..."
|
430
|
+
end
|
431
|
+
end
|
432
|
+
|
433
|
+
# ==============================================================================
|
434
|
+
|
435
|
+
section_header("8. ADMINISTRATIVE OPERATIONS")
|
436
|
+
puts "\nDemonstrating administrative functions..."
|
437
|
+
|
438
|
+
# Check current size
|
439
|
+
puts "\n๐๏ธ Current DLQ state:"
|
440
|
+
puts " - File path: #{dlq.file_path}"
|
441
|
+
puts " - File exists: #{File.exist?(dlq.file_path)}"
|
442
|
+
puts " - File size: #{File.size(dlq.file_path)} bytes" if File.exist?(dlq.file_path)
|
443
|
+
puts " - Message count: #{dlq.size}"
|
444
|
+
|
445
|
+
# Peek at next message for processing
|
446
|
+
puts "\n๐ Peeking at next message (without removing):"
|
447
|
+
next_msg = dlq.peek
|
448
|
+
if next_msg
|
449
|
+
puts " - Would process: #{next_msg[:header][:message_class]}"
|
450
|
+
puts " - Error was: #{next_msg[:error]}"
|
451
|
+
puts " - Retry count: #{next_msg[:retry_count]}"
|
452
|
+
end
|
453
|
+
|
454
|
+
# Demonstrate FIFO order
|
455
|
+
puts "\n๐ FIFO Queue Order (dequeue order):"
|
456
|
+
temp_messages = []
|
457
|
+
3.times do |i|
|
458
|
+
msg = dlq.dequeue
|
459
|
+
break unless msg
|
460
|
+
temp_messages << msg
|
461
|
+
puts " #{i + 1}. #{msg[:header][:message_class]} - #{msg[:timestamp]}"
|
462
|
+
end
|
463
|
+
|
464
|
+
# Put them back for other demos
|
465
|
+
temp_messages.each do |msg|
|
466
|
+
header = SmartMessage::Header.new(msg[:header])
|
467
|
+
wrapper = SmartMessage::Wrapper::Base.new(
|
468
|
+
header: header,
|
469
|
+
payload: msg[:payload]
|
470
|
+
)
|
471
|
+
dlq.enqueue(wrapper,
|
472
|
+
error: msg[:error],
|
473
|
+
retry_count: msg[:retry_count])
|
474
|
+
end
|
475
|
+
|
476
|
+
# ==============================================================================
|
477
|
+
|
478
|
+
section_header("9. CIRCUIT BREAKER INTEGRATION")
|
479
|
+
puts "\nDemonstrating circuit breaker with DLQ fallback..."
|
480
|
+
|
481
|
+
# This would normally be automatic, but we'll simulate it
|
482
|
+
class PaymentGateway
|
483
|
+
include BreakerMachines::DSL
|
484
|
+
|
485
|
+
circuit :payment_processing do
|
486
|
+
threshold failures: 2, within: 30.seconds
|
487
|
+
reset_after 10.seconds
|
488
|
+
|
489
|
+
# DLQ fallback
|
490
|
+
fallback do |exception|
|
491
|
+
puts " โก Circuit breaker tripped: #{exception.message}"
|
492
|
+
# In real scenario, message would go to DLQ here
|
493
|
+
{ circuit_open: true, error: exception.message }
|
494
|
+
end
|
495
|
+
end
|
496
|
+
|
497
|
+
def process_payment(payment_data)
|
498
|
+
circuit(:payment_processing).wrap do
|
499
|
+
# Simulate failure
|
500
|
+
raise "Payment gateway unavailable"
|
501
|
+
end
|
502
|
+
end
|
503
|
+
end
|
504
|
+
|
505
|
+
gateway = PaymentGateway.new
|
506
|
+
payment_data = { amount: 100.00, customer: "TEST" }
|
507
|
+
|
508
|
+
puts "Attempting payment processing with circuit breaker:"
|
509
|
+
2.times do |i|
|
510
|
+
puts "\n Attempt #{i + 1}:"
|
511
|
+
result = gateway.process_payment(payment_data)
|
512
|
+
if result[:circuit_open]
|
513
|
+
puts " ๐ด Circuit is OPEN - request blocked"
|
514
|
+
# Message would be in DLQ now
|
515
|
+
end
|
516
|
+
end
|
517
|
+
|
518
|
+
# ==============================================================================
|
519
|
+
|
520
|
+
section_header("10. CLEANUP AND FINAL STATS")
|
521
|
+
puts "\nFinal DLQ statistics before cleanup..."
|
522
|
+
|
523
|
+
final_stats = dlq.statistics
|
524
|
+
puts "\n๐ Final Statistics:"
|
525
|
+
puts " - Total messages processed: #{final_stats[:total]}"
|
526
|
+
puts " - Unique error types: #{final_stats[:by_error].keys.size}"
|
527
|
+
puts " - Message classes affected: #{final_stats[:by_class].keys.join(', ')}"
|
528
|
+
|
529
|
+
# Demonstrate clearing the queue
|
530
|
+
puts "\n๐งน Clearing the DLQ..."
|
531
|
+
puts " - Size before clear: #{dlq.size}"
|
532
|
+
dlq.clear
|
533
|
+
puts " - Size after clear: #{dlq.size}"
|
534
|
+
|
535
|
+
# Clean up demo file
|
536
|
+
FileUtils.rm_f(DEMO_DLQ_PATH)
|
537
|
+
puts "\nโจ Demo complete! DLQ file cleaned up."
|
538
|
+
|
539
|
+
# ==============================================================================
|
540
|
+
|
541
|
+
puts "\n" + "=" * 80
|
542
|
+
puts "KEY TAKEAWAYS:"
|
543
|
+
puts "=" * 80
|
544
|
+
puts """
|
545
|
+
1. DLQ automatically captures failed messages via circuit breakers
|
546
|
+
2. Manual capture available for business logic failures
|
547
|
+
3. Messages stored in JSON Lines format for efficiency
|
548
|
+
4. FIFO queue ensures oldest failures processed first
|
549
|
+
5. Replay capabilities with transport override support
|
550
|
+
6. Rich filtering and analysis tools for debugging
|
551
|
+
7. Time-based operations for historical analysis
|
552
|
+
8. Thread-safe operations for production use
|
553
|
+
9. Integration with circuit breakers for automatic capture
|
554
|
+
10. Administrative tools for queue management
|
555
|
+
|
556
|
+
The Dead Letter Queue ensures no messages are lost during failures
|
557
|
+
and provides comprehensive tools for analysis and recovery.
|
558
|
+
"""
|
559
|
+
puts "=" * 80
|