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
@@ -0,0 +1,451 @@
1
+ # Message Filtering
2
+
3
+ SmartMessage provides powerful message filtering capabilities that allow subscribers to receive only messages that match specific criteria. This enables sophisticated routing patterns for microservices architectures, environment-based deployment, and fine-grained message processing control.
4
+
5
+ ## Overview
6
+
7
+ Message filtering works at the subscription level, allowing you to specify criteria that incoming messages must match before being routed to your handlers. Filters support:
8
+
9
+ - **Exact string matching** for precise service identification
10
+ - **Regular expression patterns** for flexible service groups and environments
11
+ - **Arrays combining strings and regexps** for complex matching scenarios
12
+ - **Multi-criteria filtering** using both `from` and `to` constraints
13
+
14
+ ## Filter Dimensions
15
+
16
+ Messages can be filtered on two main dimensions:
17
+
18
+ ### `from:` - Message Sender Filtering
19
+ Filter messages based on who sent them:
20
+
21
+ ```ruby
22
+ # Exact sender
23
+ PaymentService.subscribe(from: 'payment-gateway')
24
+
25
+ # Pattern matching for sender groups
26
+ PaymentService.subscribe(from: /^payment-.*/)
27
+
28
+ # Multiple specific senders
29
+ AdminService.subscribe(from: ['admin', 'system', 'monitoring'])
30
+ ```
31
+
32
+ ### `to:` - Message Recipient Filtering
33
+ Filter messages based on their intended recipient:
34
+
35
+ ```ruby
36
+ # Exact recipient
37
+ OrderService.subscribe(to: 'order-processor')
38
+
39
+ # Environment-based routing
40
+ DevService.subscribe(to: /^(dev|staging)-.*/)
41
+ ProdService.subscribe(to: /^prod-.*/)
42
+
43
+ # Multiple target patterns
44
+ ApiService.subscribe(to: [/^api-.*/, /^web-.*/, 'gateway'])
45
+ ```
46
+
47
+ ### `broadcast:` - Broadcast Message Filtering
48
+ Filter for broadcast messages (messages with `to: nil`):
49
+
50
+ ```ruby
51
+ # Only broadcast messages
52
+ NotificationService.subscribe(broadcast: true)
53
+
54
+ # Broadcast OR directed messages (OR logic)
55
+ AlertService.subscribe(broadcast: true, to: 'alert-service')
56
+ ```
57
+
58
+ ## Filter Types
59
+
60
+ ### String Filters (Exact Match)
61
+
62
+ String filters match exactly:
63
+
64
+ ```ruby
65
+ class OrderMessage < SmartMessage::Base
66
+ # Configure message
67
+ end
68
+
69
+ # Subscribe to messages from exactly 'payment-service'
70
+ OrderMessage.subscribe(from: 'payment-service')
71
+
72
+ # Subscribe to messages directed to exactly 'order-processor'
73
+ OrderMessage.subscribe(to: 'order-processor')
74
+
75
+ # Combined exact matching
76
+ OrderMessage.subscribe(from: 'admin', to: 'order-service')
77
+ ```
78
+
79
+ ### Regular Expression Filters (Pattern Match)
80
+
81
+ Regex filters provide flexible pattern matching:
82
+
83
+ ```ruby
84
+ # Environment-based routing
85
+ OrderMessage.subscribe(to: /^(dev|staging)-.*/) # dev-api, staging-worker, etc.
86
+ OrderMessage.subscribe(to: /^prod-.*/) # prod-api, prod-worker, etc.
87
+
88
+ # Service type routing
89
+ OrderMessage.subscribe(from: /^payment-.*/) # payment-gateway, payment-processor
90
+ OrderMessage.subscribe(from: /^(api|web)-.*/) # api-server, web-frontend
91
+
92
+ # Complex patterns
93
+ OrderMessage.subscribe(from: /^admin-(dev|staging)-.+/) # admin-dev-panel, admin-staging-api
94
+ ```
95
+
96
+ ### Array Filters (Multiple Options)
97
+
98
+ Arrays allow combining exact strings and regex patterns:
99
+
100
+ ```ruby
101
+ # Multiple exact matches
102
+ OrderMessage.subscribe(from: ['admin', 'system', 'monitoring'])
103
+
104
+ # Mixed strings and patterns
105
+ OrderMessage.subscribe(from: [
106
+ 'admin', # Exact match
107
+ /^system-.*/, # Pattern match
108
+ 'legacy-service' # Another exact match
109
+ ])
110
+
111
+ # Multiple patterns
112
+ OrderMessage.subscribe(to: [
113
+ /^api-.*/, # All API services
114
+ /^web-.*/, # All web services
115
+ 'gateway' # Plus specific gateway
116
+ ])
117
+ ```
118
+
119
+ ### Combined Filters (Multi-Criteria)
120
+
121
+ Combine `from`, `to`, and `broadcast` filters:
122
+
123
+ ```ruby
124
+ # Admin services to production environments only
125
+ OrderMessage.subscribe(
126
+ from: /^admin-.*/,
127
+ to: /^prod-.*/
128
+ )
129
+
130
+ # Specific senders to multiple recipient types
131
+ OrderMessage.subscribe(
132
+ from: ['payment-gateway', 'billing-service'],
133
+ to: [/^order-.*/, /^fulfillment-.*/]
134
+ )
135
+
136
+ # Complex routing scenarios
137
+ OrderMessage.subscribe(
138
+ from: /^(admin|system)-.*/,
139
+ to: ['critical-service', /^prod-.*/]
140
+ )
141
+ ```
142
+
143
+ ## Use Cases and Patterns
144
+
145
+ ### Environment-Based Routing
146
+
147
+ Route messages based on deployment environments:
148
+
149
+ ```ruby
150
+ # Development services
151
+ class DevOrderProcessor < SmartMessage::Base
152
+ # Only receive messages to dev/staging environments
153
+ DevOrderProcessor.subscribe(to: /^(dev|staging)-.*/)
154
+ end
155
+
156
+ # Production services
157
+ class ProdOrderProcessor < SmartMessage::Base
158
+ # Only receive messages to production environments
159
+ ProdOrderProcessor.subscribe(to: /^prod-.*/)
160
+ end
161
+
162
+ # Cross-environment admin tools
163
+ class AdminDashboard < SmartMessage::Base
164
+ # Receive admin messages from any environment
165
+ AdminDashboard.subscribe(from: /^admin-.*/)
166
+ end
167
+ ```
168
+
169
+ ### Service Pattern Routing
170
+
171
+ Route based on service naming conventions:
172
+
173
+ ```ruby
174
+ # Payment services ecosystem
175
+ class PaymentProcessor < SmartMessage::Base
176
+ # Receive from all payment-related services
177
+ PaymentProcessor.subscribe(from: /^payment-.*/)
178
+ end
179
+
180
+ # API layer services
181
+ class ApiGateway < SmartMessage::Base
182
+ # Receive from web frontends and mobile apps
183
+ ApiGateway.subscribe(from: /^(web|mobile|api)-.*/)
184
+ end
185
+
186
+ # Monitoring and alerting
187
+ class MonitoringService < SmartMessage::Base
188
+ # Receive from all system monitoring components
189
+ MonitoringService.subscribe(from: /^(system|monitor|health)-.*/)
190
+ end
191
+ ```
192
+
193
+ ### Administrative and Security Routing
194
+
195
+ Route administrative and security messages:
196
+
197
+ ```ruby
198
+ # Security monitoring
199
+ class SecurityService < SmartMessage::Base
200
+ # Admin + security services + any system monitoring
201
+ SecurityService.subscribe(from: ['admin', /^security-.*/, /^system-monitor.*/])
202
+ end
203
+
204
+ # Audit logging
205
+ class AuditService < SmartMessage::Base
206
+ # Capture all admin actions across environments
207
+ AuditService.subscribe(from: /^admin-.*/)
208
+ end
209
+
210
+ # Operations dashboard
211
+ class OpsDashboard < SmartMessage::Base
212
+ # Operational messages + broadcasts
213
+ OpsDashboard.subscribe(
214
+ broadcast: true,
215
+ from: /^(ops|admin|system)-.*/
216
+ )
217
+ end
218
+ ```
219
+
220
+ ### Gateway and Transformation Patterns
221
+
222
+ Filter for message transformation and routing:
223
+
224
+ ```ruby
225
+ # Message format gateway
226
+ class FormatGateway < SmartMessage::Base
227
+ # Receive legacy format messages for transformation
228
+ FormatGateway.subscribe(from: ['legacy-system', /^old-.*/, 'mainframe'])
229
+
230
+ def self.process(header, payload)
231
+ # Transform and republish
232
+ transformed = transform_legacy_format(payload)
233
+ ModernMessage.new(transformed).publish
234
+ end
235
+ end
236
+
237
+ # Environment promotion gateway
238
+ class PromotionGateway < SmartMessage::Base
239
+ # Receive staging-approved messages for prod promotion
240
+ PromotionGateway.subscribe(
241
+ from: /^staging-.*/,
242
+ to: 'promotion-queue'
243
+ )
244
+
245
+ def self.process(header, payload)
246
+ # Republish to production
247
+ data = JSON.parse(payload)
248
+ republish_to_production(data)
249
+ end
250
+ end
251
+ ```
252
+
253
+ ## Filter Validation
254
+
255
+ SmartMessage validates filter parameters at subscription time to prevent runtime errors:
256
+
257
+ ### Valid Filter Types
258
+
259
+ ```ruby
260
+ # String filters
261
+ MyMessage.subscribe(from: 'service-name')
262
+
263
+ # Regex filters
264
+ MyMessage.subscribe(from: /^service-.*/)
265
+
266
+ # Array filters with strings and regexes
267
+ MyMessage.subscribe(from: ['exact-service', /^pattern-.*/, 'another-service'])
268
+
269
+ # Combined filters
270
+ MyMessage.subscribe(from: /^admin-.*/, to: ['service-a', /^prod-.*/])
271
+ ```
272
+
273
+ ### Invalid Filter Types
274
+
275
+ These will raise `ArgumentError` at subscription time:
276
+
277
+ ```ruby
278
+ # Invalid primitive types
279
+ MyMessage.subscribe(from: 123) # Numbers not allowed
280
+ MyMessage.subscribe(from: true) # Booleans not allowed
281
+ MyMessage.subscribe(from: {key: 'value'}) # Hashes not allowed
282
+
283
+ # Invalid array elements
284
+ MyMessage.subscribe(from: ['valid', 123]) # Mixed valid/invalid
285
+ MyMessage.subscribe(from: [/valid/, Object.new]) # Mixed valid/invalid
286
+ ```
287
+
288
+ ## Implementation Details
289
+
290
+ ### Filter Processing
291
+
292
+ Internally, filters are processed by the dispatcher's `message_matches_filters?` method:
293
+
294
+ 1. **Normalization**: String and Regexp values are converted to arrays
295
+ 2. **Validation**: Array elements are validated to be String or Regexp only
296
+ 3. **Matching**: For each filter array, check if message value matches any element:
297
+ - String elements: exact equality (`filter == value`)
298
+ - Regexp elements: pattern matching (`filter.match?(value)`)
299
+
300
+ ### Performance Considerations
301
+
302
+ - **String matching**: Very fast hash-based equality
303
+ - **Regex matching**: Slightly slower but still performant for typical patterns
304
+ - **Array processing**: Linear scan through filter array (typically small)
305
+ - **Filter caching**: Normalized filters are cached in subscription objects
306
+
307
+ ### Memory Usage
308
+
309
+ - Filter arrays are stored per subscription
310
+ - Regex objects are shared (Ruby optimizes identical regex literals)
311
+ - No dynamic regex compilation during message processing
312
+
313
+ ## Testing Filtered Subscriptions
314
+
315
+ ### Basic Filter Testing
316
+
317
+ ```ruby
318
+ class FilterTest < Minitest::Test
319
+ def setup
320
+ @transport = SmartMessage::Transport.create(:memory, auto_process: true)
321
+ TestMessage.config do
322
+ transport @transport
323
+ serializer SmartMessage::Serializer::JSON.new
324
+ end
325
+ TestMessage.unsubscribe!
326
+ end
327
+
328
+ def test_string_filter
329
+ TestMessage.subscribe(from: 'payment-service')
330
+
331
+ # Should match
332
+ message = TestMessage.new(data: 'test')
333
+ message.from('payment-service')
334
+ message.publish
335
+
336
+ # Should not match
337
+ message = TestMessage.new(data: 'test')
338
+ message.from('user-service')
339
+ message.publish
340
+
341
+ # Verify only one message was processed
342
+ assert_equal 1, processed_message_count
343
+ end
344
+
345
+ def test_regex_filter
346
+ TestMessage.subscribe(from: /^payment-.*/)
347
+
348
+ # Should match
349
+ ['payment-gateway', 'payment-processor'].each do |sender|
350
+ message = TestMessage.new(data: 'test')
351
+ message.from(sender)
352
+ message.publish
353
+ end
354
+
355
+ # Should not match
356
+ message = TestMessage.new(data: 'test')
357
+ message.from('user-service')
358
+ message.publish
359
+
360
+ # Verify two messages were processed
361
+ assert_equal 2, processed_message_count
362
+ end
363
+
364
+ def test_combined_filter
365
+ TestMessage.subscribe(from: /^admin-.*/, to: /^prod-.*/)
366
+
367
+ # Should match
368
+ message = TestMessage.new(data: 'test')
369
+ message.from('admin-panel')
370
+ message.to('prod-api')
371
+ message.publish
372
+
373
+ # Should not match (wrong from)
374
+ message = TestMessage.new(data: 'test')
375
+ message.from('user-panel')
376
+ message.to('prod-api')
377
+ message.publish
378
+
379
+ # Should not match (wrong to)
380
+ message = TestMessage.new(data: 'test')
381
+ message.from('admin-panel')
382
+ message.to('dev-api')
383
+ message.publish
384
+
385
+ # Verify only one message was processed
386
+ assert_equal 1, processed_message_count
387
+ end
388
+ end
389
+ ```
390
+
391
+ ### Performance Testing
392
+
393
+ ```ruby
394
+ def test_filter_performance
395
+ # Setup large number of subscriptions with different filters
396
+ 1000.times do |i|
397
+ TestMessage.subscribe("TestMessage.handler_#{i}", from: "service-#{i}")
398
+ end
399
+
400
+ start_time = Time.now
401
+
402
+ # Publish many messages
403
+ 100.times do |i|
404
+ message = TestMessage.new(data: i)
405
+ message.from("service-#{i % 10}") # Will match some filters
406
+ message.publish
407
+ end
408
+
409
+ processing_time = Time.now - start_time
410
+
411
+ # Verify performance is acceptable
412
+ assert processing_time < 1.0, "Filter processing took too long: #{processing_time}s"
413
+ end
414
+ ```
415
+
416
+ ## Migration Guide
417
+
418
+ ### Upgrading from String-Only Filters
419
+
420
+ If you're upgrading from a version that only supported string filters:
421
+
422
+ ```ruby
423
+ # Old (still works)
424
+ MyMessage.subscribe(from: 'exact-service')
425
+ MyMessage.subscribe(from: ['service-a', 'service-b'])
426
+
427
+ # New capabilities
428
+ MyMessage.subscribe(from: /^service-.*/) # Regex patterns
429
+ MyMessage.subscribe(from: ['exact', /^pattern-.*/]) # Mixed arrays
430
+ MyMessage.subscribe(from: /^admin-.*/, to: /^prod-.*/) # Combined criteria
431
+ ```
432
+
433
+ ### Error Handling Changes
434
+
435
+ Previous versions may have failed silently with invalid filters. The new implementation validates at subscription time:
436
+
437
+ ```ruby
438
+ # This will now raise ArgumentError instead of failing silently
439
+ begin
440
+ MyMessage.subscribe(from: 123) # Invalid type
441
+ rescue ArgumentError => e
442
+ puts "Filter validation failed: #{e.message}"
443
+ end
444
+ ```
445
+
446
+ ## Next Steps
447
+
448
+ - [Dispatcher Documentation](dispatcher.md) - How filtering integrates with message routing
449
+ - [Entity Addressing](addressing.md) - Understanding `from`, `to`, and `reply_to` fields
450
+ - [Examples](examples.md) - Complete working examples with filtering
451
+ - [Testing Guide](testing.md) - Best practices for testing filtered subscriptions
@@ -2,7 +2,7 @@
2
2
  # examples/01_point_to_point_orders.rb
