smart_message 0.0.9 → 0.0.10

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5f02a7e60919734663addbef7ae2b5582bc142993654ef33fe17c90e1a70d138
4
- data.tar.gz: 50e7250d3e217faba23eb2a1d995b6edff0d9e0d7935bf2ea31602aff483a092
3
+ metadata.gz: 8485ad851b6868c57b813977a4b2be3b1e06fe2843510a5b64c79468da3a65cf
4
+ data.tar.gz: 1c2345c52d1dd57202a83a49d3d4e7769f14b2855d0a08745d3ed4c970ef0f0e
5
5
  SHA512:
6
- metadata.gz: 7c157a60a127d4bd7a86742ccee8dff5385f11110d1449bc859742c497cc79d9a0e528dd39645d66a8e2b413602c31c4c79f73c9c560b5aacb37acc9610d3183
7
- data.tar.gz: 31fc0e365c549326e9a9314f435d50715c4c980e7380311e19a7428638198d2ce499f8acd104381a631b2a40cd66d2deb8bb7fa3e16c59a61a443cc980698b56
6
+ metadata.gz: 26cfdd1030e880074e557f7bc315220995b3267ec0a11fd4d0f621933e2101f40f35da425649388b83bf8055246934e6c9f8e60a281246e04e5cd89763c33e07
7
+ data.tar.gz: 25d66186e9ebe3f3fcf595e7093ec08c50db83d84205fcb4567b0c69804e44b80496693c40e36a0ea30b9f315a36d2070e701977bec6a0660f3c3bda327f1d88
data/CHANGELOG.md CHANGED
@@ -7,6 +7,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.0.10] 2025-08-20
11
+ ### Added
12
+ - **Handler-Scoped Message Deduplication**: Advanced DDQ (Deduplication Queue) system with automatic handler isolation
13
+ - Handler-only DDQ scoping using `"MessageClass:HandlerMethod"` key format for natural isolation
14
+ - Automatic subscriber identification eliminates need for explicit `me:` parameter in API
15
+ - Cross-process deduplication support with independent DDQ instances per handler
16
+ - Memory and Redis storage backends with O(1) performance using hybrid Array + Set data structure
17
+ - Seamless integration with dispatcher - no changes required to existing subscription code
18
+ - Per-handler statistics and monitoring capabilities with DDQ utilization tracking
19
+ - Thread-safe circular buffer implementation with automatic eviction of oldest entries
20
+ - Configuration DSL: `ddq_size`, `ddq_storage`, `enable_deduplication!`, `disable_deduplication!`
21
+ - Comprehensive documentation and examples demonstrating distributed message processing scenarios
22
+
23
+ ### Fixed
24
+ - **Message Filtering Logic**: Fixed critical bug in subscription filtering where nil broadcast filters caused incorrect behavior
25
+ - **Root Cause**: `filters.key?(:broadcast)` returned true even when broadcast value was nil
26
+ - **Solution**: Changed condition to check for non-nil values: `filters[:to] || filters[:broadcast]`
27
+ - **Impact**: Subscription filtering now works correctly with nil filter values and maintains backward compatibility
28
+ - **Circuit Breaker Test Compatibility**: Added error handling for non-existent message classes in DDQ initialization
29
+ - Added rescue blocks around `constantize` calls to gracefully handle fake test classes
30
+ - DDQ initialization now skips gracefully for test classes that cannot be constantized
31
+ - Maintains proper DDQ functionality for real message classes while supporting test scenarios
32
+
10
33
  ## [0.0.9] 2025-08-20
11
34
  ### Added
12
35
  - **Advanced Logging Configuration System**: Comprehensive logger configuration with multiple output formats and options
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- smart_message (0.0.9)
4
+ smart_message (0.0.10)
5
5
  activesupport
6
6
  breaker_machines
7
7
  colorize
data/README.md CHANGED
@@ -19,6 +19,7 @@ SmartMessage is a message abstraction framework that decouples business logic fr
19
19
  - **Concurrent Processing**: Thread-safe message routing using `Concurrent::CachedThreadPool`
20
20
  - **Advanced Logging System**: Comprehensive logging with colorized console output, JSON structured logging, and file rolling
