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
data/docs/index.md
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
# SmartMessage Documentation
|
2
|
+
|
3
|
+
<table border="0">
|
4
|
+
<tr>
|
5
|
+
<td width="30%" valign="top">
|
6
|
+
<img src="assets/images/smart_message.jpg" alt="SmartMessage Logo" width="200" />
|
7
|
+
</td>
|
8
|
+
<td width="70%" valign="top">
|
9
|
+
|
10
|
+
<string>SmartMessage</strong> is a powerful Ruby framework that transforms ordinary messages into intelligent, self-aware entities capable of routing themselves, validating their contents, and executing business logic. By abstracting away the complexities of transport mechanisms (Redis, RabbitMQ, Kafka) and serialization formats (JSON, MessagePack), SmartMessage lets you focus on what matters: your business logic.
|
11
|
+
<br/><br/>
|
12
|
+
Think of SmartMessage as ActiveRecord for messaging - just as ActiveRecord frees you from database-specific SQL, SmartMessage liberates your messages from transport-specific implementations. Each message knows how to validate itself, where it came from, where it's going, and what to do when it arrives. With built-in support for filtering, versioning, deduplication, and concurrent processing, SmartMessage provides enterprise-grade messaging capabilities with the simplicity Ruby developers love.
|
13
|
+
|
14
|
+
</td>
|
15
|
+
</tr>
|
16
|
+
</table>
|
17
|
+
|
18
|
+
## Table of Contents
|
19
|
+
|
20
|
+
### Getting Started
|
21
|
+
- [Quick Start](getting-started/quick-start.md)
|
22
|
+
- [Basic Usage Examples](getting-started/examples.md)
|
23
|
+
|
24
|
+
### Core Concepts
|
25
|
+
- [Architecture Overview](core-concepts/architecture.md)
|
26
|
+
- [Property System](core-concepts/properties.md)
|
27
|
+
- [Entity Addressing](core-concepts/addressing.md)
|
28
|
+
- [Message Filtering](core-concepts/message-filtering.md)
|
29
|
+
- [Message Processing](core-concepts/message-processing.md)
|
30
|
+
- [Dispatcher & Routing](core-concepts/dispatcher.md)
|
31
|
+
|
32
|
+
### Transports
|
33
|
+
- [Transport Layer](reference/transports.md)
|
34
|
+
- [Redis Queue Transport](transports/redis-queue-transport.md) ⭐ **Featured**
|
35
|
+
- [Redis Transport Comparison](transports/redis-transport-comparison.md)
|
36
|
+
|
37
|
+
### Guides
|
38
|
+
- [Redis Queue Getting Started](guides/redis-queue-getting-started.md)
|
39
|
+
- [Advanced Routing Patterns](guides/redis-queue-patterns.md)
|
40
|
+
- [Production Deployment](guides/redis-queue-production.md)
|
41
|
+
|
42
|
+
### Reference
|
43
|
+
- [Serializers](reference/serializers.md)
|
44
|
+
- [Logging System](reference/logging.md)
|
45
|
+
- [Dead Letter Queue](reference/dead-letter-queue.md)
|
46
|
+
- [Message Deduplication](reference/message-deduplication.md)
|
47
|
+
- [Proc Handlers](reference/proc-handlers.md)
|
48
|
+
|
49
|
+
### Development
|
50
|
+
- [Troubleshooting](development/troubleshooting.md)
|
51
|
+
- [Ideas & Roadmap](development/ideas.md)
|
52
|
+
|
53
|
+
## Quick Navigation
|
54
|
+
|
55
|
+
- **New to SmartMessage?** Start with [Quick Start](getting-started/quick-start.md)
|
56
|
+
- **Need examples?** Check out [Examples](getting-started/examples.md)
|
57
|
+
- **Understanding the architecture?** Read [Architecture Overview](core-concepts/architecture.md)
|
58
|
+
- **Having issues?** Visit [Troubleshooting](development/troubleshooting.md)
|
59
|
+
|
60
|
+
## Version
|
61
|
+
|
62
|
+
This documentation is for SmartMessage v0.0.8.
|
63
|
+
|
64
|
+
For older versions, please check the git tags and corresponding documentation.
|
@@ -93,10 +93,9 @@ The DLQ operates as a First-In-First-Out queue:
|
|
93
93
|
```ruby
|
94
94
|
dlq = SmartMessage::DeadLetterQueue.default
|
95
95
|
|
96
|
-
# Add a failed message
|
96
|
+
# Add a failed message (accepts decoded message instance)
|
97
97
|
entry = dlq.enqueue(
|
98
|
-
|
99
|
-
message_payload, # Serialized message string
|
98
|
+
decoded_message, # SmartMessage::Base instance
|
100
99
|
error: "Connection timeout",
|
101
100
|
retry_count: 0,
|
102
101
|
transport: "Redis",
|
@@ -505,4 +505,4 @@ logger.info("Test message")
|
|
505
505
|
assert_includes log_output.string, "Test message"
|
506
506
|
```
|
507
507
|
|
508
|
-
For more information, see the comprehensive logging
|
508
|
+
For more information, see the comprehensive logging examples at `examples/memory/15_logger_demo.rb` and `examples/memory/08_custom_logger_demo.rb`.
|
@@ -0,0 +1,489 @@
|
|
1
|
+
|
2
|
+
# Message Deduplication
|
3
|
+
|
4
|
+
SmartMessage provides a comprehensive message deduplication system using Deduplication Queues (DDQ) to prevent duplicate processing of messages with the same UUID. The system is designed with handler-scoped isolation, ensuring that different message handlers maintain independent deduplication state.
|
5
|
+
|
6
|
+
## Overview
|
7
|
+
|
8
|
+
Message deduplication in SmartMessage works by:
|
9
|
+
|
10
|
+
1. **Handler-Scoped Tracking**: Each message handler (subscription) gets its own DDQ instance
|
11
|
+
2. **UUID-Based Detection**: Message UUIDs are tracked in circular buffers for O(1) lookup performance
|
12
|
+
3. **Configurable Storage**: Support for both memory-based and Redis-based storage backends
|
13
|
+
4. **Automatic Integration**: Seamlessly integrates with the existing dispatcher and subscription system
|
14
|
+
|
15
|
+
## Architecture
|
16
|
+
|
17
|
+
### Handler-Only Scoping
|
18
|
+
|
19
|
+
The key innovation in SmartMessage's deduplication system is **handler-only scoping**. DDQ keys are automatically derived from the combination of message class and handler method:
|
20
|
+
|
21
|
+
```
|
22
|
+
DDQ Key Format: "MessageClass:HandlerMethod"
|
23
|
+
```
|
24
|
+
|
25
|
+
Examples:
|
26
|
+
- `"OrderMessage:PaymentService.process"`
|
27
|
+
- `"OrderMessage:FulfillmentService.handle"`
|
28
|
+
- `"InvoiceMessage:PaymentService.process"`
|
29
|
+
|
30
|
+
This design provides:
|
31
|
+
- **Natural Isolation**: Each handler has its own deduplication context
|
32
|
+
- **Cross-Process Support**: Same handler across different processes gets isolated DDQs
|
33
|
+
- **No Parameter Pollution**: No need for explicit subscriber identification in the API
|
34
|
+
|
35
|
+
### DDQ Data Structure
|
36
|
+
|
37
|
+
Each DDQ uses a hybrid data structure for optimal performance:
|
38
|
+
|
39
|
+
```ruby
|
40
|
+
# Hybrid Array + Set Design
|
41
|
+
@circular_array = Array.new(size) # Maintains insertion order for eviction
|
42
|
+
@lookup_set = Set.new # Provides O(1) UUID lookup
|
43
|
+
@index = 0 # Current insertion position
|
44
|
+
```
|
45
|
+
|
46
|
+
Benefits:
|
47
|
+
- **O(1) Lookup**: Set provides constant-time duplicate detection
|
48
|
+
- **O(1) Insertion**: Array provides constant-time insertion and eviction
|
49
|
+
- **Memory Bounded**: Circular buffer automatically evicts oldest entries
|
50
|
+
- **Thread Safe**: Mutex protection for concurrent access
|
51
|
+
|
52
|
+
## Configuration
|
53
|
+
|
54
|
+
### Basic Setup
|
55
|
+
|
56
|
+
Enable deduplication for a message class:
|
57
|
+
|
58
|
+
```ruby
|
59
|
+
class OrderMessage < SmartMessage::Base
|
60
|
+
version 1
|
61
|
+
property :order_id, required: true
|
62
|
+
property :amount, required: true
|
63
|
+
|
64
|
+
# Configure deduplication
|
65
|
+
ddq_size 100 # Track last 100 UUIDs (default: 100)
|
66
|
+
ddq_storage :memory # Storage backend (default: :memory)
|
67
|
+
enable_deduplication! # Enable DDQ for this message class
|
68
|
+
|
69
|
+
def self.process(message)
|
70
|
+
puts "Processing order: #{message.order_id}"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
```
|
74
|
+
|
75
|
+
### Storage Backends
|
76
|
+
|
77
|
+
#### Memory Storage
|
78
|
+
|
79
|
+
Best for single-process applications:
|
80
|
+
|
81
|
+
```ruby
|
82
|
+
class LocalMessage < SmartMessage::Base
|
83
|
+
ddq_size 50
|
84
|
+
ddq_storage :memory
|
85
|
+
enable_deduplication!
|
86
|
+
end
|
87
|
+
```
|
88
|
+
|
89
|
+
Memory Usage (approximate):
|
90
|
+
- 10 UUIDs: ~480 bytes
|
91
|
+
- 100 UUIDs: ~4.8 KB
|
92
|
+
- 1000 UUIDs: ~48 KB
|
93
|
+
|
94
|
+
#### Redis Storage
|
95
|
+
|
96
|
+
Best for distributed/multi-process applications:
|
97
|
+
|
98
|
+
```ruby
|
99
|
+
class DistributedMessage < SmartMessage::Base
|
100
|
+
ddq_size 1000
|
101
|
+
ddq_storage :redis,
|
102
|
+
redis_url: 'redis://localhost:6379',
|
103
|
+
redis_db: 1,
|
104
|
+
key_prefix: 'ddq'
|
105
|
+
enable_deduplication!
|
106
|
+
end
|
107
|
+
```
|
108
|
+
|
109
|
+
Redis DDQ features:
|
110
|
+
- **Distributed State**: Shared across multiple processes
|
111
|
+
- **Persistence**: Survives process restarts
|
112
|
+
- **TTL Support**: Automatic expiration of old entries
|
113
|
+
- **Atomic Operations**: Transaction safety for concurrent access
|
114
|
+
|
115
|
+
### Configuration Options
|
116
|
+
|
117
|
+
| Option | Type | Default | Description |
|
118
|
+
|--------|------|---------|-------------|
|
119
|
+
| `ddq_size` | Integer | 100 | Maximum UUIDs to track in circular buffer |
|
120
|
+
| `ddq_storage` | Symbol | `:memory` | Storage backend (`:memory` or `:redis`) |
|
121
|
+
| `redis_url` | String | `'redis://localhost:6379'` | Redis connection URL |
|
122
|
+
| `redis_db` | Integer | 0 | Redis database number |
|
123
|
+
| `key_prefix` | String | `'ddq'` | Prefix for Redis keys |
|
124
|
+
| `ttl` | Integer | 3600 | TTL for Redis entries (seconds) |
|
125
|
+
|
126
|
+
## Usage Examples
|
127
|
+
|
128
|
+
### Multiple Handlers per Message Class
|
129
|
+
|
130
|
+
```ruby
|
131
|
+
class OrderMessage < SmartMessage::Base
|
132
|
+
ddq_size 200
|
133
|
+
ddq_storage :memory
|
134
|
+
enable_deduplication!
|
135
|
+
end
|
136
|
+
|
137
|
+
# Each gets separate DDQ tracking
|
138
|
+
OrderMessage.subscribe('PaymentService.process') # DDQ: "OrderMessage:PaymentService.process"
|
139
|
+
OrderMessage.subscribe('FulfillmentService.handle') # DDQ: "OrderMessage:FulfillmentService.handle"
|
140
|
+
OrderMessage.subscribe('AuditService.log_order') # DDQ: "OrderMessage:AuditService.log_order"
|
141
|
+
|
142
|
+
# Same UUID can be processed by each handler independently
|
143
|
+
order = OrderMessage.new(order_id: "12345", amount: 99.99)
|
144
|
+
order.publish # All three handlers will process this message
|
145
|
+
```
|
146
|
+
|
147
|
+
### Cross-Message-Class Handlers
|
148
|
+
|
149
|
+
```ruby
|
150
|
+
class PaymentService
|
151
|
+
def self.process(message)
|
152
|
+
puts "PaymentService processing: #{message.class.name}"
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
# Same handler, different message classes = separate DDQs
|
157
|
+
OrderMessage.subscribe('PaymentService.process') # DDQ: "OrderMessage:PaymentService.process"
|
158
|
+
InvoiceMessage.subscribe('PaymentService.process') # DDQ: "InvoiceMessage:PaymentService.process"
|
159
|
+
RefundMessage.subscribe('PaymentService.process') # DDQ: "RefundMessage:PaymentService.process"
|
160
|
+
```
|
161
|
+
|
162
|
+
### Distributed Processing
|
163
|
+
|
164
|
+
```ruby
|
165
|
+
# Process A (payment-service-1)
|
166
|
+
class OrderMessage < SmartMessage::Base
|
167
|
+
ddq_storage :redis, redis_url: 'redis://shared-redis:6379'
|
168
|
+
enable_deduplication!
|
169
|
+
end
|
170
|
+
|
171
|
+
OrderMessage.subscribe('PaymentService.process')
|
172
|
+
|
173
|
+
# Process B (payment-service-2)
|
174
|
+
# Same configuration, same handler = shared DDQ in Redis
|
175
|
+
OrderMessage.subscribe('PaymentService.process')
|
176
|
+
|
177
|
+
# Only one process will handle each unique UUID
|
178
|
+
```
|
179
|
+
|
180
|
+
## API Reference
|
181
|
+
|
182
|
+
### Class Methods
|
183
|
+
|
184
|
+
#### `ddq_size(size)`
|
185
|
+
Configure the maximum number of UUIDs to track:
|
186
|
+
```ruby
|
187
|
+
OrderMessage.ddq_size(500) # Track last 500 UUIDs
|
188
|
+
```
|
189
|
+
|
190
|
+
#### `ddq_storage(storage, **options)`
|
191
|
+
Configure the storage backend:
|
192
|
+
```ruby
|
193
|
+
OrderMessage.ddq_storage(:memory)
|
194
|
+
OrderMessage.ddq_storage(:redis, redis_url: 'redis://localhost:6379', redis_db: 2)
|
195
|
+
```
|
196
|
+
|
197
|
+
#### `enable_deduplication!`
|
198
|
+
Enable deduplication for the message class:
|
199
|
+
```ruby
|
200
|
+
OrderMessage.enable_deduplication!
|
201
|
+
```
|
202
|
+
|
203
|
+
#### `disable_deduplication!`
|
204
|
+
Disable deduplication for the message class:
|
205
|
+
```ruby
|
206
|
+
OrderMessage.disable_deduplication!
|
207
|
+
```
|
208
|
+
|
209
|
+
#### `ddq_enabled?`
|
210
|
+
Check if deduplication is enabled:
|
211
|
+
```ruby
|
212
|
+
puts OrderMessage.ddq_enabled? # => true/false
|
213
|
+
```
|
214
|
+
|
215
|
+
#### `ddq_config`
|
216
|
+
Get current DDQ configuration:
|
217
|
+
```ruby
|
218
|
+
config = OrderMessage.ddq_config
|
219
|
+
# => {enabled: true, size: 100, storage: :memory, options: {}}
|
220
|
+
```
|
221
|
+
|
222
|
+
#### `ddq_stats`
|
223
|
+
Get DDQ statistics for all handlers:
|
224
|
+
```ruby
|
225
|
+
stats = OrderMessage.ddq_stats
|
226
|
+
# => {enabled: true, current_count: 45, utilization: 45.0, ...}
|
227
|
+
```
|
228
|
+
|
229
|
+
#### `clear_ddq!`
|
230
|
+
Clear all DDQ instances for the message class:
|
231
|
+
```ruby
|
232
|
+
OrderMessage.clear_ddq!
|
233
|
+
```
|
234
|
+
|
235
|
+
#### `duplicate_uuid?(uuid)`
|
236
|
+
Check if a UUID is tracked as duplicate:
|
237
|
+
```ruby
|
238
|
+
is_dup = OrderMessage.duplicate_uuid?("some-uuid-123") # => true/false
|
239
|
+
```
|
240
|
+
|
241
|
+
### Instance Methods
|
242
|
+
|
243
|
+
#### `duplicate?`
|
244
|
+
Check if this message instance is a duplicate:
|
245
|
+
```ruby
|
246
|
+
message = OrderMessage.new(order_id: "123", amount: 99.99)
|
247
|
+
puts message.duplicate? # => true/false
|
248
|
+
```
|
249
|
+
|
250
|
+
#### `mark_as_processed!`
|
251
|
+
Manually mark this message as processed:
|
252
|
+
```ruby
|
253
|
+
message.mark_as_processed! # Adds UUID to DDQ
|
254
|
+
```
|
255
|
+
|
256
|
+
## Integration with Dispatcher
|
257
|
+
|
258
|
+
The deduplication system integrates seamlessly with SmartMessage's dispatcher:
|
259
|
+
|
260
|
+
### Message Flow with DDQ
|
261
|
+
|
262
|
+
1. **Message Receipt**: Dispatcher receives decoded message
|
263
|
+
2. **Handler Iteration**: For each subscribed handler:
|
264
|
+
- **DDQ Check**: Check handler's DDQ for message UUID
|
265
|
+
- **Skip Duplicates**: If UUID found, log and skip to next handler
|
266
|
+
- **Process New**: If UUID not found, route to handler
|
267
|
+
- **Mark Processed**: After successful processing, add UUID to handler's DDQ
|
268
|
+
|
269
|
+
### Logging
|
270
|
+
|
271
|
+
The dispatcher provides detailed logging for deduplication events:
|
272
|
+
|
273
|
+
```
|
274
|
+
[INFO] [SmartMessage::Dispatcher] Skipping duplicate for PaymentService.process: uuid-123
|
275
|
+
[DEBUG] [SmartMessage::Dispatcher] Marked UUID as processed for FulfillmentService.handle: uuid-456
|
276
|
+
```
|
277
|
+
|
278
|
+
### Statistics Integration
|
279
|
+
|
280
|
+
DDQ statistics are integrated with SmartMessage's built-in statistics system:
|
281
|
+
|
282
|
+
```ruby
|
283
|
+
# Access via dispatcher
|
284
|
+
dispatcher = SmartMessage::Dispatcher.new
|
285
|
+
ddq_stats = dispatcher.ddq_stats
|
286
|
+
|
287
|
+
# Example output:
|
288
|
+
# {
|
289
|
+
# "OrderMessage:PaymentService.process" => {
|
290
|
+
# size: 100, current_count: 23, utilization: 23.0,
|
291
|
+
# storage_type: :memory, implementation: "SmartMessage::DDQ::Memory"
|
292
|
+
# },
|
293
|
+
# "OrderMessage:FulfillmentService.handle" => { ... }
|
294
|
+
# }
|
295
|
+
```
|
296
|
+
|
297
|
+
## Performance Characteristics
|
298
|
+
|
299
|
+
### Memory DDQ Performance
|
300
|
+
|
301
|
+
- **Lookup Time**: O(1) - Set provides constant-time contains check
|
302
|
+
- **Insertion Time**: O(1) - Array provides constant-time insertion
|
303
|
+
- **Memory Usage**: ~48 bytes per UUID (including Set and Array overhead)
|
304
|
+
- **Thread Safety**: Mutex-protected for concurrent access
|
305
|
+
|
306
|
+
### Redis DDQ Performance
|
307
|
+
|
308
|
+
- **Lookup Time**: O(1) - Redis SET provides constant-time membership test
|
309
|
+
- **Insertion Time**: O(1) - Redis LPUSH + LTRIM for circular behavior
|
310
|
+
- **Network Overhead**: 1-2 Redis commands per duplicate check
|
311
|
+
- **Persistence**: Automatic persistence and cross-process sharing
|
312
|
+
|
313
|
+
### Benchmarks
|
314
|
+
|
315
|
+
Memory DDQ (1000 entries):
|
316
|
+
- **Memory Usage**: ~57 KB
|
317
|
+
- **Lookup Performance**: 0.001ms average
|
318
|
+
- **Insertion Performance**: 0.002ms average
|
319
|
+
|
320
|
+
Redis DDQ (1000 entries):
|
321
|
+
- **Memory Usage**: Stored in Redis
|
322
|
+
- **Lookup Performance**: 0.5-2ms average (network dependent)
|
323
|
+
- **Insertion Performance**: 1-3ms average (network dependent)
|
324
|
+
|
325
|
+
## Best Practices
|
326
|
+
|
327
|
+
### 1. Choose Appropriate DDQ Size
|
328
|
+
|
329
|
+
Size DDQ based on your message volume and acceptable duplicate window:
|
330
|
+
|
331
|
+
```ruby
|
332
|
+
# High-volume service: larger DDQ
|
333
|
+
class HighVolumeMessage < SmartMessage::Base
|
334
|
+
ddq_size 10000 # Track last 10k messages
|
335
|
+
ddq_storage :redis
|
336
|
+
enable_deduplication!
|
337
|
+
end
|
338
|
+
|
339
|
+
# Low-volume service: smaller DDQ
|
340
|
+
class LowVolumeMessage < SmartMessage::Base
|
341
|
+
ddq_size 50 # Track last 50 messages
|
342
|
+
ddq_storage :memory
|
343
|
+
enable_deduplication!
|
344
|
+
end
|
345
|
+
```
|
346
|
+
|
347
|
+
### 2. Use Redis for Distributed Systems
|
348
|
+
|
349
|
+
For multi-process deployments, always use Redis storage:
|
350
|
+
|
351
|
+
```ruby
|
352
|
+
class DistributedMessage < SmartMessage::Base
|
353
|
+
ddq_storage :redis,
|
354
|
+
redis_url: ENV.fetch('REDIS_URL', 'redis://localhost:6379'),
|
355
|
+
redis_db: ENV.fetch('DDQ_REDIS_DB', 1).to_i
|
356
|
+
enable_deduplication!
|
357
|
+
end
|
358
|
+
```
|
359
|
+
|
360
|
+
### 3. Monitor DDQ Statistics
|
361
|
+
|
362
|
+
Regularly monitor DDQ utilization:
|
363
|
+
|
364
|
+
```ruby
|
365
|
+
# In monitoring/health check code
|
366
|
+
stats = OrderMessage.ddq_stats
|
367
|
+
if stats[:utilization] > 90
|
368
|
+
logger.warn "DDQ utilization high: #{stats[:utilization]}%"
|
369
|
+
end
|
370
|
+
```
|
371
|
+
|
372
|
+
### 4. Handle DDQ Errors Gracefully
|
373
|
+
|
374
|
+
The system is designed to fail-open (process messages when DDQ fails):
|
375
|
+
|
376
|
+
```ruby
|
377
|
+
# DDQ failures are logged but don't prevent message processing
|
378
|
+
# Monitor logs for DDQ-related errors:
|
379
|
+
# [ERROR] [SmartMessage::DDQ] Failed to check duplicate: Redis connection error
|
380
|
+
```
|
381
|
+
|
382
|
+
## Troubleshooting
|
383
|
+
|
384
|
+
### Common Issues
|
385
|
+
|
386
|
+
#### 1. Messages Not Being Deduplicated
|
387
|
+
|
388
|
+
**Symptoms**: Same UUID processed multiple times by same handler
|
389
|
+
**Causes**:
|
390
|
+
- Deduplication not enabled: `enable_deduplication!` missing
|
391
|
+
- Different handlers: Each handler has separate DDQ
|
392
|
+
- DDQ size too small: Old UUIDs evicted too quickly
|
393
|
+
|
394
|
+
**Solutions**:
|
395
|
+
```ruby
|
396
|
+
# Verify deduplication is enabled
|
397
|
+
puts OrderMessage.ddq_enabled? # Should be true
|
398
|
+
|
399
|
+
# Check DDQ configuration
|
400
|
+
puts OrderMessage.ddq_config
|
401
|
+
|
402
|
+
# Increase DDQ size if needed
|
403
|
+
OrderMessage.ddq_size(1000)
|
404
|
+
```
|
405
|
+
|
406
|
+
#### 2. Redis Connection Errors
|
407
|
+
|
408
|
+
**Symptoms**: DDQ errors in logs, messages still processing
|
409
|
+
**Causes**: Redis connectivity issues
|
410
|
+
|
411
|
+
**Solutions**:
|
412
|
+
```ruby
|
413
|
+
# Verify Redis connection
|
414
|
+
redis_config = OrderMessage.ddq_config[:options]
|
415
|
+
puts "Redis URL: #{redis_config[:redis_url]}"
|
416
|
+
|
417
|
+
# Test Redis connectivity
|
418
|
+
require 'redis'
|
419
|
+
redis = Redis.new(url: redis_config[:redis_url])
|
420
|
+
puts redis.ping # Should return "PONG"
|
421
|
+
```
|
422
|
+
|
423
|
+
#### 3. High Memory Usage
|
424
|
+
|
425
|
+
**Symptoms**: Increasing memory usage in memory DDQ
|
426
|
+
**Causes**: DDQ size too large for available memory
|
427
|
+
|
428
|
+
**Solutions**:
|
429
|
+
```ruby
|
430
|
+
# Check memory usage
|
431
|
+
stats = OrderMessage.ddq_stats
|
432
|
+
puts "Memory usage: #{stats[:current_count] * 48} bytes"
|
433
|
+
|
434
|
+
# Reduce DDQ size
|
435
|
+
OrderMessage.ddq_size(100) # Smaller size
|
436
|
+
|
437
|
+
# Or switch to Redis
|
438
|
+
OrderMessage.ddq_storage(:redis)
|
439
|
+
```
|
440
|
+
|
441
|
+
### Debugging DDQ Issues
|
442
|
+
|
443
|
+
```ruby
|
444
|
+
# Enable debug logging
|
445
|
+
SmartMessage.configure do |config|
|
446
|
+
config.log_level = :debug
|
447
|
+
end
|
448
|
+
|
449
|
+
# Check specific UUID
|
450
|
+
uuid = "test-uuid-123"
|
451
|
+
puts "Is duplicate: #{OrderMessage.duplicate_uuid?(uuid)}"
|
452
|
+
|
453
|
+
# Clear DDQ for testing
|
454
|
+
OrderMessage.clear_ddq!
|
455
|
+
|
456
|
+
# Monitor DDQ stats
|
457
|
+
stats = OrderMessage.ddq_stats
|
458
|
+
puts "Current count: #{stats[:current_count]}"
|
459
|
+
puts "Utilization: #{stats[:utilization]}%"
|
460
|
+
```
|
461
|
+
|
462
|
+
## Migration Guide
|
463
|
+
|
464
|
+
### From Class-Level to Handler-Level DDQ
|
465
|
+
|
466
|
+
If upgrading from a previous version with class-level deduplication:
|
467
|
+
|
468
|
+
**Before (hypothetical)**:
|
469
|
+
```ruby
|
470
|
+
# All handlers shared one DDQ per message class
|
471
|
+
OrderMessage.subscribe('PaymentService.process')
|
472
|
+
OrderMessage.subscribe('FulfillmentService.handle')
|
473
|
+
# Both shared the same DDQ
|
474
|
+
```
|
475
|
+
|
476
|
+
**After (current)**:
|
477
|
+
```ruby
|
478
|
+
# Each handler gets its own DDQ automatically
|
479
|
+
OrderMessage.subscribe('PaymentService.process') # DDQ: "OrderMessage:PaymentService.process"
|
480
|
+
OrderMessage.subscribe('FulfillmentService.handle') # DDQ: "OrderMessage:FulfillmentService.handle"
|
481
|
+
# Separate DDQs with isolated tracking
|
482
|
+
```
|
483
|
+
|
484
|
+
**Benefits of Migration**:
|
485
|
+
- **Better Isolation**: Handler failures don't affect other handlers' deduplication
|
486
|
+
- **Flexible Filtering**: Different handlers can have different subscription filters
|
487
|
+
- **Cross-Process Safety**: Handlers with same name across processes get separate DDQs
|
488
|
+
|
489
|
+
The migration is automatic - no code changes required. The new system provides better isolation and reliability.
|
@@ -92,7 +92,7 @@ MyMessage.unsubscribe(block_id)
|
|
92
92
|
|
93
93
|
- Default handler compatibility
|
94
94
|
- Block handler functionality
|
95
|
-
- Proc parameter handler functionality
|
95
|
+
- Proc parameter handler functionality
|
96
96
|
- Lambda handler functionality
|
97
97
|
- Multiple handlers for same message type
|
98
98
|
- Mixed handler types (method + proc)
|
@@ -114,7 +114,7 @@ MyMessage.unsubscribe(block_id)
|
|
114
114
|
### Test Results
|
115
115
|
|
116
116
|
- **55 total tests** (10 new proc handler tests + existing tests)
|
117
|
-
- **276 assertions**
|
117
|
+
- **276 assertions**
|
118
118
|
- **All tests passing**
|
119
119
|
- **Full backward compatibility** maintained
|
120
120
|
|
@@ -153,17 +153,17 @@ MyMessage.unsubscribe(block_id)
|
|
153
153
|
|
154
154
|
## Examples
|
155
155
|
|
156
|
-
### 1. New Working Example (`examples/
|
156
|
+
### 1. New Working Example (`examples/memory/07_proc_handlers_demo.rb`)
|
157
157
|
|
158
158
|
Complete demonstration of all handler types:
|
159
159
|
- Default handler (self.process)
|
160
|
-
- Block handlers (inline logic)
|
160
|
+
- Block handlers (inline logic)
|
161
161
|
- Proc handlers (reusable logic)
|
162
162
|
- Lambda handlers (functional style)
|
163
163
|
- Method handlers (service classes)
|
164
164
|
- Handler management and unsubscription
|
165
165
|
|
166
|
-
### 2. Enhanced IoT Example (`examples/
|
166
|
+
### 2. Enhanced IoT Example (`examples/redis/01_smart_home_iot_demo.rb`)
|
167
167
|
|
168
168
|
Production-ready Redis transport example showing real-world usage patterns.
|
169
169
|
|
@@ -234,6 +234,7 @@ Production-ready Redis transport example showing real-world usage patterns.
|
|
234
234
|
## Future Enhancements
|
235
235
|
|
236
236
|
Potential areas for future development:
|
237
|
+
|
237
238
|
- Handler priority/ordering
|
238
239
|
- Conditional handler execution
|
239
240
|
- Handler metrics and monitoring
|
@@ -244,4 +245,4 @@ Potential areas for future development:
|
|
244
245
|
|
245
246
|
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
|
|
247
|
-
This enhancement positions SmartMessage as a more versatile and developer-friendly messaging framework suitable for both simple prototypes and complex enterprise applications.
|
248
|
+
This enhancement positions SmartMessage as a more versatile and developer-friendly messaging framework suitable for both simple prototypes and complex enterprise applications.
|
@@ -26,7 +26,7 @@ The default serializer that converts messages to/from JSON format.
|
|
26
26
|
|
27
27
|
```ruby
|
28
28
|
# Basic usage
|
29
|
-
serializer = SmartMessage::Serializer::
|
29
|
+
serializer = SmartMessage::Serializer::Json.new
|
30
30
|
|
31
31
|
# Configure in message class
|
32
32
|
class UserMessage < SmartMessage::Base
|
@@ -35,7 +35,7 @@ class UserMessage < SmartMessage::Base
|
|
35
35
|
property :preferences
|
36
36
|
|
37
37
|
config do
|
38
|
-
serializer SmartMessage::Serializer::
|
38
|
+
serializer SmartMessage::Serializer::Json.new
|
39
39
|
end
|
40
40
|
end
|
41
41
|
|
@@ -569,7 +569,5 @@ end
|
|
569
569
|
|
570
570
|
## Next Steps
|
571
571
|
|
572
|
-
- [Custom Serializers](custom-serializers.md) - Build your own serializer
|
573
572
|
- [Transports](transports.md) - How serializers work with transports
|
574
|
-
- [
|
575
|
-
- [Examples](examples.md) - Real-world serialization patterns
|
573
|
+
- [Examples](../getting-started/examples.md) - Real-world serialization patterns
|