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,1381 @@
|
|
1
|
+
## Message Schema Registry
|
2
|
+
|
3
|
+
### Overview
|
4
|
+
|
5
|
+
SmartMessage can automatically register and track message schemas, enabling:
|
6
|
+
|
7
|
+
- **Automatic schema registration** when message classes are defined
|
8
|
+
- **Schema evolution tracking** across versions and deployments
|
9
|
+
- **Schema storage** in a centralized database for persistence
|
10
|
+
- **Schema versioning** to track changes over time
|
11
|
+
- **Compliance tracking** for audit requirements
|
12
|
+
|
13
|
+
### Database Schema for Message Registry
|
14
|
+
|
15
|
+
```sql
|
16
|
+
CREATE TABLE smart_message_schemas (
|
17
|
+
id BIGSERIAL PRIMARY KEY,
|
18
|
+
|
19
|
+
-- Class identification
|
20
|
+
class_name VARCHAR NOT NULL,
|
21
|
+
class_version INTEGER NOT NULL DEFAULT 1,
|
22
|
+
class_description TEXT,
|
23
|
+
|
24
|
+
-- Complete schema storage
|
25
|
+
schema_definition JSONB NOT NULL, -- Full serialized class definition
|
26
|
+
properties_schema JSONB NOT NULL, -- Properties for quick access/filtering
|
27
|
+
validations_schema JSONB, -- Validation rules and constraints
|
28
|
+
configuration_schema JSONB, -- Transport/serializer configs
|
29
|
+
|
30
|
+
-- Registration metadata
|
31
|
+
registered_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
32
|
+
registered_by VARCHAR NOT NULL, -- Service/application identifier
|
33
|
+
deployment_environment VARCHAR DEFAULT 'unknown', -- dev/staging/prod
|
34
|
+
ruby_version VARCHAR,
|
35
|
+
framework_version VARCHAR,
|
36
|
+
|
37
|
+
-- Schema evolution tracking
|
38
|
+
parent_schema_id BIGINT REFERENCES smart_message_schemas(id),
|
39
|
+
schema_hash VARCHAR(64) NOT NULL, -- SHA256 of schema for change detection
|
40
|
+
schema_fingerprint VARCHAR(32), -- Short hash for quick comparison
|
41
|
+
|
42
|
+
-- Lifecycle management
|
43
|
+
status VARCHAR DEFAULT 'active' CHECK (status IN ('active', 'deprecated', 'archived')),
|
44
|
+
deprecated_at TIMESTAMP WITH TIME ZONE,
|
45
|
+
archived_at TIMESTAMP WITH TIME ZONE,
|
46
|
+
|
47
|
+
-- Performance constraints
|
48
|
+
UNIQUE(class_name, class_version, registered_by),
|
49
|
+
INDEX idx_schema_discovery (class_name, status, registered_by),
|
50
|
+
INDEX idx_schema_evolution (parent_schema_id, registered_at),
|
51
|
+
INDEX idx_schema_fingerprint (schema_fingerprint),
|
52
|
+
INDEX idx_active_schemas (status, registered_at DESC) WHERE status = 'active'
|
53
|
+
);
|
54
|
+
```
|
55
|
+
|
56
|
+
### Automatic Schema Registration
|
57
|
+
|
58
|
+
```ruby
|
59
|
+
# Enhanced SmartMessage::Base with automatic schema registration
|
60
|
+
class Base < Hashie::Dash
|
61
|
+
# Hook that fires when any class inherits from SmartMessage::Base
|
62
|
+
def self.inherited(subclass)
|
63
|
+
super(subclass)
|
64
|
+
|
65
|
+
# Set up deferred registration after class is fully loaded
|
66
|
+
subclass.define_singleton_method(:method_added) do |method_name|
|
67
|
+
# Trigger registration after class is complete
|
68
|
+
if method_name == :initialize && !@schema_registered
|
69
|
+
@schema_registered = true
|
70
|
+
register_schema_async
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# Extract complete class schema for serialization
|
76
|
+
def self.serialize_schema
|
77
|
+
{
|
78
|
+
class_name: name,
|
79
|
+
class_version: version || 1,
|
80
|
+
class_description: description,
|
81
|
+
properties_schema: extract_properties_schema,
|
82
|
+
validations_schema: extract_validations_schema,
|
83
|
+
configuration_schema: extract_configuration_schema,
|
84
|
+
|
85
|
+
# Metadata
|
86
|
+
created_at: Time.current.iso8601,
|
87
|
+
ruby_version: RUBY_VERSION,
|
88
|
+
framework_version: SmartMessage::VERSION,
|
89
|
+
|
90
|
+
# Schema fingerprinting
|
91
|
+
schema_hash: calculate_schema_hash
|
92
|
+
}
|
93
|
+
end
|
94
|
+
|
95
|
+
private
|
96
|
+
|
97
|
+
def self.register_schema_async
|
98
|
+
# Non-blocking registration to avoid slowing class loading
|
99
|
+
Thread.new do
|
100
|
+
begin
|
101
|
+
SmartMessage::SchemaRegistry.register(self)
|
102
|
+
logger.debug "[SmartMessage] Registered schema: #{name} v#{version || 1}"
|
103
|
+
rescue => e
|
104
|
+
logger.warn "[SmartMessage] Failed to register schema for #{name}: #{e.message}"
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def self.extract_properties_schema
|
110
|
+
return [] unless respond_to?(:properties)
|
111
|
+
|
112
|
+
properties.map do |name, opts|
|
113
|
+
{
|
114
|
+
name: name.to_s,
|
115
|
+
type: opts[:type]&.name || infer_property_type(name),
|
116
|
+
required: opts[:required] || false,
|
117
|
+
default: opts[:default],
|
118
|
+
description: property_description(name),
|
119
|
+
validation_rule: extract_property_validation(name),
|
120
|
+
constraints: extract_property_constraints(name)
|
121
|
+
}
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def self.extract_configuration_schema
|
126
|
+
{
|
127
|
+
transport: transport.class.name,
|
128
|
+
serializer: serializer.class.name,
|
129
|
+
logger: logger.class.name,
|
130
|
+
plugins: extract_plugin_configuration
|
131
|
+
}
|
132
|
+
rescue
|
133
|
+
{}
|
134
|
+
end
|
135
|
+
|
136
|
+
def self.calculate_schema_hash
|
137
|
+
schema_content = {
|
138
|
+
name: name,
|
139
|
+
version: version || 1,
|
140
|
+
properties: properties&.keys&.sort,
|
141
|
+
validations: extract_validation_keys
|
142
|
+
}
|
143
|
+
Digest::SHA256.hexdigest(schema_content.to_json)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
```
|
147
|
+
|
148
|
+
### Schema Registry Implementation
|
149
|
+
|
150
|
+
```ruby
|
151
|
+
module SmartMessage
|
152
|
+
class SchemaRegistry
|
153
|
+
extend self
|
154
|
+
|
155
|
+
def register(message_class)
|
156
|
+
return unless database_available?
|
157
|
+
|
158
|
+
schema = message_class.serialize_schema
|
159
|
+
|
160
|
+
connection_pool.with do |conn|
|
161
|
+
# Check if schema already exists with same hash
|
162
|
+
existing = conn.execute(<<~SQL, [schema[:class_name], schema[:schema_hash]]).first
|
163
|
+
SELECT id FROM smart_message_schemas
|
164
|
+
WHERE class_name = $1 AND schema_hash = $2 AND status = 'active'
|
165
|
+
SQL
|
166
|
+
|
167
|
+
return if existing # Schema already registered
|
168
|
+
|
169
|
+
# Find parent schema (previous version)
|
170
|
+
parent = find_parent_schema(schema[:class_name], schema[:class_version])
|
171
|
+
|
172
|
+
# Insert new schema
|
173
|
+
conn.execute(<<~SQL, schema_insert_params(schema, parent))
|
174
|
+
INSERT INTO smart_message_schemas (
|
175
|
+
class_name, class_version, class_description, schema_definition,
|
176
|
+
properties_schema, validations_schema, configuration_schema,
|
177
|
+
registered_by, deployment_environment, ruby_version, framework_version,
|
178
|
+
parent_schema_id, schema_hash, schema_fingerprint
|
179
|
+
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)
|
180
|
+
SQL
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
# Schema evolution: compare versions
|
185
|
+
def schema_evolution(class_name)
|
186
|
+
connection_pool.with do |conn|
|
187
|
+
conn.execute(<<~SQL, [class_name])
|
188
|
+
WITH RECURSIVE evolution AS (
|
189
|
+
-- Start with latest version
|
190
|
+
SELECT id, class_name, class_version, parent_schema_id,
|
191
|
+
schema_hash, registered_at, 0 as depth
|
192
|
+
FROM smart_message_schemas
|
193
|
+
WHERE class_name = $1 AND status = 'active'
|
194
|
+
ORDER BY class_version DESC LIMIT 1
|
195
|
+
|
196
|
+
UNION ALL
|
197
|
+
|
198
|
+
-- Follow parent chain
|
199
|
+
SELECT s.id, s.class_name, s.class_version, s.parent_schema_id,
|
200
|
+
s.schema_hash, s.registered_at, e.depth + 1
|
201
|
+
FROM smart_message_schemas s
|
202
|
+
JOIN evolution e ON s.id = e.parent_schema_id
|
203
|
+
)
|
204
|
+
SELECT * FROM evolution ORDER BY depth ASC;
|
205
|
+
SQL
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
private
|
210
|
+
|
211
|
+
def database_available?
|
212
|
+
defined?(SmartMessage::Transport::DatabaseTransport) &&
|
213
|
+
SmartMessage.configuration.schema_registry_enabled?
|
214
|
+
end
|
215
|
+
|
216
|
+
def connection_pool
|
217
|
+
@connection_pool ||= SmartMessage::Transport::DatabaseTransport.default.connection_pool
|
218
|
+
end
|
219
|
+
|
220
|
+
def find_parent_schema(class_name, class_version)
|
221
|
+
return nil if class_version <= 1
|
222
|
+
|
223
|
+
connection_pool.with do |conn|
|
224
|
+
conn.execute(<<~SQL, [class_name, class_version - 1]).first
|
225
|
+
SELECT id FROM smart_message_schemas
|
226
|
+
WHERE class_name = $1 AND class_version = $2 AND status = 'active'
|
227
|
+
ORDER BY registered_at DESC LIMIT 1
|
228
|
+
SQL
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
def schema_insert_params(schema, parent)
|
233
|
+
[
|
234
|
+
schema[:class_name],
|
235
|
+
schema[:class_version],
|
236
|
+
schema[:class_description],
|
237
|
+
schema.to_json,
|
238
|
+
schema[:properties_schema].to_json,
|
239
|
+
schema[:validations_schema]&.to_json,
|
240
|
+
schema[:configuration_schema]&.to_json,
|
241
|
+
SmartMessage.configuration.service_name || 'unknown',
|
242
|
+
SmartMessage.configuration.environment || 'unknown',
|
243
|
+
schema[:ruby_version],
|
244
|
+
schema[:framework_version],
|
245
|
+
parent&.fetch('id'),
|
246
|
+
schema[:schema_hash],
|
247
|
+
schema[:schema_hash][0..7] # First 8 chars as fingerprint
|
248
|
+
]
|
249
|
+
end
|
250
|
+
end
|
251
|
+
end
|
252
|
+
```
|
253
|
+
|
254
|
+
### Schema Evolution Tracking
|
255
|
+
|
256
|
+
```ruby
|
257
|
+
# See how OrderMessage has evolved over time
|
258
|
+
evolution = SmartMessage::SchemaRegistry.schema_evolution('OrderMessage')
|
259
|
+
|
260
|
+
evolution.each do |version_info|
|
261
|
+
puts "Version #{version_info['class_version']}: #{version_info['registered_at']}"
|
262
|
+
puts " Hash: #{version_info['schema_hash'][0..7]}..."
|
263
|
+
puts " Parent: #{version_info['parent_schema_id']}"
|
264
|
+
end
|
265
|
+
|
266
|
+
# Compare schemas between versions
|
267
|
+
def compare_schemas(class_name, version1, version2)
|
268
|
+
schema1 = fetch_schema(class_name, version1)
|
269
|
+
schema2 = fetch_schema(class_name, version2)
|
270
|
+
|
271
|
+
{
|
272
|
+
properties_added: schema2['properties'] - schema1['properties'],
|
273
|
+
properties_removed: schema1['properties'] - schema2['properties'],
|
274
|
+
validations_changed: schema1['validations'] != schema2['validations']
|
275
|
+
}
|
276
|
+
end
|
277
|
+
```
|
278
|
+
|
279
|
+
### JSON Schema Representation
|
280
|
+
|
281
|
+
SmartMessage classes can be represented as standard JSON Schema documents, providing interoperability with other systems and languages.
|
282
|
+
|
283
|
+
```ruby
|
284
|
+
class Base < Hashie::Dash
|
285
|
+
def self.to_json_schema
|
286
|
+
{
|
287
|
+
"$schema" => "https://json-schema.org/draft/2020-12/schema",
|
288
|
+
"$id" => "https://smartmessage.io/schemas/#{name.underscore}/v#{version || 1}",
|
289
|
+
|
290
|
+
"title" => name,
|
291
|
+
"description" => description || "#{name} message class",
|
292
|
+
"type" => "object",
|
293
|
+
"version" => version || 1,
|
294
|
+
|
295
|
+
# Message metadata
|
296
|
+
"x-smart-message" => {
|
297
|
+
"class_name" => name,
|
298
|
+
"version" => version || 1,
|
299
|
+
"transport" => transport&.class&.name,
|
300
|
+
"serializer" => serializer&.class&.name,
|
301
|
+
"registered_at" => Time.current.iso8601,
|
302
|
+
"ruby_version" => RUBY_VERSION,
|
303
|
+
"framework_version" => SmartMessage::VERSION
|
304
|
+
},
|
305
|
+
|
306
|
+
# Properties with descriptions and validations
|
307
|
+
"properties" => properties_to_json_schema,
|
308
|
+
"required" => extract_required_properties,
|
309
|
+
"additionalProperties" => false
|
310
|
+
}
|
311
|
+
end
|
312
|
+
|
313
|
+
private
|
314
|
+
|
315
|
+
def self.properties_to_json_schema
|
316
|
+
return {} unless respond_to?(:properties)
|
317
|
+
|
318
|
+
schema_props = {}
|
319
|
+
|
320
|
+
properties.each do |name, opts|
|
321
|
+
prop_schema = {
|
322
|
+
"description" => property_description(name) || "Property: #{name}"
|
323
|
+
}
|
324
|
+
|
325
|
+
# Infer JSON Schema type from Ruby type
|
326
|
+
if opts[:type]
|
327
|
+
prop_schema["type"] = ruby_to_json_type(opts[:type])
|
328
|
+
end
|
329
|
+
|
330
|
+
# Add validation constraints
|
331
|
+
if validation = property_validation(name)
|
332
|
+
case validation
|
333
|
+
when Regexp
|
334
|
+
prop_schema["pattern"] = validation.source
|
335
|
+
when Range
|
336
|
+
prop_schema["minimum"] = validation.min if validation.min
|
337
|
+
prop_schema["maximum"] = validation.max if validation.max
|
338
|
+
when Array
|
339
|
+
prop_schema["enum"] = validation
|
340
|
+
when Proc, Method
|
341
|
+
prop_schema["x-custom-validation"] = validation.source_location.join(":")
|
342
|
+
end
|
343
|
+
end
|
344
|
+
|
345
|
+
# Add default value if present
|
346
|
+
if opts[:default]
|
347
|
+
prop_schema["default"] = opts[:default]
|
348
|
+
end
|
349
|
+
|
350
|
+
# Add format hints for common patterns
|
351
|
+
if name.to_s.include?("email")
|
352
|
+
prop_schema["format"] = "email"
|
353
|
+
elsif name.to_s.include?("date")
|
354
|
+
prop_schema["format"] = "date-time"
|
355
|
+
elsif name.to_s.include?("url") || name.to_s.include?("uri")
|
356
|
+
prop_schema["format"] = "uri"
|
357
|
+
end
|
358
|
+
|
359
|
+
schema_props[name.to_s] = prop_schema
|
360
|
+
end
|
361
|
+
|
362
|
+
# Add SmartMessage header as a property
|
363
|
+
schema_props["_sm_header"] = {
|
364
|
+
"type" => "object",
|
365
|
+
"description" => "SmartMessage routing and metadata header",
|
366
|
+
"properties" => {
|
367
|
+
"uuid" => {"type" => "string", "format" => "uuid"},
|
368
|
+
"from" => {"type" => "string", "description" => "Message sender identifier"},
|
369
|
+
"to" => {"type" => ["string", "null"], "description" => "Message recipient identifier"},
|
370
|
+
"version" => {"type" => "integer", "description" => "Schema version"},
|
371
|
+
"published_at" => {"type" => "string", "format" => "date-time"},
|
372
|
+
"message_class" => {"type" => "string"},
|
373
|
+
"thread_id" => {"type" => ["string", "null"]},
|
374
|
+
"correlation_id" => {"type" => ["string", "null"]}
|
375
|
+
}
|
376
|
+
}
|
377
|
+
|
378
|
+
schema_props
|
379
|
+
end
|
380
|
+
|
381
|
+
def self.ruby_to_json_type(ruby_type)
|
382
|
+
case ruby_type.name
|
383
|
+
when "String" then "string"
|
384
|
+
when "Integer", "Fixnum", "Bignum" then "integer"
|
385
|
+
when "Float", "BigDecimal" then "number"
|
386
|
+
when "TrueClass", "FalseClass", "Boolean" then "boolean"
|
387
|
+
when "Array" then "array"
|
388
|
+
when "Hash" then "object"
|
389
|
+
when "NilClass" then "null"
|
390
|
+
else "string" # Default fallback
|
391
|
+
end
|
392
|
+
end
|
393
|
+
|
394
|
+
def self.extract_required_properties
|
395
|
+
return [] unless respond_to?(:properties)
|
396
|
+
|
397
|
+
properties.select { |_, opts| opts[:required] }.keys.map(&:to_s)
|
398
|
+
end
|
399
|
+
end
|
400
|
+
```
|
401
|
+
|
402
|
+
#### Example: OrderMessage as JSON Schema
|
403
|
+
|
404
|
+
```ruby
|
405
|
+
class OrderMessage < SmartMessage::Base
|
406
|
+
version 2
|
407
|
+
description "Represents a customer order in the e-commerce system"
|
408
|
+
|
409
|
+
property :order_id,
|
410
|
+
required: true,
|
411
|
+
description: "Unique identifier for the order"
|
412
|
+
|
413
|
+
property :customer_email,
|
414
|
+
required: true,
|
415
|
+
validate: /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i,
|
416
|
+
description: "Customer's email address for order notifications"
|
417
|
+
|
418
|
+
property :amount,
|
419
|
+
required: true,
|
420
|
+
type: Float,
|
421
|
+
validate: ->(v) { v > 0 },
|
422
|
+
description: "Total order amount in the specified currency"
|
423
|
+
|
424
|
+
property :currency,
|
425
|
+
default: "USD",
|
426
|
+
validate: ["USD", "EUR", "GBP", "CAD"],
|
427
|
+
description: "ISO 4217 currency code"
|
428
|
+
|
429
|
+
property :items,
|
430
|
+
type: Array,
|
431
|
+
description: "List of items in the order"
|
432
|
+
|
433
|
+
property :created_at,
|
434
|
+
type: Time,
|
435
|
+
description: "Timestamp when the order was created"
|
436
|
+
end
|
437
|
+
|
438
|
+
# Generate JSON Schema
|
439
|
+
puts JSON.pretty_generate(OrderMessage.to_json_schema)
|
440
|
+
```
|
441
|
+
|
442
|
+
Output:
|
443
|
+
```json
|
444
|
+
{
|
445
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
446
|
+
"$id": "https://smartmessage.io/schemas/order_message/v2",
|
447
|
+
"title": "OrderMessage",
|
448
|
+
"description": "Represents a customer order in the e-commerce system",
|
449
|
+
"type": "object",
|
450
|
+
"version": 2,
|
451
|
+
"x-smart-message": {
|
452
|
+
"class_name": "OrderMessage",
|
453
|
+
"version": 2,
|
454
|
+
"transport": "SmartMessage::Transport::RabbitMQ",
|
455
|
+
"serializer": "SmartMessage::Serializer::JSON",
|
456
|
+
"registered_at": "2024-01-15T10:30:00Z",
|
457
|
+
"ruby_version": "3.2.0",
|
458
|
+
"framework_version": "1.0.0"
|
459
|
+
},
|
460
|
+
"properties": {
|
461
|
+
"order_id": {
|
462
|
+
"description": "Unique identifier for the order",
|
463
|
+
"type": "string"
|
464
|
+
},
|
465
|
+
"customer_email": {
|
466
|
+
"description": "Customer's email address for order notifications",
|
467
|
+
"type": "string",
|
468
|
+
"format": "email",
|
469
|
+
"pattern": "[\\w+\\-.]+@[a-z\\d\\-]+(\\.[a-z\\d\\-]+)*\\.[a-z]+"
|
470
|
+
},
|
471
|
+
"amount": {
|
472
|
+
"description": "Total order amount in the specified currency",
|
473
|
+
"type": "number",
|
474
|
+
"x-custom-validation": "order_message.rb:15"
|
475
|
+
},
|
476
|
+
"currency": {
|
477
|
+
"description": "ISO 4217 currency code",
|
478
|
+
"type": "string",
|
479
|
+
"default": "USD",
|
480
|
+
"enum": ["USD", "EUR", "GBP", "CAD"]
|
481
|
+
},
|
482
|
+
"items": {
|
483
|
+
"description": "List of items in the order",
|
484
|
+
"type": "array"
|
485
|
+
},
|
486
|
+
"created_at": {
|
487
|
+
"description": "Timestamp when the order was created",
|
488
|
+
"type": "string",
|
489
|
+
"format": "date-time"
|
490
|
+
},
|
491
|
+
"_sm_header": {
|
492
|
+
"type": "object",
|
493
|
+
"description": "SmartMessage routing and metadata header",
|
494
|
+
"properties": {
|
495
|
+
"uuid": {"type": "string", "format": "uuid"},
|
496
|
+
"from": {"type": "string", "description": "Message sender identifier"},
|
497
|
+
"to": {"type": ["string", "null"], "description": "Message recipient identifier"},
|
498
|
+
"version": {"type": "integer", "description": "Schema version"},
|
499
|
+
"published_at": {"type": "string", "format": "date-time"},
|
500
|
+
"message_class": {"type": "string"},
|
501
|
+
"thread_id": {"type": ["string", "null"]},
|
502
|
+
"correlation_id": {"type": ["string", "null"]}
|
503
|
+
}
|
504
|
+
}
|
505
|
+
},
|
506
|
+
"required": ["order_id", "customer_email", "amount"],
|
507
|
+
"additionalProperties": false
|
508
|
+
}
|
509
|
+
```
|
510
|
+
|
511
|
+
### JSON Schema Integration Benefits
|
512
|
+
|
513
|
+
1. **Standard Compliance**: Uses JSON Schema draft 2020-12 for maximum compatibility
|
514
|
+
2. **Rich Descriptions**: Both message-level and property-level descriptions included
|
515
|
+
3. **Validation Portability**: Validation rules translated to JSON Schema constraints
|
516
|
+
4. **Type Safety**: Ruby types mapped to JSON Schema types with format hints
|
517
|
+
5. **API Documentation**: Can generate OpenAPI/AsyncAPI specs from schemas
|
518
|
+
6. **Cross-Language Support**: Other languages can validate messages using the schema
|
519
|
+
7. **Tooling Integration**: Works with JSON Schema validators and code generators
|
520
|
+
|
521
|
+
### Dynamic Class Reconstruction from JSON Schema
|
522
|
+
|
523
|
+
SmartMessage can dynamically rebuild Ruby classes from stored JSON Schemas, enabling complete round-trip conversion:
|
524
|
+
|
525
|
+
```ruby
|
526
|
+
module SmartMessage
|
527
|
+
class SchemaRegistry
|
528
|
+
# Create a Ruby class from a JSON Schema stored in database
|
529
|
+
def self.from_json_schema(json_schema, namespace = Object)
|
530
|
+
schema = json_schema.is_a?(String) ? JSON.parse(json_schema) : json_schema
|
531
|
+
|
532
|
+
# Extract class name from schema
|
533
|
+
class_name = schema.dig("x-smart-message", "class_name") ||
|
534
|
+
schema["title"] ||
|
535
|
+
raise(ArgumentError, "No class name found in schema")
|
536
|
+
|
537
|
+
simple_class_name = class_name.split('::').last
|
538
|
+
|
539
|
+
# Create new class inheriting from SmartMessage::Base
|
540
|
+
dynamic_class = Class.new(SmartMessage::Base) do
|
541
|
+
# Set class metadata
|
542
|
+
version schema.dig("x-smart-message", "version") || schema["version"]
|
543
|
+
description schema["description"] if schema["description"]
|
544
|
+
|
545
|
+
# Configure plugins if specified
|
546
|
+
if transport_name = schema.dig("x-smart-message", "transport")
|
547
|
+
transport transport_name.constantize.new rescue nil
|
548
|
+
end
|
549
|
+
|
550
|
+
if serializer_name = schema.dig("x-smart-message", "serializer")
|
551
|
+
serializer serializer_name.constantize.new rescue nil
|
552
|
+
end
|
553
|
+
|
554
|
+
# Process properties from JSON Schema
|
555
|
+
if properties = schema["properties"]
|
556
|
+
required_fields = schema["required"] || []
|
557
|
+
|
558
|
+
properties.each do |prop_name, prop_schema|
|
559
|
+
# Skip the header property
|
560
|
+
next if prop_name == "_sm_header"
|
561
|
+
|
562
|
+
# Build property options
|
563
|
+
prop_options = {}
|
564
|
+
|
565
|
+
# Set required flag
|
566
|
+
prop_options[:required] = true if required_fields.include?(prop_name)
|
567
|
+
|
568
|
+
# Set Ruby type from JSON Schema type
|
569
|
+
if json_type = prop_schema["type"]
|
570
|
+
prop_options[:type] = json_to_ruby_type(json_type)
|
571
|
+
end
|
572
|
+
|
573
|
+
# Set default value
|
574
|
+
if prop_schema.key?("default")
|
575
|
+
prop_options[:default] = prop_schema["default"]
|
576
|
+
end
|
577
|
+
|
578
|
+
# Set description
|
579
|
+
prop_options[:description] = prop_schema["description"] if prop_schema["description"]
|
580
|
+
|
581
|
+
# Set validation from JSON Schema constraints
|
582
|
+
validation = extract_validation_from_json_schema(prop_schema)
|
583
|
+
prop_options[:validate] = validation if validation
|
584
|
+
|
585
|
+
# Set validation message if custom validation exists
|
586
|
+
if prop_schema["x-custom-validation"]
|
587
|
+
prop_options[:validation_message] = "Value failed custom validation"
|
588
|
+
end
|
589
|
+
|
590
|
+
# Define the property
|
591
|
+
property prop_name.to_sym, **prop_options
|
592
|
+
end
|
593
|
+
end
|
594
|
+
|
595
|
+
# Mark as dynamically created from JSON Schema
|
596
|
+
define_singleton_method(:dynamically_created?) { true }
|
597
|
+
define_singleton_method(:source_json_schema) { schema }
|
598
|
+
|
599
|
+
# Override to_json_schema to return the original
|
600
|
+
define_singleton_method(:to_json_schema) { schema }
|
601
|
+
end
|
602
|
+
|
603
|
+
# Set the class constant in the namespace
|
604
|
+
namespace.const_set(simple_class_name, dynamic_class)
|
605
|
+
dynamic_class
|
606
|
+
end
|
607
|
+
|
608
|
+
private
|
609
|
+
|
610
|
+
def self.json_to_ruby_type(json_type)
|
611
|
+
type_map = {
|
612
|
+
"string" => String,
|
613
|
+
"integer" => Integer,
|
614
|
+
"number" => Float,
|
615
|
+
"boolean" => TrueClass,
|
616
|
+
"array" => Array,
|
617
|
+
"object" => Hash,
|
618
|
+
"null" => NilClass
|
619
|
+
}
|
620
|
+
|
621
|
+
# Handle array of types (e.g., ["string", "null"])
|
622
|
+
if json_type.is_a?(Array)
|
623
|
+
# Find first non-null type
|
624
|
+
non_null_type = json_type.find { |t| t != "null" }
|
625
|
+
type_map[non_null_type] || String
|
626
|
+
else
|
627
|
+
type_map[json_type] || String
|
628
|
+
end
|
629
|
+
end
|
630
|
+
|
631
|
+
def self.extract_validation_from_json_schema(prop_schema)
|
632
|
+
# Enum validation
|
633
|
+
if enum_values = prop_schema["enum"]
|
634
|
+
return enum_values
|
635
|
+
end
|
636
|
+
|
637
|
+
# Pattern validation
|
638
|
+
if pattern = prop_schema["pattern"]
|
639
|
+
return Regexp.new(pattern)
|
640
|
+
end
|
641
|
+
|
642
|
+
# Range validation for numbers
|
643
|
+
if prop_schema["minimum"] || prop_schema["maximum"]
|
644
|
+
min = prop_schema["minimum"] || -Float::INFINITY
|
645
|
+
max = prop_schema["maximum"] || Float::INFINITY
|
646
|
+
return (min..max)
|
647
|
+
end
|
648
|
+
|
649
|
+
# Format-based validation
|
650
|
+
case prop_schema["format"]
|
651
|
+
when "email"
|
652
|
+
return /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i
|
653
|
+
when "uri", "url"
|
654
|
+
return /\A#{URI::regexp}\z/
|
655
|
+
when "uuid"
|
656
|
+
return /\A[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\z/i
|
657
|
+
end
|
658
|
+
|
659
|
+
nil
|
660
|
+
end
|
661
|
+
end
|
662
|
+
end
|
663
|
+
```
|
664
|
+
|
665
|
+
### Round-Trip Example
|
666
|
+
|
667
|
+
```ruby
|
668
|
+
# Original message class
|
669
|
+
class OrderMessage < SmartMessage::Base
|
670
|
+
version 2
|
671
|
+
description "Represents a customer order in the e-commerce system"
|
672
|
+
|
673
|
+
property :order_id,
|
674
|
+
required: true,
|
675
|
+
description: "Unique identifier for the order"
|
676
|
+
|
677
|
+
property :customer_email,
|
678
|
+
required: true,
|
679
|
+
validate: /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i,
|
680
|
+
description: "Customer's email address for order notifications"
|
681
|
+
|
682
|
+
property :amount,
|
683
|
+
required: true,
|
684
|
+
type: Float,
|
685
|
+
description: "Total order amount in the specified currency"
|
686
|
+
|
687
|
+
property :currency,
|
688
|
+
default: "USD",
|
689
|
+
validate: ["USD", "EUR", "GBP", "CAD"],
|
690
|
+
description: "ISO 4217 currency code"
|
691
|
+
end
|
692
|
+
|
693
|
+
# Step 1: Convert to JSON Schema and save to database
|
694
|
+
json_schema = OrderMessage.to_json_schema
|
695
|
+
connection.execute(
|
696
|
+
"INSERT INTO message_schemas (class_name, schema_json) VALUES ($1, $2)",
|
697
|
+
["OrderMessage", json_schema.to_json]
|
698
|
+
)
|
699
|
+
|
700
|
+
# Step 2: Later, retrieve and reconstruct the class
|
701
|
+
stored_schema = connection.execute(
|
702
|
+
"SELECT schema_json FROM message_schemas WHERE class_name = $1",
|
703
|
+
["OrderMessage"]
|
704
|
+
).first["schema_json"]
|
705
|
+
|
706
|
+
# Step 3: Dynamically create the class from JSON Schema
|
707
|
+
ReconstructedOrderMessage = SmartMessage::SchemaRegistry.from_json_schema(
|
708
|
+
stored_schema
|
709
|
+
)
|
710
|
+
|
711
|
+
# Step 4: Use the reconstructed class
|
712
|
+
order = ReconstructedOrderMessage.new(
|
713
|
+
order_id: "ORD-123",
|
714
|
+
customer_email: "customer@example.com",
|
715
|
+
amount: 99.99,
|
716
|
+
currency: "USD"
|
717
|
+
)
|
718
|
+
|
719
|
+
# Verify it works
|
720
|
+
order.valid? # => true
|
721
|
+
order.publish # Works with configured transport/serializer
|
722
|
+
```
|
723
|
+
|
724
|
+
### Schema Storage Enhancement
|
725
|
+
|
726
|
+
The schema registry can store both the internal format and JSON Schema:
|
727
|
+
|
728
|
+
```ruby
|
729
|
+
def self.serialize_schema
|
730
|
+
{
|
731
|
+
class_name: name,
|
732
|
+
class_version: version || 1,
|
733
|
+
class_description: description,
|
734
|
+
properties_schema: extract_properties_schema,
|
735
|
+
validations_schema: extract_validations_schema,
|
736
|
+
configuration_schema: extract_configuration_schema,
|
737
|
+
json_schema: to_json_schema, # Add JSON Schema representation
|
738
|
+
|
739
|
+
# Metadata
|
740
|
+
created_at: Time.current.iso8601,
|
741
|
+
ruby_version: RUBY_VERSION,
|
742
|
+
framework_version: SmartMessage::VERSION,
|
743
|
+
|
744
|
+
# Schema fingerprinting
|
745
|
+
schema_hash: calculate_schema_hash
|
746
|
+
}
|
747
|
+
end
|
748
|
+
```
|
749
|
+
|
750
|
+
### Dynamic Schema Management Use Cases
|
751
|
+
|
752
|
+
With bidirectional JSON Schema conversion, applications gain powerful capabilities:
|
753
|
+
|
754
|
+
```ruby
|
755
|
+
# Save any message class to database
|
756
|
+
json = MyMessage.to_json_schema
|
757
|
+
DB.execute("INSERT INTO schemas (name, definition) VALUES (?, ?)",
|
758
|
+
[MyMessage.name, json.to_json])
|
759
|
+
|
760
|
+
# Later, recreate the class without the original Ruby code
|
761
|
+
stored_json = DB.execute("SELECT definition FROM schemas WHERE name = ?",
|
762
|
+
["MyMessage"]).first
|
763
|
+
MyMessage = SmartMessage.from_json_schema(stored_json)
|
764
|
+
|
765
|
+
# The recreated class is fully functional
|
766
|
+
msg = MyMessage.new(data: "test")
|
767
|
+
msg.publish
|
768
|
+
```
|
769
|
+
|
770
|
+
#### Key Capabilities Enabled
|
771
|
+
|
772
|
+
1. **Schema Marketplace**
|
773
|
+
```ruby
|
774
|
+
# Service A publishes its message schemas
|
775
|
+
OrderMessage.to_json_schema.tap do |schema|
|
776
|
+
SchemaRegistry.publish(schema, visibility: :public)
|
777
|
+
end
|
778
|
+
|
779
|
+
# Service B discovers and uses them
|
780
|
+
available_schemas = SchemaRegistry.browse(tags: ["ecommerce"])
|
781
|
+
OrderMessage = SmartMessage.from_json_schema(available_schemas.first)
|
782
|
+
```
|
783
|
+
|
784
|
+
2. **Runtime Schema Updates**
|
785
|
+
```ruby
|
786
|
+
# Admin UI updates schema definition
|
787
|
+
updated_schema = modify_schema_via_ui(current_schema)
|
788
|
+
DB.execute("UPDATE schemas SET definition = ? WHERE name = ?",
|
789
|
+
[updated_schema.to_json, "OrderMessage"])
|
790
|
+
|
791
|
+
# Application reloads the class without restart
|
792
|
+
Object.send(:remove_const, :OrderMessage) if defined?(OrderMessage)
|
793
|
+
OrderMessage = SmartMessage.from_json_schema(updated_schema)
|
794
|
+
```
|
795
|
+
|
796
|
+
3. **Multi-Tenant Schemas**
|
797
|
+
```ruby
|
798
|
+
# Each tenant can have custom message schemas
|
799
|
+
tenant_schema = DB.execute(
|
800
|
+
"SELECT definition FROM tenant_schemas WHERE tenant_id = ? AND name = ?",
|
801
|
+
[tenant.id, "InvoiceMessage"]
|
802
|
+
).first
|
803
|
+
|
804
|
+
# Dynamically create tenant-specific class
|
805
|
+
tenant_class = SmartMessage.from_json_schema(
|
806
|
+
tenant_schema,
|
807
|
+
namespace: "Tenant#{tenant.id}".constantize
|
808
|
+
)
|
809
|
+
```
|
810
|
+
|
811
|
+
4. **Schema Versioning & Migration**
|
812
|
+
```ruby
|
813
|
+
# Store multiple versions
|
814
|
+
versions = DB.execute(
|
815
|
+
"SELECT version, definition FROM schema_versions WHERE name = ? ORDER BY version",
|
816
|
+
["PaymentMessage"]
|
817
|
+
)
|
818
|
+
|
819
|
+
# Create version-specific classes
|
820
|
+
versions.each do |row|
|
821
|
+
version_class = SmartMessage.from_json_schema(row['definition'])
|
822
|
+
const_set("PaymentMessageV#{row['version']}", version_class)
|
823
|
+
end
|
824
|
+
|
825
|
+
# Handle messages from different versions
|
826
|
+
def process_payment(raw_message)
|
827
|
+
version = raw_message['_sm_header']['version']
|
828
|
+
handler = const_get("PaymentMessageV#{version}")
|
829
|
+
handler.new(raw_message).process
|
830
|
+
end
|
831
|
+
```
|
832
|
+
|
833
|
+
5. **Schema-Driven Development**
|
834
|
+
```ruby
|
835
|
+
# Define schemas in a UI or configuration file
|
836
|
+
schema_config = YAML.load_file("message_schemas.yml")
|
837
|
+
|
838
|
+
# Generate all message classes at startup
|
839
|
+
schema_config.each do |name, definition|
|
840
|
+
json_schema = build_json_schema(definition)
|
841
|
+
const_set(name, SmartMessage.from_json_schema(json_schema))
|
842
|
+
end
|
843
|
+
|
844
|
+
# No Ruby message class files needed!
|
845
|
+
```
|
846
|
+
|
847
|
+
6. **Cross-Language Schema Sharing**
|
848
|
+
```ruby
|
849
|
+
# Export schemas for other languages
|
850
|
+
File.write("schemas/order_message.json", OrderMessage.to_json_schema.to_json)
|
851
|
+
|
852
|
+
# Python service can validate using standard JSON Schema
|
853
|
+
import jsonschema
|
854
|
+
schema = json.load(open("schemas/order_message.json"))
|
855
|
+
jsonschema.validate(message_data, schema)
|
856
|
+
|
857
|
+
# Then Ruby service can reconstruct the class
|
858
|
+
OrderMessage = SmartMessage.from_json_schema(
|
859
|
+
File.read("schemas/order_message.json")
|
860
|
+
)
|
861
|
+
```
|
862
|
+
|
863
|
+
7. **A/B Testing Message Formats**
|
864
|
+
```ruby
|
865
|
+
# Store experimental schema variants
|
866
|
+
variants = {
|
867
|
+
control: fetch_schema("OrderMessage", variant: "control"),
|
868
|
+
test: fetch_schema("OrderMessage", variant: "test")
|
869
|
+
}
|
870
|
+
|
871
|
+
# Dynamically select variant
|
872
|
+
variant = ab_test.variant_for(user)
|
873
|
+
OrderMessage = SmartMessage.from_json_schema(variants[variant])
|
874
|
+
```
|
875
|
+
|
876
|
+
8. **Schema Compliance & Governance**
|
877
|
+
```ruby
|
878
|
+
# Central schema repository with approval workflow
|
879
|
+
pending_schema = SchemaApproval.find(id).schema_definition
|
880
|
+
|
881
|
+
# Preview the schema before approval
|
882
|
+
PreviewClass = SmartMessage.from_json_schema(pending_schema)
|
883
|
+
preview_msg = PreviewClass.new(sample_data)
|
884
|
+
validate_compliance(preview_msg)
|
885
|
+
|
886
|
+
# Once approved, deploy to production
|
887
|
+
if approved?
|
888
|
+
ProductionMessage = SmartMessage.from_json_schema(pending_schema)
|
889
|
+
cache_class(ProductionMessage)
|
890
|
+
end
|
891
|
+
```
|
892
|
+
|
893
|
+
This "code as data" approach fundamentally changes how message contracts are managed, enabling:
|
894
|
+
- **No-code schema management** via UIs
|
895
|
+
- **Runtime flexibility** without deployments
|
896
|
+
- **Schema portability** across services and languages
|
897
|
+
- **Centralized governance** with distributed execution
|
898
|
+
- **Version coexistence** without code duplication
|
899
|
+
|
900
|
+
### JSON Schema vs Ruby Marshal/DRb Comparison
|
901
|
+
|
902
|
+
Ruby provides built-in serialization via Marshal and distributed communication via DRb. Here's how the JSON Schema approach differs:
|
903
|
+
|
904
|
+
#### Ruby Marshal/DRb Approach
|
905
|
+
|
906
|
+
```ruby
|
907
|
+
# Marshal serializes Ruby objects including their class definition
|
908
|
+
class OrderMessage
|
909
|
+
attr_accessor :order_id, :amount, :customer_email
|
910
|
+
|
911
|
+
def initialize(order_id, amount, customer_email)
|
912
|
+
@order_id = order_id
|
913
|
+
@amount = amount
|
914
|
+
@customer_email = customer_email
|
915
|
+
end
|
916
|
+
end
|
917
|
+
|
918
|
+
# Serialize the entire object
|
919
|
+
order = OrderMessage.new("123", 99.99, "test@example.com")
|
920
|
+
serialized = Marshal.dump(order)
|
921
|
+
|
922
|
+
# Deserialize - requires the OrderMessage class to exist
|
923
|
+
restored = Marshal.load(serialized) # Needs OrderMessage class loaded!
|
924
|
+
|
925
|
+
# DRb can share objects between Ruby processes
|
926
|
+
require 'drb/drb'
|
927
|
+
DRb.start_service("druby://localhost:8787", order)
|
928
|
+
```
|
929
|
+
|
930
|
+
#### JSON Schema Approach
|
931
|
+
|
932
|
+
```ruby
|
933
|
+
# Schema defines the structure, not the instance
|
934
|
+
schema = OrderMessage.to_json_schema # Just the schema, not data
|
935
|
+
|
936
|
+
# Store schema separately from instances
|
937
|
+
DB.save_schema(schema)
|
938
|
+
|
939
|
+
# Later, recreate the class definition itself
|
940
|
+
OrderMessage = SmartMessage.from_json_schema(schema)
|
941
|
+
|
942
|
+
# Now create instances
|
943
|
+
order = OrderMessage.new(order_id: "123", amount: 99.99)
|
944
|
+
```
|
945
|
+
|
946
|
+
#### Key Differences
|
947
|
+
|
948
|
+
| Aspect | Marshal/DRb | JSON Schema |
|
949
|
+
|--------|------------|-------------|
|
950
|
+
| **What's Serialized** | Object instances with data | Class structure/definition |
|
951
|
+
| **Cross-Language** | Ruby only | Any language supporting JSON Schema |
|
952
|
+
| **Schema Evolution** | Breaks on class changes | Versions tracked explicitly |
|
953
|
+
| **Security** | Can execute arbitrary code | Safe, declarative only |
|
954
|
+
| **Storage Size** | Includes Ruby internals | Compact, standard JSON |
|
955
|
+
| **Validation** | None built-in | JSON Schema validation |
|
956
|
+
| **Documentation** | Not included | Descriptions embedded |
|
957
|
+
| **Human Readable** | Binary format | Plain JSON |
|
958
|
+
| **Class Required** | Must exist before deserializing | Creates class from schema |
|
959
|
+
|
960
|
+
#### When to Use Each
|
961
|
+
|
962
|
+
**Use Marshal/DRb when:**
|
963
|
+
- Working exclusively in Ruby ecosystem
|
964
|
+
- Need to serialize complex object graphs
|
965
|
+
- Want to preserve exact Ruby object state
|
966
|
+
- Building Ruby-only distributed systems
|
967
|
+
- Performance is critical (binary is faster)
|
968
|
+
|
969
|
+
**Use JSON Schema when:**
|
970
|
+
- Need cross-language compatibility
|
971
|
+
- Want human-readable, editable schemas
|
972
|
+
- Building microservices in multiple languages
|
973
|
+
- Need schema versioning and evolution
|
974
|
+
- Want to generate documentation
|
975
|
+
- Security is a concern (no code execution)
|
976
|
+
- Building schema management tools/UIs
|
977
|
+
|
978
|
+
#### Hybrid Approach
|
979
|
+
|
980
|
+
SmartMessage can actually use both:
|
981
|
+
|
982
|
+
```ruby
|
983
|
+
# Use JSON Schema for class definition
|
984
|
+
OrderMessage = SmartMessage.from_json_schema(stored_schema)
|
985
|
+
|
986
|
+
# Use Marshal for high-performance Ruby-to-Ruby communication
|
987
|
+
order = OrderMessage.new(data)
|
988
|
+
Marshal.dump(order) # Fast binary serialization of instances
|
989
|
+
|
990
|
+
# Or use JSON for cross-language communication
|
991
|
+
order.to_json # Standard JSON for other languages
|
992
|
+
|
993
|
+
# Schema stays portable while instances can use optimal serialization
|
994
|
+
```
|
995
|
+
|
996
|
+
#### Security Consideration
|
997
|
+
|
998
|
+
```ruby
|
999
|
+
# Marshal can execute code - DANGEROUS with untrusted data
|
1000
|
+
class EvilClass
|
1001
|
+
def marshal_load(data)
|
1002
|
+
system("rm -rf /") # This would execute!
|
1003
|
+
end
|
1004
|
+
end
|
1005
|
+
|
1006
|
+
Marshal.load(untrusted_data) # Security risk!
|
1007
|
+
|
1008
|
+
# JSON Schema is safe - it's just data
|
1009
|
+
SmartMessage.from_json_schema(untrusted_schema) # Safe, no code execution
|
1010
|
+
# Worst case: invalid schema that fails to create a working class
|
1011
|
+
```
|
1012
|
+
|
1013
|
+
The JSON Schema approach is **better for**:
|
1014
|
+
- Schema management and governance
|
1015
|
+
- Cross-language systems
|
1016
|
+
- API documentation
|
1017
|
+
- Security-sensitive environments
|
1018
|
+
- Long-term schema evolution
|
1019
|
+
|
1020
|
+
The Marshal/DRb approach is **better for**:
|
1021
|
+
- Pure Ruby systems
|
1022
|
+
- High-performance requirements
|
1023
|
+
- Complex object graphs
|
1024
|
+
- Temporary serialization
|
1025
|
+
- Ruby-specific features
|
1026
|
+
|
1027
|
+
They solve different problems: Marshal serializes **instances**, JSON Schema serializes **class definitions**
|
1028
|
+
|
1029
|
+
### Cross-Language Interoperability with JSON Schema
|
1030
|
+
|
1031
|
+
The JSON Schema approach enables true cross-language message contracts. Any language can consume SmartMessage schemas and generate equivalent message classes:
|
1032
|
+
|
1033
|
+
#### Rust Implementation
|
1034
|
+
|
1035
|
+
```rust
|
1036
|
+
// Rust can generate structs from JSON Schema using schemars/serde
|
1037
|
+
use serde::{Deserialize, Serialize};
|
1038
|
+
use schemars::JsonSchema;
|
1039
|
+
use serde_json::Value;
|
1040
|
+
use validator::Validate;
|
1041
|
+
|
1042
|
+
// Generated from SmartMessage JSON Schema
|
1043
|
+
#[derive(Debug, Serialize, Deserialize, JsonSchema, Validate)]
|
1044
|
+
pub struct OrderMessage {
|
1045
|
+
#[serde(rename = "_sm_header")]
|
1046
|
+
pub sm_header: SmartMessageHeader,
|
1047
|
+
|
1048
|
+
#[validate(length(min = 1))]
|
1049
|
+
pub order_id: String,
|
1050
|
+
|
1051
|
+
#[validate(email)]
|
1052
|
+
pub customer_email: String,
|
1053
|
+
|
1054
|
+
#[validate(range(min = 0.01))]
|
1055
|
+
pub amount: f64,
|
1056
|
+
|
1057
|
+
#[serde(default = "default_currency")]
|
1058
|
+
#[validate(custom = "validate_currency")]
|
1059
|
+
pub currency: String,
|
1060
|
+
|
1061
|
+
pub items: Vec<OrderItem>,
|
1062
|
+
|
1063
|
+
pub created_at: chrono::DateTime<chrono::Utc>,
|
1064
|
+
}
|
1065
|
+
|
1066
|
+
// Rust macro to generate from JSON Schema at compile time
|
1067
|
+
use json_schema_to_rust::generate_struct;
|
1068
|
+
generate_struct!("schemas/order_message.json");
|
1069
|
+
|
1070
|
+
// Or runtime generation using a schema loader
|
1071
|
+
pub fn load_message_schema(schema_json: &str) -> Result<MessageType, Error> {
|
1072
|
+
let schema: Value = serde_json::from_str(schema_json)?;
|
1073
|
+
// Generate validation functions from schema
|
1074
|
+
let validator = JSONSchema::compile(&schema)?;
|
1075
|
+
|
1076
|
+
MessageType::new(schema, validator)
|
1077
|
+
}
|
1078
|
+
```
|
1079
|
+
|
1080
|
+
#### Python Implementation
|
1081
|
+
|
1082
|
+
```python
|
1083
|
+
# Python can use jsonschema and dataclasses
|
1084
|
+
from dataclasses import dataclass, field
|
1085
|
+
from typing import List, Optional
|
1086
|
+
from datetime import datetime
|
1087
|
+
import jsonschema
|
1088
|
+
from dataclasses_jsonschema import JsonSchemaMixin
|
1089
|
+
|
1090
|
+
# Generate from SmartMessage JSON Schema
|
1091
|
+
@dataclass
|
1092
|
+
class OrderMessage(JsonSchemaMixin):
|
1093
|
+
"""Generated from SmartMessage schema"""
|
1094
|
+
_sm_header: SmartMessageHeader
|
1095
|
+
order_id: str
|
1096
|
+
customer_email: str
|
1097
|
+
amount: float
|
1098
|
+
currency: str = "USD"
|
1099
|
+
items: List[OrderItem] = field(default_factory=list)
|
1100
|
+
created_at: datetime = field(default_factory=datetime.now)
|
1101
|
+
|
1102
|
+
def validate(self):
|
1103
|
+
schema = self.json_schema()
|
1104
|
+
jsonschema.validate(self.to_dict(), schema)
|
1105
|
+
|
1106
|
+
@classmethod
|
1107
|
+
def from_json_schema(cls, schema_path: str):
|
1108
|
+
"""Dynamically create class from JSON Schema"""
|
1109
|
+
with open(schema_path) as f:
|
1110
|
+
schema = json.load(f)
|
1111
|
+
|
1112
|
+
# Use pydantic for dynamic model generation
|
1113
|
+
from pydantic import create_model
|
1114
|
+
return create_model(
|
1115
|
+
schema['title'],
|
1116
|
+
**parse_schema_properties(schema['properties'])
|
1117
|
+
)
|
1118
|
+
```
|
1119
|
+
|
1120
|
+
#### TypeScript/JavaScript Implementation
|
1121
|
+
|
1122
|
+
```typescript
|
1123
|
+
// TypeScript can generate interfaces from JSON Schema
|
1124
|
+
import { FromSchema } from "json-schema-to-ts";
|
1125
|
+
import Ajv from "ajv";
|
1126
|
+
|
1127
|
+
// Type generated from JSON Schema at compile time
|
1128
|
+
type OrderMessage = FromSchema<typeof orderMessageSchema>;
|
1129
|
+
|
1130
|
+
// Or use json-schema-to-typescript for code generation
|
1131
|
+
import { compile } from 'json-schema-to-typescript';
|
1132
|
+
|
1133
|
+
async function generateFromSmartMessage(schemaJson: string) {
|
1134
|
+
const ts = await compile(JSON.parse(schemaJson), 'OrderMessage');
|
1135
|
+
// Generates TypeScript interface code
|
1136
|
+
}
|
1137
|
+
|
1138
|
+
// Runtime validation using the same schema
|
1139
|
+
class SmartMessage<T> {
|
1140
|
+
private ajv = new Ajv();
|
1141
|
+
private validator: any;
|
1142
|
+
|
1143
|
+
constructor(private schema: object) {
|
1144
|
+
this.validator = this.ajv.compile(schema);
|
1145
|
+
}
|
1146
|
+
|
1147
|
+
create(data: unknown): T {
|
1148
|
+
if (!this.validator(data)) {
|
1149
|
+
throw new Error(this.validator.errors);
|
1150
|
+
}
|
1151
|
+
return data as T;
|
1152
|
+
}
|
1153
|
+
}
|
1154
|
+
|
1155
|
+
// Use the SmartMessage schema directly
|
1156
|
+
const OrderMessage = new SmartMessage<OrderMessageType>(
|
1157
|
+
await fetch('/schemas/order_message.json').then(r => r.json())
|
1158
|
+
);
|
1159
|
+
```
|
1160
|
+
|
1161
|
+
#### Go Implementation
|
1162
|
+
|
1163
|
+
```go
|
1164
|
+
// Go can generate structs from JSON Schema
|
1165
|
+
package messages
|
1166
|
+
|
1167
|
+
import (
|
1168
|
+
"github.com/xeipuuv/gojsonschema"
|
1169
|
+
"encoding/json"
|
1170
|
+
)
|
1171
|
+
|
1172
|
+
// Generated from SmartMessage JSON Schema using go-jsonschema
|
1173
|
+
type OrderMessage struct {
|
1174
|
+
SmHeader SmartMessageHeader `json:"_sm_header"`
|
1175
|
+
OrderID string `json:"order_id" validate:"required"`
|
1176
|
+
CustomerEmail string `json:"customer_email" validate:"required,email"`
|
1177
|
+
Amount float64 `json:"amount" validate:"required,min=0.01"`
|
1178
|
+
Currency string `json:"currency" default:"USD"`
|
1179
|
+
Items []OrderItem `json:"items"`
|
1180
|
+
CreatedAt time.Time `json:"created_at"`
|
1181
|
+
}
|
1182
|
+
|
1183
|
+
// Validate using the JSON Schema
|
1184
|
+
func (m *OrderMessage) Validate() error {
|
1185
|
+
schemaLoader := gojsonschema.NewReferenceLoader("file://./schemas/order_message.json")
|
1186
|
+
documentLoader := gojsonschema.NewGoLoader(m)
|
1187
|
+
|
1188
|
+
result, err := gojsonschema.Validate(schemaLoader, documentLoader)
|
1189
|
+
if err != nil {
|
1190
|
+
return err
|
1191
|
+
}
|
1192
|
+
|
1193
|
+
if !result.Valid() {
|
1194
|
+
return fmt.Errorf("validation failed: %v", result.Errors())
|
1195
|
+
}
|
1196
|
+
return nil
|
1197
|
+
}
|
1198
|
+
```
|
1199
|
+
|
1200
|
+
#### Java Implementation
|
1201
|
+
|
1202
|
+
```java
|
1203
|
+
// Java can use jsonschema2pojo or similar tools
|
1204
|
+
import com.fasterxml.jackson.annotation.JsonProperty;
|
1205
|
+
import javax.validation.constraints.*;
|
1206
|
+
import com.networknt.schema.JsonSchema;
|
1207
|
+
import com.networknt.schema.JsonSchemaFactory;
|
1208
|
+
|
1209
|
+
// Generated from SmartMessage JSON Schema
|
1210
|
+
public class OrderMessage {
|
1211
|
+
@JsonProperty("_sm_header")
|
1212
|
+
private SmartMessageHeader smHeader;
|
1213
|
+
|
1214
|
+
@NotBlank
|
1215
|
+
private String orderId;
|
1216
|
+
|
1217
|
+
@NotBlank
|
1218
|
+
@Email
|
1219
|
+
private String customerEmail;
|
1220
|
+
|
1221
|
+
@NotNull
|
1222
|
+
@DecimalMin("0.01")
|
1223
|
+
private BigDecimal amount;
|
1224
|
+
|
1225
|
+
@Pattern(regexp = "USD|EUR|GBP|CAD")
|
1226
|
+
private String currency = "USD";
|
1227
|
+
|
1228
|
+
private List<OrderItem> items;
|
1229
|
+
|
1230
|
+
private Instant createdAt;
|
1231
|
+
|
1232
|
+
// Validate against JSON Schema
|
1233
|
+
public void validate() throws ValidationException {
|
1234
|
+
JsonSchemaFactory factory = JsonSchemaFactory.getInstance();
|
1235
|
+
JsonSchema schema = factory.getSchema(
|
1236
|
+
getClass().getResourceAsStream("/schemas/order_message.json")
|
1237
|
+
);
|
1238
|
+
|
1239
|
+
Set<ValidationMessage> errors = schema.validate(this.toJson());
|
1240
|
+
if (!errors.isEmpty()) {
|
1241
|
+
throw new ValidationException(errors.toString());
|
1242
|
+
}
|
1243
|
+
}
|
1244
|
+
}
|
1245
|
+
```
|
1246
|
+
|
1247
|
+
#### Shared Schema Registry
|
1248
|
+
|
1249
|
+
```yaml
|
1250
|
+
# All services can share schemas via a central registry
|
1251
|
+
services:
|
1252
|
+
ruby_service:
|
1253
|
+
language: ruby
|
1254
|
+
schema_fetch: |
|
1255
|
+
schema = fetch_schema("OrderMessage")
|
1256
|
+
OrderMessage = SmartMessage.from_json_schema(schema)
|
1257
|
+
|
1258
|
+
rust_service:
|
1259
|
+
language: rust
|
1260
|
+
schema_fetch: |
|
1261
|
+
let schema = fetch_schema("OrderMessage")?;
|
1262
|
+
generate_struct!(schema);
|
1263
|
+
|
1264
|
+
python_service:
|
1265
|
+
language: python
|
1266
|
+
schema_fetch: |
|
1267
|
+
schema = fetch_schema("OrderMessage")
|
1268
|
+
OrderMessage = create_model_from_schema(schema)
|
1269
|
+
|
1270
|
+
node_service:
|
1271
|
+
language: typescript
|
1272
|
+
schema_fetch: |
|
1273
|
+
const schema = await fetchSchema("OrderMessage");
|
1274
|
+
const OrderMessage = new SmartMessage(schema);
|
1275
|
+
```
|
1276
|
+
|
1277
|
+
#### Key Cross-Language Benefits
|
1278
|
+
|
1279
|
+
1. **Single Source of Truth**: One schema defines the contract for all languages
|
1280
|
+
2. **Automatic Code Generation**: Most languages have JSON Schema → code generators
|
1281
|
+
3. **Consistent Validation**: All services validate messages the same way
|
1282
|
+
4. **Type Safety**: Strongly-typed languages get compile-time checking
|
1283
|
+
5. **Documentation**: Schema includes descriptions for all languages
|
1284
|
+
6. **Evolution Tracking**: Version changes are visible to all services
|
1285
|
+
7. **No Manual Sync**: Changes propagate automatically through the schema
|
1286
|
+
|
1287
|
+
#### Example: Polyglot Microservices
|
1288
|
+
|
1289
|
+
```ruby
|
1290
|
+
# Ruby service publishes schema
|
1291
|
+
OrderMessage.to_json_schema.tap do |schema|
|
1292
|
+
Redis.set("schema:OrderMessage:v2", schema.to_json)
|
1293
|
+
AMQP.publish("schema.updated", {name: "OrderMessage", version: 2})
|
1294
|
+
end
|
1295
|
+
|
1296
|
+
# Rust service receives update and regenerates
|
1297
|
+
let schema_json = redis.get("schema:OrderMessage:v2")?;
|
1298
|
+
rebuild_message_types(schema_json);
|
1299
|
+
|
1300
|
+
# Python service validates incoming message
|
1301
|
+
schema = redis.get("schema:OrderMessage:v2")
|
1302
|
+
validator = Draft7Validator(json.loads(schema))
|
1303
|
+
validator.validate(incoming_message)
|
1304
|
+
|
1305
|
+
# All services stay in sync automatically!
|
1306
|
+
```
|
1307
|
+
|
1308
|
+
This enables true **polyglot microservices** where:
|
1309
|
+
- Ruby defines the canonical message schema
|
1310
|
+
- Rust gets memory-safe, zero-cost abstractions
|
1311
|
+
- Python gets dynamic typing with validation
|
1312
|
+
- TypeScript gets compile-time type checking
|
1313
|
+
- Go gets efficient JSON marshaling
|
1314
|
+
- Java gets enterprise integration
|
1315
|
+
|
1316
|
+
All from the same SmartMessage JSON Schema!
|
1317
|
+
|
1318
|
+
### Implementation Roadmap
|
1319
|
+
|
1320
|
+
To implement this schema registry system:
|
1321
|
+
|
1322
|
+
1. **Phase 1: Core Schema Generation**
|
1323
|
+
- Add `to_json_schema` method to SmartMessage::Base
|
1324
|
+
- Include property descriptions and validations
|
1325
|
+
- Map Ruby types to JSON Schema types
|
1326
|
+
- Generate standard-compliant JSON Schema documents
|
1327
|
+
|
1328
|
+
2. **Phase 2: Schema Persistence**
|
1329
|
+
- Create database table for schema storage
|
1330
|
+
- Implement automatic registration hooks
|
1331
|
+
- Add schema versioning and evolution tracking
|
1332
|
+
- Store both internal format and JSON Schema
|
1333
|
+
|
1334
|
+
3. **Phase 3: Dynamic Class Creation**
|
1335
|
+
- Implement `from_json_schema` method
|
1336
|
+
- Support validation reconstruction from JSON Schema
|
1337
|
+
- Enable round-trip conversion (Ruby → JSON Schema → Ruby)
|
1338
|
+
- Preserve transport and serializer configuration
|
1339
|
+
|
1340
|
+
4. **Phase 4: Cross-Language Support**
|
1341
|
+
- Document schema consumption patterns for each language
|
1342
|
+
- Create example implementations for Rust, Python, TypeScript, Go, Java
|
1343
|
+
- Build shared schema registry for polyglot services
|
1344
|
+
- Enable automatic schema synchronization
|
1345
|
+
|
1346
|
+
5. **Phase 5: Advanced Features**
|
1347
|
+
- Schema marketplace for sharing between services
|
1348
|
+
- Runtime schema updates without deployment
|
1349
|
+
- Multi-tenant schema support
|
1350
|
+
- A/B testing of message formats
|
1351
|
+
- Schema compliance and governance tools
|
1352
|
+
|
1353
|
+
### Summary
|
1354
|
+
|
1355
|
+
The SmartMessage Schema Registry transforms message definitions from static code into dynamic, manageable data. By leveraging JSON Schema as a universal contract language, it enables:
|
1356
|
+
|
1357
|
+
- **Code as Data**: Message schemas become first-class data entities that can be stored, versioned, and managed independently of application code
|
1358
|
+
- **Polyglot Interoperability**: Any language can consume and implement SmartMessage contracts through standard JSON Schema
|
1359
|
+
- **Runtime Flexibility**: Classes can be created, updated, and versioned without code deployment
|
1360
|
+
- **Schema Governance**: Central management with distributed execution across services
|
1361
|
+
- **Zero-Code Development**: Business users can define message schemas through UIs without writing Ruby code
|
1362
|
+
|
1363
|
+
This approach fundamentally shifts how distributed systems manage message contracts, providing the flexibility of dynamic languages with the safety of schema validation, all while maintaining cross-language compatibility through industry-standard JSON Schema.
|
1364
|
+
|
1365
|
+
### Benefits
|
1366
|
+
|
1367
|
+
1. **Automatic Registration**: Schemas are captured automatically when classes load
|
1368
|
+
2. **Schema Evolution**: Track how message formats change over time
|
1369
|
+
3. **Change Detection**: SHA256 hashing detects any schema modifications
|
1370
|
+
4. **Audit Trail**: Complete history of all message schemas for compliance
|
1371
|
+
5. **Version Management**: Track parent-child relationships between versions
|
1372
|
+
6. **Environment Tracking**: Know which schemas are used in which environments
|
1373
|
+
7. **Performance**: Indexed for fast lookups and discovery operations
|
1374
|
+
8. **JSON Schema Export**: Standard format for cross-platform compatibility
|
1375
|
+
9. **Rich Documentation**: Descriptions at message and property levels
|
1376
|
+
10. **Dynamic Class Creation**: Rebuild Ruby classes from stored schemas
|
1377
|
+
11. **Cross-Language Support**: Generate equivalent classes in any language
|
1378
|
+
12. **Runtime Updates**: Modify schemas without redeploying code
|
1379
|
+
13. **Schema Marketplace**: Share and discover schemas across services
|
1380
|
+
14. **Security**: Safe schema sharing without code execution risks
|
1381
|
+
15. **Governance**: Centralized schema management with approval workflows
|