21
21
  - **Built-in Statistics**: Message processing metrics and monitoring
22
+ - **Message Deduplication**: Handler-scoped deduplication queues (DDQ) with memory or Redis storage for preventing duplicate message processing
22
23
  - **Development Tools**: STDOUT and in-memory transports for testing
23
24
  - **Production Ready**: Redis transport with automatic reconnection and error handling
24
25
  - **Dead Letter Queue**: File-based DLQ with JSON Lines format for failed message capture and replay
@@ -201,7 +202,103 @@ DevService.subscribe(to: /^(dev|staging)-.*/)
201
202
  ProdService.subscribe(to: /^prod-.*/)
202
203
  ```
203
204
 
204
- ### 5. Entity Addressing
205
+ ### 5. Message Deduplication
206
+
207
+ SmartMessage provides handler-scoped message deduplication to prevent duplicate processing of messages with the same UUID. Each handler gets its own Deduplication Queue (DDQ) that tracks recently processed message UUIDs.
208
+
209
+ #### Basic Deduplication Setup
210
+
211
+ ```ruby
212
+ class OrderMessage < SmartMessage::Base
213
+ version 1
214
+ property :order_id, required: true
215
+ property :amount, required: true
216
+
217
+ from "order-service"
218
+
219
+ # Configure deduplication
220
+ ddq_size 100 # Track last 100 message UUIDs
221
+ ddq_storage :memory # Use memory storage (or :redis for distributed)
222
+ enable_deduplication! # Enable deduplication for this message class
223
+
224
+ def self.process(message)
225
+ puts "Processing order: #{message.order_id}"
226
+ # Business logic here
227
+ end
228
+ end
229
+ ```
230
+
231
+ #### Handler-Scoped Isolation
232
+
233
+ Each handler gets its own DDQ scope, preventing cross-contamination between different subscribers:
234
+
235
+ ```ruby
236
+ # Each handler gets separate deduplication tracking
237
+ OrderMessage.subscribe('PaymentService.process') # DDQ: "OrderMessage:PaymentService.process"
238
+ OrderMessage.subscribe('FulfillmentService.handle') # DDQ: "OrderMessage:FulfillmentService.handle"
239
+ OrderMessage.subscribe('AuditService.log_order') # DDQ: "OrderMessage:AuditService.log_order"
240
+
241
+ # Same handler across message classes = separate DDQs
242
+ PaymentMessage.subscribe('PaymentService.process') # DDQ: "PaymentMessage:PaymentService.process"
243
+ InvoiceMessage.subscribe('PaymentService.process') # DDQ: "InvoiceMessage:PaymentService.process"
244
+ ```
245
+
246
+ #### Storage Options
247
+
248
+ ```ruby
249
+ # Memory-based DDQ (single process)
250
+ class LocalMessage < SmartMessage::Base
251
+ ddq_size 50
252
+ ddq_storage :memory
253
+ enable_deduplication!
254
+ end
255
+
256
+ # Redis-based DDQ (distributed/multi-process)
257
+ class DistributedMessage < SmartMessage::Base
258
+ ddq_size 1000
259
+ ddq_storage :redis, redis_url: 'redis://localhost:6379', redis_db: 1
260
+ enable_deduplication!
261
+ end
262
+ ```
263
+
264
+ #### DDQ Statistics and Management
265
+
266
+ ```ruby
267
+ # Check deduplication configuration
268
+ config = OrderMessage.ddq_config
269
+ puts "Enabled: #{config[:enabled]}"
270
+ puts "Size: #{config[:size]}"
271
+ puts "Storage: #{config[:storage]}"
272
+
273
+ # Get DDQ statistics
274
+ stats = OrderMessage.ddq_stats
275
+ puts "Current count: #{stats[:current_count]}"
276
+ puts "Utilization: #{stats[:utilization]}%"
277
+
278
+ # Clear DDQ if needed
279
+ OrderMessage.clear_ddq!
280
+
281
+ # Check if specific UUID is duplicate
282
+ OrderMessage.duplicate_uuid?("some-uuid-123")
283
+ ```
284
+
285
+ #### How Deduplication Works
286
+
287
+ 1. **Message Receipt**: When a message arrives, the dispatcher checks the handler's DDQ for the message UUID
288
+ 2. **Duplicate Detection**: If UUID exists in DDQ, the message is ignored (logged but not processed)
289
+ 3. **Processing**: If UUID is new, the message is processed by the handler
290
+ 4. **UUID Storage**: After successful processing, the UUID is added to the handler's DDQ
291
+ 5. **Circular Buffer**: When DDQ reaches capacity, oldest UUIDs are evicted to make room for new ones
292
+
293
+ #### Benefits
294
+
295
+ - **Handler Isolation**: Each handler maintains independent deduplication state
296
+ - **Cross-Process Support**: Redis DDQ enables deduplication across multiple processes
297
+ - **Memory Efficient**: Circular buffer with configurable size limits memory usage
298
+ - **High Performance**: O(1) UUID lookup using hybrid array + set data structure
299
+ - **Automatic Integration**: Seamlessly works with existing subscription patterns
300
+
301
+ ### 6. Entity Addressing
205
302
 
206
303
  SmartMessage supports entity-to-entity addressing with FROM/TO/REPLY_TO fields for advanced message routing. You can configure addressing using three different approaches:
207
304
 
@@ -412,9 +509,10 @@ Pluggable message encoding/decoding:
412
509
  #### Dispatcher
413
510
  Concurrent message routing engine that:
414
511
  - Uses thread pools for async processing
415
- - Routes messages to subscribed handlers
416
- - Provides processing statistics
512
+ - Routes messages to subscribed handlers with handler-scoped deduplication
513
+ - Provides processing statistics and DDQ management
417
514
  - Handles graceful shutdown
515
+ - Maintains separate DDQ instances per handler for isolated deduplication tracking
418
516
 
419
517
  ### Plugin Architecture
420
518
 
data/docs/architecture.md CHANGED
@@ -16,37 +16,53 @@ 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
- └─────────────────────┘
19
+ ```mermaid
20
+ graph TB
21
+ subgraph "SmartMessage Core"
22
+ Base[SmartMessage::Base]
23
+ Header[Message Header<br/>• UUID<br/>• Timestamps<br/>• Addressing]
24
+ Props[Message Properties<br/>• Business Data<br/>• Validation<br/>• Versioning]
25
+ end
26
+
27
+ subgraph "Plugin System"
28
+ Transport[Transport Plugin<br/>• publish()<br/>• subscribe()<br/>• Memory/Redis/STDOUT]
29
+ Serializer[Serializer Plugin<br/>• encode()<br/>• decode()<br/>• JSON/Custom]
30
+ Logger[Logger Plugin<br/>• Structured logging<br/>• Multiple outputs<br/>• Colorization]
31
+ end
32
+
33
+ subgraph "Message Processing"
34
+ Dispatcher[Dispatcher<br/>• Route messages<br/>• Thread pool<br/>• Subscriptions<br/>• DDQ management]
35
+ DDQ[Deduplication Queue<br/>• Handler-scoped<br/>• Memory/Redis storage<br/>• O(1) performance<br/>• Circular buffer]
36
+ Handlers[Message Handlers<br/>• Default handler<br/>• Block handlers<br/>• Proc handlers<br/>• Method handlers]
37
+ end
38
+
39
+ subgraph "Reliability Layer"
40
+ CircuitBreaker[Circuit Breaker<br/>• Failure thresholds<br/>• Automatic fallback<br/>• Recovery detection]
41
+ DLQ[Dead Letter Queue<br/>• Failed messages<br/>• Replay mechanism<br/>• JSON Lines format]
42
+ end
43
+
44
+ subgraph "Monitoring"
45
+ Stats[Statistics<br/>• Message counts<br/>• Processing metrics<br/>• Thread pool status]
46
+ Filters[Message Filtering<br/>• Entity-aware routing<br/>• Regex patterns<br/>• Broadcast handling]
47
+ end
48
+
49
+ Base --> Header
50
+ Base --> Props
51
+ Base --> Transport
52
+ Base --> Serializer
53
+ Base --> Logger
54
+
55
+ Transport --> Dispatcher
56
+ Dispatcher --> DDQ
57
+ Dispatcher --> Handlers
58
+ Dispatcher --> Stats
59
+ Dispatcher --> Filters
60
+
61
+ Transport --> CircuitBreaker
62
+ CircuitBreaker --> DLQ
63
+
64
+ DDQ -.-> Stats
65
+ Handlers -.-> Stats
50
66
  ```
51
67
 
52
68
  ## Core Components
@@ -126,38 +142,84 @@ end
126
142
 
127
143
  ### 4. Dispatcher
128
144
 
129
- Routes incoming messages to appropriate handlers using concurrent processing.
145
+ Routes incoming messages to appropriate handlers using concurrent processing with integrated deduplication.
130
146
 
131
147
  **Key Responsibilities:**
132
148
  - Message routing based on class
133
- - Thread pool management
149
+ - Thread pool management
134
150
  - Subscription catalog management
135
- - Statistics collection
151
+ - Handler-scoped DDQ management
152
+ - Message filtering and statistics collection
136
153
 
137
- **Location:** `lib/smart_message/dispatcher.rb:11-147`
154
+ **Location:** `lib/smart_message/dispatcher.rb`
138
155
 
139
156
  ```ruby