3
3
  #
4
4
  # 1-to-1 Messaging Example: Order Processing System
5
- #
5
+ #
6
6
  # This example demonstrates point-to-point messaging between an OrderService
7
7
  # and a PaymentService. Each order gets processed by exactly one payment processor.
8
8
 
@@ -11,67 +11,69 @@ require_relative '../lib/smart_message'
11
11
  puts "=== SmartMessage Example: Point-to-Point Order Processing ==="
12
12
  puts
13
13
 
14
+ SmartMessage.configure do |config|
15
+ config.logger = STDERR
16
+ end
17
+
14
18
  # Define the Order Message
15
19
  class OrderMessage < SmartMessage::Base
16
20
  description "Represents customer orders for processing through the payment system"
17
-
18
- property :order_id,
21
+
22
+ property :order_id,
19
23
  description: "Unique identifier for the order (e.g., ORD-1001)"
20
- property :customer_id,
24
+ property :customer_id,
21
25
  description: "Unique identifier for the customer placing the order"
22
- property :amount,
26
+ property :amount,
23
27
  description: "Total order amount in decimal format (e.g., 99.99)"
24
- property :currency,
28
+ property :currency,
25
29
  default: 'USD',
26
30
  description: "ISO currency code for the order (defaults to USD)"
27
- property :payment_method,
31
+ property :payment_method,
28
32
  description: "Payment method selected by customer (credit_card, debit_card, paypal, etc.)"
