smart_message 0.0.10 → 0.0.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/deploy-github-pages.yml +38 -0
- data/.gitignore +5 -0
- data/CHANGELOG.md +30 -0
- data/Gemfile.lock +35 -4
- data/README.md +169 -71
- data/Rakefile +29 -4
- data/docs/assets/images/ddq_architecture.svg +130 -0
- data/docs/assets/images/dlq_architecture.svg +115 -0
- data/docs/assets/images/enhanced-dual-publishing.svg +136 -0
- data/docs/assets/images/enhanced-fluent-api.svg +149 -0
- data/docs/assets/images/enhanced-microservices-routing.svg +115 -0
- data/docs/assets/images/enhanced-pattern-matching.svg +107 -0
- data/docs/assets/images/fluent-api-demo.svg +59 -0
- data/docs/assets/images/performance-comparison.svg +161 -0
- data/docs/assets/images/redis-basic-architecture.svg +53 -0
- data/docs/assets/images/redis-enhanced-architecture.svg +88 -0
- data/docs/assets/images/redis-queue-architecture.svg +101 -0
- data/docs/assets/images/smart_message.jpg +0 -0
- data/docs/assets/images/smart_message_walking.jpg +0 -0
- data/docs/assets/images/smartmessage_architecture_overview.svg +173 -0
- data/docs/assets/images/transport-comparison-matrix.svg +171 -0
- data/docs/assets/javascripts/mathjax.js +17 -0
- data/docs/assets/stylesheets/extra.css +51 -0
- data/docs/{addressing.md → core-concepts/addressing.md} +5 -7
- data/docs/{architecture.md → core-concepts/architecture.md} +78 -138
- data/docs/{dispatcher.md → core-concepts/dispatcher.md} +21 -21
- data/docs/{message_filtering.md → core-concepts/message-filtering.md} +2 -3
- data/docs/{message_processing.md → core-concepts/message-processing.md} +17 -17
- data/docs/{troubleshooting.md → development/troubleshooting.md} +7 -7
- data/docs/{examples.md → getting-started/examples.md} +115 -89
- data/docs/{getting-started.md → getting-started/quick-start.md} +47 -18
- data/docs/guides/redis-queue-getting-started.md +697 -0
- data/docs/guides/redis-queue-patterns.md +889 -0
- data/docs/guides/redis-queue-production.md +1091 -0
- data/docs/index.md +64 -0
- data/docs/{dead_letter_queue.md → reference/dead-letter-queue.md} +2 -3
- data/docs/{logging.md → reference/logging.md} +1 -1
- data/docs/{message_deduplication.md → reference/message-deduplication.md} +1 -0
- data/docs/{proc_handlers_summary.md → reference/proc-handlers.md} +7 -6
- data/docs/{serializers.md → reference/serializers.md} +3 -5
- data/docs/{transports.md → reference/transports.md} +133 -11
- data/docs/transports/memory-transport.md +374 -0
- data/docs/transports/redis-enhanced-transport.md +524 -0
- data/docs/transports/redis-queue-transport.md +1304 -0
- data/docs/transports/redis-transport-comparison.md +496 -0
- data/docs/transports/redis-transport.md +509 -0
- data/examples/README.md +98 -5
- data/examples/city_scenario/911_emergency_call_flow.svg +99 -0
- data/examples/city_scenario/README.md +515 -0
- data/examples/city_scenario/ai_visitor_intelligence_flow.svg +108 -0
- data/examples/city_scenario/citizen.rb +195 -0
- data/examples/city_scenario/city_diagram.svg +125 -0
- data/examples/city_scenario/common/health_monitor.rb +80 -0
- data/examples/city_scenario/common/logger.rb +30 -0
- data/examples/city_scenario/emergency_dispatch_center.rb +270 -0
- data/examples/city_scenario/fire_department.rb +446 -0
- data/examples/city_scenario/fire_emergency_flow.svg +95 -0
- data/examples/city_scenario/health_department.rb +100 -0
- data/examples/city_scenario/health_monitoring_system.svg +130 -0
- data/examples/city_scenario/house.rb +244 -0
- data/examples/city_scenario/local_bank.rb +217 -0
- data/examples/city_scenario/messages/emergency_911_message.rb +81 -0
- data/examples/city_scenario/messages/emergency_resolved_message.rb +43 -0
- data/examples/city_scenario/messages/fire_dispatch_message.rb +43 -0
- data/examples/city_scenario/messages/fire_emergency_message.rb +45 -0
- data/examples/city_scenario/messages/health_check_message.rb +22 -0
- data/examples/city_scenario/messages/health_status_message.rb +35 -0
- data/examples/city_scenario/messages/police_dispatch_message.rb +46 -0
- data/examples/city_scenario/messages/silent_alarm_message.rb +38 -0
- data/examples/city_scenario/police_department.rb +316 -0
- data/examples/city_scenario/redis_monitor.rb +129 -0
- data/examples/city_scenario/redis_stats.rb +743 -0
- data/examples/city_scenario/room_for_improvement.md +240 -0
- data/examples/city_scenario/security_emergency_flow.svg +95 -0
- data/examples/city_scenario/service_internal_architecture.svg +154 -0
- data/examples/city_scenario/smart_message_ai_agent.rb +364 -0
- data/examples/city_scenario/start_demo.sh +236 -0
- data/examples/city_scenario/stop_demo.sh +106 -0
- data/examples/city_scenario/visitor.rb +631 -0
- data/examples/{10_message_deduplication.rb → memory/01_message_deduplication_demo.rb} +1 -1
- data/examples/{09_dead_letter_queue_demo.rb → memory/02_dead_letter_queue_demo.rb} +13 -40
- data/examples/{01_point_to_point_orders.rb → memory/03_point_to_point_orders.rb} +1 -1
- data/examples/{02_publish_subscribe_events.rb → memory/04_publish_subscribe_events.rb} +2 -2
- data/examples/{03_many_to_many_chat.rb → memory/05_many_to_many_chat.rb} +4 -4
- data/examples/{show_me.rb → memory/06_pretty_print_demo.rb} +1 -1
- data/examples/{05_proc_handlers.rb → memory/07_proc_handlers_demo.rb} +2 -2
- data/examples/{06_custom_logger_example.rb → memory/08_custom_logger_demo.rb} +17 -14
- data/examples/{07_error_handling_scenarios.rb → memory/09_error_handling_demo.rb} +4 -4
- data/examples/{08_entity_addressing_basic.rb → memory/10_entity_addressing_basic.rb} +8 -8
- data/examples/{08_entity_addressing_with_filtering.rb → memory/11_entity_addressing_with_filtering.rb} +6 -6
- data/examples/{09_regex_filtering_microservices.rb → memory/12_regex_filtering_microservices.rb} +2 -2
- data/examples/{10_header_block_configuration.rb → memory/13_header_block_configuration.rb} +6 -6
- data/examples/{11_global_configuration_example.rb → memory/14_global_configuration_demo.rb} +19 -8
- data/examples/{show_logger.rb → memory/15_logger_demo.rb} +1 -1
- data/examples/memory/README.md +163 -0
- data/examples/memory/memory_transport_architecture.svg +90 -0
- data/examples/memory/point_to_point_pattern.svg +94 -0
- data/examples/memory/publish_subscribe_pattern.svg +125 -0
- data/examples/{04_redis_smart_home_iot.rb → redis/01_smart_home_iot_demo.rb} +5 -5
- data/examples/redis/README.md +230 -0
- data/examples/redis/alert_system_flow.svg +127 -0
- data/examples/redis/dashboard_status_flow.svg +107 -0
- data/examples/redis/device_command_flow.svg +113 -0
- data/examples/redis/redis_transport_architecture.svg +115 -0
- data/examples/{smart_home_iot_dataflow.md → redis/smart_home_iot_dataflow.md} +4 -116
- data/examples/redis/smart_home_system_architecture.svg +133 -0
- data/examples/redis_enhanced/README.md +319 -0
- data/examples/redis_enhanced/enhanced_01_basic_patterns.rb +233 -0
- data/examples/redis_enhanced/enhanced_02_fluent_api.rb +331 -0
- data/examples/redis_enhanced/enhanced_03_dual_publishing.rb +281 -0
- data/examples/redis_enhanced/enhanced_04_advanced_routing.rb +419 -0
- data/examples/redis_queue/01_basic_messaging.rb +221 -0
- data/examples/redis_queue/01_comprehensive_examples.rb +508 -0
- data/examples/redis_queue/02_pattern_routing.rb +405 -0
- data/examples/redis_queue/03_fluent_api.rb +422 -0
- data/examples/redis_queue/04_load_balancing.rb +486 -0
- data/examples/redis_queue/05_microservices.rb +735 -0
- data/examples/redis_queue/06_emergency_alerts.rb +777 -0
- data/examples/redis_queue/07_queue_management.rb +587 -0
- data/examples/redis_queue/README.md +366 -0
- data/examples/redis_queue/enhanced_01_basic_patterns.rb +233 -0
- data/examples/redis_queue/enhanced_02_fluent_api.rb +331 -0
- data/examples/redis_queue/enhanced_03_dual_publishing.rb +281 -0
- data/examples/redis_queue/enhanced_04_advanced_routing.rb +419 -0
- data/examples/redis_queue/redis_queue_architecture.svg +148 -0
- data/ideas/README.md +41 -0
- data/ideas/agents.md +1001 -0
- data/ideas/database_transport.md +980 -0
- data/ideas/improvement.md +359 -0
- data/ideas/meshage.md +1788 -0
- data/ideas/message_discovery.md +178 -0
- data/ideas/message_schema.md +1381 -0
- data/lib/smart_message/.idea/.gitignore +8 -0
- data/lib/smart_message/.idea/markdown.xml +6 -0
- data/lib/smart_message/.idea/misc.xml +4 -0
- data/lib/smart_message/.idea/modules.xml +8 -0
- data/lib/smart_message/.idea/smart_message.iml +16 -0
- data/lib/smart_message/.idea/vcs.xml +6 -0
- data/lib/smart_message/addressing.rb +15 -0
- data/lib/smart_message/base.rb +0 -2
- data/lib/smart_message/configuration.rb +1 -1
- data/lib/smart_message/logger.rb +15 -4
- data/lib/smart_message/plugins.rb +5 -2
- data/lib/smart_message/serializer.rb +14 -0
- data/lib/smart_message/transport/redis_enhanced_transport.rb +399 -0
- data/lib/smart_message/transport/redis_queue_transport.rb +555 -0
- data/lib/smart_message/transport/registry.rb +1 -0
- data/lib/smart_message/transport.rb +34 -1
- data/lib/smart_message/version.rb +1 -1
- data/lib/smart_message.rb +5 -52
- data/mkdocs.yml +184 -0
- data/p2p_plan.md +326 -0
- data/p2p_roadmap.md +287 -0
- data/smart_message.gemspec +2 -0
- data/smart_message.svg +51 -0
- metadata +170 -44
- data/docs/README.md +0 -57
- data/examples/dead_letters.jsonl +0 -12
- data/examples/temp.txt +0 -94
- data/examples/tmux_chat/README.md +0 -283
- data/examples/tmux_chat/bot_agent.rb +0 -278
- data/examples/tmux_chat/human_agent.rb +0 -199
- data/examples/tmux_chat/room_monitor.rb +0 -160
- data/examples/tmux_chat/shared_chat_system.rb +0 -328
- data/examples/tmux_chat/start_chat_demo.sh +0 -190
- data/examples/tmux_chat/stop_chat_demo.sh +0 -22
- /data/docs/{properties.md → core-concepts/properties.md} +0 -0
- /data/docs/{ideas_to_think_about.md → development/ideas.md} +0 -0
data/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
|
-
|
4
|
-
|
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.
|
5
16
|
|
6
|
-
SmartMessage
|
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.
|
18
|
+
|
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,7 +32,7 @@ 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
|
22
38
|
- **Message Deduplication**: Handler-scoped deduplication queues (DDQ) with memory or Redis storage for preventing duplicate message processing
|
@@ -35,78 +51,106 @@ gem 'smart_message'
|
|
35
51
|
|
36
52
|
And then execute:
|
37
53
|
|
38
|
-
|
54
|
+
bundle install
|
39
55
|
|
40
56
|
Or install it yourself as:
|
41
57
|
|
42
|
-
|
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
|
+
```
|
43
81
|
|
44
82
|
## Quick Start
|
45
83
|
|
46
84
|
### 1. Define a Message Class
|
47
85
|
|
48
86
|
```ruby
|
87
|
+
require 'smart_message'
|
88
|
+
|
49
89
|
class OrderMessage < SmartMessage::Base
|
50
90
|
# Declare schema version for compatibility tracking
|
51
91
|
version 2
|
52
|
-
|
92
|
+
|
53
93
|
# Add a description for the message class
|
54
94
|
description "Represents customer order data for processing and fulfillment"
|
55
|
-
|
95
|
+
|
56
96
|
# Configure entity addressing (Method 1: Direct methods)
|
57
97
|
from 'order-service'
|
58
98
|
to 'fulfillment-service' # Point-to-point message
|
59
99
|
reply_to 'order-service' # Responses come back here
|
60
|
-
|
100
|
+
|
61
101
|
# Alternative Method 2: Using header block
|
62
102
|
# header do
|
63
103
|
# from 'order-service'
|
64
104
|
# to 'fulfillment-service'
|
65
105
|
# reply_to 'order-service'
|
66
106
|
# end
|
67
|
-
|
107
|
+
|
68
108
|
# Required properties with validation
|
69
|
-
property :order_id,
|
109
|
+
property :order_id,
|
70
110
|
required: true,
|
71
111
|
message: "Order ID is required",
|
72
112
|
validate: ->(v) { v.is_a?(String) && v.length > 0 },
|
73
113
|
validation_message: "Order ID must be a non-empty string",
|
74
114
|
description: "Unique order identifier"
|
75
|
-
|
76
|
-
property :customer_id,
|
115
|
+
|
116
|
+
property :customer_id,
|
77
117
|
required: true,
|
78
118
|
message: "Customer ID is required",
|
79
119
|
description: "Customer's unique ID"
|
80
|
-
|
81
|
-
property :amount,
|
120
|
+
|
121
|
+
property :amount,
|
82
122
|
required: true,
|
83
123
|
message: "Amount is required",
|
84
124
|
validate: ->(v) { v.is_a?(Numeric) && v > 0 },
|
85
125
|
validation_message: "Amount must be a positive number",
|
86
126
|
description: "Total order amount in dollars"
|
87
|
-
|
88
|
-
property :items,
|
127
|
+
|
128
|
+
property :items,
|
89
129
|
default: [],
|
90
130
|
description: "Array of ordered items"
|
91
131
|
|
92
132
|
# Configure transport and serializer at class level
|
93
133
|
config do
|
134
|
+
# Option 1: Simple STDOUT for development
|
94
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
|
+
|
95
143
|
serializer SmartMessage::Serializer::JSON.new
|
96
144
|
end
|
97
145
|
|
98
146
|
# Business logic for processing received messages
|
99
|
-
def self.process(
|
100
|
-
#
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
# Process the order
|
105
|
-
puts "Processing order #{order.order_id} for customer #{order.customer_id}"
|
106
|
-
puts "Amount: $#{order.amount}"
|
107
|
-
|
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
|
+
|
108
152
|
# Your business logic here
|
109
|
-
process_order(
|
153
|
+
process_order(message_instance)
|
110
154
|
end
|
111
155
|
|
112
156
|
private
|
@@ -123,7 +167,7 @@ end
|
|
123
167
|
# Create and publish a message (automatically validated before publishing)
|
124
168
|
order = OrderMessage.new(
|
125
169
|
order_id: "ORD-123",
|
126
|
-
customer_id: "CUST-456",
|
170
|
+
customer_id: "CUST-456",
|
127
171
|
amount: 99.99,
|
128
172
|
items: ["Widget A", "Widget B"]
|
129
173
|
)
|
@@ -152,20 +196,23 @@ OrderMessage.subscribe
|
|
152
196
|
OrderMessage.subscribe("PaymentService.process_order")
|
153
197
|
|
154
198
|
# 3. Block handler (NEW!)
|
155
|
-
OrderMessage.subscribe do |
|
199
|
+
OrderMessage.subscribe do |wrapper|
|
200
|
+
header, payload = wrapper.split
|
156
201
|
order_data = JSON.parse(payload)
|
157
202
|
puts "Quick processing: Order #{order_data['order_id']}"
|
158
203
|
end
|
159
204
|
|
160
205
|
# 4. Proc handler (NEW!)
|
161
|
-
order_processor = proc do |
|
206
|
+
order_processor = proc do |wrapper|
|
207
|
+
header, payload = wrapper.split
|
162
208
|
order_data = JSON.parse(payload)
|
163
209
|
EmailService.send_confirmation(order_data['customer_id'])
|
164
210
|
end
|
165
211
|
OrderMessage.subscribe(order_processor)
|
166
212
|
|
167
213
|
# 5. Lambda handler (NEW!)
|
168
|
-
audit_handler = lambda do |
|
214
|
+
audit_handler = lambda do |wrapper|
|
215
|
+
header, payload = wrapper.split
|
169
216
|
AuditLog.record("Order processed at #{header.published_at}")
|
170
217
|
end
|
171
218
|
OrderMessage.subscribe(audit_handler)
|
@@ -193,7 +240,7 @@ OrderMessage.subscribe(to: /^(dev|staging)-.*/)
|
|
193
240
|
|
194
241
|
# Combined filtering
|
195
242
|
OrderMessage.subscribe(
|
196
|
-
from: /^admin-.*/,
|
243
|
+
from: /^admin-.*/,
|
197
244
|
to: ['order-service', /^fulfillment-.*/]
|
198
245
|
)
|
199
246
|
|
@@ -213,16 +260,16 @@ class OrderMessage < SmartMessage::Base
|
|
213
260
|
version 1
|
214
261
|
property :order_id, required: true
|
215
262
|
property :amount, required: true
|
216
|
-
|
263
|
+
|
217
264
|
from "order-service"
|
218
|
-
|
265
|
+
|
219
266
|
# Configure deduplication
|
220
267
|
ddq_size 100 # Track last 100 message UUIDs
|
221
268
|
ddq_storage :memory # Use memory storage (or :redis for distributed)
|
222
269
|
enable_deduplication! # Enable deduplication for this message class
|
223
|
-
|
224
|
-
def self.process(
|
225
|
-
puts "Processing order: #{
|
270
|
+
|
271
|
+
def self.process(message_instance)
|
272
|
+
puts "Processing order: #{message_instance.order_id}"
|
226
273
|
# Business logic here
|
227
274
|
end
|
228
275
|
end
|
@@ -309,7 +356,7 @@ class PaymentMessage < SmartMessage::Base
|
|
309
356
|
from 'payment-service' # Required: sender identity
|
310
357
|
to 'bank-gateway' # Optional: specific recipient
|
311
358
|
reply_to 'payment-service' # Optional: where responses go
|
312
|
-
|
359
|
+
|
313
360
|
property :amount, required: true
|
314
361
|
property :account_id, required: true
|
315
362
|
end
|
@@ -319,14 +366,14 @@ end
|
|
319
366
|
```ruby
|
320
367
|
class PaymentMessage < SmartMessage::Base
|
321
368
|
version 1
|
322
|
-
|
369
|
+
|
323
370
|
# Configure all addressing in a single block
|
324
371
|
header do
|
325
372
|
from 'payment-service'
|
326
373
|
to 'bank-gateway'
|
327
374
|
reply_to 'payment-service'
|
328
375
|
end
|
329
|
-
|
376
|
+
|
330
377
|
property :amount, required: true
|
331
378
|
property :account_id, required: true
|
332
379
|
end
|
@@ -364,13 +411,13 @@ payment.publish
|
|
364
411
|
```ruby
|
365
412
|
class SystemAnnouncementMessage < SmartMessage::Base
|
366
413
|
version 1
|
367
|
-
|
414
|
+
|
368
415
|
# Using header block for broadcast configuration
|
369
416
|
header do
|
370
417
|
from 'admin-service' # Required: sender identity
|
371
418
|
# No 'to' field = broadcast to all subscribers
|
372
419
|
end
|
373
|
-
|
420
|
+
|
374
421
|
property :message, required: true
|
375
422
|
property :priority, default: 'normal'
|
376
423
|
end
|
@@ -379,7 +426,7 @@ end
|
|
379
426
|
#### Messaging Patterns Supported
|
380
427
|
|
381
428
|
- **Point-to-Point**: Set `to` field for direct entity targeting
|
382
|
-
- **Broadcast**: Omit `to` field (nil) for message broadcast to all subscribers
|
429
|
+
- **Broadcast**: Omit `to` field (nil) for message broadcast to all subscribers
|
383
430
|
- **Request-Reply**: Use `reply_to` field to specify response routing
|
384
431
|
- **Gateway Patterns**: Override addressing at instance level for message forwarding
|
385
432
|
|
@@ -433,9 +480,9 @@ SmartMessage.configure do |config|
|
|
433
480
|
end
|
434
481
|
|
435
482
|
logger = SmartMessage.configuration.default_logger
|
436
|
-
logger.info("User action",
|
437
|
-
user_id: 12345,
|
438
|
-
action: "login",
|
483
|
+
logger.info("User action",
|
484
|
+
user_id: 12345,
|
485
|
+
action: "login",
|
439
486
|
ip_address: "192.168.1.1")
|
440
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`"}
|
441
488
|
```
|
@@ -449,7 +496,7 @@ SmartMessage.configure do |config|
|
|
449
496
|
roll_by_size: true,
|
450
497
|
max_file_size: 10 * 1024 * 1024, # 10 MB
|
451
498
|
keep_files: 5, # Keep 5 old files
|
452
|
-
|
499
|
+
|
453
500
|
# Date-based rolling (alternative to size-based)
|
454
501
|
roll_by_date: false, # Set to true for date-based
|
455
502
|
date_pattern: '%Y-%m-%d' # Daily rolling pattern
|
@@ -465,11 +512,11 @@ SmartMessage classes automatically use the configured logger:
|
|
465
512
|
class OrderMessage < SmartMessage::Base
|
466
513
|
property :order_id, required: true
|
467
514
|
property :amount, required: true
|
468
|
-
|
515
|
+
|
469
516
|
def process
|
470
517
|
# Logger is automatically available
|
471
|
-
logger.info("Processing order",
|
472
|
-
order_id: order_id,
|
518
|
+
logger.info("Processing order",
|
519
|
+
order_id: order_id,
|
473
520
|
amount: amount,
|
474
521
|
header: _sm_header.to_h,
|
475
522
|
payload: _sm_payload)
|
@@ -539,6 +586,57 @@ This enables gateway patterns where messages can be received from one transport/
|
|
539
586
|
|
540
587
|
## Transport Implementations
|
541
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
|
+
|
542
640
|
### STDOUT Transport (Development)
|
543
641
|
|
544
642
|
```ruby
|
@@ -571,7 +669,7 @@ transport.process_all # Process all pending messages
|
|
571
669
|
|
572
670
|
```ruby
|
573
671
|
# Basic Redis configuration
|
574
|
-
transport = SmartMessage::Transport.create(:redis,
|
672
|
+
transport = SmartMessage::Transport.create(:redis,
|
575
673
|
url: 'redis://localhost:6379',
|
576
674
|
db: 0
|
577
675
|
)
|
@@ -606,7 +704,7 @@ The Redis transport uses the message class name as the Redis channel name, enabl
|
|
606
704
|
```ruby
|
607
705
|
class WebhookTransport < SmartMessage::Transport::Base
|
608
706
|
def default_options
|
609
|
-
{
|
707
|
+
{
|
610
708
|
webhook_url: "https://api.example.com/webhooks",
|
611
709
|
timeout: 30,
|
612
710
|
retries: 3
|
@@ -621,19 +719,19 @@ class WebhookTransport < SmartMessage::Transport::Base
|
|
621
719
|
def publish(message_header, message_payload)
|
622
720
|
http = Net::HTTP.new(@uri.host, @uri.port)
|
623
721
|
http.use_ssl = @uri.scheme == 'https'
|
624
|
-
|
722
|
+
|
625
723
|
request = Net::HTTP::Post.new(@uri)
|
626
724
|
request['Content-Type'] = 'application/json'
|
627
725
|
request['X-Message-Class'] = message_header.message_class
|
628
726
|
request.body = message_payload
|
629
|
-
|
727
|
+
|
630
728
|
response = http.request(request)
|
631
729
|
raise "Webhook failed: #{response.code}" unless response.code.to_i < 400
|
632
730
|
end
|
633
731
|
|
634
732
|
def subscribe(message_class, process_method)
|
635
733
|
super
|
636
|
-
# For webhooks, subscription would typically be configured
|
734
|
+
# For webhooks, subscription would typically be configured
|
637
735
|
# externally on the webhook provider's side
|
638
736
|
end
|
639
737
|
end
|
@@ -643,7 +741,7 @@ SmartMessage::Transport.register(:webhook, WebhookTransport)
|
|
643
741
|
|
644
742
|
# Use the transport
|
645
743
|
MyMessage.config do
|
646
|
-
transport SmartMessage::Transport.create(:webhook,
|
744
|
+
transport SmartMessage::Transport.create(:webhook,
|
647
745
|
webhook_url: "https://api.myservice.com/messages"
|
648
746
|
)
|
649
747
|
end
|
@@ -674,7 +772,7 @@ Declare your message schema version using the `version` class method:
|
|
674
772
|
```ruby
|
675
773
|
class OrderMessage < SmartMessage::Base
|
676
774
|
version 2 # Schema version 2
|
677
|
-
|
775
|
+
|
678
776
|
property :order_id, required: true
|
679
777
|
property :customer_email # Added in version 2
|
680
778
|
end
|
@@ -687,22 +785,22 @@ Properties support multiple validation types with custom error messages:
|
|
687
785
|
```ruby
|
688
786
|
class UserMessage < SmartMessage::Base
|
689
787
|
version 1
|
690
|
-
|
788
|
+
|
691
789
|
# Required field validation (Hashie built-in)
|
692
|
-
property :user_id,
|
790
|
+
property :user_id,
|
693
791
|
required: true,
|
694
792
|
message: "User ID is required and cannot be blank"
|
695
|
-
|
793
|
+
|
696
794
|
# Custom validation with lambda
|
697
795
|
property :age,
|
698
796
|
validate: ->(v) { v.is_a?(Integer) && v.between?(1, 120) },
|
699
797
|
validation_message: "Age must be an integer between 1 and 120"
|
700
|
-
|
798
|
+
|
701
799
|
# Email validation with regex
|
702
800
|
property :email,
|
703
801
|
validate: /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i,
|
704
802
|
validation_message: "Must be a valid email address"
|
705
|
-
|
803
|
+
|
706
804
|
# Inclusion validation with array
|
707
805
|
property :status,
|
708
806
|
validate: ['active', 'inactive', 'pending'],
|
@@ -780,20 +878,20 @@ Use the `description` DSL method to document what your message class represents:
|
|
780
878
|
```ruby
|
781
879
|
class OrderMessage < SmartMessage::Base
|
782
880
|
description "Represents customer order data for processing and fulfillment"
|
783
|
-
|
881
|
+
|
784
882
|
property :order_id, required: true
|
785
883
|
property :amount, required: true
|
786
884
|
end
|
787
885
|
|
788
|
-
class UserMessage < SmartMessage::Base
|
886
|
+
class UserMessage < SmartMessage::Base
|
789
887
|
description "Handles user management operations including registration and updates"
|
790
|
-
|
888
|
+
|
791
889
|
property :user_id, required: true
|
792
890
|
property :email, required: true
|
793
891
|
end
|
794
892
|
|
795
893
|
# Access descriptions
|
796
|
-
puts OrderMessage.description
|
894
|
+
puts OrderMessage.description
|
797
895
|
# => "Represents customer order data for processing and fulfillment"
|
798
896
|
|
799
897
|
puts UserMessage.description
|
@@ -814,7 +912,7 @@ class MyMessage < SmartMessage::Base
|
|
814
912
|
property :data
|
815
913
|
end
|
816
914
|
|
817
|
-
puts MyMessage.description
|
915
|
+
puts MyMessage.description
|
818
916
|
# => "MyMessage is a SmartMessage"
|
819
917
|
```
|
820
918
|
|
@@ -825,10 +923,10 @@ Combine class descriptions with property descriptions for comprehensive document
|
|
825
923
|
```ruby
|
826
924
|
class FullyDocumented < SmartMessage::Base
|
827
925
|
description "A fully documented message class for demonstration purposes"
|
828
|
-
|
829
|
-
property :id,
|
926
|
+
|
927
|
+
property :id,
|
830
928
|
description: "Unique identifier for the record"
|
831
|
-
property :name,
|
929
|
+
property :name,
|
832
930
|
description: "Display name for the entity"
|
833
931
|
property :status,
|
834
932
|
description: "Current processing status",
|
@@ -1043,4 +1141,4 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/MadBom
|
|
1043
1141
|
|
1044
1142
|
## License
|
1045
1143
|
|
1046
|
-
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
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
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
|