140
157
  dispatcher = SmartMessage::Dispatcher.new
141
158
  dispatcher.add("MyMessage", "MyMessage.process")
142
159
  dispatcher.route(header, payload)
160
+
161
+ # DDQ integration is automatic when enabled
162
+ MyMessage.enable_deduplication!
163
+ ```
164
+
165
+ ### 5. Deduplication Queue (DDQ)
166
+
167
+ Handler-scoped message deduplication system preventing duplicate processing.
168
+
169
+ **Key Responsibilities:**
170
+ - UUID-based duplicate detection
171
+ - Handler isolation (each handler gets own DDQ)
172
+ - Memory and Redis storage backends
173
+ - O(1) performance with hybrid Array + Set data structure
174
+
175
+ **Architecture:**
176
+ ```mermaid
177
+ graph LR
178
+ subgraph "Handler A DDQ"
179
+ A1[Circular Array]
180
+ A2[Lookup Set]
181
+ A3[Mutex Lock]
182
+ end
183
+
184
+ subgraph "Handler B DDQ"
185
+ B1[Circular Array]
186
+ B2[Lookup Set]
187
+ B3[Mutex Lock]
188
+ end
189
+
190
+ Message[Incoming Message<br/>UUID: abc-123] --> Dispatcher
191
+ Dispatcher --> |Check Handler A| A2
192
+ Dispatcher --> |Check Handler B| B2
193
+
194
+ A2 --> |Not Found| ProcessA[Process with Handler A]
195
+ B2 --> |Found| SkipB[Skip Handler B - Duplicate]
196
+
197
+ ProcessA --> |Add UUID| A1
198
+ ProcessA --> |Add UUID| A2
143
199
  ```
144
200
 
145
- ### 5. Message Headers
201
+ **Location:** `lib/smart_message/deduplication.rb`, `lib/smart_message/ddq/`
202
+
203
+ ### 6. Message Headers
146
204
 
147
- Standard metadata attached to every message.
205
+ Standard metadata attached to every message with entity addressing support.
148
206
 
149
207
  **Key Responsibilities:**
150
208
  - Message identification (UUID)
151
- - Routing information (message class)
209
+ - Routing information (message class, version)
152
210
  - Tracking data (timestamps, process IDs)
211
+ - Entity addressing (from, to, reply_to)
153
212
 
154
- **Location:** `lib/smart_message/header.rb:9-20`
213
+ **Location:** `lib/smart_message/header.rb`
155
214
 
156
215
  ```ruby