29
- property :items,
33
+ property :items,
30
34
  description: "Array of item names or descriptions included in the order"
31
35
 
32
36
  # Configure to use memory transport for this example
33
37
  config do
34
38
  transport SmartMessage::Transport::StdoutTransport.new(loopback: true)
35
- serializer SmartMessage::Serializer::JSON.new
39
+ serializer SmartMessage::Serializer::Json.new
36
40
  end
37
41
 
38
42
  # Default processing - just logs the order
39
- def self.process(message_header, message_payload)
40
- order_data = JSON.parse(message_payload)
41
- puts "📋 Order received: #{order_data['order_id']} for $#{order_data['amount']}"
43
+ def self.process(message)
44
+ puts "📋 Order received: #{message.order_id} for $#{message.amount}"
42
45
  end
43
46
  end
44
47
 
45
- # Define the Payment Response Message
48
+ # Define the Payment Response Message
46
49
  class PaymentResponseMessage < SmartMessage::Base
47
50
  description "Contains payment processing results sent back to the order system"
48
-
49
- property :order_id,
51
+
52
+ property :order_id,
50
53
  description: "Reference to the original order being processed"
51
- property :payment_id,
54
+ property :payment_id,
52
55
  description: "Unique identifier for the payment transaction (e.g., PAY-5001)"
