smart_message 0.0.10 → 0.0.13
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 +64 -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} +80 -145
- 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} +103 -89
- data/docs/{getting-started.md → getting-started/quick-start.md} +47 -23
- 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/reference/serializers.md +245 -0
- data/docs/{transports.md → reference/transports.md} +9 -11
- data/docs/transports/memory-transport.md +374 -0
- data/docs/transports/redis-transport-comparison.md +361 -0
- data/docs/transports/redis-transport.md +490 -0
- data/examples/README.md +104 -14
- 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 +80 -0
- data/examples/city_scenario/messages/emergency_resolved_message.rb +42 -0
- data/examples/city_scenario/messages/fire_dispatch_message.rb +42 -0
- data/examples/city_scenario/messages/fire_emergency_message.rb +44 -0
- data/examples/city_scenario/messages/health_check_message.rb +21 -0
- data/examples/city_scenario/messages/health_status_message.rb +34 -0
- data/examples/city_scenario/messages/police_dispatch_message.rb +45 -0
- data/examples/city_scenario/messages/silent_alarm_message.rb +37 -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 -3
- data/examples/{09_dead_letter_queue_demo.rb → memory/02_dead_letter_queue_demo.rb} +10 -40
- data/examples/{01_point_to_point_orders.rb → memory/03_point_to_point_orders.rb} +1 -3
- data/examples/{02_publish_subscribe_events.rb → memory/04_publish_subscribe_events.rb} +1 -2
- data/examples/{03_many_to_many_chat.rb → memory/05_many_to_many_chat.rb} +1 -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} +1 -2
- data/examples/{06_custom_logger_example.rb → memory/08_custom_logger_demo.rb} +13 -14
- data/examples/{07_error_handling_scenarios.rb → memory/09_error_handling_demo.rb} +1 -4
- data/examples/{08_entity_addressing_basic.rb → memory/10_entity_addressing_basic.rb} +2 -8
- data/examples/{08_entity_addressing_with_filtering.rb → memory/11_entity_addressing_with_filtering.rb} +2 -6
- data/examples/{09_regex_filtering_microservices.rb → memory/12_regex_filtering_microservices.rb} +1 -2
- data/examples/{10_header_block_configuration.rb → memory/13_header_block_configuration.rb} +1 -6
- data/examples/{11_global_configuration_example.rb → memory/14_global_configuration_demo.rb} +17 -8
- data/examples/{show_logger.rb → memory/15_logger_demo.rb} +1 -2
- 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} +1 -5
- data/examples/redis/README.md +228 -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/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 +19 -12
- data/lib/smart_message/configuration.rb +2 -23
- data/lib/smart_message/dead_letter_queue.rb +1 -1
- data/lib/smart_message/logger.rb +15 -4
- data/lib/smart_message/messaging.rb +3 -62
- data/lib/smart_message/plugins.rb +6 -44
- data/lib/smart_message/serializer.rb +14 -0
- data/lib/smart_message/transport/base.rb +42 -8
- data/lib/smart_message/transport/memory_transport.rb +23 -4
- data/lib/smart_message/transport/redis_transport.rb +11 -0
- data/lib/smart_message/transport/stdout_transport.rb +28 -10
- data/lib/smart_message/transport.rb +33 -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 +145 -45
- data/docs/README.md +0 -57
- data/docs/serializers.md +0 -575
- 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
@@ -9,61 +9,14 @@ SmartMessage is designed around the principle that **messages should be independ
|
|
9
9
|
### Core Principles
|
10
10
|
|
11
11
|
1. **Separation of Concerns**: Message content, transport, and serialization are independent
|
12
|
-
2. **Plugin Architecture**: Pluggable transports
|
12
|
+
2. **Plugin Architecture**: Pluggable transports
|
13
13
|
3. **Dual Configuration**: Both class-level and instance-level configuration
|
14
14
|
4. **Thread Safety**: Concurrent message processing with thread pools
|
15
|
-
5. **Gateway Support**: Messages can flow between different transports
|
15
|
+
5. **Gateway Support**: Messages can flow between different transports
|
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
|
|
@@ -73,11 +26,11 @@ The foundation class that all messages inherit from, built on `Hashie::Dash`.
|
|
73
26
|
|
74
27
|
**Key Responsibilities:**
|
75
28
|
- Property management and validation
|
76
|
-
- Plugin configuration (transport,
|
29
|
+
- Plugin configuration (transport, logger)
|
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
|
@@ -87,7 +40,6 @@ class MyMessage < SmartMessage::Base
|
|
87
40
|
|
88
41
|
config do
|
89
42
|
transport MyTransport.new
|
90
|
-
serializer MySerializer.new
|
91
43
|
end
|
92
44
|
end
|
93
45
|
```
|
@@ -107,12 +59,13 @@ Handles message delivery and routing between systems.
|
|
107
59
|
```ruby
|
108
60
|
# Transport interface
|
109
61
|
class CustomTransport < SmartMessage::Transport::Base
|
110
|
-
def
|
62
|
+
def do_publish(message_class, serialized_message)
|
111
63
|
# Send message via your transport
|
112
64
|
end
|
113
65
|
|
114
|
-
def subscribe(message_class, process_method)
|
115
|
-
# Set up subscription
|
66
|
+
def subscribe(message_class, process_method, filter_options = {})
|
67
|
+
# Set up subscription via dispatcher
|
68
|
+
@dispatcher.add(message_class, process_method, filter_options)
|
116
69
|
end
|
117
70
|
end
|
118
71
|
```
|
@@ -130,12 +83,14 @@ Handles encoding and decoding of message content.
|
|
130
83
|
|
131
84
|
```ruby
|
132
85
|
class CustomSerializer < SmartMessage::Serializer::Base
|
133
|
-
def
|
134
|
-
# Convert to wire format
|
86
|
+
def do_encode(message_instance)
|
87
|
+
# Convert message instance to wire format
|
88
|
+
# Default implementation uses message_instance.to_h
|
135
89
|
end
|
136
90
|
|
137
|
-
def
|
138
|
-
# Convert from wire format
|
91
|
+
def do_decode(payload)
|
92
|
+
# Convert from wire format back to hash
|
93
|
+
# Must return hash compatible with message initialization
|
139
94
|
end
|
140
95
|
end
|
141
96
|
```
|
@@ -156,7 +111,7 @@ Routes incoming messages to appropriate handlers using concurrent processing wit
|
|
156
111
|
```ruby
|
157
112
|
dispatcher = SmartMessage::Dispatcher.new
|
158
113
|
dispatcher.add("MyMessage", "MyMessage.process")
|
159
|
-
dispatcher.route(
|
114
|
+
dispatcher.route(decoded_message)
|
160
115
|
|
161
116
|
# DDQ integration is automatic when enabled
|
162
117
|
MyMessage.enable_deduplication!
|
@@ -173,33 +128,18 @@ Handler-scoped message deduplication system preventing duplicate processing.
|
|
173
128
|
- O(1) performance with hybrid Array + Set data structure
|
174
129
|
|
175
130
|
**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
|
-
```
|
131
|
+
|
132
|
+

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

|
472
406
|
|
473
407
|
**DLQ Features:**
|
474
408
|
- JSON Lines format for efficient append operations
|
@@ -521,7 +455,6 @@ Configuration uses method-based DSL:
|
|
521
455
|
```ruby
|
522
456
|
config do
|
523
457
|
transport MyTransport.new(option1: value1)
|
524
|
-
serializer MySerializer.new(option2: value2)
|
525
458
|
logger MyLogger.new(level: :debug)
|
526
459
|
end
|
527
460
|
```
|
@@ -536,7 +469,9 @@ When a message needs a plugin:
|
|
536
469
|
|
537
470
|
```ruby
|
538
471
|
def transport
|
539
|
-
@transport ||
|
472
|
+
@transport || self.class.class_variable_get(:@@transport) || raise(Errors::TransportNotConfigured)
|
473
|
+
rescue NameError
|
474
|
+
raise(Errors::TransportNotConfigured)
|
540
475
|
end
|
541
476
|
```
|
542
477
|
|
@@ -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
|
```
|