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
@@ -0,0 +1,980 @@
|
|
1
|
+
# Database Transport Implementation Ideas
|
2
|
+
|
3
|
+
This document explores implementing database-backed transports for SmartMessage, focusing on persistent messaging, advanced routing capabilities, and high-performance batching operations.
|
4
|
+
|
5
|
+
## Overview
|
6
|
+
|
7
|
+
Database transports provide a **persistent, transactional messaging layer** that complements existing in-memory (Redis, Memory) transports. The recommended architecture uses a **hybrid approach**:
|
8
|
+
|
9
|
+
- **Database Transport**: Publish-only persistent archive (no subscriptions)
|
10
|
+
- **Redis Transport**: Real-time pub/sub processing (existing functionality)
|
11
|
+
- **Multi-Transport Publishing**: Messages published to both simultaneously
|
12
|
+
|
13
|
+
This hybrid model delivers enterprise-grade persistence with Redis-speed real-time processing, while dramatically simplifying the database transport implementation by eliminating all subscription complexity.
|
14
|
+
|
15
|
+
## Core Benefits
|
16
|
+
|
17
|
+
### 1. Reliability & Persistence
|
18
|
+
- **ACID Guarantees**: Transactional message processing with rollback capabilities
|
19
|
+
- **Message Survival**: Messages persist across application restarts and failures
|
20
|
+
- **Built-in Dead Letter Queue**: Failed messages automatically retained in database
|
21
|
+
- **Complete Audit Trail**: Every message stored with timestamps, processing status
|
22
|
+
- **Guaranteed Delivery**: Messages remain until successfully processed
|
23
|
+
- **Disaster Recovery**: Database backups include full message history
|
24
|
+
|
25
|
+
### 2. Advanced Routing Capabilities
|
26
|
+
- **SQL-based Filtering**: Complex routing rules using WHERE clauses
|
27
|
+
- **Priority Queues**: ORDER BY priority, created_at for message ordering
|
28
|
+
- **Delayed Processing**: Schedule messages for future processing with WHERE process_after < NOW()
|
29
|
+
- **Content-based Routing**: Route messages based on payload content using JSONB queries
|
30
|
+
- **Entity Targeting**: Perfect integration with FROM/TO/REPLY_TO addressing system
|
31
|
+
|
32
|
+
### 3. Performance Through Batching
|
33
|
+
- **Bulk Operations**: INSERT/UPDATE operations 100x faster than individual operations
|
34
|
+
- **Transaction Efficiency**: Process multiple messages atomically
|
35
|
+
- **Connection Pool Utilization**: Leverage database connection pooling
|
36
|
+
- **Reduced I/O Overhead**: Batch database round-trips for high-throughput scenarios
|
37
|
+
|
38
|
+
## Database Schema Design
|
39
|
+
|
40
|
+
### Core Messages Table
|
41
|
+
```sql
|
42
|
+
CREATE TABLE smart_messages (
|
43
|
+
id BIGSERIAL PRIMARY KEY,
|
44
|
+
|
45
|
+
-- Header fields (matches SmartMessage::Header exactly)
|
46
|
+
uuid UUID NOT NULL UNIQUE,
|
47
|
+
message_class VARCHAR NOT NULL,
|
48
|
+
published_at TIMESTAMP WITH TIME ZONE NOT NULL,
|
49
|
+
publisher_pid INTEGER NOT NULL,
|
50
|
+
version INTEGER NOT NULL DEFAULT 1,
|
51
|
+
serializer VARCHAR, -- Tracks serializer used for DLQ/gateway patterns
|
52
|
+
|
53
|
+
-- Entity addressing fields (SmartMessage::Header addressing)
|
54
|
+
from_entity VARCHAR NOT NULL, -- Required: sender entity ID
|
55
|
+
to_entity VARCHAR, -- NULL = broadcast to all subscribers
|
56
|
+
reply_to VARCHAR, -- Optional: response routing entity
|
57
|
+
|
58
|
+
-- Message content and processing
|
59
|
+
payload JSONB NOT NULL, -- Serialized message payload
|
60
|
+
payload_format VARCHAR DEFAULT 'json', -- Format tracking for multi-serializer support
|
61
|
+
|
62
|
+
-- Processing state tracking
|
63
|
+
status VARCHAR DEFAULT 'pending' CHECK (status IN ('pending', 'processing', 'completed', 'failed')),
|
64
|
+
processed_at TIMESTAMP WITH TIME ZONE,
|
65
|
+
processing_attempts INTEGER DEFAULT 0,
|
66
|
+
last_error TEXT,
|
67
|
+
last_processed_by VARCHAR, -- Handler/processor identification
|
68
|
+
|
69
|
+
-- Advanced features for enterprise usage
|
70
|
+
priority INTEGER DEFAULT 0, -- Higher numbers = higher priority
|
71
|
+
process_after TIMESTAMP WITH TIME ZONE DEFAULT NOW(), -- Delayed processing
|
72
|
+
expires_at TIMESTAMP WITH TIME ZONE, -- Message expiration
|
73
|
+
correlation_id VARCHAR, -- Request-reply correlation
|
74
|
+
|
75
|
+
-- Audit and compliance
|
76
|
+
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
77
|
+
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
78
|
+
|
79
|
+
-- Constraints
|
80
|
+
CONSTRAINT smart_messages_uuid_key UNIQUE (uuid),
|
81
|
+
CONSTRAINT smart_messages_version_positive CHECK (version > 0),
|
82
|
+
CONSTRAINT smart_messages_priority_range CHECK (priority >= 0),
|
83
|
+
CONSTRAINT smart_messages_processing_attempts_positive CHECK (processing_attempts >= 0)
|
84
|
+
);
|
85
|
+
|
86
|
+
-- Optimized performance indexes based on SmartMessage usage patterns
|
87
|
+
CREATE INDEX idx_smart_messages_processing_queue
|
88
|
+
ON smart_messages (status, process_after, priority DESC, created_at ASC)
|
89
|
+
WHERE status IN ('pending', 'processing');
|
90
|
+
|
91
|
+
CREATE INDEX idx_smart_messages_entity_routing
|
92
|
+
ON smart_messages (message_class, to_entity, status);
|
93
|
+
|
94
|
+
CREATE INDEX idx_smart_messages_from_entity_audit
|
95
|
+
ON smart_messages (from_entity, created_at DESC);
|
96
|
+
|
97
|
+
CREATE INDEX idx_smart_messages_correlation
|
98
|
+
ON smart_messages (correlation_id)
|
99
|
+
WHERE correlation_id IS NOT NULL;
|
100
|
+
|
101
|
+
CREATE INDEX idx_smart_messages_expiration
|
102
|
+
ON smart_messages (expires_at)
|
103
|
+
WHERE expires_at IS NOT NULL AND status = 'pending';
|
104
|
+
|
105
|
+
-- Partial index for active messages only (significant performance gain)
|
106
|
+
CREATE INDEX idx_smart_messages_active_by_class
|
107
|
+
ON smart_messages (message_class, priority DESC, created_at ASC)
|
108
|
+
WHERE status IN ('pending', 'processing');
|
109
|
+
```
|
110
|
+
|
111
|
+
### Subscription Registry Table
|
112
|
+
```sql
|
113
|
+
CREATE TABLE smart_subscriptions (
|
114
|
+
id BIGSERIAL PRIMARY KEY,
|
115
|
+
message_class VARCHAR NOT NULL,
|
116
|
+
processor_method VARCHAR NOT NULL,
|
117
|
+
|
118
|
+
-- Entity-aware filtering support (matches SmartMessage v0.0.6+ filtering)
|
119
|
+
subscriber_entity_id VARCHAR, -- The entity subscribing (for targeting)
|
120
|
+
from_filter VARCHAR[], -- Array of from entity patterns
|
121
|
+
to_filter VARCHAR[], -- Array of to entity patterns
|
122
|
+
broadcast_filter BOOLEAN, -- Filter for broadcast messages
|
123
|
+
|
124
|
+
-- Filter type tracking for regex vs exact matching
|
125
|
+
from_filter_types VARCHAR[], -- 'exact' or 'regex' for each from_filter
|
126
|
+
to_filter_types VARCHAR[], -- 'exact' or 'regex' for each to_filter
|
127
|
+
|
128
|
+
-- Subscription metadata
|
129
|
+
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
130
|
+
last_matched_at TIMESTAMP WITH TIME ZONE,
|
131
|
+
match_count BIGINT DEFAULT 0,
|
132
|
+
|
133
|
+
-- Deduplication settings per subscription
|
134
|
+
ddq_enabled BOOLEAN DEFAULT true,
|
135
|
+
ddq_size INTEGER DEFAULT 100,
|
136
|
+
|
137
|
+
UNIQUE(message_class, processor_method, subscriber_entity_id)
|
138
|
+
);
|
139
|
+
|
140
|
+
-- Optimized indexes for subscription matching
|
141
|
+
CREATE INDEX idx_smart_subscriptions_message_routing
|
142
|
+
ON smart_subscriptions (message_class);
|
143
|
+
|
144
|
+
CREATE INDEX idx_smart_subscriptions_entity_targeting
|
145
|
+
ON smart_subscriptions (subscriber_entity_id, message_class);
|
146
|
+
|
147
|
+
CREATE INDEX idx_smart_subscriptions_broadcast
|
148
|
+
ON smart_subscriptions (message_class, broadcast_filter)
|
149
|
+
WHERE broadcast_filter = true;
|
150
|
+
```
|
151
|
+
|
152
|
+
### Deduplication Queue Table (Database-backed DDQ)
|
153
|
+
```sql
|
154
|
+
CREATE TABLE smart_ddq_entries (
|
155
|
+
id BIGSERIAL PRIMARY KEY,
|
156
|
+
handler_key VARCHAR NOT NULL, -- "MessageClass:HandlerMethod" format
|
157
|
+
message_uuid UUID NOT NULL,
|
158
|
+
processed_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
159
|
+
|
160
|
+
-- Circular buffer metadata
|
161
|
+
position INTEGER NOT NULL, -- Position in circular buffer (0 to ddq_size-1)
|
162
|
+
|
163
|
+
UNIQUE(handler_key, position), -- Ensures circular buffer constraint
|
164
|
+
INDEX idx_ddq_handler_lookup (handler_key, message_uuid),
|
165
|
+
INDEX idx_ddq_cleanup (processed_at) -- For TTL cleanup
|
166
|
+
);
|
167
|
+
|
168
|
+
-- Partitioning by handler_key for high-throughput scenarios
|
169
|
+
-- CREATE TABLE smart_ddq_entries_handler1 PARTITION OF smart_ddq_entries FOR VALUES IN ('OrderMessage:process');
|
170
|
+
```
|
171
|
+
|
172
|
+
### Dead Letter Queue Table
|
173
|
+
```sql
|
174
|
+
CREATE TABLE smart_dead_letters (
|
175
|
+
id BIGSERIAL PRIMARY KEY,
|
176
|
+
|
177
|
+
-- Reference to original message
|
178
|
+
original_message_id BIGINT REFERENCES smart_messages(id),
|
179
|
+
original_uuid UUID NOT NULL,
|
180
|
+
|
181
|
+
-- Failure tracking
|
182
|
+
failed_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
183
|
+
failure_reason VARCHAR NOT NULL, -- 'processing_error', 'timeout', 'circuit_breaker', 'max_retries'
|
184
|
+
error_message TEXT,
|
185
|
+
error_details JSONB, -- Structured error information
|
186
|
+
stack_trace TEXT,
|
187
|
+
retry_count INTEGER DEFAULT 0,
|
188
|
+
|
189
|
+
-- Handler context
|
190
|
+
failed_handler VARCHAR, -- Which handler failed
|
191
|
+
failed_processor VARCHAR, -- Which processor/method failed
|
192
|
+
|
193
|
+
-- Complete message preservation for replay
|
194
|
+
message_class VARCHAR NOT NULL,
|
195
|
+
from_entity VARCHAR NOT NULL,
|
196
|
+
to_entity VARCHAR,
|
197
|
+
reply_to VARCHAR,
|
198
|
+
payload JSONB NOT NULL,
|
199
|
+
payload_format VARCHAR DEFAULT 'json',
|
200
|
+
|
201
|
+
-- Original header preservation
|
202
|
+
original_published_at TIMESTAMP WITH TIME ZONE,
|
203
|
+
original_publisher_pid INTEGER,
|
204
|
+
original_version INTEGER,
|
205
|
+
original_serializer VARCHAR,
|
206
|
+
|
207
|
+
-- DLQ management
|
208
|
+
replay_count INTEGER DEFAULT 0,
|
209
|
+
last_replay_at TIMESTAMP WITH TIME ZONE,
|
210
|
+
replay_successful BOOLEAN,
|
211
|
+
|
212
|
+
-- Audit
|
213
|
+
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
214
|
+
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
215
|
+
);
|
216
|
+
|
217
|
+
-- Indexes for DLQ management and analysis
|
218
|
+
CREATE INDEX idx_dead_letters_failure_analysis
|
219
|
+
ON smart_dead_letters (message_class, failure_reason, failed_at DESC);
|
220
|
+
|
221
|
+
CREATE INDEX idx_dead_letters_replay_queue
|
222
|
+
ON smart_dead_letters (failed_at ASC)
|
223
|
+
WHERE replay_successful IS NULL;
|
224
|
+
|
225
|
+
CREATE INDEX idx_dead_letters_handler_errors
|
226
|
+
ON smart_dead_letters (failed_handler, failed_at DESC);
|
227
|
+
```
|
228
|
+
|
229
|
+
## Hybrid Architecture: Publish-Only Database + Redis Processing
|
230
|
+
|
231
|
+
### Recommended Architecture
|
232
|
+
|
233
|
+
The optimal database transport implementation uses a **publish-only pattern** combined with Redis for real-time processing:
|
234
|
+
|
235
|
+
```
|
236
|
+
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
|
237
|
+
│ Application │ │ SmartMessage │ │ Multi-Trans │
|
238
|
+
│ Publishes ├───►│ Message ├───►│ Publishing │
|
239
|
+
│ OrderMessage │ │ Instance │ │ │
|
240
|
+
└─────────────────┘ └──────────────────┘ └─────────┬───────┘
|
241
|
+
│
|
242
|
+
┌───────────────┼───────────────┐
|
243
|
+
│ │ │
|
244
|
+
▼ ▼ ▼
|
245
|
+
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
|
246
|
+
│ Database │ │ Redis │ │ S3 │
|
247
|
+
│ (Archive) │ │(Processing) │ │ (Compliance)│
|
248
|
+
│ Publish-Only│ │ Pub/Sub │ │Archive-Only │
|
249
|
+
└─────────────┘ └─────────────┘ └─────────────┘
|
250
|
+
│ │ │
|
251
|
+
▼ ▼ ▼
|
252
|
+
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
|
253
|
+
│ Audit │ │ Subscribers │ │ Long-term │
|
254
|
+
│ Trails │ │ Get Messages│ │ Storage │
|
255
|
+
│ Reports │ │ Immediately │ │ │
|
256
|
+
└─────────────┘ └─────────────┘ └─────────────┘
|
257
|
+
```
|
258
|
+
|
259
|
+
### Database Transport: Publish-Only Implementation
|
260
|
+
|
261
|
+
```ruby
|
262
|
+
class DatabaseTransport < Base
|
263
|
+
# Database transport does NOT support subscriptions
|
264
|
+
def subscribe(message_class, process_method, filter_options = {})
|
265
|
+
raise NotImplementedError,
|
266
|
+
"DatabaseTransport is publish-only. Use RedisTransport for message processing subscriptions."
|
267
|
+
end
|
268
|
+
|
269
|
+
# Only implement publishing for persistent archiving
|
270
|
+
def do_publish(message_class, serialized_message)
|
271
|
+
message_data = JSON.parse(serialized_message)
|
272
|
+
header = message_data['header']
|
273
|
+
payload = message_data['payload']
|
274
|
+
|
275
|
+
# Simple INSERT - optimized for archival, not processing
|
276
|
+
@connection_pool.with do |conn|
|
277
|
+
conn.execute(<<~SQL, [
|
278
|
+
header['uuid'],
|
279
|
+
header['message_class'],
|
280
|
+
header['published_at'],
|
281
|
+
header['publisher_pid'],
|
282
|
+
header['version'],
|
283
|
+
header['serializer'],
|
284
|
+
header['from'],
|
285
|
+
header['to'],
|
286
|
+
header['reply_to'],
|
287
|
+
payload,
|
288
|
+
'json',
|
289
|
+
'archived' # Status: archived, not for processing
|
290
|
+
])
|
291
|
+
INSERT INTO smart_messages (
|
292
|
+
uuid, message_class, published_at, publisher_pid, version, serializer,
|
293
|
+
from_entity, to_entity, reply_to, payload, payload_format, status
|
294
|
+
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
|
295
|
+
SQL
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
# Simplified connection management (no complex subscription threads)
|
300
|
+
def connected?
|
301
|
+
@connection_pool.with { |conn| conn.execute("SELECT 1").first }
|
302
|
+
true
|
303
|
+
rescue
|
304
|
+
false
|
305
|
+
end
|
306
|
+
|
307
|
+
def connect
|
308
|
+
# Database connections are managed by connection pool
|
309
|
+
# No subscription threads to start
|
310
|
+
end
|
311
|
+
|
312
|
+
def disconnect
|
313
|
+
# No subscription threads to stop
|
314
|
+
@connection_pool&.shutdown { |conn| conn.close }
|
315
|
+
end
|
316
|
+
end
|
317
|
+
```
|
318
|
+
|
319
|
+
### Multi-Transport Message Configuration
|
320
|
+
|
321
|
+
Messages can be configured to publish to multiple transports simultaneously:
|
322
|
+
|
323
|
+
```ruby
|
324
|
+
class OrderMessage < SmartMessage::Base
|
325
|
+
property :order_id, required: true
|
326
|
+
property :amount, required: true
|
327
|
+
property :customer_id, required: true
|
328
|
+
|
329
|
+
config do
|
330
|
+
# Multi-transport: Redis for processing + Database for archiving
|
331
|
+
transport [
|
332
|
+
redis_transport, # Real-time pub/sub processing
|
333
|
+
database_transport # Persistent archive (publish-only)
|
334
|
+
]
|
335
|
+
serializer SmartMessage::Serializer::JSON.new
|
336
|
+
end
|
337
|
+
end
|
338
|
+
|
339
|
+
# Publishing sends to ALL configured transports
|
340
|
+
message = OrderMessage.new(order_id: "123", amount: 99.99, customer_id: "cust_456")
|
341
|
+
message.from = "order_service"
|
342
|
+
message.to = "payment_service"
|
343
|
+
message.publish # → Redis pub/sub (immediate) + Database INSERT (archived)
|
344
|
+
```
|
345
|
+
|
346
|
+
### Environment-Specific Transport Configurations
|
347
|
+
|
348
|
+
```ruby
|
349
|
+
# config/initializers/smart_message.rb
|
350
|
+
|
351
|
+
# Development: Redis only for speed
|
352
|
+
if Rails.env.development?
|
353
|
+
SmartMessage.configure do
|
354
|
+
default_transport redis_transport
|
355
|
+
end
|
356
|
+
end
|
357
|
+
|
358
|
+
# Production: Redis + Database + S3 for enterprise requirements
|
359
|
+
if Rails.env.production?
|
360
|
+
SmartMessage.configure do
|
361
|
+
default_transport [
|
362
|
+
redis_transport, # Real-time processing
|
363
|
+
database_transport(database_url: ENV['DATABASE_URL']), # Audit archive
|
364
|
+
s3_transport(bucket: ENV['AUDIT_BUCKET']) # Compliance storage
|
365
|
+
]
|
366
|
+
end
|
367
|
+
end
|
368
|
+
|
369
|
+
# Testing: Memory only
|
370
|
+
if Rails.env.test?
|
371
|
+
SmartMessage.configure do
|
372
|
+
default_transport memory_transport
|
373
|
+
end
|
374
|
+
end
|
375
|
+
```
|
376
|
+
|
377
|
+
### Benefits of Publish-Only Database Transport
|
378
|
+
|
379
|
+
1. **Simplified Implementation**: No subscription complexity, polling, or notification systems
|
380
|
+
2. **High Performance**: Optimized for write-heavy archival workloads
|
381
|
+
3. **Clear Separation**: Database = persistence, Redis = processing
|
382
|
+
4. **Scalability**: Database optimized for storage, Redis optimized for speed
|
383
|
+
5. **Reliability**: Every message guaranteed to be archived regardless of processing failures
|
384
|
+
6. **Compliance**: Complete audit trail with ACID guarantees
|
385
|
+
7. **Flexibility**: Add/remove archive transports without affecting processing
|
386
|
+
|
387
|
+
### Multi-Transport Publishing Logic
|
388
|
+
|
389
|
+
SmartMessage Base class handles multi-transport publishing automatically:
|
390
|
+
|
391
|
+
```ruby
|
392
|
+
# In SmartMessage::Base
|
393
|
+
def publish
|
394
|
+
validate!
|
395
|
+
|
396
|
+
# Support both single transport and array of transports
|
397
|
+
transports = Array(self.class.transport)
|
398
|
+
|
399
|
+
transports.each do |transport|
|
400
|
+
begin
|
401
|
+
serialized_message = serialize
|
402
|
+
transport.publish(self.class.name, serialized_message)
|
403
|
+
rescue => e
|
404
|
+
# Continue publishing to other transports even if one fails
|
405
|
+
logger.error "Failed to publish to #{transport.class.name}: #{e.message}"
|
406
|
+
# Could integrate with circuit breaker for fallback behavior
|
407
|
+
end
|
408
|
+
end
|
409
|
+
end
|
410
|
+
```
|
411
|
+
|
412
|
+
This architecture provides the best of all worlds:
|
413
|
+
- **Redis speed** for real-time processing
|
414
|
+
- **Database persistence** for enterprise requirements
|
415
|
+
- **Simple implementation** with clear separation of concerns
|
416
|
+
- **Flexible configuration** for different environments
|
417
|
+
|
418
|
+
## Subscription and Notification Strategy
|
419
|
+
|
420
|
+
### Push vs Pull: Database Notification Systems
|
421
|
+
|
422
|
+
**Challenge**: Unlike Redis pub/sub which pushes messages instantly, databases require a mechanism to notify applications when new messages arrive. Traditional polling is inefficient and adds latency.
|
423
|
+
|
424
|
+
**Solution**: Use database-native push notification systems for real-time message delivery.
|
425
|
+
|
426
|
+
#### PostgreSQL LISTEN/NOTIFY (Recommended)
|
427
|
+
PostgreSQL provides a built-in pub/sub system that delivers true push notifications:
|
428
|
+
|
429
|
+
```sql
|
430
|
+
-- Database trigger to notify on new messages
|
431
|
+
CREATE OR REPLACE FUNCTION notify_new_message()
|
432
|
+
RETURNS TRIGGER AS $$
|
433
|
+
BEGIN
|
434
|
+
-- Send notification with message details
|
435
|
+
PERFORM pg_notify('new_smart_message', json_build_object(
|
436
|
+
'message_id', NEW.id,
|
437
|
+
'message_class', NEW.message_class,
|
438
|
+
'priority', NEW.priority,
|
439
|
+
'to_entity', NEW.to_entity
|
440
|
+
)::text);
|
441
|
+
|
442
|
+
RETURN NEW;
|
443
|
+
END;
|
444
|
+
$$ LANGUAGE plpgsql;
|
445
|
+
|
446
|
+
-- Trigger fires automatically on INSERT
|
447
|
+
CREATE TRIGGER trigger_notify_new_message
|
448
|
+
AFTER INSERT ON smart_messages
|
449
|
+
FOR EACH ROW
|
450
|
+
EXECUTE FUNCTION notify_new_message();
|
451
|
+
```
|
452
|
+
|
453
|
+
```ruby
|
454
|
+
class DatabaseTransport < Base
|
455
|
+
def start_postgres_listener
|
456
|
+
@notification_thread = Thread.new do
|
457
|
+
@connection_pool.with do |conn|
|
458
|
+
# Subscribe to database notifications
|
459
|
+
conn.execute("LISTEN new_smart_message")
|
460
|
+
|
461
|
+
loop do
|
462
|
+
# Block until notification received (no polling!)
|
463
|
+
conn.raw_connection.wait_for_notify do |channel, pid, payload|
|
464
|
+
handle_notification(payload)
|
465
|
+
end
|
466
|
+
end
|
467
|
+
end
|
468
|
+
end
|
469
|
+
end
|
470
|
+
|
471
|
+
private
|
472
|
+
|
473
|
+
def handle_notification(payload)
|
474
|
+
message_info = JSON.parse(payload)
|
475
|
+
message_id = message_info['message_id']
|
476
|
+
|
477
|
+
# Fetch specific message and process immediately
|
478
|
+
message_data = fetch_message_by_id(message_id)
|
479
|
+
if message_data
|
480
|
+
# Convert to SmartMessage format and route
|
481
|
+
receive(message_data['message_class'], reconstruct_serialized_message(message_data))
|
482
|
+
|
483
|
+
# Mark as processing to prevent duplicate handling
|
484
|
+
mark_message_processing(message_id)
|
485
|
+
end
|
486
|
+
end
|
487
|
+
|
488
|
+
def fetch_message_by_id(message_id)
|
489
|
+
@connection_pool.with do |conn|
|
490
|
+
conn.execute(<<~SQL, [message_id]).first
|
491
|
+
SELECT * FROM smart_messages
|
492
|
+
WHERE id = $1 AND status = 'pending'
|
493
|
+
SQL
|
494
|
+
end
|
495
|
+
end
|
496
|
+
end
|
497
|
+
```
|
498
|
+
|
499
|
+
#### MySQL/Other Databases (Polling Fallback)
|
500
|
+
For databases without native push notifications:
|
501
|
+
|
502
|
+
```ruby
|
503
|
+
def start_polling_subscriber
|
504
|
+
@polling_thread = Thread.new do
|
505
|
+
loop do
|
506
|
+
poll_for_messages
|
507
|
+
sleep(@options[:poll_interval] || 1) # Default 1 second
|
508
|
+
end
|
509
|
+
end
|
510
|
+
end
|
511
|
+
|
512
|
+
def poll_for_messages
|
513
|
+
messages = retrieve_pending_messages(limit: @options[:batch_size] || 100)
|
514
|
+
|
515
|
+
messages.each do |message_data|
|
516
|
+
receive(message_data['message_class'], reconstruct_serialized_message(message_data))
|
517
|
+
mark_message_processing(message_data['id'])
|
518
|
+
end
|
519
|
+
end
|
520
|
+
```
|
521
|
+
|
522
|
+
#### Hybrid Detection Strategy
|
523
|
+
```ruby
|
524
|
+
def initialize(**options)
|
525
|
+
super
|
526
|
+
@notification_method = detect_notification_method
|
527
|
+
configure_subscriber
|
528
|
+
end
|
529
|
+
|
530
|
+
private
|
531
|
+
|
532
|
+
def detect_notification_method
|
533
|
+
case @connection_adapter_name
|
534
|
+
when /postgresql/i
|
535
|
+
:listen_notify
|
536
|
+
when /mysql/i
|
537
|
+
:polling # Could add MySQL events or external triggers
|
538
|
+
when /sqlite/i
|
539
|
+
:polling # Limited to single-process anyway
|
540
|
+
else
|
541
|
+
:polling
|
542
|
+
end
|
543
|
+
end
|
544
|
+
|
545
|
+
def start_subscriber
|
546
|
+
case @notification_method
|
547
|
+
when :listen_notify
|
548
|
+
start_postgres_listener
|
549
|
+
else
|
550
|
+
start_polling_subscriber
|
551
|
+
end
|
552
|
+
end
|
553
|
+
```
|
554
|
+
|
555
|
+
### Performance Comparison
|
556
|
+
|
557
|
+
| Method | Latency | CPU Usage | Database Support | Complexity |
|
558
|
+
|--------|---------|-----------|------------------|------------|
|
559
|
+
| **PostgreSQL LISTEN/NOTIFY** | ~1ms | Very Low | PostgreSQL | Medium |
|
560
|
+
| **MySQL Events** | ~1s | Low | MySQL | Medium |
|
561
|
+
| **Polling (1s interval)** | ~500ms avg | Medium | All | Low |
|
562
|
+
| **Polling (100ms interval)** | ~50ms avg | High | All | Low |
|
563
|
+
|
564
|
+
### Benefits of PostgreSQL LISTEN/NOTIFY
|
565
|
+
|
566
|
+
- **Real-time Delivery**: Sub-millisecond notification latency
|
567
|
+
- **Zero Polling Overhead**: No constant database queries
|
568
|
+
- **Connection Resilience**: Automatic reconnection and notification replay
|
569
|
+
- **Scalable**: Multiple subscribers can listen to same channel
|
570
|
+
- **Native Integration**: Built into PostgreSQL, no external dependencies
|
571
|
+
- **Filtered Notifications**: Can include message metadata in notification payload
|
572
|
+
|
573
|
+
This approach gives database transport **the same real-time characteristics as Redis pub/sub** while maintaining all the persistence, ACID guarantees, and enterprise features that databases provide.
|
574
|
+
|
575
|
+
## Implementation Architecture
|
576
|
+
|
577
|
+
### Transport Class Structure
|
578
|
+
```ruby
|
579
|
+
module SmartMessage
|
580
|
+
module Transport
|
581
|
+
class DatabaseTransport < Base
|
582
|
+
attr_reader :connection_pool, :batch_processor
|
583
|
+
|
584
|
+
def initialize(**options)
|
585
|
+
super
|
586
|
+
@connection_pool = setup_connection_pool
|
587
|
+
@batch_processor = BatchProcessor.new(self)
|
588
|
+
@pending_messages = Queue.new
|
589
|
+
setup_batch_timer if options[:enable_batching]
|
590
|
+
end
|
591
|
+
|
592
|
+
# Individual message publishing (immediate)
|
593
|
+
def publish(message_header, message_payload)
|
594
|
+
if @options[:enable_batching]
|
595
|
+
@pending_messages << [message_header, message_payload]
|
596
|
+
else
|
597
|
+
publish_single(message_header, message_payload)
|
598
|
+
end
|
599
|
+
end
|
600
|
+
|
601
|
+
# Batch publishing for high-throughput scenarios
|
602
|
+
def publish_batch(messages)
|
603
|
+
@connection_pool.with do |conn|
|
604
|
+
conn.transaction do
|
605
|
+
insert_data = messages.map { |header, payload| prepare_message_data(header, payload) }
|
606
|
+
conn.execute(batch_insert_sql, insert_data.flatten)
|
607
|
+
end
|
608
|
+
end
|
609
|
+
end
|
610
|
+
|
611
|
+
# Message retrieval with advanced routing
|
612
|
+
def retrieve_messages(entity_id: nil, limit: 100)
|
613
|
+
@connection_pool.with do |conn|
|
614
|
+
conn.execute(retrieval_sql, [entity_id, entity_id, Time.current, limit])
|
615
|
+
end
|
616
|
+
end
|
617
|
+
|
618
|
+
private
|
619
|
+
|
620
|
+
def retrieval_sql
|
621
|
+
<<~SQL
|
622
|
+
SELECT id, uuid, from_entity, to_entity, reply_to, message_class,
|
623
|
+
payload, published_at, priority
|
624
|
+
FROM smart_messages
|
625
|
+
WHERE processed_at IS NULL
|
626
|
+
AND process_after <= $3
|
627
|
+
AND (to_entity = $1 OR to_entity IS NULL OR $2 IS NULL)
|
628
|
+
ORDER BY priority DESC, published_at ASC
|
629
|
+
LIMIT $4
|
630
|
+
SQL
|
631
|
+
end
|
632
|
+
end
|
633
|
+
end
|
634
|
+
end
|
635
|
+
```
|
636
|
+
|
637
|
+
### Batch Processing System
|
638
|
+
```ruby
|
639
|
+
class BatchProcessor
|
640
|
+
def initialize(transport)
|
641
|
+
@transport = transport
|
642
|
+
@batch = []
|
643
|
+
@mutex = Mutex.new
|
644
|
+
@batch_size = transport.options[:batch_size] || 100
|
645
|
+
@batch_timeout = transport.options[:batch_timeout] || 5.seconds
|
646
|
+
end
|
647
|
+
|
648
|
+
def add_message(header, payload)
|
649
|
+
@mutex.synchronize do
|
650
|
+
@batch << [header, payload]
|
651
|
+
flush_if_ready
|
652
|
+
end
|
653
|
+
end
|
654
|
+
|
655
|
+
def flush_if_ready
|
656
|
+
if @batch.size >= @batch_size
|
657
|
+
flush_batch
|
658
|
+
end
|
659
|
+
end
|
660
|
+
|
661
|
+
def flush_batch
|
662
|
+
return if @batch.empty?
|
663
|
+
|
664
|
+
batch_to_process = @batch.dup
|
665
|
+
@batch.clear
|
666
|
+
|
667
|
+
@transport.publish_batch(batch_to_process)
|
668
|
+
end
|
669
|
+
end
|
670
|
+
```
|
671
|
+
|
672
|
+
## Advanced Routing Queries
|
673
|
+
|
674
|
+
### Entity-Specific and Broadcast Messaging
|
675
|
+
```sql
|
676
|
+
-- Retrieve messages for specific entity or broadcast messages
|
677
|
+
SELECT * FROM smart_messages
|
678
|
+
WHERE processed_at IS NULL
|
679
|
+
AND (to_entity = 'user_123' OR to_entity IS NULL)
|
680
|
+
AND process_after <= NOW()
|
681
|
+
ORDER BY priority DESC, published_at ASC;
|
682
|
+
```
|
683
|
+
|
684
|
+
### Priority-Based Processing
|
685
|
+
```sql
|
686
|
+
-- High priority messages first, then by publish time
|
687
|
+
SELECT * FROM smart_messages
|
688
|
+
WHERE processed_at IS NULL
|
689
|
+
AND message_class = 'OrderProcessingMessage'
|
690
|
+
ORDER BY priority DESC, published_at ASC
|
691
|
+
LIMIT 50;
|
692
|
+
```
|
693
|
+
|
694
|
+
### Content-Based Routing
|
695
|
+
```sql
|
696
|
+
-- Route based on payload content using JSONB operators
|
697
|
+
SELECT * FROM smart_messages
|
698
|
+
WHERE processed_at IS NULL
|
699
|
+
AND message_class = 'NotificationMessage'
|
700
|
+
AND payload->>'urgency' = 'high'
|
701
|
+
AND payload->'recipient'->>'region' = 'us-west';
|
702
|
+
```
|
703
|
+
|
704
|
+
### Delayed Processing
|
705
|
+
```sql
|
706
|
+
-- Messages scheduled for future processing
|
707
|
+
SELECT * FROM smart_messages
|
708
|
+
WHERE processed_at IS NULL
|
709
|
+
AND process_after <= NOW()
|
710
|
+
ORDER BY process_after ASC, priority DESC;
|
711
|
+
```
|
712
|
+
|
713
|
+
## Integration with SmartMessage Improvements
|
714
|
+
|
715
|
+
### 1. Message Wrapper Enhancement Integration
|
716
|
+
The database schema naturally supports the proposed addressing system:
|
717
|
+
```ruby
|
718
|
+
# Publishing with addressing
|
719
|
+
message = OrderMessage.new(order_id: "123", amount: 99.99)
|
720
|
+
message.from = "order_service"
|
721
|
+
message.to = "payment_service" # Specific targeting
|
722
|
+
message.reply_to = "order_service"
|
723
|
+
message.publish
|
724
|
+
|
725
|
+
# Broadcast (to_entity = NULL)
|
726
|
+
broadcast = SystemAlert.new(message: "Maintenance starting")
|
727
|
+
broadcast.from = "admin_service"
|
728
|
+
# No .to specified = broadcast
|
729
|
+
broadcast.publish
|
730
|
+
```
|
731
|
+
|
732
|
+
### 2. Circuit Breaker Integration
|
733
|
+
```ruby
|
734
|
+
class DatabaseTransport < Base
|
735
|
+
include BreakerMachines::DSL
|
736
|
+
|
737
|
+
circuit :database_publish do
|
738
|
+
threshold failures: 5, within: 2.minutes
|
739
|
+
reset_after 30.seconds
|
740
|
+
fallback { |error| handle_publish_failure(error) }
|
741
|
+
end
|
742
|
+
|
743
|
+
def publish(message_header, message_payload)
|
744
|
+
circuit(:database_publish).wrap do
|
745
|
+
# Database publishing logic
|
746
|
+
end
|
747
|
+
end
|
748
|
+
end
|
749
|
+
```
|
750
|
+
|
751
|
+
### 3. Dead Letter Queue Integration
|
752
|
+
```ruby
|
753
|
+
def mark_message_failed(message_id, error)
|
754
|
+
@connection_pool.with do |conn|
|
755
|
+
conn.transaction do
|
756
|
+
# Move to dead letter queue
|
757
|
+
conn.execute(insert_dead_letter_sql, [message_id, error.message])
|
758
|
+
|
759
|
+
# Update original message
|
760
|
+
conn.execute(
|
761
|
+
"UPDATE smart_messages SET processed_at = NOW(), last_error = $1 WHERE id = $2",
|
762
|
+
[error.message, message_id]
|
763
|
+
)
|
764
|
+
end
|
765
|
+
end
|
766
|
+
end
|
767
|
+
```
|
768
|
+
|
769
|
+
### 4. Ractor-Based Processing Integration
|
770
|
+
```ruby
|
771
|
+
class DatabaseMessageProcessor
|
772
|
+
def initialize(database_transport)
|
773
|
+
@transport = database_transport
|
774
|
+
@ractor_pool = RactorPool.new(size: 4)
|
775
|
+
end
|
776
|
+
|
777
|
+
def process_messages
|
778
|
+
messages = @transport.retrieve_messages(limit: 100)
|
779
|
+
|
780
|
+
messages.each do |message_data|
|
781
|
+
@ractor_pool.post(message_data) do |data|
|
782
|
+
# Process in isolated Ractor
|
783
|
+
process_single_message(data)
|
784
|
+
end
|
785
|
+
end
|
786
|
+
end
|
787
|
+
end
|
788
|
+
```
|
789
|
+
|
790
|
+
## Performance Characteristics
|
791
|
+
|
792
|
+
### Benchmarking Scenarios
|
793
|
+
|
794
|
+
**Single Message Operations:**
|
795
|
+
- Individual INSERT: ~1-2ms per message
|
796
|
+
- Individual SELECT: ~0.5-1ms per message
|
797
|
+
- Network latency typically dominates
|
798
|
+
|
799
|
+
**Batch Operations:**
|
800
|
+
- Batch INSERT (100 messages): ~10-20ms total (10-20x improvement)
|
801
|
+
- Bulk UPDATE (marking processed): ~5-10ms for 100 messages
|
802
|
+
- Transaction overhead amortized across batch
|
803
|
+
|
804
|
+
**Query Performance:**
|
805
|
+
- Indexed entity routing: Sub-millisecond
|
806
|
+
- Priority ordering: ~1-2ms for large queues
|
807
|
+
- JSONB content filtering: ~2-5ms depending on complexity
|
808
|
+
|
809
|
+
### Scalability Considerations
|
810
|
+
- **Read Replicas**: Route message retrieval to read replicas
|
811
|
+
- **Partitioning**: Partition by message_class or date for large volumes
|
812
|
+
- **Archival**: Move old processed messages to archive tables
|
813
|
+
- **Connection Pooling**: Scale with database connection limits
|
814
|
+
|
815
|
+
## Configuration Options
|
816
|
+
|
817
|
+
```ruby
|
818
|
+
# Database transport configuration
|
819
|
+
database_transport = SmartMessage::Transport.create(:database,
|
820
|
+
# Database connection
|
821
|
+
database_url: ENV['DATABASE_URL'],
|
822
|
+
connection_pool_size: 10,
|
823
|
+
|
824
|
+
# Batching configuration
|
825
|
+
enable_batching: true,
|
826
|
+
batch_size: 100,
|
827
|
+
batch_timeout: 5.seconds,
|
828
|
+
|
829
|
+
# Processing options
|
830
|
+
default_priority: 0,
|
831
|
+
enable_delayed_processing: true,
|
832
|
+
|
833
|
+
# Reliability options
|
834
|
+
max_processing_attempts: 3,
|
835
|
+
dead_letter_queue_enabled: true,
|
836
|
+
|
837
|
+
# Performance options
|
838
|
+
retrieval_limit: 100,
|
839
|
+
enable_content_routing: true
|
840
|
+
)
|
841
|
+
```
|
842
|
+
|
843
|
+
## Use Cases
|
844
|
+
|
845
|
+
### 1. Financial Transactions
|
846
|
+
- **ACID Requirements**: Transaction messages must be durable
|
847
|
+
- **Audit Trail**: Regulatory compliance requires complete history
|
848
|
+
- **Guaranteed Delivery**: Payment processing cannot lose messages
|
849
|
+
- **Priority Processing**: High-value transactions get priority
|
850
|
+
|
851
|
+
### 2. Order Processing Systems
|
852
|
+
- **State Persistence**: Order state changes must survive failures
|
853
|
+
- **Entity Routing**: Messages targeted to specific services
|
854
|
+
- **Delayed Processing**: Scheduled order fulfillment
|
855
|
+
- **Batch Efficiency**: High-volume order processing
|
856
|
+
|
857
|
+
### 3. Notification Systems
|
858
|
+
- **Content Routing**: Different notification types to different handlers
|
859
|
+
- **Priority Levels**: Urgent notifications processed first
|
860
|
+
- **Delivery Guarantees**: Critical notifications must be delivered
|
861
|
+
- **Scheduling**: Time-based notification delivery
|
862
|
+
|
863
|
+
### 4. Enterprise Integration
|
864
|
+
- **Multiple Systems**: Database provides universal messaging layer
|
865
|
+
- **Gateway Patterns**: Transform messages between different formats
|
866
|
+
- **Monitoring**: Complete visibility into message flow
|
867
|
+
- **Compliance**: Audit trails for regulatory requirements
|
868
|
+
|
869
|
+
## Migration Strategy
|
870
|
+
|
871
|
+
### Phase 1: Basic Implementation
|
872
|
+
1. Create database schema
|
873
|
+
2. Implement basic DatabaseTransport class
|
874
|
+
3. Add to transport registry
|
875
|
+
4. Create basic publish/subscribe functionality
|
876
|
+
|
877
|
+
### Phase 2: Addressing Integration
|
878
|
+
1. Add FROM/TO/REPLY_TO fields to schema
|
879
|
+
2. Implement entity-specific routing
|
880
|
+
3. Update dispatcher to handle database retrieval
|
881
|
+
4. Add broadcast vs targeted message support
|
882
|
+
|
883
|
+
### Phase 3: Advanced Features
|
884
|
+
1. Implement batch processing
|
885
|
+
2. Add priority and delayed processing
|
886
|
+
3. Create dead letter queue functionality
|
887
|
+
4. Add content-based routing
|
888
|
+
|
889
|
+
### Phase 4: Performance & Reliability
|
890
|
+
1. Optimize queries and indexes
|
891
|
+
2. Add connection pooling
|
892
|
+
3. Implement circuit breakers
|
893
|
+
4. Add monitoring and metrics
|
894
|
+
|
895
|
+
## Testing Strategy
|
896
|
+
|
897
|
+
### Unit Tests
|
898
|
+
```ruby
|
899
|
+
RSpec.describe SmartMessage::Transport::DatabaseTransport do
|
900
|
+
let(:transport) { described_class.new(database_url: test_db_url) }
|
901
|
+
|
902
|
+
describe '#publish' do
|
903
|
+
it 'stores message in database' do
|
904
|
+
header = create_header(from: 'service_a', to: 'service_b')
|
905
|
+
payload = '{"test": true}'
|
906
|
+
|
907
|
+
transport.publish(header, payload)
|
908
|
+
|
909
|
+
message = SmartMessage::DatabaseMessage.last
|
910
|
+
expect(message.from_entity).to eq('service_a')
|
911
|
+
expect(message.to_entity).to eq('service_b')
|
912
|
+
expect(message.payload).to eq('{"test": true}')
|
913
|
+
end
|
914
|
+
end
|
915
|
+
|
916
|
+
describe '#retrieve_messages' do
|
917
|
+
it 'returns messages for specific entity' do
|
918
|
+
create_message(to_entity: 'service_a')
|
919
|
+
create_message(to_entity: 'service_b')
|
920
|
+
create_message(to_entity: nil) # broadcast
|
921
|
+
|
922
|
+
messages = transport.retrieve_messages(entity_id: 'service_a')
|
923
|
+
|
924
|
+
expect(messages.count).to eq(2) # targeted + broadcast
|
925
|
+
end
|
926
|
+
end
|
927
|
+
end
|
928
|
+
```
|
929
|
+
|
930
|
+
### Integration Tests
|
931
|
+
```ruby
|
932
|
+
RSpec.describe 'Database Transport Integration' do
|
933
|
+
it 'handles complete message lifecycle' do
|
934
|
+
# Setup message class with database transport
|
935
|
+
class TestMessage < SmartMessage::Base
|
936
|
+
property :data
|
937
|
+
|
938
|
+
config do
|
939
|
+
transport database_transport
|
940
|
+
serializer SmartMessage::Serializer::JSON.new
|
941
|
+
end
|
942
|
+
|
943
|
+
def self.process(header, payload)
|
944
|
+
# Processing logic
|
945
|
+
end
|
946
|
+
end
|
947
|
+
|
948
|
+
# Subscribe
|
949
|
+
TestMessage.subscribe
|
950
|
+
|
951
|
+
# Publish
|
952
|
+
message = TestMessage.new(data: 'test')
|
953
|
+
message.from = 'test_service'
|
954
|
+
message.publish
|
955
|
+
|
956
|
+
# Verify storage and processing
|
957
|
+
expect(SmartMessage::DatabaseMessage.count).to eq(1)
|
958
|
+
|
959
|
+
# Simulate processing
|
960
|
+
transport.process_pending_messages
|
961
|
+
|
962
|
+
db_message = SmartMessage::DatabaseMessage.last
|
963
|
+
expect(db_message.processed_at).not_to be_nil
|
964
|
+
end
|
965
|
+
end
|
966
|
+
```
|
967
|
+
|
968
|
+
## Conclusion
|
969
|
+
|
970
|
+
Database transports represent a **significant architectural enhancement** to SmartMessage, enabling:
|
971
|
+
|
972
|
+
1. **Enterprise-grade reliability** through persistence and ACID guarantees
|
973
|
+
2. **Advanced routing capabilities** through SQL-based filtering and entity targeting
|
974
|
+
3. **High-performance batching** for throughput-intensive applications
|
975
|
+
4. **Complete observability** through built-in audit trails and message history
|
976
|
+
5. **Natural integration** with other planned improvements (addressing, circuit breakers, Ractors)
|
977
|
+
|
978
|
+
This positions SmartMessage as a production-ready messaging framework capable of handling mission-critical applications while maintaining the simplicity and elegance of the current API.
|
979
|
+
|
980
|
+
The database transport complements rather than replaces existing transports - Redis for speed, Memory for testing, Database for reliability and persistence.
|