53
- property :status,
56
+ property :status,
54
57
  description: "Payment processing status: 'success', 'failed', or 'pending'"
55
- property :message,
58
+ property :message,
56
59
  description: "Human-readable description of the payment result"
57
- property :processed_at,
60
+ property :processed_at,
58
61
  description: "ISO8601 timestamp when the payment was processed"
59
62
 
60
63
  config do
61
64
  transport SmartMessage::Transport::StdoutTransport.new(loopback: true)
62
- serializer SmartMessage::Serializer::JSON.new
65
+ serializer SmartMessage::Serializer::Json.new
63
66
  end
64
67
 
65
- def self.process(message_header, message_payload)
66
- response_data = JSON.parse(message_payload)
67
- status_emoji = case response_data['status']
68
+ def self.process(message)
69
+ status_emoji = case message.status
68
70
  when 'success' then '✅'
69
71
  when 'failed' then '❌'
70
72
  when 'pending' then '⏳'
71
73
  else '❓'
72
74
  end
73
-
74
- puts "#{status_emoji} Payment #{response_data['status']}: Order #{response_data['order_id']} - #{response_data['message']}"
75
+
76
+ puts "#{status_emoji} Payment #{message.status}: Order #{message.order_id} - #{message.message}"
75
77
  end
76
78
  end
