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.
Files changed (176) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/deploy-github-pages.yml +38 -0
  3. data/.gitignore +5 -0
  4. data/CHANGELOG.md +53 -0
  5. data/Gemfile.lock +35 -4
  6. data/README.md +265 -69
  7. data/Rakefile +29 -4
  8. data/docs/assets/images/ddq_architecture.svg +130 -0
  9. data/docs/assets/images/dlq_architecture.svg +115 -0
  10. data/docs/assets/images/enhanced-dual-publishing.svg +136 -0
  11. data/docs/assets/images/enhanced-fluent-api.svg +149 -0
  12. data/docs/assets/images/enhanced-microservices-routing.svg +115 -0
  13. data/docs/assets/images/enhanced-pattern-matching.svg +107 -0
  14. data/docs/assets/images/fluent-api-demo.svg +59 -0
  15. data/docs/assets/images/performance-comparison.svg +161 -0
  16. data/docs/assets/images/redis-basic-architecture.svg +53 -0
  17. data/docs/assets/images/redis-enhanced-architecture.svg +88 -0
  18. data/docs/assets/images/redis-queue-architecture.svg +101 -0
  19. data/docs/assets/images/smart_message.jpg +0 -0
  20. data/docs/assets/images/smart_message_walking.jpg +0 -0
  21. data/docs/assets/images/smartmessage_architecture_overview.svg +173 -0
  22. data/docs/assets/images/transport-comparison-matrix.svg +171 -0
  23. data/docs/assets/javascripts/mathjax.js +17 -0
  24. data/docs/assets/stylesheets/extra.css +51 -0
  25. data/docs/{addressing.md → core-concepts/addressing.md} +5 -7
  26. data/docs/{architecture.md → core-concepts/architecture.md} +129 -119
  27. data/docs/{dispatcher.md → core-concepts/dispatcher.md} +21 -21
  28. data/docs/{message_filtering.md → core-concepts/message-filtering.md} +2 -3
  29. data/docs/{message_processing.md → core-concepts/message-processing.md} +17 -17
  30. data/docs/{troubleshooting.md → development/troubleshooting.md} +7 -7
  31. data/docs/{examples.md → getting-started/examples.md} +115 -89
  32. data/docs/{getting-started.md → getting-started/quick-start.md} +47 -18
  33. data/docs/guides/redis-queue-getting-started.md +697 -0
  34. data/docs/guides/redis-queue-patterns.md +889 -0
  35. data/docs/guides/redis-queue-production.md +1091 -0
  36. data/docs/index.md +64 -0
  37. data/docs/{dead_letter_queue.md → reference/dead-letter-queue.md} +2 -3
  38. data/docs/{logging.md → reference/logging.md} +1 -1
  39. data/docs/reference/message-deduplication.md +489 -0
  40. data/docs/{proc_handlers_summary.md → reference/proc-handlers.md} +7 -6
  41. data/docs/{serializers.md → reference/serializers.md} +3 -5
  42. data/docs/{transports.md → reference/transports.md} +133 -11
  43. data/docs/transports/memory-transport.md +374 -0
  44. data/docs/transports/redis-enhanced-transport.md +524 -0
  45. data/docs/transports/redis-queue-transport.md +1304 -0
  46. data/docs/transports/redis-transport-comparison.md +496 -0
  47. data/docs/transports/redis-transport.md +509 -0
  48. data/examples/README.md +98 -5
  49. data/examples/city_scenario/911_emergency_call_flow.svg +99 -0
  50. data/examples/city_scenario/README.md +515 -0
  51. data/examples/city_scenario/ai_visitor_intelligence_flow.svg +108 -0
  52. data/examples/city_scenario/citizen.rb +195 -0
  53. data/examples/city_scenario/city_diagram.svg +125 -0
  54. data/examples/city_scenario/common/health_monitor.rb +80 -0
  55. data/examples/city_scenario/common/logger.rb +30 -0
  56. data/examples/city_scenario/emergency_dispatch_center.rb +270 -0
  57. data/examples/city_scenario/fire_department.rb +446 -0
  58. data/examples/city_scenario/fire_emergency_flow.svg +95 -0
  59. data/examples/city_scenario/health_department.rb +100 -0
  60. data/examples/city_scenario/health_monitoring_system.svg +130 -0
  61. data/examples/city_scenario/house.rb +244 -0
  62. data/examples/city_scenario/local_bank.rb +217 -0
  63. data/examples/city_scenario/messages/emergency_911_message.rb +81 -0
  64. data/examples/city_scenario/messages/emergency_resolved_message.rb +43 -0
  65. data/examples/city_scenario/messages/fire_dispatch_message.rb +43 -0
  66. data/examples/city_scenario/messages/fire_emergency_message.rb +45 -0
  67. data/examples/city_scenario/messages/health_check_message.rb +22 -0
  68. data/examples/city_scenario/messages/health_status_message.rb +35 -0
  69. data/examples/city_scenario/messages/police_dispatch_message.rb +46 -0
  70. data/examples/city_scenario/messages/silent_alarm_message.rb +38 -0
  71. data/examples/city_scenario/police_department.rb +316 -0
  72. data/examples/city_scenario/redis_monitor.rb +129 -0
  73. data/examples/city_scenario/redis_stats.rb +743 -0
  74. data/examples/city_scenario/room_for_improvement.md +240 -0
  75. data/examples/city_scenario/security_emergency_flow.svg +95 -0
  76. data/examples/city_scenario/service_internal_architecture.svg +154 -0
  77. data/examples/city_scenario/smart_message_ai_agent.rb +364 -0
  78. data/examples/city_scenario/start_demo.sh +236 -0
  79. data/examples/city_scenario/stop_demo.sh +106 -0
  80. data/examples/city_scenario/visitor.rb +631 -0
  81. data/examples/memory/01_message_deduplication_demo.rb +209 -0
  82. data/examples/{09_dead_letter_queue_demo.rb → memory/02_dead_letter_queue_demo.rb} +13 -40
  83. data/examples/{01_point_to_point_orders.rb → memory/03_point_to_point_orders.rb} +1 -1
  84. data/examples/{02_publish_subscribe_events.rb → memory/04_publish_subscribe_events.rb} +2 -2
  85. data/examples/{03_many_to_many_chat.rb → memory/05_many_to_many_chat.rb} +4 -4
  86. data/examples/{show_me.rb → memory/06_pretty_print_demo.rb} +1 -1
  87. data/examples/{05_proc_handlers.rb → memory/07_proc_handlers_demo.rb} +2 -2
  88. data/examples/{06_custom_logger_example.rb → memory/08_custom_logger_demo.rb} +17 -14
  89. data/examples/{07_error_handling_scenarios.rb → memory/09_error_handling_demo.rb} +4 -4
  90. data/examples/{08_entity_addressing_basic.rb → memory/10_entity_addressing_basic.rb} +8 -8
  91. data/examples/{08_entity_addressing_with_filtering.rb → memory/11_entity_addressing_with_filtering.rb} +6 -6
  92. data/examples/{09_regex_filtering_microservices.rb → memory/12_regex_filtering_microservices.rb} +2 -2
  93. data/examples/{10_header_block_configuration.rb → memory/13_header_block_configuration.rb} +6 -6
  94. data/examples/{11_global_configuration_example.rb → memory/14_global_configuration_demo.rb} +19 -8
  95. data/examples/{show_logger.rb → memory/15_logger_demo.rb} +1 -1
  96. data/examples/memory/README.md +163 -0
  97. data/examples/memory/memory_transport_architecture.svg +90 -0
  98. data/examples/memory/point_to_point_pattern.svg +94 -0
  99. data/examples/memory/publish_subscribe_pattern.svg +125 -0
  100. data/examples/{04_redis_smart_home_iot.rb → redis/01_smart_home_iot_demo.rb} +5 -5
  101. data/examples/redis/README.md +230 -0
  102. data/examples/redis/alert_system_flow.svg +127 -0
  103. data/examples/redis/dashboard_status_flow.svg +107 -0
  104. data/examples/redis/device_command_flow.svg +113 -0
  105. data/examples/redis/redis_transport_architecture.svg +115 -0
  106. data/examples/{smart_home_iot_dataflow.md → redis/smart_home_iot_dataflow.md} +4 -116
  107. data/examples/redis/smart_home_system_architecture.svg +133 -0
  108. data/examples/redis_enhanced/README.md +319 -0
  109. data/examples/redis_enhanced/enhanced_01_basic_patterns.rb +233 -0
  110. data/examples/redis_enhanced/enhanced_02_fluent_api.rb +331 -0
  111. data/examples/redis_enhanced/enhanced_03_dual_publishing.rb +281 -0
  112. data/examples/redis_enhanced/enhanced_04_advanced_routing.rb +419 -0
  113. data/examples/redis_queue/01_basic_messaging.rb +221 -0
  114. data/examples/redis_queue/01_comprehensive_examples.rb +508 -0
  115. data/examples/redis_queue/02_pattern_routing.rb +405 -0
  116. data/examples/redis_queue/03_fluent_api.rb +422 -0
  117. data/examples/redis_queue/04_load_balancing.rb +486 -0
  118. data/examples/redis_queue/05_microservices.rb +735 -0
  119. data/examples/redis_queue/06_emergency_alerts.rb +777 -0
  120. data/examples/redis_queue/07_queue_management.rb +587 -0
  121. data/examples/redis_queue/README.md +366 -0
  122. data/examples/redis_queue/enhanced_01_basic_patterns.rb +233 -0
  123. data/examples/redis_queue/enhanced_02_fluent_api.rb +331 -0
  124. data/examples/redis_queue/enhanced_03_dual_publishing.rb +281 -0
  125. data/examples/redis_queue/enhanced_04_advanced_routing.rb +419 -0
  126. data/examples/redis_queue/redis_queue_architecture.svg +148 -0
  127. data/ideas/README.md +41 -0
  128. data/ideas/agents.md +1001 -0
  129. data/ideas/database_transport.md +980 -0
  130. data/ideas/improvement.md +359 -0
  131. data/ideas/meshage.md +1788 -0
  132. data/ideas/message_discovery.md +178 -0
  133. data/ideas/message_schema.md +1381 -0
  134. data/lib/smart_message/.idea/.gitignore +8 -0
  135. data/lib/smart_message/.idea/markdown.xml +6 -0
  136. data/lib/smart_message/.idea/misc.xml +4 -0
  137. data/lib/smart_message/.idea/modules.xml +8 -0
  138. data/lib/smart_message/.idea/smart_message.iml +16 -0
  139. data/lib/smart_message/.idea/vcs.xml +6 -0
  140. data/lib/smart_message/addressing.rb +15 -0
  141. data/lib/smart_message/base.rb +2 -2
  142. data/lib/smart_message/configuration.rb +1 -1
  143. data/lib/smart_message/ddq/base.rb +71 -0
  144. data/lib/smart_message/ddq/memory.rb +109 -0
  145. data/lib/smart_message/ddq/redis.rb +168 -0
  146. data/lib/smart_message/ddq.rb +31 -0
  147. data/lib/smart_message/deduplication.rb +174 -0
  148. data/lib/smart_message/dispatcher.rb +175 -18
  149. data/lib/smart_message/logger.rb +15 -4
  150. data/lib/smart_message/plugins.rb +5 -2
  151. data/lib/smart_message/serializer.rb +14 -0
  152. data/lib/smart_message/subscription.rb +10 -7
  153. data/lib/smart_message/transport/redis_enhanced_transport.rb +399 -0
  154. data/lib/smart_message/transport/redis_queue_transport.rb +555 -0
  155. data/lib/smart_message/transport/registry.rb +1 -0
  156. data/lib/smart_message/transport.rb +34 -1
  157. data/lib/smart_message/version.rb +1 -1
  158. data/lib/smart_message.rb +5 -52
  159. data/mkdocs.yml +184 -0
  160. data/p2p_plan.md +326 -0
  161. data/p2p_roadmap.md +287 -0
  162. data/smart_message.gemspec +2 -0
  163. data/smart_message.svg +51 -0
  164. metadata +175 -42
  165. data/docs/README.md +0 -57
  166. data/examples/dead_letters.jsonl +0 -12
  167. data/examples/temp.txt +0 -94
  168. data/examples/tmux_chat/README.md +0 -283
  169. data/examples/tmux_chat/bot_agent.rb +0 -278
  170. data/examples/tmux_chat/human_agent.rb +0 -199
  171. data/examples/tmux_chat/room_monitor.rb +0 -160
  172. data/examples/tmux_chat/shared_chat_system.rb +0 -328
  173. data/examples/tmux_chat/start_chat_demo.sh +0 -190
  174. data/examples/tmux_chat/stop_chat_demo.sh +0 -22
  175. /data/docs/{properties.md → core-concepts/properties.md} +0 -0
  176. /data/docs/{ideas_to_think_about.md → development/ideas.md} +0 -0
