smart_message 0.0.9 → 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 +53 -0
- data/Gemfile.lock +35 -4
- data/README.md +265 -69
- 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} +129 -119
- 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/reference/message-deduplication.md +489 -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/memory/01_message_deduplication_demo.rb +209 -0
- 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 +2 -2
- data/lib/smart_message/configuration.rb +1 -1
- data/lib/smart_message/ddq/base.rb +71 -0
- data/lib/smart_message/ddq/memory.rb +109 -0
- data/lib/smart_message/ddq/redis.rb +168 -0
- data/lib/smart_message/ddq.rb +31 -0
- data/lib/smart_message/deduplication.rb +174 -0
- data/lib/smart_message/dispatcher.rb +175 -18
- 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/subscription.rb +10 -7
- 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 +175 -42
- 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,38 +16,7 @@ SmartMessage is designed around the principle that **messages should be independ
|
|
16
16
|
|
17
17
|
## Architecture Overview
|
18
18
|
|
19
|
-
|
20
|
-
┌─────────────────────────────────────────────────────────────┐
|
21
|
-
│ SmartMessage::Base │
|
22
|
-
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐│
|
23
|
-
│ │ Message │ │ Transport │ │ Serializer ││
|
24
|
-
│ │ Properties │ │ Plugin │ │ Plugin ││
|
25
|
-
│ │ │ │ │ │ ││
|
26
|
-
│ │ • user_id │ │ • publish() │ │ • encode() ││
|
27
|
-
│ │ • action │ │ • subscribe() │ │ • decode() ││
|
28
|
-
│ │ • timestamp │ │ • receive() │ │ ││
|
29
|
-
│ └─────────────────┘ └─────────────────┘ └─────────────────┘│
|
30
|
-
└─────────────────────────────────────────────────────────────┘
|
31
|
-
│
|
32
|
-
▼
|
33
|
-
┌─────────────────────┐
|
34
|
-
│ Dispatcher │
|
35
|
-
│ │
|
36
|
-
│ • Route messages │
|
37
|
-
│ • Thread pool │
|
38
|
-
│ • Subscriptions │
|
39
|
-
└─────────────────────┘
|
40
|
-
│
|
41
|
-
▼
|
42
|
-
┌─────────────────────┐
|
43
|
-
│ Message Handlers │
|
44
|
-
│ │
|
45
|
-
│ • Default handler │
|
46
|
-
│ • Block handlers │
|
47
|
-
│ • Proc handlers │
|
48
|
-
│ • Method handlers │
|
49
|
-
└─────────────────────┘
|
50
|
-
```
|
19
|
+

|
51
20
|
|
52
21
|
## Core Components
|
53
22
|
|
@@ -61,7 +30,7 @@ The foundation class that all messages inherit from, built on `Hashie::Dash`.
|
|
61
30
|
- Message lifecycle management
|
62
31
|
- Header generation and management
|
63
32
|
|
64
|
-
**Location:** `lib/smart_message/base.rb:
|
33
|
+
**Location:** `lib/smart_message/base.rb:17-199`
|
65
34
|
|
66
35
|
```ruby
|
67
36
|
class MyMessage < SmartMessage::Base
|
@@ -91,12 +60,13 @@ Handles message delivery and routing between systems.
|
|
91
60
|
```ruby
|
92
61
|
# Transport interface
|
93
62
|
class CustomTransport < SmartMessage::Transport::Base
|
94
|
-
def
|
63
|
+
def do_publish(message_class, serialized_message)
|
95
64
|
# Send message via your transport
|
96
65
|
end
|
97
66
|
|
98
|
-
def subscribe(message_class, process_method)
|
99
|
-
# 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)
|
100
70
|
end
|
101
71
|
end
|
102
72
|
```
|
@@ -114,50 +84,86 @@ Handles encoding and decoding of message content.
|
|
114
84
|
|
115
85
|
```ruby
|
116
86
|
class CustomSerializer < SmartMessage::Serializer::Base
|
117
|
-
def
|
118
|
-
# 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
|
119
90
|
end
|
120
91
|
|
121
|
-
def
|
122
|
-
# 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
|
123
95
|
end
|
124
96
|
end
|
125
97
|
```
|
126
98
|
|
127
99
|
### 4. Dispatcher
|
128
100
|
|
129
|
-
Routes incoming messages to appropriate handlers using concurrent processing.
|
101
|
+
Routes incoming messages to appropriate handlers using concurrent processing with integrated deduplication.
|
130
102
|
|
131
103
|
**Key Responsibilities:**
|
132
104
|
- Message routing based on class
|
133
|
-
- Thread pool management
|
105
|
+
- Thread pool management
|
134
106
|
- Subscription catalog management
|
135
|
-
-
|
107
|
+
- Handler-scoped DDQ management
|
108
|
+
- Message filtering and statistics collection
|
136
109
|
|
137
|
-
**Location:** `lib/smart_message/dispatcher.rb
|
110
|
+
**Location:** `lib/smart_message/dispatcher.rb`
|
138
111
|
|
139
112
|
```ruby
|
140
113
|
dispatcher = SmartMessage::Dispatcher.new
|
141
114
|
dispatcher.add("MyMessage", "MyMessage.process")
|
142
|
-
dispatcher.route(
|
115
|
+
dispatcher.route(decoded_message)
|
116
|
+
|
117
|
+
# DDQ integration is automatic when enabled
|
118
|
+
MyMessage.enable_deduplication!
|
143
119
|
```
|
144
120
|
|
145
|
-
### 5.
|
121
|
+
### 5. Deduplication Queue (DDQ)
|
122
|
+
|
123
|
+
Handler-scoped message deduplication system preventing duplicate processing.
|
124
|
+
|
125
|
+
**Key Responsibilities:**
|
126
|
+
- UUID-based duplicate detection
|
127
|
+
- Handler isolation (each handler gets own DDQ)
|
128
|
+
- Memory and Redis storage backends
|
129
|
+
- O(1) performance with hybrid Array + Set data structure
|
130
|
+
|
131
|
+
**Architecture:**
|
132
|
+
|
133
|
+