157
216
  header = message._sm_header
158
217
  puts header.uuid # "550e8400-e29b-41d4-a716-446655440000"
159
218
  puts header.message_class # "MyMessage"
160
219
  puts header.published_at # 2025-08-17 10:30:00 UTC
220
+ puts header.from # "payment-service"
221
+ puts header.to # "order-service"
222
+ puts header.version # 1
161
223
  ```
162
224
 
163
225
  ## Message Lifecycle
@@ -177,17 +239,27 @@ end
177
239
 
178
240
  ### 2. Subscription Phase
179
241
  ```ruby
242
+ # Basic subscription
180
243
  OrderMessage.subscribe
181
- # Registers "OrderMessage.process" with dispatcher
244
+
245
+ # Subscription with filtering
246
+ OrderMessage.subscribe(from: /^payment-.*/, to: 'order-service')
247
+ OrderMessage.subscribe('PaymentService.process', broadcast: true)
248
+
249
+ # Each subscription gets its own DDQ automatically
250
+ # DDQ Key: "OrderMessage:OrderMessage.process"
251
+ # DDQ Key: "OrderMessage:PaymentService.process"
182
252
  ```
183
253
 
184
254
  ### 3. Publishing Phase
185
255
  ```ruby
186
256
  order = OrderMessage.new(order_id: "123", amount: 99.99)