77
79
 
@@ -84,9 +86,9 @@ class OrderService
84
86
 
85
87
  def create_order(customer_id:, amount:, payment_method:, items:)
86
88
  order_id = "ORD-#{@order_counter += 1}"
87
-
89
+
88
90
  puts "\n🏪 OrderService: Creating order #{order_id}"
89
-
91
+
90
92
  order = OrderMessage.new(
91
93
  order_id: order_id,
92
94
  customer_id: customer_id,
@@ -95,10 +97,10 @@ class OrderService
95
97
  items: items,
96
98
  from: 'OrderService'
97
99
  )
98
-
100
+
99
101
  puts "🏪 OrderService: Sending order to payment processing..."
100
102
  order.publish
101
-
103
+
102
104
  order_id
103
105
  end
104
106
  end
@@ -108,47 +110,46 @@ class PaymentService
108
110
  def initialize
109
111
  puts "💳 PaymentService: Starting up..."
110
112
  @payment_counter = 5000
111
-
113
+
112
114
  # Subscribe to order messages with custom processor
113
115
  OrderMessage.subscribe('PaymentService.process_order')
114
116
  end
115
117
 
116
- def self.process_order(message_header, message_payload)
118
+ def self.process_order(message)
117
119
  processor = new
118
- processor.handle_order(message_header, message_payload)
120
+ processor.handle_order(message)
119
121
  end
