smart_message 0.0.2 → 0.0.4
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/CHANGELOG.md +31 -1
- data/Gemfile.lock +6 -1
- data/README.md +92 -14
- data/docs/README.md +1 -0
- data/docs/architecture.md +41 -8
- data/docs/dispatcher.md +52 -16
- data/docs/getting-started.md +64 -2
- data/docs/logging.md +452 -0
- data/docs/message_processing.md +423 -0
- data/docs/proc_handlers_summary.md +247 -0
- data/docs/transports.md +202 -8
- data/examples/.gitignore +2 -0
- data/examples/04_redis_smart_home_iot.rb +649 -0
- data/examples/05_proc_handlers.rb +181 -0
- data/examples/06_custom_logger_example.rb +620 -0
- data/examples/README.md +118 -3
- data/examples/smart_home_iot_dataflow.md +257 -0
- data/lib/smart_message/base.rb +94 -4
- data/lib/smart_message/dispatcher.rb +22 -6
- data/lib/smart_message/logger/default.rb +217 -0
- data/lib/smart_message/logger.rb +9 -1
- data/lib/smart_message/transport/redis_transport.rb +190 -0
- data/lib/smart_message/transport/registry.rb +1 -0
- data/lib/smart_message/transport.rb +1 -0
- data/lib/smart_message/version.rb +1 -1
- data/smart_message.gemspec +1 -0
- metadata +25 -1
@@ -0,0 +1,423 @@
|
|
1
|
+
# Message Processing in SmartMessage
|
2
|
+
|
3
|
+
## Understanding the `self.process` Method
|
4
|
+
|
5
|
+
The `self.process` method in SmartMessage classes serves as the **default message handler**. It defines what should happen when a message of that type is received by a subscriber.
|
6
|
+
|
7
|
+
## Purpose of `self.process`
|
8
|
+
|
9
|
+
The `self.process` method defines **what happens when a message is received**. It's the entry point for processing incoming messages of that type.
|
10
|
+
|
11
|
+
## How it Works
|
12
|
+
|
13
|
+
### 1. Message Publishing Flow
|
14
|
+
```ruby
|
15
|
+
# Someone publishes a message
|
16
|
+
SensorDataMessage.new(device_id: "THERM-001", value: 22.5).publish
|
17
|
+
```
|
18
|
+
|
19
|
+
### 2. Subscription & Routing
|
20
|
+
```ruby
|
21
|
+
# A class subscribes to receive messages
|
22
|
+
SensorDataMessage.subscribe # Uses default "SensorDataMessage.process"
|
23
|
+
# OR with custom method
|
24
|
+
SensorDataMessage.subscribe("MyService.custom_handler")
|
25
|
+
```
|
26
|
+
|
27
|
+
### 3. Message Processing
|
28
|
+
When a message arrives, the dispatcher calls the registered handler method with:
|
29
|
+
- `message_header` - metadata (timestamp, UUID, message class, etc.)
|
30
|
+
- `message_payload` - the serialized message data (usually JSON)
|
31
|
+
|
32
|
+
## Message Handler Options
|
33
|
+
|
34
|
+
SmartMessage supports multiple ways to handle incoming messages:
|
35
|
+
|
36
|
+
### 1. Default Handler Pattern (using `self.process`)
|
37
|
+
```ruby
|
38
|
+
class SensorDataMessage < SmartMessage::Base
|
39
|
+
def self.process(message_header, message_payload)
|
40
|
+
# This gets called when a SensorDataMessage is received
|
41
|
+
data = JSON.parse(message_payload)
|
42
|
+
puts "Sensor reading: #{data['value']}"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
SensorDataMessage.subscribe # Uses "SensorDataMessage.process"
|
47
|
+
```
|
48
|
+
|
49
|
+
### 2. Custom Method Handler Pattern
|
50
|
+
```ruby
|
51
|
+
class ThermostatService
|
52
|
+
def self.handle_sensor_data(message_header, message_payload)
|
53
|
+
# Custom processing logic
|
54
|
+
data = JSON.parse(message_payload)
|
55
|
+
adjust_temperature(data)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
SensorDataMessage.subscribe("ThermostatService.handle_sensor_data")
|
60
|
+
```
|
61
|
+
|
62
|
+
### 3. Block Handler Pattern (NEW)
|
63
|
+
```ruby
|
64
|
+
# Subscribe with a block - perfect for simple handlers
|
65
|
+
SensorDataMessage.subscribe do |header, payload|
|
66
|
+
data = JSON.parse(payload)
|
67
|
+
puts "Temperature: #{data['value']}°C from #{data['device_id']}"
|
68
|
+
|
69
|
+
# You can access header information too
|
70
|
+
puts "Received at: #{header.published_at}"
|
71
|
+
end
|
72
|
+
```
|
73
|
+
|
74
|
+
### 4. Proc/Lambda Handler Pattern (NEW)
|
75
|
+
```ruby
|
76
|
+
# Create a reusable handler
|
77
|
+
temperature_handler = proc do |header, payload|
|
78
|
+
data = JSON.parse(payload)
|
79
|
+
if data['value'] > 30
|
80
|
+
puts "⚠️ High temperature alert: #{data['value']}°C"
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# Use the proc as a handler
|
85
|
+
SensorDataMessage.subscribe(temperature_handler)
|
86
|
+
|
87
|
+
# Or use a lambda
|
88
|
+
alert_handler = lambda do |header, payload|
|
89
|
+
data = JSON.parse(payload)
|
90
|
+
AlertService.process_sensor_data(data)
|
91
|
+
end
|
92
|
+
|
93
|
+
SensorDataMessage.subscribe(alert_handler)
|
94
|
+
```
|
95
|
+
|
96
|
+
## Real Example from IoT Code
|
97
|
+
|
98
|
+
Looking at the smart home IoT example:
|
99
|
+
|
100
|
+
```ruby
|
101
|
+
class SensorDataMessage < SmartMessage::Base
|
102
|
+
def self.process(message_header, message_payload)
|
103
|
+
sensor_data = JSON.parse(message_payload)
|
104
|
+
icon = case sensor_data['device_type']
|
105
|
+
when 'thermostat' then '🌡️'
|
106
|
+
when 'security_camera' then '📹'
|
107
|
+
when 'door_lock' then '🚪'
|
108
|
+
end
|
109
|
+
|
110
|
+
puts "#{icon} Sensor data: #{sensor_data['device_id']} - #{sensor_data['value']}"
|
111
|
+
end
|
112
|
+
end
|
113
|
+
```
|
114
|
+
|
115
|
+
This `process` method gets called every time a `SensorDataMessage` is published and received by a subscriber.
|
116
|
+
|
117
|
+
## Message Handler Parameters
|
118
|
+
|
119
|
+
### `message_header`
|
120
|
+
Contains metadata about the message:
|
121
|
+
```ruby
|
122
|
+
message_header.uuid # Unique message ID
|
123
|
+
message_header.message_class # "SensorDataMessage"
|
124
|
+
message_header.published_at # Timestamp when published
|
125
|
+
message_header.publisher_pid # Process ID of publisher
|
126
|
+
```
|
127
|
+
|
128
|
+
### `message_payload`
|
129
|
+
The serialized message content (typically JSON):
|
130
|
+
```ruby
|
131
|
+
# Example payload
|
132
|
+
{
|
133
|
+
"device_id": "THERM-001",
|
134
|
+
"device_type": "thermostat",
|
135
|
+
"value": 22.5,
|
136
|
+
"unit": "celsius",
|
137
|
+
"timestamp": "2025-08-18T10:30:00Z"
|
138
|
+
}
|
139
|
+
```
|
140
|
+
|
141
|
+
## Multiple Handlers for One Message Type
|
142
|
+
|
143
|
+
A single message type can have multiple subscribers with different handlers using any combination of the handler patterns:
|
144
|
+
|
145
|
+
```ruby
|
146
|
+
# Default handler for logging
|
147
|
+
class SensorDataMessage < SmartMessage::Base
|
148
|
+
def self.process(message_header, message_payload)
|
149
|
+
data = JSON.parse(message_payload)
|
150
|
+
puts "📊 Sensor data logged: #{data['device_id']}"
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
# Custom method handler for specific services
|
155
|
+
class ThermostatService
|
156
|
+
def self.handle_sensor_data(message_header, message_payload)
|
157
|
+
data = JSON.parse(message_payload)
|
158
|
+
return unless data['device_type'] == 'thermostat'
|
159
|
+
adjust_temperature(data['value'])
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
# Register all handlers - mix of different types
|
164
|
+
SensorDataMessage.subscribe # Uses default process method
|
165
|
+
|
166
|
+
SensorDataMessage.subscribe("ThermostatService.handle_sensor_data") # Method handler
|
167
|
+
|
168
|
+
SensorDataMessage.subscribe do |header, payload| # Block handler
|
169
|
+
data = JSON.parse(payload)
|
170
|
+
if data['value'] > 30
|
171
|
+
puts "🚨 High temperature alert: #{data['value']}°C"
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
# Proc handler for reusable logic
|
176
|
+
database_logger = proc do |header, payload|
|
177
|
+
data = JSON.parse(payload)
|
178
|
+
Database.insert(:sensor_readings, data)
|
179
|
+
end
|
180
|
+
|
181
|
+
SensorDataMessage.subscribe(database_logger) # Proc handler
|
182
|
+
```
|
183
|
+
|
184
|
+
## Message Processing Lifecycle
|
185
|
+
|
186
|
+
1. **Message Published**: `message.publish` is called
|
187
|
+
2. **Transport Delivery**: Message is sent via configured transport (Redis, stdout, etc.)
|
188
|
+
3. **Dispatcher Routing**: Dispatcher receives message and looks up subscribers
|
189
|
+
4. **Handler Execution**: Each registered handler is called in its own thread
|
190
|
+
5. **Business Logic**: Your `process` method executes the business logic
|
191
|
+
|
192
|
+
## Threading and Concurrency
|
193
|
+
|
194
|
+
- Each message handler runs in its own thread from the dispatcher's thread pool
|
195
|
+
- Multiple handlers for the same message run concurrently
|
196
|
+
- Handlers should be thread-safe if they access shared resources
|
197
|
+
|
198
|
+
```ruby
|
199
|
+
class SensorDataMessage < SmartMessage::Base
|
200
|
+
def self.process(message_header, message_payload)
|
201
|
+
# This runs in its own thread
|
202
|
+
# Be careful with shared state
|
203
|
+
data = JSON.parse(message_payload)
|
204
|
+
|
205
|
+
# Thread-safe operations
|
206
|
+
update_local_cache(data)
|
207
|
+
|
208
|
+
# Avoid shared mutable state without synchronization
|
209
|
+
end
|
210
|
+
end
|
211
|
+
```
|
212
|
+
|
213
|
+
## Error Handling in Handlers
|
214
|
+
|
215
|
+
Handlers should include proper error handling:
|
216
|
+
|
217
|
+
```ruby
|
218
|
+
class SensorDataMessage < SmartMessage::Base
|
219
|
+
def self.process(message_header, message_payload)
|
220
|
+
begin
|
221
|
+
data = JSON.parse(message_payload)
|
222
|
+
|
223
|
+
# Validate required fields
|
224
|
+
raise "Missing device_id" unless data['device_id']
|
225
|
+
|
226
|
+
# Process the message
|
227
|
+
process_sensor_reading(data)
|
228
|
+
|
229
|
+
rescue JSON::ParserError => e
|
230
|
+
logger.error "Invalid JSON in sensor message: #{e.message}"
|
231
|
+
rescue => e
|
232
|
+
logger.error "Error processing sensor data: #{e.message}"
|
233
|
+
# Consider dead letter queue or retry logic
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|
237
|
+
```
|
238
|
+
|
239
|
+
## Choosing the Right Handler Type
|
240
|
+
|
241
|
+
### When to Use Each Handler Type
|
242
|
+
|
243
|
+
**Default `self.process` method:**
|
244
|
+
- Simple message types with basic processing
|
245
|
+
- When you want a standard handler for the message class
|
246
|
+
- Good for prototyping and simple applications
|
247
|
+
|
248
|
+
**Custom method handlers (`"ClassName.method_name"`):**
|
249
|
+
- Complex business logic that belongs in a service class
|
250
|
+
- When you need testable, organized code
|
251
|
+
- Handlers that need to be called from multiple places
|
252
|
+
- Enterprise applications with well-defined service layers
|
253
|
+
|
254
|
+
**Block handlers (`subscribe do |header, payload|`):**
|
255
|
+
- Simple, one-off processing logic
|
256
|
+
- Quick prototyping and experimentation
|
257
|
+
- Inline filtering or formatting
|
258
|
+
- When the logic is specific to the subscription point
|
259
|
+
|
260
|
+
**Proc/Lambda handlers:**
|
261
|
+
- Reusable handlers across multiple message types
|
262
|
+
- Dynamic handler creation based on configuration
|
263
|
+
- Functional programming patterns
|
264
|
+
- When you need to pass handlers as parameters
|
265
|
+
|
266
|
+
### Examples of Each Use Case
|
267
|
+
|
268
|
+
```ruby
|
269
|
+
# Default - simple logging
|
270
|
+
class UserEventMessage < SmartMessage::Base
|
271
|
+
def self.process(header, payload)
|
272
|
+
puts "User event: #{JSON.parse(payload)['event_type']}"
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
# Method handler - complex business logic
|
277
|
+
class EmailService
|
278
|
+
def self.send_welcome_email(header, payload)
|
279
|
+
user_data = JSON.parse(payload)
|
280
|
+
return unless user_data['event_type'] == 'user_registered'
|
281
|
+
|
282
|
+
EmailTemplate.render(:welcome, user_data)
|
283
|
+
.deliver_to(user_data['email'])
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
UserEventMessage.subscribe("EmailService.send_welcome_email")
|
288
|
+
|
289
|
+
# Block handler - simple inline logic
|
290
|
+
UserEventMessage.subscribe do |header, payload|
|
291
|
+
data = JSON.parse(payload)
|
292
|
+
puts "🎉 Welcome #{data['username']}!" if data['event_type'] == 'user_registered'
|
293
|
+
end
|
294
|
+
|
295
|
+
# Proc handler - reusable across message types
|
296
|
+
audit_logger = proc do |header, payload|
|
297
|
+
AuditLog.create(
|
298
|
+
message_type: header.message_class,
|
299
|
+
timestamp: header.published_at,
|
300
|
+
data: payload
|
301
|
+
)
|
302
|
+
end
|
303
|
+
|
304
|
+
UserEventMessage.subscribe(audit_logger)
|
305
|
+
OrderEventMessage.subscribe(audit_logger) # Reuse the same proc
|
306
|
+
PaymentEventMessage.subscribe(audit_logger)
|
307
|
+
```
|
308
|
+
|
309
|
+
## Best Practices
|
310
|
+
|
311
|
+
### 1. Keep Handlers Fast
|
312
|
+
```ruby
|
313
|
+
def self.process(message_header, message_payload)
|
314
|
+
# Quick validation
|
315
|
+
data = JSON.parse(message_payload)
|
316
|
+
return unless valid_message?(data)
|
317
|
+
|
318
|
+
# Delegate heavy work to background jobs
|
319
|
+
BackgroundJob.perform_async(data)
|
320
|
+
end
|
321
|
+
```
|
322
|
+
|
323
|
+
### 2. Use Descriptive Handler Names
|
324
|
+
```ruby
|
325
|
+
# Good method names
|
326
|
+
SensorDataMessage.subscribe("ThermostatService.handle_temperature_reading")
|
327
|
+
SensorDataMessage.subscribe("AlertService.monitor_for_anomalies")
|
328
|
+
|
329
|
+
# Good block handlers with comments
|
330
|
+
SensorDataMessage.subscribe do |header, payload| # Temperature monitoring
|
331
|
+
data = JSON.parse(payload)
|
332
|
+
monitor_temperature_thresholds(data)
|
333
|
+
end
|
334
|
+
|
335
|
+
# Good proc handlers with descriptive variable names
|
336
|
+
temperature_validator = proc do |header, payload|
|
337
|
+
data = JSON.parse(payload)
|
338
|
+
validate_temperature_range(data)
|
339
|
+
end
|
340
|
+
|
341
|
+
SensorDataMessage.subscribe(temperature_validator)
|
342
|
+
|
343
|
+
# Less clear
|
344
|
+
SensorDataMessage.subscribe("Service1.method1")
|
345
|
+
SensorDataMessage.subscribe do |h, p|; process_stuff(p); end
|
346
|
+
```
|
347
|
+
|
348
|
+
### 3. Filter Messages Early
|
349
|
+
```ruby
|
350
|
+
def self.handle_thermostat_data(message_header, message_payload)
|
351
|
+
data = JSON.parse(message_payload)
|
352
|
+
|
353
|
+
# Filter early to avoid unnecessary processing
|
354
|
+
return unless data['device_type'] == 'thermostat'
|
355
|
+
return unless data['device_id']&.start_with?('THERM-')
|
356
|
+
|
357
|
+
# Process only relevant messages
|
358
|
+
adjust_temperature(data)
|
359
|
+
end
|
360
|
+
```
|
361
|
+
|
362
|
+
### 4. Include Logging and Monitoring
|
363
|
+
```ruby
|
364
|
+
def self.process(message_header, message_payload)
|
365
|
+
start_time = Time.now
|
366
|
+
|
367
|
+
begin
|
368
|
+
data = JSON.parse(message_payload)
|
369
|
+
logger.info "Processing sensor data from #{data['device_id']}"
|
370
|
+
|
371
|
+
# Business logic here
|
372
|
+
result = process_sensor_reading(data)
|
373
|
+
|
374
|
+
# Success metrics
|
375
|
+
duration = Time.now - start_time
|
376
|
+
metrics.histogram('message.processing.duration', duration)
|
377
|
+
|
378
|
+
rescue => e
|
379
|
+
logger.error "Failed to process sensor data: #{e.message}"
|
380
|
+
metrics.increment('message.processing.errors')
|
381
|
+
raise
|
382
|
+
end
|
383
|
+
end
|
384
|
+
```
|
385
|
+
|
386
|
+
## Summary
|
387
|
+
|
388
|
+
SmartMessage provides flexible options for handling incoming messages, from simple default handlers to sophisticated proc-based solutions.
|
389
|
+
|
390
|
+
### Handler Types Summary:
|
391
|
+
|
392
|
+
1. **Default Handler** (`self.process`): Built-in method for basic message processing
|
393
|
+
2. **Method Handler** (`"Class.method"`): Organized, testable handlers in service classes
|
394
|
+
3. **Block Handler** (`subscribe do |h,p|`): Inline logic perfect for simple processing
|
395
|
+
4. **Proc Handler** (`subscribe(proc {...})`): Reusable, composable handlers
|
396
|
+
|
397
|
+
### Key Points:
|
398
|
+
|
399
|
+
- **Flexibility**: Choose the right handler type for your use case
|
400
|
+
- **Parameters**: All handlers receive `(message_header, message_payload)`
|
401
|
+
- **Payload**: Usually JSON that needs to be parsed back into Ruby objects
|
402
|
+
- **Multiple Handlers**: One message type can have multiple subscribers with different handler types
|
403
|
+
- **Threading**: Each handler runs in its own thread via the dispatcher's thread pool
|
404
|
+
- **Error Handling**: Include proper error handling for production reliability
|
405
|
+
- **Unsubscription**: All handler types can be unsubscribed using their returned identifiers
|
406
|
+
|
407
|
+
### Return Values:
|
408
|
+
|
409
|
+
The `subscribe` method always returns a string identifier that can be used for unsubscription:
|
410
|
+
|
411
|
+
```ruby
|
412
|
+
# All of these return identifiers for unsubscription
|
413
|
+
default_id = MyMessage.subscribe
|
414
|
+
method_id = MyMessage.subscribe("Service.handle")
|
415
|
+
block_id = MyMessage.subscribe { |h,p| puts p }
|
416
|
+
proc_id = MyMessage.subscribe(my_proc)
|
417
|
+
|
418
|
+
# Unsubscribe any handler type
|
419
|
+
MyMessage.unsubscribe(block_id)
|
420
|
+
MyMessage.unsubscribe(proc_id)
|
421
|
+
```
|
422
|
+
|
423
|
+
This enhanced subscription system provides the foundation for building sophisticated, event-driven applications while maintaining simplicity for basic use cases.
|
@@ -0,0 +1,247 @@
|
|
1
|
+
# SmartMessage Proc and Block Handlers - Implementation Summary
|
2
|
+
|
3
|
+
## Overview
|
4
|
+
|
5
|
+
This document summarizes the enhanced subscription functionality added to SmartMessage, which extends the framework to support multiple message handler patterns including blocks, procs, and lambdas alongside the existing default and method handlers.
|
6
|
+
|
7
|
+
## New Features Added
|
8
|
+
|
9
|
+
### 1. Enhanced `subscribe` Method
|
10
|
+
|
11
|
+
The `subscribe` method in `SmartMessage::Base` now supports multiple parameter types:
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
# Original functionality (still works)
|
15
|
+
MyMessage.subscribe # Default: "MyMessage.process"
|
16
|
+
MyMessage.subscribe("Service.handle_message") # Method handler
|
17
|
+
|
18
|
+
# NEW functionality
|
19
|
+
MyMessage.subscribe do |header, payload| # Block handler
|
20
|
+
# Inline processing logic
|
21
|
+
end
|
22
|
+
|
23
|
+
MyMessage.subscribe(proc { |h,p| ... }) # Proc handler
|
24
|
+
MyMessage.subscribe(lambda { |h,p| ... }) # Lambda handler
|
25
|
+
```
|
26
|
+
|
27
|
+
### 2. Proc Registry System
|
28
|
+
|
29
|
+
- **Storage**: `@@proc_handlers` class variable stores all proc-based handlers
|
30
|
+
- **Unique IDs**: Each proc handler gets a unique identifier like `"MessageClass.proc_abc123"`
|
31
|
+
- **Cleanup**: Automatic cleanup when unsubscribing proc handlers
|
32
|
+
- **Registry Management**: Helper methods for registration, lookup, and cleanup
|
33
|
+
|
34
|
+
### 3. Enhanced Dispatcher Routing
|
35
|
+
|
36
|
+
The dispatcher (`lib/smart_message/dispatcher.rb`) now handles multiple handler types:
|
37
|
+
|
38
|
+
```ruby
|
39
|
+
def route(message_header, message_payload)
|
40
|
+
@subscribers[message_klass].each do |message_processor|
|
41
|
+
@router_pool.post do
|
42
|
+
if proc_handler?(message_processor)
|
43
|
+
# Route to proc handler
|
44
|
+
SmartMessage::Base.call_proc_handler(message_processor, message_header, message_payload)
|
45
|
+
else
|
46
|
+
# Route to method handler (original logic)
|
47
|
+
target_klass.constantize.method(class_method).call(message_header, message_payload)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
```
|
53
|
+
|
54
|
+
## Implementation Details
|
55
|
+
|
56
|
+
### Core Files Modified
|
57
|
+
|
58
|
+
1. **`lib/smart_message/base.rb`**:
|
59
|
+
- Enhanced `subscribe` method with block and proc support
|
60
|
+
- Added proc registry management (`@@proc_handlers`)
|
61
|
+
- Added helper methods: `register_proc_handler`, `call_proc_handler`, `unregister_proc_handler`, `proc_handler?`
|
62
|
+
- Updated `unsubscribe` method to clean up proc handlers
|
63
|
+
|
64
|
+
2. **`lib/smart_message/dispatcher.rb`**:
|
65
|
+
- Updated `route` method to handle both method and proc handlers
|
66
|
+
- Added `proc_handler?` check for routing decisions
|
67
|
+
- Maintained thread safety and error handling
|
68
|
+
|
69
|
+
### Handler Type Identification
|
70
|
+
|
71
|
+
- **Method handlers**: String format `"ClassName.method_name"`
|
72
|
+
- **Proc handlers**: String format `"ClassName.proc_[hex_id]"` stored in `@@proc_handlers` registry
|
73
|
+
- **Default handlers**: String format `"ClassName.process"`
|
74
|
+
|
75
|
+
### Return Values
|
76
|
+
|
77
|
+
All subscription methods return identifiers for unsubscription:
|
78
|
+
|
79
|
+
```ruby
|
80
|
+
default_id = MyMessage.subscribe # "MyMessage.process"
|
81
|
+
method_id = MyMessage.subscribe("Service.handle") # "Service.handle"
|
82
|
+
block_id = MyMessage.subscribe { |h,p| } # "MyMessage.proc_abc123"
|
83
|
+
proc_id = MyMessage.subscribe(my_proc) # "MyMessage.proc_def456"
|
84
|
+
|
85
|
+
# All can be unsubscribed using the returned ID
|
86
|
+
MyMessage.unsubscribe(block_id)
|
87
|
+
```
|
88
|
+
|
89
|
+
## Testing Coverage
|
90
|
+
|
91
|
+
### Unit Tests (`test/proc_handler_test.rb`)
|
92
|
+
|
93
|
+
- Default handler compatibility
|
94
|
+
- Block handler functionality
|
95
|
+
- Proc parameter handler functionality
|
96
|
+
- Lambda handler functionality
|
97
|
+
- Multiple handlers for same message type
|
98
|
+
- Mixed handler types (method + proc)
|
99
|
+
- Handler unsubscription and cleanup
|
100
|
+
- Error handling in proc handlers
|
101
|
+
- Complex logic processing
|
102
|
+
- Return value handling
|
103
|
+
|
104
|
+
### Integration Tests (`test/proc_handler_integration_test.rb`)
|
105
|
+
|
106
|
+
- End-to-end testing with real transports (Memory, Stdout)
|
107
|
+
- Multiple proc handlers with concurrent execution
|
108
|
+
- Error handling and graceful degradation
|
109
|
+
- Handler lifecycle management
|
110
|
+
- Mixed handler type integration
|
111
|
+
- Lambda vs proc behavior
|
112
|
+
- Concurrent execution verification
|
113
|
+
|
114
|
+
### Test Results
|
115
|
+
|
116
|
+
- **55 total tests** (10 new proc handler tests + existing tests)
|
117
|
+
- **276 assertions**
|
118
|
+
- **All tests passing**
|
119
|
+
- **Full backward compatibility** maintained
|
120
|
+
|
121
|
+
## Documentation Updates
|
122
|
+
|
123
|
+
### 1. Main README.md
|
124
|
+
- Updated Quick Start section with new subscription examples
|
125
|
+
- Enhanced Features section
|
126
|
+
- Updated Message Lifecycle section
|
127
|
+
|
128
|
+
### 2. Getting Started Guide (`docs/getting-started.md`)
|
129
|
+
- Added comprehensive handler types section
|
130
|
+
- Updated subscription examples
|
131
|
+
- Added guidance on when to use each handler type
|
132
|
+
|
133
|
+
### 3. Architecture Documentation (`docs/architecture.md`)
|
134
|
+
- Updated system architecture diagram
|
135
|
+
- Enhanced message processing section
|
136
|
+
- Added handler routing process details
|
137
|
+
|
138
|
+
### 4. Dispatcher Documentation (`docs/dispatcher.md`)
|
139
|
+
- Updated subscription management section
|
140
|
+
- Enhanced message routing process
|
141
|
+
- Added handler type processing details
|
142
|
+
|
143
|
+
### 5. Examples Documentation (`examples/README.md`)
|
144
|
+
- Added new proc handler example description
|
145
|
+
- Updated examples overview
|
146
|
+
- Added handler pattern demonstrations
|
147
|
+
|
148
|
+
### 6. New Comprehensive Guide (`docs/message_processing.md`)
|
149
|
+
- Complete guide to all handler types
|
150
|
+
- Best practices and use cases
|
151
|
+
- Performance considerations
|
152
|
+
- Error handling patterns
|
153
|
+
|
154
|
+
## Examples
|
155
|
+
|
156
|
+
### 1. New Working Example (`examples/05_proc_handlers.rb`)
|
157
|
+
|
158
|
+
Complete demonstration of all handler types:
|
159
|
+
- Default handler (self.process)
|
160
|
+
- Block handlers (inline logic)
|
161
|
+
- Proc handlers (reusable logic)
|
162
|
+
- Lambda handlers (functional style)
|
163
|
+
- Method handlers (service classes)
|
164
|
+
- Handler management and unsubscription
|
165
|
+
|
166
|
+
### 2. Enhanced IoT Example (`examples/04_redis_smart_home_iot.rb`)
|
167
|
+
|
168
|
+
Production-ready Redis transport example showing real-world usage patterns.
|
169
|
+
|
170
|
+
## Benefits Delivered
|
171
|
+
|
172
|
+
### 1. **Flexibility**
|
173
|
+
- Choose the right handler pattern for each use case
|
174
|
+
- Mix multiple handler types for the same message
|
175
|
+
- Easy migration path from simple to complex handlers
|
176
|
+
|
177
|
+
### 2. **Developer Experience**
|
178
|
+
- Intuitive block syntax for simple cases
|
179
|
+
- Reusable proc handlers for cross-cutting concerns
|
180
|
+
- Organized method handlers for complex business logic
|
181
|
+
- Clear return values for handler management
|
182
|
+
|
183
|
+
### 3. **Performance**
|
184
|
+
- Efficient proc registry with minimal overhead
|
185
|
+
- Thread-safe concurrent processing
|
186
|
+
- No impact on existing method handler performance
|
187
|
+
|
188
|
+
### 4. **Maintainability**
|
189
|
+
- Clean separation between handler types
|
190
|
+
- Proper cleanup and memory management
|
191
|
+
- Comprehensive error handling
|
192
|
+
- Full backward compatibility
|
193
|
+
|
194
|
+
## Production Considerations
|
195
|
+
|
196
|
+
### 1. **Handler Selection Guidelines**
|
197
|
+
|
198
|
+
- **Default handlers**: Simple built-in processing
|
199
|
+
- **Block handlers**: Quick inline logic, prototyping
|
200
|
+
- **Proc handlers**: Reusable cross-message functionality (auditing, logging)
|
201
|
+
- **Method handlers**: Complex business logic, organized service classes
|
202
|
+
|
203
|
+
### 2. **Memory Management**
|
204
|
+
|
205
|
+
- Proc handlers are stored in class variables and persist until explicitly unsubscribed
|
206
|
+
- Use returned handler IDs for proper cleanup
|
207
|
+
- Consider memory usage with many long-lived proc handlers
|
208
|
+
|
209
|
+
### 3. **Performance**
|
210
|
+
|
211
|
+
- Proc handlers have minimal overhead vs method handlers
|
212
|
+
- All handlers execute in parallel threads
|
213
|
+
- No impact on existing code performance
|
214
|
+
|
215
|
+
### 4. **Error Handling**
|
216
|
+
|
217
|
+
- Proc handler errors are isolated and don't crash the dispatcher
|
218
|
+
- Same error handling patterns as method handlers
|
219
|
+
- Debug output available for troubleshooting
|
220
|
+
|
221
|
+
## Migration Path
|
222
|
+
|
223
|
+
### For Existing Code
|
224
|
+
- **No changes required** - all existing code continues to work
|
225
|
+
- **Gradual adoption** - can introduce proc handlers incrementally
|
226
|
+
- **Mixed usage** - can use multiple handler types simultaneously
|
227
|
+
|
228
|
+
### For New Development
|
229
|
+
- **Start with defaults** for simple cases
|
230
|
+
- **Use blocks** for quick inline processing
|
231
|
+
- **Use procs** for reusable functionality
|
232
|
+
- **Use methods** for complex business logic
|
233
|
+
|
234
|
+
## Future Enhancements
|
235
|
+
|
236
|
+
Potential areas for future development:
|
237
|
+
- Handler priority/ordering
|
238
|
+
- Conditional handler execution
|
239
|
+
- Handler metrics and monitoring
|
240
|
+
- Dynamic handler registration/deregistration
|
241
|
+
- Handler middleware/interceptors
|
242
|
+
|
243
|
+
## Conclusion
|
244
|
+
|
245
|
+
The enhanced subscription functionality provides SmartMessage users with powerful, flexible options for message processing while maintaining the simplicity and elegance of the original design. The implementation is production-ready, thoroughly tested, and fully documented.
|
246
|
+
|
247
|
+
This enhancement positions SmartMessage as a more versatile and developer-friendly messaging framework suitable for both simple prototypes and complex enterprise applications.
|