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,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::
|
39
|
+
serializer SmartMessage::Serializer::Json.new
|
36
40
|
end
|
37
41
|
|
38
42
|
# Default processing - just logs the order
|
39
|
-
def self.process(
|
40
|
-
|
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::
|
65
|
+
serializer SmartMessage::Serializer::Json.new
|
63
66
|
end
|
64
67
|
|
65
|
-
def self.process(
|
66
|
-
|
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 #{
|
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(
|
118
|
+
def self.process_order(message)
|
117
119
|
processor = new
|
118
|
-
processor.handle_order(
|
120
|
+
processor.handle_order(message)
|
119
121
|
end
|
120
122
|
|
121
|
-
def handle_order(
|
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 #{
|
126
|
-
|
125
|
+
|
126
|
+
puts "💳 PaymentService: Processing payment for order #{message.order_id}"
|
127
|
+
|
127
128
|
# Simulate payment processing logic
|
128
|
-
success = simulate_payment_processing(
|
129
|
-
|
129
|
+
success = simulate_payment_processing(message)
|
130
|
+
|
130
131
|
# Send response back
|
131
132
|
response = PaymentResponseMessage.new(
|
132
|
-
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(
|
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
|
-
|
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
|