|
134
|
+
|
135
|
+
**Location:** `lib/smart_message/deduplication.rb`, `lib/smart_message/ddq/`
|
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
|
146
143
|
|
147
|
-
|
144
|
+
### 6. Message Headers
|
145
|
+
|
146
|
+
Standard metadata attached to every message with entity addressing support.
|
148
147
|
|
149
148
|
**Key Responsibilities:**
|
150
149
|
- Message identification (UUID)
|
151
|
-
- Routing information (message class)
|
150
|
+
- Routing information (message class, version)
|
152
151
|
- Tracking data (timestamps, process IDs)
|
152
|
+
- Entity addressing (from, to, reply_to)
|
153
153
|
|
154
|
-
**Location:** `lib/smart_message/header.rb
|
154
|
+
**Location:** `lib/smart_message/header.rb`
|
155
155
|
|
156
156
|
```ruby
|
157
157
|
header = message._sm_header
|
158
158
|
puts header.uuid # "550e8400-e29b-41d4-a716-446655440000"
|
159
159
|
puts header.message_class # "MyMessage"
|
160
160
|
puts header.published_at # 2025-08-17 10:30:00 UTC
|
161
|
+
puts header.publisher_pid # 12345
|
162
|
+
puts header.from # "payment-service"
|
163
|
+
puts header.to # "order-service"
|
164
|
+
puts header.reply_to # "payment-service" (defaults to from)
|
165
|
+
puts header.version # 1
|
166
|
+
puts header.serializer # "SmartMessage::Serializer::JSON"
|
161
167
|
```
|
162
168
|
|
163
169
|
## Message Lifecycle
|
@@ -170,34 +176,46 @@ class OrderMessage < SmartMessage::Base
|
|
170
176
|
|
171
177
|
config do
|
172
178
|
transport SmartMessage::Transport.create(:memory)
|
173
|
-
serializer SmartMessage::Serializer::
|
179
|
+
serializer SmartMessage::Serializer::Json.new
|
174
180
|
end
|
175
181
|
end
|
176
182
|
```
|
177
183
|
|
178
184
|
### 2. Subscription Phase
|
179
185
|
```ruby
|
186
|
+
# Basic subscription
|
180
187
|
OrderMessage.subscribe
|
181
|
-
|
188
|
+
|
189
|
+
# Subscription with filtering
|
190
|
+
OrderMessage.subscribe(from: /^payment-.*/, to: 'order-service')
|
191
|
+
OrderMessage.subscribe('PaymentService.process', broadcast: true)
|
192
|
+
|
193
|
+
# Each subscription gets its own DDQ automatically
|
194
|
+
# DDQ Key: "OrderMessage:OrderMessage.process"
|
195
|
+
# DDQ Key: "OrderMessage:PaymentService.process"
|
182
196
|
```
|
183
197
|
|
184
198
|
### 3. Publishing Phase
|
185
199
|
```ruby
|
186
200
|
order = OrderMessage.new(order_id: "123", amount: 99.99)
|
201
|
+
order.from("order-service").to("payment-service")
|
187
202
|
order.publish
|
188
|
-
# 1. Creates header with UUID, timestamp,
|
189
|
-
# 2. Encodes message via serializer
|
203
|
+
# 1. Creates header with UUID, timestamp, addressing
|
204
|
+
# 2. Encodes message via serializer
|
190
205
|
# 3. Sends via transport
|
206
|
+
# 4. Circuit breaker monitors for failures
|
191
207
|
```
|
192
208
|
|
193
209
|
### 4. Receiving Phase
|
194
210
|
```ruby
|
195
|
-
# Transport receives message
|
196
|
-
transport.receive(
|
197
|
-
# 1.
|
198
|
-
# 2.
|
199
|
-
# 3.
|
200
|
-
# 4.
|
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
|
201
219
|
```
|
202
220
|
|
203
221
|
### 5. Message Handler Processing
|
@@ -205,43 +223,42 @@ transport.receive(header, payload)
|
|
205
223
|
SmartMessage supports multiple handler types, routed through the dispatcher:
|
206
224
|
|
207
225
|
```ruby
|
208
|
-
# Default handler (self.process method)
|
209
|
-
def self.process(
|
210
|
-
|
211
|
-
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
|
212
229
|
fulfill_order(order)
|
213
230
|
end
|
214
231
|
|
215
|
-
# Block handler (inline processing)
|
216
|
-
OrderMessage.subscribe do |
|
217
|
-
|
218
|
-
quick_processing(data)
|
232
|
+
# Block handler (inline processing) - receives decoded message instance
|
233
|
+
OrderMessage.subscribe do |decoded_message|
|
234
|
+
quick_processing(decoded_message)
|
219
235
|
end
|
220
236
|
|
221
|
-
# Proc handler (reusable across message types)
|
222
|
-
audit_proc = proc do |
|
223
|
-
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)
|
224
240
|
end
|
225
241
|
OrderMessage.subscribe(audit_proc)
|
226
242
|
|
227
|
-
# Method handler (service class processing)
|
243
|
+
# Method handler (service class processing) - receives decoded message instance
|
228
244
|
class OrderService
|
229
|
-
def self.process_order(
|
230
|
-
|
231
|
-
complex_business_logic(data)
|
245
|
+
def self.process_order(decoded_message)
|
246
|
+
complex_business_logic(decoded_message)
|
232
247
|
end
|
233
248
|
end
|
234
249
|
OrderMessage.subscribe("OrderService.process_order")
|
235
250
|
```
|
236
251
|
|
237
252
|
**Handler Routing Process:**
|
238
|
-
1. Dispatcher receives message
|
253
|
+
1. Dispatcher receives decoded message instance
|
239
254
|
2. Looks up all registered handlers for message class
|
240
|
-
3. For each handler:
|
255
|
+
3. For each handler that matches filters:
|
256
|
+
- Checks DDQ for duplicates (handler-scoped)
|
241
257
|
- **String handlers**: Resolves to class method via constantize
|
242
258
|
- **Proc handlers**: Calls proc directly from registry
|
243
|
-
4. Executes handlers in parallel threads
|
244
|
-
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
|
245
262
|
|
246
263
|
## Plugin System Architecture
|
247
264
|
|
@@ -291,10 +308,15 @@ end
|
|
291
308
|
The dispatcher uses `Concurrent::CachedThreadPool` for processing:
|
292
309
|
|
293
310
|
```ruby
|
294
|
-
# Each message processing happens in its own thread
|
311
|
+
# Each message processing happens in its own thread with circuit breaker protection
|
295
312
|
@router_pool.post do
|
296
|
-
|
297
|
-
|
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
|
298
320
|
end
|
299
321
|
```
|
300
322
|
|
@@ -323,11 +345,13 @@ puts "Completed tasks: #{status[:completed_task_count]}"
|
|
323
345
|
Processing exceptions are isolated to prevent cascade failures:
|
324
346
|
|
325
347
|
```ruby
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
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)
|
331
355
|
end
|
332
356
|
```
|
333
357
|
|
@@ -342,6 +366,7 @@ module SmartMessage::Errors
|
|
342
366
|
class NotImplemented < RuntimeError; end
|
343
367
|
class ReceivedMessageNotSubscribed < RuntimeError; end
|
344
368
|
class UnknownMessageClass < RuntimeError; end
|
369
|
+
class ValidationError < RuntimeError; end
|
345
370
|
end
|
346
371
|
```
|
347
372
|
|
@@ -352,13 +377,12 @@ end
|
|
352
377
|
SmartMessage integrates BreakerMachines for production-grade reliability:
|
353
378
|
|
354
379
|
```ruby
|
355
|
-
#
|
380
|
+
# Circuit breakers are automatically configured for all transports
|
356
381
|
class MyTransport < SmartMessage::Transport::Base
|
357
|
-
circuit :
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
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
|
362
386
|
end
|
363
387
|
```
|
364
388
|
|
@@ -377,35 +401,12 @@ message.publish # If transport fails, goes to DLQ
|
|
377
401
|
|
378
402
|
# Manual capture for business logic failures
|
379
403
|
dlq = SmartMessage::DeadLetterQueue.default
|
380
|
-
dlq.enqueue(
|
404
|
+
dlq.enqueue(decoded_message, error: "Validation failed", transport: "manual")
|
381
405
|
```
|
382
406
|
|
383
407
|
**DLQ Architecture:**
|
384
408
|
|
385
|
-
|
386
|
-
┌─────────────────────────────────────────────────────────────┐
|
387
|
-
│ Message Publishing │
|
388
|
-
│ │ │
|
389
|
-
│ ▼ │
|
390
|
-
│ ┌─────────────────┐ │
|
391
|
-
│ │ Circuit Breaker │ │
|
392
|
-
│ │ (Monitoring) │ │
|
393
|
-
│ └─────────────────┘ │
|
394
|
-
│ │ │ │
|
395
|
-
│ Success │ │ Failure │
|
396
|
-
│ ▼ ▼ │
|
397
|
-
│ ┌──────────┐ ┌─────────────┐ │
|
398
|
-
│ │Transport │ │Dead Letter │ │
|
399
|
-
│ │ │ │ Queue │ │
|
400
|
-
│ └──────────┘ └─────────────┘ │
|
401
|
-
│ │ │
|
402
|
-
│ ▼ │
|
403
|
-
│ ┌─────────────┐ │
|
404
|
-
│ │ Replay │ │
|
405
|
-
│ │ Mechanism │ │
|
406
|
-
│ └─────────────┘ │
|
407
|
-
└─────────────────────────────────────────────────────────────┘
|
408
|
-
```
|
409
|
+

|
409
410
|
|
410
411
|
**DLQ Features:**
|
411
412
|
- JSON Lines format for efficient append operations
|
@@ -417,7 +418,7 @@ dlq.enqueue(header, payload, error: "Validation failed")
|
|
417
418
|
|
418
419
|
### Built-in Statistics
|
419
420
|
|
420
|
-
SmartMessage automatically collects processing statistics:
|
421
|
+
SmartMessage automatically collects processing statistics including DDQ metrics:
|
421
422
|
|
422
423
|
```ruby
|
423
424
|
# Statistics are collected for:
|
@@ -427,6 +428,11 @@ SS.add(message_class, process_method, 'routed')
|
|
427
428
|
# Access statistics
|
428
429
|
puts SS.stat
|
429
430
|
puts SS.get("MyMessage", "publish")
|
431
|
+
|
432
|
+
# DDQ-specific statistics
|
433
|
+
stats = OrderMessage.ddq_stats
|
434
|
+
puts "DDQ utilization: #{stats[:utilization]}%"
|
435
|
+
puts "Current count: #{stats[:current_count]}"
|
430
436
|
```
|
431
437
|
|
432
438
|
### Monitoring Points
|
@@ -435,6 +441,8 @@ puts SS.get("MyMessage", "publish")
|
|
435
441
|
2. **Message Routing**: Count of routed messages per processor
|
436
442
|
3. **Thread Pool**: Queue length, completed tasks, running status
|
437
443
|
4. **Transport Status**: Connection status, message counts
|
444
|
+
5. **DDQ Metrics**: Utilization, duplicate detection rates, memory usage
|
445
|
+
6. **Message Filtering**: Filter match rates, entity-aware routing statistics
|
438
446
|
|
439
447
|
## Configuration Architecture
|
440
448
|
|
@@ -466,7 +474,9 @@ When a message needs a plugin:
|
|
466
474
|
|
467
475
|
```ruby
|
468
476
|
def transport
|
469
|
-
@transport ||
|
477
|
+
@transport || self.class.class_variable_get(:@@transport) || raise(Errors::TransportNotConfigured)
|
478
|
+
rescue NameError
|
479
|
+
raise(Errors::TransportNotConfigured)
|
470
480
|
end
|
471
481
|
```
|
472
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
|