257
+ order.from("order-service").to("payment-service")
187
258
  order.publish
188
- # 1. Creates header with UUID, timestamp, etc.
189
- # 2. Encodes message via serializer
259
+ # 1. Creates header with UUID, timestamp, addressing
260
+ # 2. Encodes message via serializer
190
261
  # 3. Sends via transport
262
+ # 4. Circuit breaker monitors for failures
191
263
  ```
192
264
 
193
265
  ### 4. Receiving Phase
@@ -195,9 +267,10 @@ order.publish
195
267
  # Transport receives message
196
268
  transport.receive(header, payload)
197
269
  # 1. Routes to dispatcher
198
- # 2. Dispatcher finds subscribers
199
- # 3. Spawns thread for processing
200
- # 4. Calls registered message handlers
270
+ # 2. Dispatcher checks DDQ for duplicates per handler
271
+ # 3. Applies message filters (from/to/broadcast)
272
+ # 4. Spawns thread for processing matching handlers
273
+ # 5. Marks UUID as processed in handler's DDQ
201
274
  ```
202
275
 
203
276
  ### 5. Message Handler Processing
@@ -382,29 +455,19 @@ dlq.enqueue(header, payload, error: "Validation failed")
382
455
 
383
456
  **DLQ Architecture:**
384
457
 
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
- └─────────────────────────────────────────────────────────────┘
458
+ ```mermaid
459
+ graph TB
460
+ Publish[Message Publishing]
461
+ CB[Circuit Breaker<br/>Monitoring]
462
+ Transport[Transport<br/>Success]
463
+ DLQ[Dead Letter Queue<br/>Failure Storage]
464
+ Replay[Replay Mechanism<br/>Manual/Automated]
465
+
466
+ Publish --> CB
467
+ CB --> |Success| Transport
468
+ CB --> |Failure| DLQ
469
+ DLQ --> Replay
470
+ Replay --> |Retry| Publish
408
471
  ```
409
472
 
410
473
  **DLQ Features:**
@@ -417,7 +480,7 @@ dlq.enqueue(header, payload, error: "Validation failed")
417
480
 
418
481
  ### Built-in Statistics
419
482
 
420
- SmartMessage automatically collects processing statistics:
483
+ SmartMessage automatically collects processing statistics including DDQ metrics:
421
484
 
422
485
  ```ruby
423
486
  # Statistics are collected for:
@@ -427,6 +490,11 @@ SS.add(message_class, process_method, 'routed')
427
490
  # Access statistics
428
491
  puts SS.stat
429
492
  puts SS.get("MyMessage", "publish")
493
+
494
+ # DDQ-specific statistics
495
+ stats = OrderMessage.ddq_stats
496
+ puts "DDQ utilization: #{stats[:utilization]}%"
497
+ puts "Current count: #{stats[:current_count]}"
430
498
  ```
431
499
 
432
500
  ### Monitoring Points
@@ -435,6 +503,8 @@ puts SS.get("MyMessage", "publish")
435
503
  2. **Message Routing**: Count of routed messages per processor
436
504
  3. **Thread Pool**: Queue length, completed tasks, running status
437
505
  4. **Transport Status**: Connection status, message counts
506
+ 5. **DDQ Metrics**: Utilization, duplicate detection rates, memory usage
507
+ 6. **Message Filtering**: Filter match rates, entity-aware routing statistics
438
508
 
439
509
  ## Configuration Architecture
440
510