data/README.md CHANGED
@@ -1,13 +1,29 @@
1
+ <table border="0">
2
+ <tr>
3
+ <td width="30%" valign="top">
4
+
5
+ <img src="docs/assets/images/smart_message.jpg" alt="SmartMessage Logo" />
6
+ <br/>
7
+ See <a href="https://madbomber.github.io/smart_message/">Documentation Websit</a>
8
+ </td>
9
+ <td width="70%" valign="top">
10
+
1
11
  # SmartMessage
2
12
 
3
- [![Gem Version](https://badge.fury.io/rb/smart_message.svg)](https://badge.fury.io/rb/smart_message)
4
- [![Ruby](https://img.shields.io/badge/ruby-%3E%3D%203.0.0-ruby.svg)](https://www.ruby-lang.org/en/)
13
+ Can Walk, Talk, and Think at the Same Time
14
+
15
+ **SmartMessage** 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.
16
+
17
+ 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.
5
18
 
6
- SmartMessage is a message abstraction framework that decouples business logic from message transports and serialization formats. Much like ActiveRecord abstracts models from database implementations, SmartMessage abstracts messages from their backend transport and serialization mechanisms.
19
+ </td>
20
+ </tr>
21
+ </table>
7
22
 
8
23
  ## Features
9
24
 
10
25
  - **Transport Abstraction**: Plugin architecture supporting multiple message transports (Redis, RabbitMQ, Kafka, etc.)
26
+ - **🌟 Redis Queue Transport**: Advanced transport with RabbitMQ-style routing patterns, persistent FIFO queues, load balancing, and 10x faster performance than traditional message brokers. Built on Ruby's Async framework for fiber-based concurrency supporting thousands of concurrent subscriptions - [see full documentation](docs/transports/redis-queue.md)
11
27
  - **Serialization Flexibility**: Pluggable serialization formats (JSON, MessagePack, etc.)
12
28
  - **Entity-to-Entity Addressing**: Built-in FROM/TO/REPLY_TO addressing for point-to-point and broadcast messaging patterns
13
29
  - **Advanced Message Filtering**: Filter subscriptions using exact strings, regular expressions, or mixed arrays for precise message routing
@@ -16,9 +32,10 @@ SmartMessage is a message abstraction framework that decouples business logic fr
16
32
  - **Message Documentation**: Built-in documentation support for message classes and properties with automatic defaults
17
33
  - **Flexible Message Handlers**: Multiple subscription patterns - default methods, custom methods, blocks, procs, and lambdas
18
34
  - **Dual-Level Configuration**: Class and instance-level plugin overrides for gateway patterns
19
- - **Concurrent Processing**: Thread-safe message routing using `Concurrent::CachedThreadPool`
35
+ - **Concurrent Processing**: Thread-safe message routing using `Concurrent::CachedThreadPool` with Async/Fiber-based Redis Queue Transport for massive scalability
20
36
  - **Advanced Logging System**: Comprehensive logging with colorized console output, JSON structured logging, and file rolling
21
37
  - **Built-in Statistics**: Message processing metrics and monitoring
38
+ - **Message Deduplication**: Handler-scoped deduplication queues (DDQ) with memory or Redis storage for preventing duplicate message processing
22
39
  - **Development Tools**: STDOUT and in-memory transports for testing
23
40
  - **Production Ready**: Redis transport with automatic reconnection and error handling
24
41
  - **Dead Letter Queue**: File-based DLQ with JSON Lines format for failed message capture and replay
@@ -34,78 +51,106 @@ gem 'smart_message'
34
51
 
35
52
  And then execute:
36
53
 
37
- $ bundle install
54
+ bundle install
38
55
 
39
56
  Or install it yourself as:
40
57
 
41
- $ gem install smart_message
58
+ gem install smart_message
59
+
60
+ ### Redis Transport Setup
61
+
62
+ To use the built-in Redis transport, you'll need to have Redis server installed:
63
+
64
+ **macOS:**
65
+ ```bash
66
+ brew install redis
67
+ brew services start redis # To start Redis as a service
68
+ ```
69
+
70
+ **Ubuntu/Debian:**
71
+ ```bash
72
+ sudo apt-get update
73
+ sudo apt-get install redis-server
74
+ ```
75
+
76
+ **CentOS/RHEL/Fedora:**
77
+ ```bash
78
+ sudo yum install redis
79
+ sudo systemctl start redis
80
+ ```
42
81
 
43
82
  ## Quick Start
44
83
 
45
84
  ### 1. Define a Message Class
46
85
 
47
86
  ```ruby
87
+ require 'smart_message'
88
+
48
89
  class OrderMessage < SmartMessage::Base
49
90
  # Declare schema version for compatibility tracking
50
91
  version 2
51
-
92
+
52
93
  # Add a description for the message class
53
94
  description "Represents customer order data for processing and fulfillment"
54
-
95
+
55
96
  # Configure entity addressing (Method 1: Direct methods)
56
97
  from 'order-service'
57
98
  to 'fulfillment-service' # Point-to-point message
58
99
  reply_to 'order-service' # Responses come back here
59
-
100
+
60
101
  # Alternative Method 2: Using header block
61
102
  # header do
62
103
  # from 'order-service'
63
104
  # to 'fulfillment-service'
64
105
  # reply_to 'order-service'
65
106
  # end
66
-
107
+
67
108
  # Required properties with validation
68
- property :order_id,
109
+ property :order_id,
69
110
  required: true,
70
111
  message: "Order ID is required",
71
112
  validate: ->(v) { v.is_a?(String) && v.length > 0 },
72
113
  validation_message: "Order ID must be a non-empty string",
73
114
  description: "Unique order identifier"
74
-
75
- property :customer_id,
115
+
116
+ property :customer_id,
76
117
  required: true,
77
118
  message: "Customer ID is required",
78
119
  description: "Customer's unique ID"
79
-
80
- property :amount,
120
+
121
+ property :amount,
81
122
  required: true,
82
123
  message: "Amount is required",
83
124
  validate: ->(v) { v.is_a?(Numeric) && v > 0 },
84
125
  validation_message: "Amount must be a positive number",
85
126
  description: "Total order amount in dollars"
86
-
87
- property :items,
127
+
128
+ property :items,
88
129
  default: [],
89
130
  description: "Array of ordered items"
90
131
 
91
132
  # Configure transport and serializer at class level
92
133
  config do
134
+ # Option 1: Simple STDOUT for development
93
135
  transport SmartMessage::Transport.create(:stdout, loopback: true)
136
+
137
+ # Option 2: Redis Queue for production (10x faster than RabbitMQ!)
138
+ # transport SmartMessage::Transport.create(:redis_queue,
139
+ # url: 'redis://localhost:6379',
140
+ # queue_prefix: 'myapp'
141
+ # )
142
+
94
143
  serializer SmartMessage::Serializer::JSON.new
95
144
  end
96
145
 
97
146
  # Business logic for processing received messages
98
- def self.process(message_header, message_payload)
99
- # Decode the message
100
- order_data = JSON.parse(message_payload)
101
- order = new(order_data)
102
-
103
- # Process the order
104
- puts "Processing order #{order.order_id} for customer #{order.customer_id}"
105
- puts "Amount: $#{order.amount}"
106
-
147
+ def self.process(message_instance)
148
+ # Message instance is already decoded and validated
149
+ puts "Processing order #{message_instance.order_id} for customer #{message_instance.customer_id}"
150
+ puts "Amount: $#{message_instance.amount}"
151
+
107
152
  # Your business logic here
108
- process_order(order)
153
+ process_order(message_instance)
109
154
  end
110
155
 
111
156
  private
@@ -122,7 +167,7 @@ end
122
167
  # Create and publish a message (automatically validated before publishing)
123
168
  order = OrderMessage.new(
124
169
  order_id: "ORD-123",
125
- customer_id: "CUST-456",
170
+ customer_id: "CUST-456",
126
171
  amount: 99.99,
127
172
  items: ["Widget A", "Widget B"]
128
173
  )
@@ -151,20 +196,23 @@ OrderMessage.subscribe
151
196
  OrderMessage.subscribe("PaymentService.process_order")
152
197
 
153
198
  # 3. Block handler (NEW!)
154
- OrderMessage.subscribe do |header, payload|
199
+ OrderMessage.subscribe do |wrapper|
200
+ header, payload = wrapper.split
155
201
  order_data = JSON.parse(payload)
156
202
  puts "Quick processing: Order #{order_data['order_id']}"
157
203
  end
158
204
 
159
205
  # 4. Proc handler (NEW!)
160
- order_processor = proc do |header, payload|
206
+ order_processor = proc do |wrapper|
207
+ header, payload = wrapper.split
161
208
  order_data = JSON.parse(payload)
162
209
  EmailService.send_confirmation(order_data['customer_id'])
163
210
  end
164
211
  OrderMessage.subscribe(order_processor)
165
212
 
166
213
  # 5. Lambda handler (NEW!)
167
- audit_handler = lambda do |header, payload|
214
+ audit_handler = lambda do |wrapper|
215
+ header, payload = wrapper.split
168
216
  AuditLog.record("Order processed at #{header.published_at}")
169
217
  end
170
218
  OrderMessage.subscribe(audit_handler)
@@ -192,7 +240,7 @@ OrderMessage.subscribe(to: /^(dev|staging)-.*/)
192
240
 
193
241
  # Combined filtering
194
242
  OrderMessage.subscribe(
195
- from: /^admin-.*/,
243
+ from: /^admin-.*/,
196
244
  to: ['order-service', /^fulfillment-.*/]
197
245
  )
198
246
 
@@ -201,7 +249,103 @@ DevService.subscribe(to: /^(dev|staging)-.*/)
201
249
  ProdService.subscribe(to: /^prod-.*/)
202
250
  ```
203
251
 
204
- ### 5. Entity Addressing
252
+ ### 5. Message Deduplication
253
+
254
+ 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.
255
+
256
+ #### Basic Deduplication Setup
257
+
258
+ ```ruby
259
+ class OrderMessage < SmartMessage::Base
260
+ version 1
261
+ property :order_id, required: true
262
+ property :amount, required: true
263
+
264
+ from "order-service"
265
+
266
+ # Configure deduplication
267
+ ddq_size 100 # Track last 100 message UUIDs
268
+ ddq_storage :memory # Use memory storage (or :redis for distributed)
269
+ enable_deduplication! # Enable deduplication for this message class
270
+
271
+ def self.process(message_instance)
272
+ puts "Processing order: #{message_instance.order_id}"
273
+ # Business logic here
274
+ end
275
+ end
276
+ ```
277
+
278
+ #### Handler-Scoped Isolation
279
+
280
+ Each handler gets its own DDQ scope, preventing cross-contamination between different subscribers:
281
+
282
+ ```ruby
283
+ # Each handler gets separate deduplication tracking
284
+ OrderMessage.subscribe('PaymentService.process') # DDQ: "OrderMessage:PaymentService.process"
285
+ OrderMessage.subscribe('FulfillmentService.handle') # DDQ: "OrderMessage:FulfillmentService.handle"
286
+ OrderMessage.subscribe('AuditService.log_order') # DDQ: "OrderMessage:AuditService.log_order"
287
+
288
+ # Same handler across message classes = separate DDQs
289
+ PaymentMessage.subscribe('PaymentService.process') # DDQ: "PaymentMessage:PaymentService.process"
290
+ InvoiceMessage.subscribe('PaymentService.process') # DDQ: "InvoiceMessage:PaymentService.process"
291
+ ```
292
+
293
+ #### Storage Options
294
+
295
+ ```ruby
296
+ # Memory-based DDQ (single process)
297
+ class LocalMessage < SmartMessage::Base
298
+ ddq_size 50
299
+ ddq_storage :memory
300
+ enable_deduplication!
301
+ end
302
+
303
+ # Redis-based DDQ (distributed/multi-process)
304
+ class DistributedMessage < SmartMessage::Base
305
+ ddq_size 1000
306
+ ddq_storage :redis, redis_url: 'redis://localhost:6379', redis_db: 1
307
+ enable_deduplication!
308
+ end
309
+ ```
310
+
311
+ #### DDQ Statistics and Management
312
+
313
+ ```ruby
314
+ # Check deduplication configuration
315
+ config = OrderMessage.ddq_config
316
+ puts "Enabled: #{config[:enabled]}"
317
+ puts "Size: #{config[:size]}"
318
+ puts "Storage: #{config[:storage]}"
319
+
320
+ # Get DDQ statistics
321
+ stats = OrderMessage.ddq_stats
322
+ puts "Current count: #{stats[:current_count]}"
323
+ puts "Utilization: #{stats[:utilization]}%"
324
+
325
+ # Clear DDQ if needed
326
+ OrderMessage.clear_ddq!
327
+
328
+ # Check if specific UUID is duplicate
329
+ OrderMessage.duplicate_uuid?("some-uuid-123")
330
+ ```
331
+
332
+ #### How Deduplication Works
333
+
334
+ 1. **Message Receipt**: When a message arrives, the dispatcher checks the handler's DDQ for the message UUID
335
+ 2. **Duplicate Detection**: If UUID exists in DDQ, the message is ignored (logged but not processed)
336
+ 3. **Processing**: If UUID is new, the message is processed by the handler
337
+ 4. **UUID Storage**: After successful processing, the UUID is added to the handler's DDQ
338
+ 5. **Circular Buffer**: When DDQ reaches capacity, oldest UUIDs are evicted to make room for new ones
339
+
340
+ #### Benefits
341
+
342
+ - **Handler Isolation**: Each handler maintains independent deduplication state
343
+ - **Cross-Process Support**: Redis DDQ enables deduplication across multiple processes
344
+ - **Memory Efficient**: Circular buffer with configurable size limits memory usage
345
+ - **High Performance**: O(1) UUID lookup using hybrid array + set data structure
346
+ - **Automatic Integration**: Seamlessly works with existing subscription patterns
347
+
348
+ ### 6. Entity Addressing
205
349
 
206
350
  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
351
 
@@ -212,7 +356,7 @@ class PaymentMessage < SmartMessage::Base
212
356
  from 'payment-service' # Required: sender identity
213
357
  to 'bank-gateway' # Optional: specific recipient
214
358
  reply_to 'payment-service' # Optional: where responses go
215
-
359
+
216
360
  property :amount, required: true
217
361
  property :account_id, required: true
218
362
  end
@@ -222,14 +366,14 @@ end
222
366
  ```ruby
223
367
  class PaymentMessage < SmartMessage::Base
224
368
  version 1
225
-
369
+
226
370
  # Configure all addressing in a single block
227
371
  header do
228
372
  from 'payment-service'
229
373
  to 'bank-gateway'
230
374
  reply_to 'payment-service'
231
375
  end
232
-
376
+
233
377
  property :amount, required: true
234
378
  property :account_id, required: true
235
379
  end
@@ -267,13 +411,13 @@ payment.publish
267
411
  ```ruby
268
412
  class SystemAnnouncementMessage < SmartMessage::Base
269
413
  version 1
270
-
414
+
271
415
  # Using header block for broadcast configuration
272
416
  header do
273
417
  from 'admin-service' # Required: sender identity
274
418
  # No 'to' field = broadcast to all subscribers
275
419
  end
276
-
420
+
277
421
  property :message, required: true
278
422
  property :priority, default: 'normal'
279
423
  end
@@ -282,7 +426,7 @@ end
282
426
  #### Messaging Patterns Supported
283
427
 
284
428
  - **Point-to-Point**: Set `to` field for direct entity targeting
285
- - **Broadcast**: Omit `to` field (nil) for message broadcast to all subscribers
429
+ - **Broadcast**: Omit `to` field (nil) for message broadcast to all subscribers
286
430
  - **Request-Reply**: Use `reply_to` field to specify response routing
287
431
  - **Gateway Patterns**: Override addressing at instance level for message forwarding
288
432
 
@@ -336,9 +480,9 @@ SmartMessage.configure do |config|
336
480
  end
337
481
 
338
482
  logger = SmartMessage.configuration.default_logger
339
- logger.info("User action",
340
- user_id: 12345,
341
- action: "login",
483
+ logger.info("User action",
484
+ user_id: 12345,
485
+ action: "login",
342
486
  ip_address: "192.168.1.1")
343
487
  # 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`"}
344
488
  ```
@@ -352,7 +496,7 @@ SmartMessage.configure do |config|
352
496
  roll_by_size: true,
353
497
  max_file_size: 10 * 1024 * 1024, # 10 MB
354
498
  keep_files: 5, # Keep 5 old files
355
-
499
+
356
500
  # Date-based rolling (alternative to size-based)
357
501
  roll_by_date: false, # Set to true for date-based
358
502
  date_pattern: '%Y-%m-%d' # Daily rolling pattern
@@ -368,11 +512,11 @@ SmartMessage classes automatically use the configured logger:
368
512
  class OrderMessage < SmartMessage::Base
369
513
  property :order_id, required: true
370
514
  property :amount, required: true
371
-
515
+
372
516
  def process
373
517
  # Logger is automatically available
374
- logger.info("Processing order",
375
- order_id: order_id,
518
+ logger.info("Processing order",
519
+ order_id: order_id,
376
520
  amount: amount,
377
521
  header: _sm_header.to_h,
378
522
  payload: _sm_payload)
@@ -412,9 +556,10 @@ Pluggable message encoding/decoding:
412
556
  #### Dispatcher
413
557
  Concurrent message routing engine that:
414
558
  - Uses thread pools for async processing
415
- - Routes messages to subscribed handlers
416
- - Provides processing statistics
559
+ - Routes messages to subscribed handlers with handler-scoped deduplication
560
+ - Provides processing statistics and DDQ management
417
561
  - Handles graceful shutdown
562
+ - Maintains separate DDQ instances per handler for isolated deduplication tracking
418
563
 
419
564
  ### Plugin Architecture
420
565
 
@@ -441,6 +586,57 @@ This enables gateway patterns where messages can be received from one transport/
441
586
 
442
587
  ## Transport Implementations
443
588
 
589
+ ### Redis Queue Transport (Featured) 🌟
590
+
591
+ The Redis Queue Transport provides enterprise-grade message routing with exceptional performance:
592
+
593
+ ```ruby
594
+ # Configure with RabbitMQ-style routing
595
+ transport = SmartMessage::Transport.create(:redis_queue,
596
+ url: 'redis://localhost:6379',
597
+ queue_prefix: 'myapp',
598
+ consumer_group: 'workers'
599
+ )
600
+
601
+ # Pattern-based subscriptions (RabbitMQ compatible)
602
+ transport.subscribe_pattern("#.*.payment_service") # All messages TO payment_service
603
+ transport.subscribe_pattern("#.api_gateway.*") # All messages FROM api_gateway
604
+ transport.subscribe_pattern("order.#.*.*") # All order messages
605
+
606
+ # Fluent API for complex routing
607
+ transport.where
608
+ .from('web_app')
609
+ .to('analytics')
610
+ .consumer_group('analytics_workers')
611
+ .subscribe
612
+
613
+ # Configure message class
614
+ class OrderMessage < SmartMessage::Base
615
+ transport :redis_queue
616
+
617
+ property :order_id, required: true
618
+ property :amount, required: true
619
+ end
620
+
621
+ # Publish with enhanced routing
622
+ OrderMessage.new(
623
+ order_id: 'ORD-001',
624
+ amount: 99.99,
625
+ _sm_header: { from: 'api_gateway', to: 'payment_service' }
626
+ ).publish
627
+ ```
628
+
629
+ **Key Features:**
630
+ - 10x faster than RabbitMQ (0.5ms vs 5ms latency)
631
+ - Pattern routing with `#` and `*` wildcards
632
+ - Persistent FIFO queues using Redis Lists
633
+ - Load balancing via consumer groups
634
+ - Enhanced routing keys: `namespace.type.from.to`
635
+ - Queue monitoring and management
636
+ - Production-ready with circuit breakers and dead letter queues
637
+
638
+ 📚 **Full Documentation:** [Redis Queue Transport Guide](docs/transports/redis-queue.md) | [Getting Started](docs/guides/redis-queue-getting-started.md) | [Examples](examples/redis_queue/)
639
+
444
640
  ### STDOUT Transport (Development)
445
641
 
446
642
  ```ruby
@@ -473,7 +669,7 @@ transport.process_all # Process all pending messages
473
669
 
474
670
  ```ruby
475
671
  # Basic Redis configuration
476
- transport = SmartMessage::Transport.create(:redis,
672
+ transport = SmartMessage::Transport.create(:redis,
477
673
  url: 'redis://localhost:6379',
478
674
  db: 0
479
675
  )
@@ -508,7 +704,7 @@ The Redis transport uses the message class name as the Redis channel name, enabl
508
704
  ```ruby
509
705
  class WebhookTransport < SmartMessage::Transport::Base
510
706
  def default_options
511
- {
707
+ {
512
708
  webhook_url: "https://api.example.com/webhooks",
513
709
  timeout: 30,
514
710
  retries: 3
@@ -523,19 +719,19 @@ class WebhookTransport < SmartMessage::Transport::Base
523
719
  def publish(message_header, message_payload)
524
720
  http = Net::HTTP.new(@uri.host, @uri.port)
525
721
  http.use_ssl = @uri.scheme == 'https'
526
-
722
+
527
723
  request = Net::HTTP::Post.new(@uri)
528
724
  request['Content-Type'] = 'application/json'
529
725
  request['X-Message-Class'] = message_header.message_class
530
726
  request.body = message_payload
531
-
727
+
532
728
  response = http.request(request)
533
729
  raise "Webhook failed: #{response.code}" unless response.code.to_i < 400
534
730
  end
535
731
 
536
732
  def subscribe(message_class, process_method)
537
733
  super
538
- # For webhooks, subscription would typically be configured
734
+ # For webhooks, subscription would typically be configured
539
735
  # externally on the webhook provider's side
540
736
  end
541
737
  end
@@ -545,7 +741,7 @@ SmartMessage::Transport.register(:webhook, WebhookTransport)
545
741
 
546
742
  # Use the transport
547
743
  MyMessage.config do
548
- transport SmartMessage::Transport.create(:webhook,
744
+ transport SmartMessage::Transport.create(:webhook,
549
745
  webhook_url: "https://api.myservice.com/messages"
550
746
  )
551
747
  end
@@ -576,7 +772,7 @@ Declare your message schema version using the `version` class method:
576
772
  ```ruby
577
773
  class OrderMessage < SmartMessage::Base
578
774
  version 2 # Schema version 2
579
-
775
+
580
776
  property :order_id, required: true
581
777
  property :customer_email # Added in version 2
582
778
  end
@@ -589,22 +785,22 @@ Properties support multiple validation types with custom error messages:
589
785
  ```ruby
590
786
  class UserMessage < SmartMessage::Base
591
787
  version 1
592
-
788
+
593
789
  # Required field validation (Hashie built-in)
594
- property :user_id,
790
+ property :user_id,
595
791
  required: true,
596
792
  message: "User ID is required and cannot be blank"
597
-
793
+
598
794
  # Custom validation with lambda
599
795
  property :age,
600
796
  validate: ->(v) { v.is_a?(Integer) && v.between?(1, 120) },
601
797
  validation_message: "Age must be an integer between 1 and 120"
602
-
798
+
603
799
  # Email validation with regex
604
800
  property :email,
605
801
  validate: /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i,
606
802
  validation_message: "Must be a valid email address"
607
-
803
+
608
804
  # Inclusion validation with array
609
805
  property :status,
610
806
  validate: ['active', 'inactive', 'pending'],
@@ -682,20 +878,20 @@ Use the `description` DSL method to document what your message class represents:
682
878
  ```ruby
683
879
  class OrderMessage < SmartMessage::Base
684
880
  description "Represents customer order data for processing and fulfillment"
685
-
881
+
686
882
  property :order_id, required: true
687
883
  property :amount, required: true
688
884
  end
689
885
 
690
- class UserMessage < SmartMessage::Base
886
+ class UserMessage < SmartMessage::Base
691
887
  description "Handles user management operations including registration and updates"
692
-
888
+
693
889
  property :user_id, required: true
694
890
  property :email, required: true
695
891
  end
696
892
 
697
893
  # Access descriptions
698
- puts OrderMessage.description
894
+ puts OrderMessage.description
699
895
  # => "Represents customer order data for processing and fulfillment"
700
896
 
701
897
  puts UserMessage.description
@@ -716,7 +912,7 @@ class MyMessage < SmartMessage::Base
716
912
  property :data
717
913
  end
718
914
 
719
- puts MyMessage.description
915
+ puts MyMessage.description
720
916
  # => "MyMessage is a SmartMessage"
721
917
  ```
722
918
 
@@ -727,10 +923,10 @@ Combine class descriptions with property descriptions for comprehensive document
727
923
  ```ruby
728
924
  class FullyDocumented < SmartMessage::Base
729
925
  description "A fully documented message class for demonstration purposes"
730
-
731
- property :id,
926
+
927
+ property :id,
732
928
  description: "Unique identifier for the record"
733
- property :name,
929
+ property :name,
734
930
  description: "Display name for the entity"
735
931
  property :status,
736
932
  description: "Current processing status",
@@ -945,4 +1141,4 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/MadBom
945
1141
 
946
1142
  ## License
947
1143
 
948
- The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
1144
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile CHANGED
@@ -1,10 +1,35 @@
1
1
  require "bundler/gem_tasks"
2
2
  require "rake/testtask"
3
3
 
4
- Rake::TestTask.new(:test) do |t|
5
- t.libs << "test"
6
- t.libs << "lib"
7
- t.test_files = FileList["test/**/*_test.rb"]
4
+ # Main test task - runs all tests with proper environment setup
5
+ task :test do
6
+ ENV['SM_LOGGER_TEST'] = 'true'
7
+
8
+ puts "Running SmartMessage test suite..."
9
+ puts "=" * 40
10
+
11
+ # Check for Redis availability for informational purposes
12
+ begin
13
+ require 'redis'
14
+ redis = Redis.new(url: 'redis://localhost:6379')
15
+ redis.ping
16
+ puts "✅ Redis available - all tests including Redis Queue Transport will run"
17
+ redis.quit
18
+ rescue => e
19
+ puts "⚠️ Redis not available: #{e.message}"
20
+ puts " Redis Queue Transport tests will be skipped automatically"
21
+ end
22
+
23
+ puts ""
24
+
25
+ # Run the actual tests
26
+ Rake::TestTask.new(:run_tests) do |t|
27
+ t.libs << "test"
28
+ t.libs << "lib"
29
+ t.test_files = FileList["test/**/*_test.rb"]
30
+ end
31
+
32
+ Rake::Task[:run_tests].invoke
8
33
  end
9
34
 
10
35
  task :default => :test