smart_message 0.0.10 → 0.0.12
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/.github/workflows/deploy-github-pages.yml +38 -0
- data/.gitignore +5 -0
- data/CHANGELOG.md +30 -0
- data/Gemfile.lock +35 -4
- data/README.md +169 -71
- data/Rakefile +29 -4
- data/docs/assets/images/ddq_architecture.svg +130 -0
- data/docs/assets/images/dlq_architecture.svg +115 -0
- data/docs/assets/images/enhanced-dual-publishing.svg +136 -0
- data/docs/assets/images/enhanced-fluent-api.svg +149 -0
- data/docs/assets/images/enhanced-microservices-routing.svg +115 -0
- data/docs/assets/images/enhanced-pattern-matching.svg +107 -0
- data/docs/assets/images/fluent-api-demo.svg +59 -0
- data/docs/assets/images/performance-comparison.svg +161 -0
- data/docs/assets/images/redis-basic-architecture.svg +53 -0
- data/docs/assets/images/redis-enhanced-architecture.svg +88 -0
- data/docs/assets/images/redis-queue-architecture.svg +101 -0
- data/docs/assets/images/smart_message.jpg +0 -0
- data/docs/assets/images/smart_message_walking.jpg +0 -0
- data/docs/assets/images/smartmessage_architecture_overview.svg +173 -0
- data/docs/assets/images/transport-comparison-matrix.svg +171 -0
- data/docs/assets/javascripts/mathjax.js +17 -0
- data/docs/assets/stylesheets/extra.css +51 -0
- data/docs/{addressing.md → core-concepts/addressing.md} +5 -7
- data/docs/{architecture.md → core-concepts/architecture.md} +78 -138
- data/docs/{dispatcher.md → core-concepts/dispatcher.md} +21 -21
- data/docs/{message_filtering.md → core-concepts/message-filtering.md} +2 -3
- data/docs/{message_processing.md → core-concepts/message-processing.md} +17 -17
- data/docs/{troubleshooting.md → development/troubleshooting.md} +7 -7
- data/docs/{examples.md → getting-started/examples.md} +115 -89
- data/docs/{getting-started.md → getting-started/quick-start.md} +47 -18
- data/docs/guides/redis-queue-getting-started.md +697 -0
- data/docs/guides/redis-queue-patterns.md +889 -0
- data/docs/guides/redis-queue-production.md +1091 -0
- data/docs/index.md +64 -0
- data/docs/{dead_letter_queue.md → reference/dead-letter-queue.md} +2 -3
- data/docs/{logging.md → reference/logging.md} +1 -1
- data/docs/{message_deduplication.md → reference/message-deduplication.md} +1 -0
- data/docs/{proc_handlers_summary.md → reference/proc-handlers.md} +7 -6
- data/docs/{serializers.md → reference/serializers.md} +3 -5
- data/docs/{transports.md → reference/transports.md} +133 -11
- data/docs/transports/memory-transport.md +374 -0
- data/docs/transports/redis-enhanced-transport.md +524 -0
- data/docs/transports/redis-queue-transport.md +1304 -0
- data/docs/transports/redis-transport-comparison.md +496 -0
- data/docs/transports/redis-transport.md +509 -0
- data/examples/README.md +98 -5
- data/examples/city_scenario/911_emergency_call_flow.svg +99 -0
- data/examples/city_scenario/README.md +515 -0
- data/examples/city_scenario/ai_visitor_intelligence_flow.svg +108 -0
- data/examples/city_scenario/citizen.rb +195 -0
- data/examples/city_scenario/city_diagram.svg +125 -0
- data/examples/city_scenario/common/health_monitor.rb +80 -0
- data/examples/city_scenario/common/logger.rb +30 -0
- data/examples/city_scenario/emergency_dispatch_center.rb +270 -0
- data/examples/city_scenario/fire_department.rb +446 -0
- data/examples/city_scenario/fire_emergency_flow.svg +95 -0
- data/examples/city_scenario/health_department.rb +100 -0
- data/examples/city_scenario/health_monitoring_system.svg +130 -0
- data/examples/city_scenario/house.rb +244 -0
- data/examples/city_scenario/local_bank.rb +217 -0
- data/examples/city_scenario/messages/emergency_911_message.rb +81 -0
- data/examples/city_scenario/messages/emergency_resolved_message.rb +43 -0
- data/examples/city_scenario/messages/fire_dispatch_message.rb +43 -0
- data/examples/city_scenario/messages/fire_emergency_message.rb +45 -0
- data/examples/city_scenario/messages/health_check_message.rb +22 -0
- data/examples/city_scenario/messages/health_status_message.rb +35 -0
- data/examples/city_scenario/messages/police_dispatch_message.rb +46 -0
- data/examples/city_scenario/messages/silent_alarm_message.rb +38 -0
- data/examples/city_scenario/police_department.rb +316 -0
- data/examples/city_scenario/redis_monitor.rb +129 -0
- data/examples/city_scenario/redis_stats.rb +743 -0
- data/examples/city_scenario/room_for_improvement.md +240 -0
- data/examples/city_scenario/security_emergency_flow.svg +95 -0
- data/examples/city_scenario/service_internal_architecture.svg +154 -0
- data/examples/city_scenario/smart_message_ai_agent.rb +364 -0
- data/examples/city_scenario/start_demo.sh +236 -0
- data/examples/city_scenario/stop_demo.sh +106 -0
- data/examples/city_scenario/visitor.rb +631 -0
- data/examples/{10_message_deduplication.rb → memory/01_message_deduplication_demo.rb} +1 -1
- data/examples/{09_dead_letter_queue_demo.rb → memory/02_dead_letter_queue_demo.rb} +13 -40
- data/examples/{01_point_to_point_orders.rb → memory/03_point_to_point_orders.rb} +1 -1
- data/examples/{02_publish_subscribe_events.rb → memory/04_publish_subscribe_events.rb} +2 -2
- data/examples/{03_many_to_many_chat.rb → memory/05_many_to_many_chat.rb} +4 -4
- data/examples/{show_me.rb → memory/06_pretty_print_demo.rb} +1 -1
- data/examples/{05_proc_handlers.rb → memory/07_proc_handlers_demo.rb} +2 -2
- data/examples/{06_custom_logger_example.rb → memory/08_custom_logger_demo.rb} +17 -14
- data/examples/{07_error_handling_scenarios.rb → memory/09_error_handling_demo.rb} +4 -4
- data/examples/{08_entity_addressing_basic.rb → memory/10_entity_addressing_basic.rb} +8 -8
- data/examples/{08_entity_addressing_with_filtering.rb → memory/11_entity_addressing_with_filtering.rb} +6 -6
- data/examples/{09_regex_filtering_microservices.rb → memory/12_regex_filtering_microservices.rb} +2 -2
- data/examples/{10_header_block_configuration.rb → memory/13_header_block_configuration.rb} +6 -6
- data/examples/{11_global_configuration_example.rb → memory/14_global_configuration_demo.rb} +19 -8
- data/examples/{show_logger.rb → memory/15_logger_demo.rb} +1 -1
- data/examples/memory/README.md +163 -0
- data/examples/memory/memory_transport_architecture.svg +90 -0
- data/examples/memory/point_to_point_pattern.svg +94 -0
- data/examples/memory/publish_subscribe_pattern.svg +125 -0
- data/examples/{04_redis_smart_home_iot.rb → redis/01_smart_home_iot_demo.rb} +5 -5
- data/examples/redis/README.md +230 -0
- data/examples/redis/alert_system_flow.svg +127 -0
- data/examples/redis/dashboard_status_flow.svg +107 -0
- data/examples/redis/device_command_flow.svg +113 -0
- data/examples/redis/redis_transport_architecture.svg +115 -0
- data/examples/{smart_home_iot_dataflow.md → redis/smart_home_iot_dataflow.md} +4 -116
- data/examples/redis/smart_home_system_architecture.svg +133 -0
- data/examples/redis_enhanced/README.md +319 -0
- data/examples/redis_enhanced/enhanced_01_basic_patterns.rb +233 -0
- data/examples/redis_enhanced/enhanced_02_fluent_api.rb +331 -0
- data/examples/redis_enhanced/enhanced_03_dual_publishing.rb +281 -0
- data/examples/redis_enhanced/enhanced_04_advanced_routing.rb +419 -0
- data/examples/redis_queue/01_basic_messaging.rb +221 -0
- data/examples/redis_queue/01_comprehensive_examples.rb +508 -0
- data/examples/redis_queue/02_pattern_routing.rb +405 -0
- data/examples/redis_queue/03_fluent_api.rb +422 -0
- data/examples/redis_queue/04_load_balancing.rb +486 -0
- data/examples/redis_queue/05_microservices.rb +735 -0
- data/examples/redis_queue/06_emergency_alerts.rb +777 -0
- data/examples/redis_queue/07_queue_management.rb +587 -0
- data/examples/redis_queue/README.md +366 -0
- data/examples/redis_queue/enhanced_01_basic_patterns.rb +233 -0
- data/examples/redis_queue/enhanced_02_fluent_api.rb +331 -0
- data/examples/redis_queue/enhanced_03_dual_publishing.rb +281 -0
- data/examples/redis_queue/enhanced_04_advanced_routing.rb +419 -0
- data/examples/redis_queue/redis_queue_architecture.svg +148 -0
- data/ideas/README.md +41 -0
- data/ideas/agents.md +1001 -0
- data/ideas/database_transport.md +980 -0
- data/ideas/improvement.md +359 -0
- data/ideas/meshage.md +1788 -0
- data/ideas/message_discovery.md +178 -0
- data/ideas/message_schema.md +1381 -0
- data/lib/smart_message/.idea/.gitignore +8 -0
- data/lib/smart_message/.idea/markdown.xml +6 -0
- data/lib/smart_message/.idea/misc.xml +4 -0
- data/lib/smart_message/.idea/modules.xml +8 -0
- data/lib/smart_message/.idea/smart_message.iml +16 -0
- data/lib/smart_message/.idea/vcs.xml +6 -0
- data/lib/smart_message/addressing.rb +15 -0
- data/lib/smart_message/base.rb +0 -2
- data/lib/smart_message/configuration.rb +1 -1
- data/lib/smart_message/logger.rb +15 -4
- data/lib/smart_message/plugins.rb +5 -2
- data/lib/smart_message/serializer.rb +14 -0
- data/lib/smart_message/transport/redis_enhanced_transport.rb +399 -0
- data/lib/smart_message/transport/redis_queue_transport.rb +555 -0
- data/lib/smart_message/transport/registry.rb +1 -0
- data/lib/smart_message/transport.rb +34 -1
- data/lib/smart_message/version.rb +1 -1
- data/lib/smart_message.rb +5 -52
- data/mkdocs.yml +184 -0
- data/p2p_plan.md +326 -0
- data/p2p_roadmap.md +287 -0
- data/smart_message.gemspec +2 -0
- data/smart_message.svg +51 -0
- metadata +170 -44
- data/docs/README.md +0 -57
- data/examples/dead_letters.jsonl +0 -12
- data/examples/temp.txt +0 -94
- data/examples/tmux_chat/README.md +0 -283
- data/examples/tmux_chat/bot_agent.rb +0 -278
- data/examples/tmux_chat/human_agent.rb +0 -199
- data/examples/tmux_chat/room_monitor.rb +0 -160
- data/examples/tmux_chat/shared_chat_system.rb +0 -328
- data/examples/tmux_chat/start_chat_demo.sh +0 -190
- data/examples/tmux_chat/stop_chat_demo.sh +0 -22
- /data/docs/{properties.md → core-concepts/properties.md} +0 -0
- /data/docs/{ideas_to_think_about.md → development/ideas.md} +0 -0
@@ -16,54 +16,7 @@ SmartMessage is designed around the principle that **messages should be independ
|
|
16
16
|
|
17
17
|
## Architecture Overview
|
18
18
|
|
19
|
-
|
20
|
-
graph TB
|
21
|
-
subgraph "SmartMessage Core"
|
22
|
-
Base[SmartMessage::Base]
|
23
|
-
Header[Message Header<br/>• UUID<br/>• Timestamps<br/>• Addressing]
|
24
|
-
Props[Message Properties<br/>• Business Data<br/>• Validation<br/>• Versioning]
|
25
|
-
end
|
26
|
-
|
27
|
-
subgraph "Plugin System"
|
28
|
-
Transport[Transport Plugin<br/>• publish()<br/>• subscribe()<br/>• Memory/Redis/STDOUT]
|
29
|
-
Serializer[Serializer Plugin<br/>• encode()<br/>• decode()<br/>• JSON/Custom]
|
30
|
-
Logger[Logger Plugin<br/>• Structured logging<br/>• Multiple outputs<br/>• Colorization]
|
31
|
-
end
|
32
|
-
|
33
|
-
subgraph "Message Processing"
|
34
|
-
Dispatcher[Dispatcher<br/>• Route messages<br/>• Thread pool<br/>• Subscriptions<br/>• DDQ management]
|
35
|
-
DDQ[Deduplication Queue<br/>• Handler-scoped<br/>• Memory/Redis storage<br/>• O(1) performance<br/>• Circular buffer]
|
36
|
-
Handlers[Message Handlers<br/>• Default handler<br/>• Block handlers<br/>• Proc handlers<br/>• Method handlers]
|
37
|
-
end
|
38
|
-
|
39
|
-
subgraph "Reliability Layer"
|
40
|
-
CircuitBreaker[Circuit Breaker<br/>• Failure thresholds<br/>• Automatic fallback<br/>• Recovery detection]
|
41
|
-
DLQ[Dead Letter Queue<br/>• Failed messages<br/>• Replay mechanism<br/>• JSON Lines format]
|
42
|
-
end
|
43
|
-
|
44
|
-
subgraph "Monitoring"
|
45
|
-
Stats[Statistics<br/>• Message counts<br/>• Processing metrics<br/>• Thread pool status]
|
46
|
-
Filters[Message Filtering<br/>• Entity-aware routing<br/>• Regex patterns<br/>• Broadcast handling]
|
47
|
-
end
|
48
|
-
|
49
|
-
Base --> Header
|
50
|
-
Base --> Props
|
51
|
-
Base --> Transport
|
52
|
-
Base --> Serializer
|
53
|
-
Base --> Logger
|
54
|
-
|
55
|
-
Transport --> Dispatcher
|
56
|
-
Dispatcher --> DDQ
|
57
|
-
Dispatcher --> Handlers
|
58
|
-
Dispatcher --> Stats
|
59
|
-
Dispatcher --> Filters
|
60
|
-
|
61
|
-
Transport --> CircuitBreaker
|
62
|
-
CircuitBreaker --> DLQ
|
63
|
-
|
64
|
-
DDQ -.-> Stats
|
65
|
-
Handlers -.-> Stats
|
66
|
-
```
|
19
|
+

|
67
20
|
|
68
21
|
## Core Components
|
69
22
|
|
@@ -77,7 +30,7 @@ The foundation class that all messages inherit from, built on `Hashie::Dash`.
|
|
77
30
|
- Message lifecycle management
|
78
31
|
- Header generation and management
|
79
32
|
|
80
|
-
**Location:** `lib/smart_message/base.rb:
|
33
|
+
**Location:** `lib/smart_message/base.rb:17-199`
|
81
34
|
|
82
35
|
```ruby
|
83
36
|
class MyMessage < SmartMessage::Base
|
@@ -107,12 +60,13 @@ Handles message delivery and routing between systems.
|
|
107
60
|
```ruby
|
108
61
|
# Transport interface
|
109
62
|
class CustomTransport < SmartMessage::Transport::Base
|
110
|
-
def
|
63
|
+
def do_publish(message_class, serialized_message)
|
111
64
|
# Send message via your transport
|
112
65
|
end
|
113
66
|
|
114
|
-
def subscribe(message_class, process_method)
|
115
|
-
# Set up subscription
|
67
|
+
def subscribe(message_class, process_method, filter_options = {})
|
68
|
+
# Set up subscription via dispatcher
|
69
|
+
@dispatcher.add(message_class, process_method, filter_options)
|
116
70
|
end
|
117
71
|
end
|
118
72
|
```
|
@@ -130,12 +84,14 @@ Handles encoding and decoding of message content.
|
|
130
84
|
|
131
85
|
```ruby
|
132
86
|
class CustomSerializer < SmartMessage::Serializer::Base
|
133
|
-
def
|
134
|
-
# Convert to wire format
|
87
|
+
def do_encode(message_instance)
|
88
|
+
# Convert message instance to wire format
|
89
|
+
# Default implementation uses message_instance.to_h
|
135
90
|
end
|
136
91
|
|
137
|
-
def
|
138
|
-
# Convert from wire format
|
92
|
+
def do_decode(payload)
|
93
|
+
# Convert from wire format back to hash
|
94
|
+
# Must return hash compatible with message initialization
|
139
95
|
end
|
140
96
|
end
|
141
97
|
```
|
@@ -156,7 +112,7 @@ Routes incoming messages to appropriate handlers using concurrent processing wit
|
|
156
112
|
```ruby
|
157
113
|
dispatcher = SmartMessage::Dispatcher.new
|
158
114
|
dispatcher.add("MyMessage", "MyMessage.process")
|
159
|
-
dispatcher.route(
|
115
|
+
dispatcher.route(decoded_message)
|
160
116
|
|
161
117
|
# DDQ integration is automatic when enabled
|
162
118
|
MyMessage.enable_deduplication!
|
@@ -173,33 +129,18 @@ Handler-scoped message deduplication system preventing duplicate processing.
|
|
173
129
|
- O(1) performance with hybrid Array + Set data structure
|
174
130
|
|
175
131
|
**Architecture:**
|
176
|
-
|
177
|
-
|
178
|
-
subgraph "Handler A DDQ"
|
179
|
-
A1[Circular Array]
|
180
|
-
A2[Lookup Set]
|
181
|
-
A3[Mutex Lock]
|
182
|
-
end
|
183
|
-
|
184
|
-
subgraph "Handler B DDQ"
|
185
|
-
B1[Circular Array]
|
186
|
-
B2[Lookup Set]
|
187
|
-
B3[Mutex Lock]
|
188
|
-
end
|
189
|
-
|
190
|
-
Message[Incoming Message<br/>UUID: abc-123] --> Dispatcher
|
191
|
-
Dispatcher --> |Check Handler A| A2
|
192
|
-
Dispatcher --> |Check Handler B| B2
|
193
|
-
|
194
|
-
A2 --> |Not Found| ProcessA[Process with Handler A]
|
195
|
-
B2 --> |Found| SkipB[Skip Handler B - Duplicate]
|
196
|
-
|
197
|
-
ProcessA --> |Add UUID| A1
|
198
|
-
ProcessA --> |Add UUID| A2
|
199
|
-
```
|
132
|
+
|
133
|
+

|
200
134
|
|
201
135
|
**Location:** `lib/smart_message/deduplication.rb`, `lib/smart_message/ddq/`
|
202
136
|
|
137
|
+
**Key Features:**
|
138
|
+
- Handler-scoped deduplication (each handler gets its own DDQ)
|
139
|
+
- UUID-based duplicate detection
|
140
|
+
- Multiple storage backends (Memory, Redis)
|
141
|
+
- O(1) performance with hybrid Array + Set data structure
|
142
|
+
- Thread-safe operations with mutex locks
|
143
|
+
|
203
144
|
### 6. Message Headers
|
204
145
|
|
205
146
|
Standard metadata attached to every message with entity addressing support.
|
@@ -217,9 +158,12 @@ header = message._sm_header
|
|
217
158
|
puts header.uuid # "550e8400-e29b-41d4-a716-446655440000"
|
218
159
|
puts header.message_class # "MyMessage"
|
219
160
|
puts header.published_at # 2025-08-17 10:30:00 UTC
|
161
|
+
puts header.publisher_pid # 12345
|
220
162
|
puts header.from # "payment-service"
|
221
163
|
puts header.to # "order-service"
|
164
|
+
puts header.reply_to # "payment-service" (defaults to from)
|
222
165
|
puts header.version # 1
|
166
|
+
puts header.serializer # "SmartMessage::Serializer::JSON"
|
223
167
|
```
|
224
168
|
|
225
169
|
## Message Lifecycle
|
@@ -232,7 +176,7 @@ class OrderMessage < SmartMessage::Base
|
|
232
176
|
|
233
177
|
config do
|
234
178
|
transport SmartMessage::Transport.create(:memory)
|
235
|
-
serializer SmartMessage::Serializer::
|
179
|
+
serializer SmartMessage::Serializer::Json.new
|
236
180
|
end
|
237
181
|
end
|
238
182
|
```
|
@@ -264,13 +208,14 @@ order.publish
|
|
264
208
|
|
265
209
|
### 4. Receiving Phase
|
266
210
|
```ruby
|
267
|
-
# Transport receives message
|
268
|
-
transport.receive(
|
269
|
-
# 1.
|
270
|
-
# 2.
|
271
|
-
# 3.
|
272
|
-
# 4.
|
273
|
-
# 5.
|
211
|
+
# Transport receives serialized message
|
212
|
+
transport.receive(message_class, serialized_message)
|
213
|
+
# 1. Decodes message using class's configured serializer
|
214
|
+
# 2. Routes decoded message to dispatcher
|
215
|
+
# 3. Dispatcher checks DDQ for duplicates per handler
|
216
|
+
# 4. Applies message filters (from/to/broadcast)
|
217
|
+
# 5. Spawns thread for processing matching handlers
|
218
|
+
# 6. Marks UUID as processed in handler's DDQ after successful processing
|
274
219
|
```
|
275
220
|
|
276
221
|
### 5. Message Handler Processing
|
@@ -278,43 +223,42 @@ transport.receive(header, payload)
|
|
278
223
|
SmartMessage supports multiple handler types, routed through the dispatcher:
|
279
224
|
|
280
225
|
```ruby
|
281
|
-
# Default handler (self.process method)
|
282
|
-
def self.process(
|
283
|
-
|
284
|
-
order = new(data)
|
226
|
+
# Default handler (self.process method) - receives decoded message instance
|
227
|
+
def self.process(decoded_message)
|
228
|
+
order = decoded_message # Already a fully decoded OrderMessage instance
|
285
229
|
fulfill_order(order)
|
286
230
|
end
|
287
231
|
|
288
|
-
# Block handler (inline processing)
|
289
|
-
OrderMessage.subscribe do |
|
290
|
-
|
291
|
-
quick_processing(data)
|
232
|
+
# Block handler (inline processing) - receives decoded message instance
|
233
|
+
OrderMessage.subscribe do |decoded_message|
|
234
|
+
quick_processing(decoded_message)
|
292
235
|
end
|
293
236
|
|
294
|
-
# Proc handler (reusable across message types)
|
295
|
-
audit_proc = proc do |
|
296
|
-
AuditService.log_message(
|
237
|
+
# Proc handler (reusable across message types) - receives decoded message instance
|
238
|
+
audit_proc = proc do |decoded_message|
|
239
|
+
AuditService.log_message(decoded_message.class.name, decoded_message)
|
297
240
|
end
|
298
241
|
OrderMessage.subscribe(audit_proc)
|
299
242
|
|
300
|
-
# Method handler (service class processing)
|
243
|
+
# Method handler (service class processing) - receives decoded message instance
|
301
244
|
class OrderService
|
302
|
-
def self.process_order(
|
303
|
-
|
304
|
-
complex_business_logic(data)
|
245
|
+
def self.process_order(decoded_message)
|
246
|
+
complex_business_logic(decoded_message)
|
305
247
|
end
|
306
248
|
end
|
307
249
|
OrderMessage.subscribe("OrderService.process_order")
|
308
250
|
```
|
309
251
|
|
310
252
|
**Handler Routing Process:**
|
311
|
-
1. Dispatcher receives message
|
253
|
+
1. Dispatcher receives decoded message instance
|
312
254
|
2. Looks up all registered handlers for message class
|
313
|
-
3. For each handler:
|
255
|
+
3. For each handler that matches filters:
|
256
|
+
- Checks DDQ for duplicates (handler-scoped)
|
314
257
|
- **String handlers**: Resolves to class method via constantize
|
315
258
|
- **Proc handlers**: Calls proc directly from registry
|
316
|
-
4. Executes handlers in parallel threads
|
317
|
-
5.
|
259
|
+
4. Executes handlers in parallel threads with circuit breaker protection
|
260
|
+
5. Marks UUID as processed in handler's DDQ after successful completion
|
261
|
+
6. Collects statistics and handles errors
|
318
262
|
|
319
263
|
## Plugin System Architecture
|
320
264
|
|
@@ -364,10 +308,15 @@ end
|
|
364
308
|
The dispatcher uses `Concurrent::CachedThreadPool` for processing:
|
365
309
|
|
366
310
|
```ruby
|
367
|
-
# Each message processing happens in its own thread
|
311
|
+
# Each message processing happens in its own thread with circuit breaker protection
|
368
312
|
@router_pool.post do
|
369
|
-
|
370
|
-
|
313
|
+
circuit_result = circuit(:message_processor).wrap do
|
314
|
+
if proc_handler?(message_processor)
|
315
|
+
SmartMessage::Base.call_proc_handler(message_processor, decoded_message)
|
316
|
+
else
|
317
|
+
target_class.constantize.method(class_method).call(decoded_message)
|
318
|
+
end
|
319
|
+
end
|
371
320
|
end
|
372
321
|
```
|
373
322
|
|
@@ -396,11 +345,13 @@ puts "Completed tasks: #{status[:completed_task_count]}"
|
|
396
345
|
Processing exceptions are isolated to prevent cascade failures:
|
397
346
|
|
398
347
|
```ruby
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
348
|
+
circuit_result = circuit(:message_processor).wrap do
|
349
|
+
# Handler execution with circuit breaker protection
|
350
|
+
end
|
351
|
+
|
352
|
+
# Handle circuit breaker fallback responses
|
353
|
+
if circuit_result.is_a?(Hash) && circuit_result[:circuit_breaker]
|
354
|
+
handle_circuit_breaker_fallback(circuit_result, decoded_message, message_processor)
|
404
355
|
end
|
405
356
|
```
|
406
357
|
|
@@ -415,6 +366,7 @@ module SmartMessage::Errors
|
|
415
366
|
class NotImplemented < RuntimeError; end
|
416
367
|
class ReceivedMessageNotSubscribed < RuntimeError; end
|
417
368
|
class UnknownMessageClass < RuntimeError; end
|
369
|
+
class ValidationError < RuntimeError; end
|
418
370
|
end
|
419
371
|
```
|
420
372
|
|
@@ -425,13 +377,12 @@ end
|
|
425
377
|
SmartMessage integrates BreakerMachines for production-grade reliability:
|
426
378
|
|
427
379
|
```ruby
|
428
|
-
#
|
380
|
+
# Circuit breakers are automatically configured for all transports
|
429
381
|
class MyTransport < SmartMessage::Transport::Base
|
430
|
-
circuit :
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
end
|
382
|
+
# Inherits circuit breaker configuration:
|
383
|
+
# - :transport_publish for publishing operations
|
384
|
+
# - :transport_subscribe for subscription operations
|
385
|
+
# - Automatic DLQ fallback for failed publishes
|
435
386
|
end
|
436
387
|
```
|
437
388
|
|
@@ -450,25 +401,12 @@ message.publish # If transport fails, goes to DLQ
|
|
450
401
|
|
451
402
|
# Manual capture for business logic failures
|
452
403
|
dlq = SmartMessage::DeadLetterQueue.default
|
453
|
-
dlq.enqueue(
|
404
|
+
dlq.enqueue(decoded_message, error: "Validation failed", transport: "manual")
|
454
405
|
```
|
455
406
|
|
456
407
|
**DLQ Architecture:**
|
457
408
|
|
458
|
-
|
459
|
-
graph TB
|
460
|
-
Publish[Message Publishing]
|
461
|
-
CB[Circuit Breaker<br/>Monitoring]
|
462
|
-
Transport[Transport<br/>Success]
|
463
|
-
DLQ[Dead Letter Queue<br/>Failure Storage]
|
464
|
-
Replay[Replay Mechanism<br/>Manual/Automated]
|
465
|
-
|
466
|
-
Publish --> CB
|
467
|
-
CB --> |Success| Transport
|
468
|
-
CB --> |Failure| DLQ
|
469
|
-
DLQ --> Replay
|
470
|
-
Replay --> |Retry| Publish
|
471
|
-
```
|
409
|
+

|
472
410
|
|
473
411
|
**DLQ Features:**
|
474
412
|
- JSON Lines format for efficient append operations
|
@@ -536,7 +474,9 @@ When a message needs a plugin:
|
|
536
474
|
|
537
475
|
```ruby
|
538
476
|
def transport
|
539
|
-
@transport ||
|
477
|
+
@transport || self.class.class_variable_get(:@@transport) || raise(Errors::TransportNotConfigured)
|
478
|
+
rescue NameError
|
479
|
+
raise(Errors::TransportNotConfigured)
|
540
480
|
end
|
541
481
|
```
|
542
482
|
|
@@ -187,10 +187,10 @@ puts dispatcher.subscribers["MyMessage"]
|
|
187
187
|
When a transport receives a message, it calls the dispatcher:
|
188
188
|
|
189
189
|
```ruby
|
190
|
-
# Transport receives message and routes it
|
191
|
-
transport.receive(
|
192
|
-
# This internally calls:
|
193
|
-
dispatcher.route(
|
190
|
+
# Transport receives serialized message and routes it
|
191
|
+
transport.receive(message_class, serialized_message)
|
192
|
+
# This internally decodes the message and calls:
|
193
|
+
dispatcher.route(decoded_message)
|
194
194
|
```
|
195
195
|
|
196
196
|
### 2. Subscription Lookup
|
@@ -198,12 +198,12 @@ dispatcher.route(message_header, message_payload)
|
|
198
198
|
The dispatcher finds all registered handlers:
|
199
199
|
|
200
200
|
```ruby
|
201
|
-
def route(
|
202
|
-
message_klass =
|
201
|
+
def route(decoded_message)
|
202
|
+
message_klass = decoded_message._sm_header.message_class
|
203
203
|
return nil if @subscribers[message_klass].empty?
|
204
204
|
|
205
|
-
@subscribers[message_klass].each do |
|
206
|
-
# Process each handler
|
205
|
+
@subscribers[message_klass].each do |subscription|
|
206
|
+
# Process each handler with filters
|
207
207
|
end
|
208
208
|
end
|
209
209
|
```
|
@@ -213,16 +213,17 @@ end
|
|
213
213
|
Each handler is processed in its own thread, with support for both method and proc handlers:
|
214
214
|
|
215
215
|
```ruby
|
216
|
-
@subscribers[message_klass].each do |
|
216
|
+
@subscribers[message_klass].each do |subscription|
|
217
|
+
message_processor = subscription[:process_method]
|
217
218
|
SS.add(message_klass, message_processor, 'routed')
|
218
219
|
|
219
220
|
@router_pool.post do
|
220
|
-
# This runs in a separate thread
|
221
|
-
|
221
|
+
# This runs in a separate thread with circuit breaker protection
|
222
|
+
circuit_result = circuit(:message_processor).wrap do
|
222
223
|
# Check if this is a proc handler or a regular method call
|
223
224
|
if proc_handler?(message_processor)
|
224
225
|
# Call the proc handler via SmartMessage::Base
|
225
|
-
SmartMessage::Base.call_proc_handler(message_processor,
|
226
|
+
SmartMessage::Base.call_proc_handler(message_processor, decoded_message)
|
226
227
|
else
|
227
228
|
# Original method call logic
|
228
229
|
parts = message_processor.split('.')
|
@@ -231,11 +232,13 @@ Each handler is processed in its own thread, with support for both method and pr
|
|
231
232
|
|
232
233
|
target_klass.constantize
|
233
234
|
.method(class_method)
|
234
|
-
.call(
|
235
|
+
.call(decoded_message)
|
235
236
|
end
|
236
|
-
|
237
|
-
|
238
|
-
|
237
|
+
end
|
238
|
+
|
239
|
+
# Handle circuit breaker fallback if triggered
|
240
|
+
if circuit_result.is_a?(Hash) && circuit_result[:circuit_breaker]
|
241
|
+
handle_circuit_breaker_fallback(circuit_result, decoded_message, message_processor)
|
239
242
|
end
|
240
243
|
end
|
241
244
|
end
|
@@ -691,7 +694,7 @@ RSpec.describe "Message Processing" do
|
|
691
694
|
before do
|
692
695
|
TestMessage.config do
|
693
696
|
transport transport
|
694
|
-
serializer SmartMessage::Serializer::
|
697
|
+
serializer SmartMessage::Serializer::Json.new
|
695
698
|
end
|
696
699
|
|
697
700
|
TestMessage.subscribe
|
@@ -710,7 +713,4 @@ end
|
|
710
713
|
|
711
714
|
## Next Steps
|
712
715
|
|
713
|
-
- [
|
714
|
-
- [Statistics & Monitoring](monitoring.md) - Detailed monitoring guide
|
715
|
-
- [Custom Transports](custom-transports.md) - How transports interact with the dispatcher
|
716
|
-
- [Troubleshooting](troubleshooting.md) - Common dispatcher issues
|
716
|
+
- [Troubleshooting](../development/troubleshooting.md) - Common dispatcher issues
|
@@ -320,7 +320,7 @@ class FilterTest < Minitest::Test
|
|
320
320
|
@transport = SmartMessage::Transport.create(:memory, auto_process: true)
|
321
321
|
TestMessage.config do
|
322
322
|
transport @transport
|
323
|
-
serializer SmartMessage::Serializer::
|
323
|
+
serializer SmartMessage::Serializer::Json.new
|
324
324
|
end
|
325
325
|
TestMessage.unsubscribe!
|
326
326
|
end
|
@@ -447,5 +447,4 @@ end
|
|
447
447
|
|
448
448
|
- [Dispatcher Documentation](dispatcher.md) - How filtering integrates with message routing
|
449
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
|
450
|
+
- [Examples](../getting-started/examples.md) - Complete working examples with filtering
|
@@ -36,9 +36,9 @@ SmartMessage supports multiple ways to handle incoming messages:
|
|
36
36
|
### 1. Default Handler Pattern (using `self.process`)
|
37
37
|
```ruby
|
38
38
|
class SensorDataMessage < SmartMessage::Base
|
39
|
-
def self.process(
|
39
|
+
def self.process(decoded_message)
|
40
40
|
# This gets called when a SensorDataMessage is received
|
41
|
-
|
41
|
+
# decoded_message is already a message instance
|
42
42
|
puts "Sensor reading: #{data['value']}"
|
43
43
|
end
|
44
44
|
end
|
@@ -51,7 +51,7 @@ SensorDataMessage.subscribe # Uses "SensorDataMessage.process"
|
|
51
51
|
class ThermostatService
|
52
52
|
def self.handle_sensor_data(message_header, message_payload)
|
53
53
|
# Custom processing logic
|
54
|
-
|
54
|
+
# decoded_message is already a message instance
|
55
55
|
adjust_temperature(data)
|
56
56
|
end
|
57
57
|
end
|
@@ -99,8 +99,8 @@ Looking at the smart home IoT example:
|
|
99
99
|
|
100
100
|
```ruby
|
101
101
|
class SensorDataMessage < SmartMessage::Base
|
102
|
-
def self.process(
|
103
|
-
|
102
|
+
def self.process(decoded_message)
|
103
|
+
sensor_# decoded_message is already a message instance
|
104
104
|
icon = case sensor_data['device_type']
|
105
105
|
when 'thermostat' then '🌡️'
|
106
106
|
when 'security_camera' then '📹'
|
@@ -145,8 +145,8 @@ A single message type can have multiple subscribers with different handlers usin
|
|
145
145
|
```ruby
|
146
146
|
# Default handler for logging
|
147
147
|
class SensorDataMessage < SmartMessage::Base
|
148
|
-
def self.process(
|
149
|
-
|
148
|
+
def self.process(decoded_message)
|
149
|
+
# decoded_message is already a message instance
|
150
150
|
puts "📊 Sensor data logged: #{data['device_id']}"
|
151
151
|
end
|
152
152
|
end
|
@@ -154,7 +154,7 @@ end
|
|
154
154
|
# Custom method handler for specific services
|
155
155
|
class ThermostatService
|
156
156
|
def self.handle_sensor_data(message_header, message_payload)
|
157
|
-
|
157
|
+
# decoded_message is already a message instance
|
158
158
|
return unless data['device_type'] == 'thermostat'
|
159
159
|
adjust_temperature(data['value'])
|
160
160
|
end
|
@@ -197,10 +197,10 @@ SensorDataMessage.subscribe(database_logger) # Proc handler
|
|
197
197
|
|
198
198
|
```ruby
|
199
199
|
class SensorDataMessage < SmartMessage::Base
|
200
|
-
def self.process(
|
200
|
+
def self.process(decoded_message)
|
201
201
|
# This runs in its own thread
|
202
202
|
# Be careful with shared state
|
203
|
-
|
203
|
+
# decoded_message is already a message instance
|
204
204
|
|
205
205
|
# Thread-safe operations
|
206
206
|
update_local_cache(data)
|
@@ -216,9 +216,9 @@ Handlers should include proper error handling:
|
|
216
216
|
|
217
217
|
```ruby
|
218
218
|
class SensorDataMessage < SmartMessage::Base
|
219
|
-
def self.process(
|
219
|
+
def self.process(decoded_message)
|
220
220
|
begin
|
221
|
-
|
221
|
+
# decoded_message is already a message instance
|
222
222
|
|
223
223
|
# Validate required fields
|
224
224
|
raise "Missing device_id" unless data['device_id']
|
@@ -310,9 +310,9 @@ PaymentEventMessage.subscribe(audit_logger)
|
|
310
310
|
|
311
311
|
### 1. Keep Handlers Fast
|
312
312
|
```ruby
|
313
|
-
def self.process(
|
313
|
+
def self.process(decoded_message)
|
314
314
|
# Quick validation
|
315
|
-
|
315
|
+
# decoded_message is already a message instance
|
316
316
|
return unless valid_message?(data)
|
317
317
|
|
318
318
|
# Delegate heavy work to background jobs
|
@@ -348,7 +348,7 @@ SensorDataMessage.subscribe do |h, p|; process_stuff(p); end
|
|
348
348
|
### 3. Filter Messages Early
|
349
349
|
```ruby
|
350
350
|
def self.handle_thermostat_data(message_header, message_payload)
|
351
|
-
|
351
|
+
# decoded_message is already a message instance
|
352
352
|
|
353
353
|
# Filter early to avoid unnecessary processing
|
354
354
|
return unless data['device_type'] == 'thermostat'
|
@@ -361,11 +361,11 @@ end
|
|
361
361
|
|
362
362
|
### 4. Include Logging and Monitoring
|
363
363
|
```ruby
|
364
|
-
def self.process(
|
364
|
+
def self.process(decoded_message)
|
365
365
|
start_time = Time.now
|
366
366
|
|
367
367
|
begin
|
368
|
-
|
368
|
+
# decoded_message is already a message instance
|
369
369
|
logger.info "Processing sensor data from #{data['device_id']}"
|
370
370
|
|
371
371
|
# Business logic here
|
@@ -39,7 +39,7 @@ class MyMessage < SmartMessage::Base
|
|
39
39
|
|
40
40
|
config do
|
41
41
|
transport SmartMessage::Transport.create(:stdout) # No loopback
|
42
|
-
serializer SmartMessage::Serializer::
|
42
|
+
serializer SmartMessage::Serializer::Json.new
|
43
43
|
end
|
44
44
|
end
|
45
45
|
|
@@ -47,7 +47,7 @@ end
|
|
47
47
|
class MyMessage < SmartMessage::Base
|
48
48
|
config do
|
49
49
|
transport SmartMessage::Transport.create(:stdout, loopback: true)
|
50
|
-
serializer SmartMessage::Serializer::
|
50
|
+
serializer SmartMessage::Serializer::Json.new
|
51
51
|
end
|
52
52
|
end
|
53
53
|
```
|
@@ -102,7 +102,7 @@ MyMessage.new(data: "test").publish
|
|
102
102
|
class MyMessage < SmartMessage::Base
|
103
103
|
config do
|
104
104
|
transport SmartMessage::Transport.create(:memory)
|
105
|
-
serializer SmartMessage::Serializer::
|
105
|
+
serializer SmartMessage::Serializer::Json.new
|
106
106
|
end
|
107
107
|
end
|
108
108
|
```
|
@@ -114,7 +114,7 @@ class ProblematicMessage < SmartMessage::Base
|
|
114
114
|
property :data
|
115
115
|
|
116
116
|
config do
|
117
|
-
serializer SmartMessage::Serializer::
|
117
|
+
serializer SmartMessage::Serializer::Json.new
|
118
118
|
end
|
119
119
|
end
|
120
120
|
|
@@ -154,7 +154,7 @@ class MyMessage < SmartMessage::Base
|
|
154
154
|
property :data
|
155
155
|
|
156
156
|
config do
|
157
|
-
serializer SmartMessage::Serializer::
|
157
|
+
serializer SmartMessage::Serializer::Json.new
|
158
158
|
# Missing transport!
|
159
159
|
end
|
160
160
|
end
|
@@ -165,7 +165,7 @@ MyMessage.new(data: "test").publish # Throws TransportNotConfigured
|
|
165
165
|
class MyMessage < SmartMessage::Base
|
166
166
|
config do
|
167
167
|
transport SmartMessage::Transport.create(:memory)
|
168
|
-
serializer SmartMessage::Serializer::
|
168
|
+
serializer SmartMessage::Serializer::Json.new
|
169
169
|
end
|
170
170
|
end
|
171
171
|
```
|
@@ -471,7 +471,7 @@ class MyMessage < SmartMessage::Base
|
|
471
471
|
# Reconfigure
|
472
472
|
config do
|
473
473
|
transport SmartMessage::Transport.create(:memory)
|
474
|
-
serializer SmartMessage::Serializer::
|
474
|
+
serializer SmartMessage::Serializer::Json.new
|
475
475
|
end
|
476
476
|
end
|
477
477
|
```
|