120
122
 
121
- def handle_order(message_header, message_payload)
122
- order_data = JSON.parse(message_payload)
123
+ def handle_order(message)
123
124
  payment_id = "PAY-#{@payment_counter += 1}"
124
-
125
- puts "💳 PaymentService: Processing payment for order #{order_data['order_id']}"
126
-
125
+
126
+ puts "💳 PaymentService: Processing payment for order #{message.order_id}"
127
+
127
128
  # Simulate payment processing logic
128
- success = simulate_payment_processing(order_data)
129
-
129
+ success = simulate_payment_processing(message)
130
+
130
131
  # Send response back
131
132
  response = PaymentResponseMessage.new(
132
- order_id: order_data['order_id'],
133
+ order_id: message.order_id,
133
134
  payment_id: payment_id,
134
135
  status: success ? 'success' : 'failed',
135
136
  message: success ? 'Payment processed successfully' : 'Insufficient funds',
136
137
  processed_at: Time.now.iso8601,
137
138
  from: 'PaymentService'
138
139
  )
139
-
140
+
140
141
  puts "💳 PaymentService: Sending payment response..."
141
142
  response.publish
142
143
  end
143
144
 
144
145
  private
145
146
 
146
- def simulate_payment_processing(order_data)
147
+ def simulate_payment_processing(message)
147
148
  # Simulate processing time
148
149
  sleep(0.1)
149
-
150
+
150
151
  # Simulate success/failure based on amount (fail large orders for demo)
151
- order_data['amount'] < 1000
152
+ message.amount < 1000
152
153
  end
153
154
  end
154
155
 
@@ -156,18 +157,18 @@ end
156
157
  class OrderProcessingDemo
157
158
  def run
158
159
  puts "🚀 Starting Order Processing Demo\n"
159
-
160
+
160
161
  # Start services
161
162
  order_service = OrderService.new
162
163
  payment_service = PaymentService.new
163
-
164
+
164
165
  # Subscribe to payment responses
165
166
  PaymentResponseMessage.subscribe
166
-
167
+
167
168
  puts "\n" + "="*60
168
169
  puts "Processing Sample Orders"
169
170
  puts "="*60
170
-
171
+
171
172
  # Create some sample orders
172
173
  orders = [
173
174
  {
@@ -177,7 +178,7 @@ class OrderProcessingDemo
177
178
  items: ["Widget A", "Widget B"]
178
179
  },
179
180
  {
180
- customer_id: "CUST-002",
181
+ customer_id: "CUST-002",
181
182
  amount: 1299.99, # This will fail (too large)
182
183
  payment_method: "debit_card",
183
184
  items: ["Premium Widget", "Extended Warranty"]
@@ -193,15 +194,15 @@ class OrderProcessingDemo
193
194
  orders.each_with_index do |order_params, index|
194
195
  puts "\n--- Order #{index + 1} ---"
195
196
  order_id = order_service.create_order(**order_params)
196
-
197
+
197
198
  # Brief pause between orders for clarity
198
199
  sleep(0.5)
199
200
  end
200
-
201
+
201
202
  # Give time for all async processing to complete
202
203
  puts "\n⏳ Waiting for all payments to process..."
203
204
  sleep(2)
204
-
205
+
205
206
  puts "\n✨ Demo completed!"
206
207
  puts "\nThis example demonstrated:"
207
208
  puts "• Point-to-point messaging between OrderService and PaymentService"
@@ -215,4 +216,4 @@ end
215
216
  if __FILE__ == $0
216
217
  demo = OrderProcessingDemo.new
217
218
  demo.run
218
- end
219
+ end