smart_message 0.0.8 → 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 +4 -4
- data/.gitignore +1 -0
- data/.irbrc +24 -0
- data/CHANGELOG.md +119 -0
- data/Gemfile.lock +6 -1
- data/README.md +389 -17
- data/docs/README.md +3 -1
- data/docs/addressing.md +119 -13
- data/docs/architecture.md +184 -46
- data/docs/dead_letter_queue.md +673 -0
- data/docs/dispatcher.md +87 -0
- data/docs/examples.md +59 -1
- data/docs/getting-started.md +8 -1
- data/docs/logging.md +382 -326
- data/docs/message_deduplication.md +488 -0
- data/docs/message_filtering.md +451 -0
- data/examples/01_point_to_point_orders.rb +54 -53
- data/examples/02_publish_subscribe_events.rb +14 -10
- data/examples/03_many_to_many_chat.rb +16 -8
- data/examples/04_redis_smart_home_iot.rb +20 -10
- data/examples/05_proc_handlers.rb +12 -11
- data/examples/06_custom_logger_example.rb +95 -100
- data/examples/07_error_handling_scenarios.rb +4 -2
- data/examples/08_entity_addressing_basic.rb +18 -6
- data/examples/08_entity_addressing_with_filtering.rb +27 -9
- data/examples/09_dead_letter_queue_demo.rb +559 -0
- data/examples/09_regex_filtering_microservices.rb +407 -0
- data/examples/10_header_block_configuration.rb +263 -0
- data/examples/10_message_deduplication.rb +209 -0
- data/examples/11_global_configuration_example.rb +219 -0
- data/examples/README.md +102 -0
- data/examples/dead_letters.jsonl +12 -0
- data/examples/performance_metrics/benchmark_results_ractor_20250818_205603.json +135 -0
- data/examples/performance_metrics/benchmark_results_ractor_20250818_205831.json +135 -0
- data/examples/performance_metrics/benchmark_results_test_20250818_204942.json +130 -0
- data/examples/performance_metrics/benchmark_results_threadpool_20250818_204942.json +130 -0
- data/examples/performance_metrics/benchmark_results_threadpool_20250818_204959.json +130 -0
- data/examples/performance_metrics/benchmark_results_threadpool_20250818_205044.json +130 -0
- data/examples/performance_metrics/benchmark_results_threadpool_20250818_205109.json +130 -0
- data/examples/performance_metrics/benchmark_results_threadpool_20250818_205252.json +130 -0
- data/examples/performance_metrics/benchmark_results_unknown_20250819_172852.json +130 -0
- data/examples/performance_metrics/compare_benchmarks.rb +519 -0
- data/examples/performance_metrics/dead_letters.jsonl +3100 -0
- data/examples/performance_metrics/performance_benchmark.rb +344 -0
- data/examples/show_logger.rb +367 -0
- data/examples/show_me.rb +145 -0
- data/examples/temp.txt +94 -0
- data/examples/tmux_chat/bot_agent.rb +4 -2
- data/examples/tmux_chat/human_agent.rb +4 -2
- data/examples/tmux_chat/room_monitor.rb +4 -2
- data/examples/tmux_chat/shared_chat_system.rb +6 -3
- data/lib/smart_message/addressing.rb +259 -0
- data/lib/smart_message/base.rb +123 -599
- data/lib/smart_message/circuit_breaker.rb +2 -1
- data/lib/smart_message/configuration.rb +199 -0
- 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/dead_letter_queue.rb +27 -10
- data/lib/smart_message/deduplication.rb +174 -0
- data/lib/smart_message/dispatcher.rb +259 -61
- data/lib/smart_message/header.rb +5 -0
- data/lib/smart_message/logger/base.rb +21 -1
- data/lib/smart_message/logger/default.rb +88 -138
- data/lib/smart_message/logger/lumberjack.rb +324 -0
- data/lib/smart_message/logger/null.rb +81 -0
- data/lib/smart_message/logger.rb +17 -9
- data/lib/smart_message/messaging.rb +100 -0
- data/lib/smart_message/plugins.rb +132 -0
- data/lib/smart_message/serializer/base.rb +25 -8
- data/lib/smart_message/serializer/json.rb +5 -4
- data/lib/smart_message/subscription.rb +196 -0
- data/lib/smart_message/transport/base.rb +72 -41
- data/lib/smart_message/transport/memory_transport.rb +7 -5
- data/lib/smart_message/transport/redis_transport.rb +15 -45
- data/lib/smart_message/transport/stdout_transport.rb +18 -8
- data/lib/smart_message/transport.rb +1 -34
- data/lib/smart_message/utilities.rb +142 -0
- data/lib/smart_message/version.rb +1 -1
- data/lib/smart_message/versioning.rb +85 -0
- data/lib/smart_message/wrapper.rb.bak +132 -0
- data/lib/smart_message.rb +74 -28
- data/smart_message.gemspec +3 -0
- metadata +83 -3
- data/lib/smart_message/serializer.rb +0 -10
- data/lib/smart_message/wrapper.rb +0 -43
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8485ad851b6868c57b813977a4b2be3b1e06fe2843510a5b64c79468da3a65cf
|
4
|
+
data.tar.gz: 1c2345c52d1dd57202a83a49d3d4e7769f14b2855d0a08745d3ed4c970ef0f0e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 26cfdd1030e880074e557f7bc315220995b3267ec0a11fd4d0f621933e2101f40f35da425649388b83bf8055246934e6c9f8e60a281246e04e5cd89763c33e07
|
7
|
+
data.tar.gz: 25d66186e9ebe3f3fcf595e7093ec08c50db83d84205fcb4567b0c69804e44b80496693c40e36a0ea30b9f315a36d2070e701977bec6a0660f3c3bda327f1d88
|
data/.gitignore
CHANGED
data/.irbrc
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# smart_message/.irbrc
|
2
|
+
|
3
|
+
require_relative './lib/smart_message'
|
4
|
+
|
5
|
+
SmartMessage.configure do |config|
|
6
|
+
config.logger = STDOUT
|
7
|
+
config.log_level = :info
|
8
|
+
config.log_format = :text
|
9
|
+
config.log_colorize = true
|
10
|
+
end
|
11
|
+
|
12
|
+
LOG = SmartMessage.configuration.default_logger
|
13
|
+
|
14
|
+
class TheMessage < SmartMessage::Base
|
15
|
+
version 3
|
16
|
+
description "my simple message"
|
17
|
+
|
18
|
+
header do
|
19
|
+
from 'dewayne'
|
20
|
+
to 'madbomber'
|
21
|
+
end
|
22
|
+
|
23
|
+
property :data, required: true
|
24
|
+
end
|
data/CHANGELOG.md
CHANGED
@@ -7,6 +7,125 @@ 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
|
+
|
33
|
+
## [0.0.9] 2025-08-20
|
34
|
+
### Added
|
35
|
+
- **Advanced Logging Configuration System**: Comprehensive logger configuration with multiple output formats and options
|
36
|
+
- New SmartMessage configuration options: `log_level`, `log_format`, `log_colorize`, `log_include_source`, `log_structured_data`, `log_options`
|
37
|
+
- Support for symbol-based log levels: `:info`, `:debug`, `:warn`, `:error`, `:fatal`
|
38
|
+
- Colorized console output with customizable color schemes using ANSI terminal colors
|
39
|
+
- File-based logging without colorization for clean log files
|
40
|
+
- JSON structured logging support with metadata inclusion
|
41
|
+
- Log rolling capabilities: size-based and date-based rolling with configurable retention
|
42
|
+
- Source location tracking for debugging (file, line number, method)
|
43
|
+
- Complete Lumberjack logger integration with SmartMessage configuration system
|
44
|
+
- New comprehensive logger demonstration: `examples/show_logger.rb`
|
45
|
+
- Application-level logger access through `SmartMessage.configuration.default_logger`
|
46
|
+
- **Transport Layer Abstraction**: Complete refactoring from legacy Broker to modern Transport architecture
|
47
|
+
- New `SmartMessage::Transport` module providing pluggable message delivery backends
|
48
|
+
- Transport registry system for dynamic plugin discovery and registration
|
49
|
+
- Base transport class with standardized interface for all implementations
|
50
|
+
- Memory, STDOUT, and Redis transport implementations with production-ready features
|
51
|
+
- File-based transport support for inter-process communication
|
52
|
+
- **Message Wrapper Architecture**: Unified message envelope system for cleaner dataflow
|
53
|
+
- New `SmartMessage::Wrapper::Base` class consolidating header and payload
|
54
|
+
- Simplified method signatures throughout message processing pipeline
|
55
|
+
- `_sm_` prefixed properties to avoid collision with user message definitions
|
56
|
+
- Support for different initialization patterns and automatic header generation
|
57
|
+
- **HeaderDSL for Configuration**: Simplified message class setup with declarative syntax
|
58
|
+
- New `SmartMessage::Addressing::HeaderDSL` providing cleaner class-level configuration
|
59
|
+
- Declarative `from`, `to`, `reply_to` methods for entity addressing
|
60
|
+
- Automatic header field configuration without boilerplate code
|
61
|
+
- **Advanced Message Filtering**: Regular expression support for flexible subscription patterns
|
62
|
+
- Regex pattern matching for `from:` and `to:` filters (e.g., `from: /^payment-.*/`)
|
63
|
+
- Array filters combining exact strings and patterns: `from: ['admin', /^system-.*/]`
|
64
|
+
- Environment-based routing patterns for dev/staging/production separation
|
65
|
+
- Service pattern routing for microservices architecture
|
66
|
+
- New example demonstrating regex filtering in microservices (`09_regex_filtering_microservices.rb`)
|
67
|
+
- **Performance Benchmarking Tools**: Comprehensive performance measurement infrastructure
|
68
|
+
- New benchmark suite comparing thread pool and Ractor implementations
|
69
|
+
- JSON-based benchmark result storage with detailed metrics
|
70
|
+
- Benchmark comparison tool for analyzing performance across runs
|
71
|
+
- Ractor-based message processing implementation for parallel execution
|
72
|
+
- **Improved Documentation Structure**: Complete documentation overhaul
|
73
|
+
- New modular documentation in `docs/` directory covering all major features
|
74
|
+
- Dedicated guides for transports, serializers, dispatcher, and filtering
|
75
|
+
- Architecture overview with component relationships
|
76
|
+
- Troubleshooting guide for common issues
|
77
|
+
- Example README with comprehensive usage patterns
|
78
|
+
|
79
|
+
### Changed
|
80
|
+
- **BREAKING: Transport API Migration**: Complete replacement of Broker with Transport
|
81
|
+
- All `broker` references must be updated to `transport`
|
82
|
+
- `SmartMessage::Broker` removed in favor of `SmartMessage::Transport`
|
83
|
+
- Transport implementations now in `lib/smart_message/transport/` directory
|
84
|
+
- Updated all examples and tests to use new transport terminology
|
85
|
+
- **Simplified Module Structure**: Cleaner separation of concerns
|
86
|
+
- Extracted addressing logic into `SmartMessage::Addressing` module
|
87
|
+
- Separated messaging functionality into `SmartMessage::Messaging` module
|
88
|
+
- Created dedicated `SmartMessage::Plugins` module for plugin management
|
89
|
+
- Moved versioning logic to `SmartMessage::Versioning` module
|
90
|
+
- Utilities consolidated in `SmartMessage::Utilities` module
|
91
|
+
- **Enhanced Error Messages**: More descriptive error handling throughout
|
92
|
+
- Improved header validation error messages with context
|
93
|
+
- Better transport registration error reporting
|
94
|
+
- Clearer serialization failure messages
|
95
|
+
|
96
|
+
### Fixed
|
97
|
+
- **Message Processing Simplification**: Removed unnecessary complexity in message wrapper
|
98
|
+
- Eliminated redundant processing steps in wrapper initialization
|
99
|
+
- Streamlined header and payload handling
|
100
|
+
- Fixed potential race conditions in concurrent message processing
|
101
|
+
- **Header Method Handling**: Improved reliability of header field access
|
102
|
+
- Fixed edge cases in header method delegation
|
103
|
+
- Ensured consistent behavior across all header field operations
|
104
|
+
- Better error messages for invalid header operations
|
105
|
+
|
106
|
+
### Enhanced
|
107
|
+
- **Developer Experience**: Significant improvements to API usability
|
108
|
+
- More intuitive transport registration and discovery
|
109
|
+
- Cleaner message class definition with HeaderDSL
|
110
|
+
- Simplified subscription syntax with powerful filtering
|
111
|
+
- Better separation between framework internals and user code
|
112
|
+
- **Testing Infrastructure**: Expanded test coverage
|
113
|
+
- New tests for transport layer abstraction
|
114
|
+
- Comprehensive filtering tests including regex patterns
|
115
|
+
- Performance benchmark test suite
|
116
|
+
- Proc handler integration tests
|
117
|
+
- **Example Programs**: Enhanced educational value
|
118
|
+
- Added performance benchmarking examples
|
119
|
+
- New regex filtering demonstration for microservices
|
120
|
+
- Dead letter queue demonstration with retry scenarios
|
121
|
+
- Updated all examples to use modern transport architecture
|
122
|
+
|
123
|
+
### Deprecated
|
124
|
+
- **Broker System**: Legacy broker implementation marked for removal
|
125
|
+
- `SmartMessage::Broker` and all subclasses deprecated
|
126
|
+
- Migration path provided through transport layer
|
127
|
+
- Broker terminology replaced with transport throughout codebase
|
128
|
+
|
10
129
|
## [0.0.8] 2025-08-19
|
11
130
|
|
12
131
|
### Added
|
data/Gemfile.lock
CHANGED
@@ -1,12 +1,15 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
smart_message (0.0.
|
4
|
+
smart_message (0.0.10)
|
5
5
|
activesupport
|
6
6
|
breaker_machines
|
7
|
+
colorize
|
7
8
|
concurrent-ruby
|
8
9
|
hashie
|
10
|
+
lumberjack
|
9
11
|
redis
|
12
|
+
zeitwerk
|
10
13
|
|
11
14
|
GEM
|
12
15
|
remote: https://rubygems.org/
|
@@ -33,6 +36,7 @@ GEM
|
|
33
36
|
concurrent-ruby (~> 1.3)
|
34
37
|
state_machines (>= 0.50.0)
|
35
38
|
zeitwerk (~> 2.7)
|
39
|
+
colorize (1.1.0)
|
36
40
|
concurrent-ruby (1.3.5)
|
37
41
|
connection_pool (2.5.3)
|
38
42
|
debug_me (1.1.1)
|
@@ -41,6 +45,7 @@ GEM
|
|
41
45
|
i18n (1.14.7)
|
42
46
|
concurrent-ruby (~> 1.0)
|
43
47
|
logger (1.7.0)
|
48
|
+
lumberjack (1.4.0)
|
44
49
|
minitest (5.25.5)
|
45
50
|
minitest-power_assert (0.3.1)
|
46
51
|
minitest
|
data/README.md
CHANGED
@@ -10,15 +10,20 @@ SmartMessage is a message abstraction framework that decouples business logic fr
|
|
10
10
|
- **Transport Abstraction**: Plugin architecture supporting multiple message transports (Redis, RabbitMQ, Kafka, etc.)
|
11
11
|
- **Serialization Flexibility**: Pluggable serialization formats (JSON, MessagePack, etc.)
|
12
12
|
- **Entity-to-Entity Addressing**: Built-in FROM/TO/REPLY_TO addressing for point-to-point and broadcast messaging patterns
|
13
|
+
- **Advanced Message Filtering**: Filter subscriptions using exact strings, regular expressions, or mixed arrays for precise message routing
|
13
14
|
- **Schema Versioning**: Built-in version management with automatic compatibility validation
|
14
15
|
- **Comprehensive Validation**: Property validation with custom error messages and automatic validation before publishing
|
15
16
|
- **Message Documentation**: Built-in documentation support for message classes and properties with automatic defaults
|
16
17
|
- **Flexible Message Handlers**: Multiple subscription patterns - default methods, custom methods, blocks, procs, and lambdas
|
17
18
|
- **Dual-Level Configuration**: Class and instance-level plugin overrides for gateway patterns
|
18
19
|
- **Concurrent Processing**: Thread-safe message routing using `Concurrent::CachedThreadPool`
|
20
|
+
- **Advanced Logging System**: Comprehensive logging with colorized console output, JSON structured logging, and file rolling
|
19
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
|
20
23
|
- **Development Tools**: STDOUT and in-memory transports for testing
|
21
24
|
- **Production Ready**: Redis transport with automatic reconnection and error handling
|
25
|
+
- **Dead Letter Queue**: File-based DLQ with JSON Lines format for failed message capture and replay
|
26
|
+
- **Circuit Breaker Integration**: Production-grade reliability with BreakerMachines for automatic fallback and recovery
|
22
27
|
|
23
28
|
## Installation
|
24
29
|
|
@@ -48,11 +53,18 @@ class OrderMessage < SmartMessage::Base
|
|
48
53
|
# Add a description for the message class
|
49
54
|
description "Represents customer order data for processing and fulfillment"
|
50
55
|
|
51
|
-
# Configure entity addressing
|
56
|
+
# Configure entity addressing (Method 1: Direct methods)
|
52
57
|
from 'order-service'
|
53
58
|
to 'fulfillment-service' # Point-to-point message
|
54
59
|
reply_to 'order-service' # Responses come back here
|
55
60
|
|
61
|
+
# Alternative Method 2: Using header block
|
62
|
+
# header do
|
63
|
+
# from 'order-service'
|
64
|
+
# to 'fulfillment-service'
|
65
|
+
# reply_to 'order-service'
|
66
|
+
# end
|
67
|
+
|
56
68
|
# Required properties with validation
|
57
69
|
property :order_id,
|
58
70
|
required: true,
|
@@ -159,12 +171,139 @@ end
|
|
159
171
|
OrderMessage.subscribe(audit_handler)
|
160
172
|
```
|
161
173
|
|
162
|
-
### 4.
|
174
|
+
### 4. Message Filtering (NEW!)
|
175
|
+
|
176
|
+
SmartMessage supports powerful message filtering using exact strings, regular expressions, or arrays:
|
177
|
+
|
178
|
+
```ruby
|
179
|
+
# Filter by exact sender
|
180
|
+
OrderMessage.subscribe(from: 'payment-service')
|
181
|
+
|
182
|
+
# Filter by sender pattern (all payment services)
|
183
|
+
OrderMessage.subscribe(from: /^payment-.*/)
|
184
|
+
|
185
|
+
# Filter by multiple senders
|
186
|
+
OrderMessage.subscribe(from: ['admin', 'system', 'monitoring'])
|
187
|
+
|
188
|
+
# Mixed exact and pattern matching
|
189
|
+
OrderMessage.subscribe(from: ['admin', /^system-.*/, 'legacy-service'])
|
190
|
+
|
191
|
+
# Filter by recipient patterns
|
192
|
+
OrderMessage.subscribe(to: /^(dev|staging)-.*/)
|
193
|
+
|
194
|
+
# Combined filtering
|
195
|
+
OrderMessage.subscribe(
|
196
|
+
from: /^admin-.*/,
|
197
|
+
to: ['order-service', /^fulfillment-.*/]
|
198
|
+
)
|
199
|
+
|
200
|
+
# Environment-based routing
|
201
|
+
DevService.subscribe(to: /^(dev|staging)-.*/)
|
202
|
+
ProdService.subscribe(to: /^prod-.*/)
|
203
|
+
```
|
204
|
+
|
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
|
163
302
|
|
164
|
-
SmartMessage supports entity-to-entity addressing with FROM/TO/REPLY_TO fields for advanced message routing:
|
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:
|
165
304
|
|
305
|
+
#### Method 1: Direct Class Methods
|
166
306
|
```ruby
|
167
|
-
# Point-to-point messaging
|
168
307
|
class PaymentMessage < SmartMessage::Base
|
169
308
|
version 1
|
170
309
|
from 'payment-service' # Required: sender identity
|
@@ -174,26 +313,67 @@ class PaymentMessage < SmartMessage::Base
|
|
174
313
|
property :amount, required: true
|
175
314
|
property :account_id, required: true
|
176
315
|
end
|
316
|
+
```
|
177
317
|
|
178
|
-
|
179
|
-
|
318
|
+
#### Method 2: Header Block DSL
|
319
|
+
```ruby
|
320
|
+
class PaymentMessage < SmartMessage::Base
|
180
321
|
version 1
|
181
|
-
from 'admin-service' # Required: sender identity
|
182
|
-
# No 'to' field = broadcast to all subscribers
|
183
322
|
|
184
|
-
|
185
|
-
|
323
|
+
# Configure all addressing in a single block
|
324
|
+
header do
|
325
|
+
from 'payment-service'
|
326
|
+
to 'bank-gateway'
|
327
|
+
reply_to 'payment-service'
|
328
|
+
end
|
329
|
+
|
330
|
+
property :amount, required: true
|
331
|
+
property :account_id, required: true
|
186
332
|
end
|
333
|
+
```
|
187
334
|
|
188
|
-
|
335
|
+
#### Method 3: Instance-Level Configuration
|
336
|
+
```ruby
|
337
|
+
|
338
|
+
# Create payment instance
|
189
339
|
payment = PaymentMessage.new(amount: 100.00, account_id: "ACCT-123")
|
190
|
-
payment.to('backup-gateway') # Override destination for this instance
|
191
|
-
payment.publish
|
192
340
|
|
193
|
-
#
|
194
|
-
|
341
|
+
# Override addressing at instance level
|
342
|
+
payment.to('backup-gateway') # Method chaining supported
|
343
|
+
payment.from('urgent-processor')
|
344
|
+
|
345
|
+
# Alternative setter syntax
|
346
|
+
payment.from = 'urgent-processor'
|
347
|
+
payment.to = 'backup-gateway'
|
348
|
+
|
349
|
+
# Access addressing (shortcut methods)
|
350
|
+
puts payment.from # => 'urgent-processor'
|
351
|
+
puts payment.to # => 'backup-gateway'
|
352
|
+
puts payment.reply_to # => 'payment-service'
|
353
|
+
|
354
|
+
# Access via header (full path)
|
355
|
+
puts payment._sm_header.from # => 'urgent-processor'
|
195
356
|
puts payment._sm_header.to # => 'backup-gateway'
|
196
357
|
puts payment._sm_header.reply_to # => 'payment-service'
|
358
|
+
|
359
|
+
# Publish with updated addressing
|
360
|
+
payment.publish
|
361
|
+
```
|
362
|
+
|
363
|
+
#### Broadcast Messaging Example
|
364
|
+
```ruby
|
365
|
+
class SystemAnnouncementMessage < SmartMessage::Base
|
366
|
+
version 1
|
367
|
+
|
368
|
+
# Using header block for broadcast configuration
|
369
|
+
header do
|
370
|
+
from 'admin-service' # Required: sender identity
|
371
|
+
# No 'to' field = broadcast to all subscribers
|
372
|
+
end
|
373
|
+
|
374
|
+
property :message, required: true
|
375
|
+
property :priority, default: 'normal'
|
376
|
+
end
|
197
377
|
```
|
198
378
|
|
199
379
|
#### Messaging Patterns Supported
|
@@ -203,6 +383,104 @@ puts payment._sm_header.reply_to # => 'payment-service'
|
|
203
383
|
- **Request-Reply**: Use `reply_to` field to specify response routing
|
204
384
|
- **Gateway Patterns**: Override addressing at instance level for message forwarding
|
205
385
|
|
386
|
+
## Logging Configuration
|
387
|
+
|
388
|
+
SmartMessage includes a comprehensive logging system with support for multiple output formats, colorization, and file rolling capabilities.
|
389
|
+
|
390
|
+
### Basic Logging Configuration
|
391
|
+
|
392
|
+
```ruby
|
393
|
+
# Configure SmartMessage logging
|
394
|
+
SmartMessage.configure do |config|
|
395
|
+
config.logger = STDOUT # Output destination (file path, STDOUT, STDERR)
|
396
|
+
config.log_level = :info # Log level (:debug, :info, :warn, :error, :fatal)
|
397
|
+
config.log_format = :text # Output format (:text, :json)
|
398
|
+
config.log_colorize = true # Enable colorized console output
|
399
|
+
config.log_include_source = false # Include source file/line information
|
400
|
+
config.log_structured_data = false # Enable structured data logging
|
401
|
+
end
|
402
|
+
|
403
|
+
# Access the configured logger in your application
|
404
|
+
logger = SmartMessage.configuration.default_logger
|
405
|
+
logger.info("Application started", component: "main", pid: Process.pid)
|
406
|
+
```
|
407
|
+
|
408
|
+
### Advanced Logging Features
|
409
|
+
|
410
|
+
#### Colorized Console Output
|
411
|
+
```ruby
|
412
|
+
SmartMessage.configure do |config|
|
413
|
+
config.logger = STDOUT
|
414
|
+
config.log_colorize = true
|
415
|
+
config.log_format = :text
|
416
|
+
end
|
417
|
+
|
418
|
+
logger = SmartMessage.configuration.default_logger
|
419
|
+
logger.debug("Debug message") # Green background, white text
|
420
|
+
logger.info("Info message") # White text
|
421
|
+
logger.warn("Warning message") # Yellow background, white bold text
|
422
|
+
logger.error("Error message") # Light red background, white bold text
|
423
|
+
logger.fatal("Fatal message") # Light red background, yellow bold text
|
424
|
+
```
|
425
|
+
|
426
|
+
#### JSON Structured Logging
|
427
|
+
```ruby
|
428
|
+
SmartMessage.configure do |config|
|
429
|
+
config.logger = "log/application.log"
|
430
|
+
config.log_format = :json
|
431
|
+
config.log_structured_data = true
|
432
|
+
config.log_include_source = true
|
433
|
+
end
|
434
|
+
|
435
|
+
logger = SmartMessage.configuration.default_logger
|
436
|
+
logger.info("User action",
|
437
|
+
user_id: 12345,
|
438
|
+
action: "login",
|
439
|
+
ip_address: "192.168.1.1")
|
440
|
+
# Output: {"timestamp":"2025-01-15T10:30:45.123Z","level":"INFO","message":"User action","user_id":12345,"action":"login","ip_address":"192.168.1.1","source":"app.rb:42:in `authenticate`"}
|
441
|
+
```
|
442
|
+
|
443
|
+
#### File Rolling Configuration
|
444
|
+
```ruby
|
445
|
+
SmartMessage.configure do |config|
|
446
|
+
config.logger = "log/application.log"
|
447
|
+
config.log_options = {
|
448
|
+
# Size-based rolling
|
449
|
+
roll_by_size: true,
|
450
|
+
max_file_size: 10 * 1024 * 1024, # 10 MB
|
451
|
+
keep_files: 5, # Keep 5 old files
|
452
|
+
|
453
|
+
# Date-based rolling (alternative to size-based)
|
454
|
+
roll_by_date: false, # Set to true for date-based
|
455
|
+
date_pattern: '%Y-%m-%d' # Daily rolling pattern
|
456
|
+
}
|
457
|
+
end
|
458
|
+
```
|
459
|
+
|
460
|
+
### SmartMessage Integration
|
461
|
+
|
462
|
+
SmartMessage classes automatically use the configured logger:
|
463
|
+
|
464
|
+
```ruby
|
465
|
+
class OrderMessage < SmartMessage::Base
|
466
|
+
property :order_id, required: true
|
467
|
+
property :amount, required: true
|
468
|
+
|
469
|
+
def process
|
470
|
+
# Logger is automatically available
|
471
|
+
logger.info("Processing order",
|
472
|
+
order_id: order_id,
|
473
|
+
amount: amount,
|
474
|
+
header: _sm_header.to_h,
|
475
|
+
payload: _sm_payload)
|
476
|
+
end
|
477
|
+
end
|
478
|
+
|
479
|
+
# Messages inherit the global logger configuration
|
480
|
+
message = OrderMessage.new(order_id: "123", amount: 99.99)
|
481
|
+
message.publish # Uses configured logger for any internal logging
|
482
|
+
```
|
483
|
+
|
206
484
|
## Architecture
|
207
485
|
|
208
486
|
### Core Components
|
@@ -231,9 +509,10 @@ Pluggable message encoding/decoding:
|
|
231
509
|
#### Dispatcher
|
232
510
|
Concurrent message routing engine that:
|
233
511
|
- Uses thread pools for async processing
|
234
|
-
- Routes messages to subscribed handlers
|
235
|
-
- Provides processing statistics
|
512
|
+
- Routes messages to subscribed handlers with handler-scoped deduplication
|
513
|
+
- Provides processing statistics and DDQ management
|
236
514
|
- Handles graceful shutdown
|
515
|
+
- Maintains separate DDQ instances per handler for isolated deduplication tracking
|
237
516
|
|
238
517
|
### Plugin Architecture
|
239
518
|
|
@@ -644,6 +923,99 @@ puts message._sm_header.to
|
|
644
923
|
puts message._sm_header.reply_to
|
645
924
|
```
|
646
925
|
|
926
|
+
### Dead Letter Queue
|
927
|
+
|
928
|
+
SmartMessage includes a comprehensive file-based Dead Letter Queue system for handling failed messages:
|
929
|
+
|
930
|
+
```ruby
|
931
|
+
# Configure global DLQ (optional - defaults to 'dead_letters.jsonl')
|
932
|
+
SmartMessage::DeadLetterQueue.configure_default('/var/log/app/dlq.jsonl')
|
933
|
+
|
934
|
+
# Or use environment-based configuration
|
935
|
+
SmartMessage::DeadLetterQueue.configure_default(
|
936
|
+
ENV.fetch('SMART_MESSAGE_DLQ_PATH', 'dead_letters.jsonl')
|
937
|
+
)
|
938
|
+
|
939
|
+
# Access the default DLQ instance
|
940
|
+
dlq = SmartMessage::DeadLetterQueue.default
|
941
|
+
|
942
|
+
# Create a custom DLQ instance for specific needs
|
943
|
+
custom_dlq = SmartMessage::DeadLetterQueue.new('/tmp/critical_failures.jsonl')
|
944
|
+
```
|
945
|
+
|
946
|
+
#### DLQ Operations
|
947
|
+
|
948
|
+
```ruby
|
949
|
+
# Messages are automatically captured when circuit breakers trip
|
950
|
+
# But you can also manually enqueue failed messages:
|
951
|
+
dlq.enqueue(
|
952
|
+
message._sm_header,
|
953
|
+
message_payload,
|
954
|
+
error: "Connection timeout",
|
955
|
+
transport: "Redis",
|
956
|
+
retry_count: 3
|
957
|
+
)
|
958
|
+
|
959
|
+
# Inspect queue status
|
960
|
+
puts "Queue size: #{dlq.size}"
|
961
|
+
puts "Next message: #{dlq.peek}" # Look without removing
|
962
|
+
|
963
|
+
# Get statistics
|
964
|
+
stats = dlq.statistics
|
965
|
+
puts "Total messages: #{stats[:total]}"
|
966
|
+
puts "By error type: #{stats[:by_error]}"
|
967
|
+
puts "By message class: #{stats[:by_class]}"
|
968
|
+
```
|
969
|
+
|
970
|
+
#### Message Replay
|
971
|
+
|
972
|
+
```ruby
|
973
|
+
# Replay messages back through their original transport
|
974
|
+
dlq.replay_one # Replay oldest message
|
975
|
+
dlq.replay_batch(10) # Replay next 10 messages
|
976
|
+
dlq.replay_all # Replay entire queue
|
977
|
+
|
978
|
+
# Replay with a different transport
|
979
|
+
redis_transport = SmartMessage::Transport.create(:redis)
|
980
|
+
dlq.replay_one(redis_transport) # Override original transport
|
981
|
+
```
|
982
|
+
|
983
|
+
#### Administrative Functions
|
984
|
+
|
985
|
+
```ruby
|
986
|
+
# Filter messages for analysis
|
987
|
+
failed_orders = dlq.filter_by_class('OrderMessage')
|
988
|
+
timeout_errors = dlq.filter_by_error_pattern(/timeout/i)
|
989
|
+
|
990
|
+
# Export messages within a time range
|
991
|
+
yesterday = Time.now - 86400
|
992
|
+
today = Time.now
|
993
|
+
recent_failures = dlq.export_range(yesterday, today)
|
994
|
+
|
995
|
+
# Clear the queue when needed
|
996
|
+
dlq.clear # Remove all messages
|
997
|
+
```
|
998
|
+
|
999
|
+
#### Integration with Circuit Breakers
|
1000
|
+
|
1001
|
+
Dead Letter Queue is automatically integrated with circuit breakers:
|
1002
|
+
|
1003
|
+
```ruby
|
1004
|
+
class PaymentMessage < SmartMessage::Base
|
1005
|
+
config do
|
1006
|
+
transport SmartMessage::Transport.create(:redis)
|
1007
|
+
# Messages automatically go to DLQ when circuit breaker trips
|
1008
|
+
end
|
1009
|
+
end
|
1010
|
+
|
1011
|
+
# Monitor circuit breaker status
|
1012
|
+
transport = PaymentMessage.transport
|
1013
|
+
stats = transport.transport_circuit_stats
|
1014
|
+
if stats[:transport_publish][:open]
|
1015
|
+
puts "Circuit open - messages going to DLQ"
|
1016
|
+
end
|
1017
|
+
```
|
1018
|
+
|
647
1019
|
## Development
|
648
1020
|
|
649
1021
|
After checking out the repo, run:
|