smart_message 0.0.12 → 0.0.13
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/CHANGELOG.md +35 -1
- data/Gemfile.lock +5 -5
- data/docs/core-concepts/architecture.md +5 -10
- data/docs/getting-started/examples.md +0 -12
- data/docs/getting-started/quick-start.md +4 -9
- data/docs/index.md +4 -4
- data/docs/reference/serializers.md +160 -488
- data/docs/reference/transports.md +1 -125
- data/docs/transports/redis-transport-comparison.md +215 -350
- data/docs/transports/redis-transport.md +3 -22
- data/examples/README.md +6 -9
- data/examples/city_scenario/README.md +1 -1
- data/examples/city_scenario/messages/emergency_911_message.rb +0 -1
- data/examples/city_scenario/messages/emergency_resolved_message.rb +0 -1
- data/examples/city_scenario/messages/fire_dispatch_message.rb +0 -1
- data/examples/city_scenario/messages/fire_emergency_message.rb +0 -1
- data/examples/city_scenario/messages/health_check_message.rb +0 -1
- data/examples/city_scenario/messages/health_status_message.rb +0 -1
- data/examples/city_scenario/messages/police_dispatch_message.rb +0 -1
- data/examples/city_scenario/messages/silent_alarm_message.rb +0 -1
- data/examples/memory/01_message_deduplication_demo.rb +0 -2
- data/examples/memory/02_dead_letter_queue_demo.rb +0 -3
- data/examples/memory/03_point_to_point_orders.rb +0 -2
- data/examples/memory/04_publish_subscribe_events.rb +0 -1
- data/examples/memory/05_many_to_many_chat.rb +0 -3
- data/examples/memory/07_proc_handlers_demo.rb +0 -1
- data/examples/memory/08_custom_logger_demo.rb +0 -4
- data/examples/memory/09_error_handling_demo.rb +0 -3
- data/examples/memory/10_entity_addressing_basic.rb +0 -6
- data/examples/memory/11_entity_addressing_with_filtering.rb +0 -4
- data/examples/memory/12_regex_filtering_microservices.rb +0 -1
- data/examples/memory/13_header_block_configuration.rb +0 -5
- data/examples/memory/14_global_configuration_demo.rb +0 -2
- data/examples/memory/15_logger_demo.rb +0 -1
- data/examples/memory/README.md +3 -3
- data/examples/redis/01_smart_home_iot_demo.rb +0 -4
- data/examples/redis/README.md +0 -2
- data/lib/smart_message/base.rb +19 -10
- data/lib/smart_message/configuration.rb +2 -23
- data/lib/smart_message/dead_letter_queue.rb +1 -1
- data/lib/smart_message/messaging.rb +3 -62
- data/lib/smart_message/plugins.rb +1 -42
- data/lib/smart_message/transport/base.rb +42 -8
- data/lib/smart_message/transport/memory_transport.rb +23 -4
- data/lib/smart_message/transport/redis_transport.rb +11 -0
- data/lib/smart_message/transport/registry.rb +0 -1
- data/lib/smart_message/transport/stdout_transport.rb +28 -10
- data/lib/smart_message/transport.rb +0 -1
- data/lib/smart_message/version.rb +1 -1
- metadata +2 -28
- data/docs/guides/redis-queue-getting-started.md +0 -697
- data/docs/guides/redis-queue-patterns.md +0 -889
- data/docs/guides/redis-queue-production.md +0 -1091
- data/docs/transports/redis-enhanced-transport.md +0 -524
- data/docs/transports/redis-queue-transport.md +0 -1304
- data/examples/redis_enhanced/README.md +0 -319
- data/examples/redis_enhanced/enhanced_01_basic_patterns.rb +0 -233
- data/examples/redis_enhanced/enhanced_02_fluent_api.rb +0 -331
- data/examples/redis_enhanced/enhanced_03_dual_publishing.rb +0 -281
- data/examples/redis_enhanced/enhanced_04_advanced_routing.rb +0 -419
- data/examples/redis_queue/01_basic_messaging.rb +0 -221
- data/examples/redis_queue/01_comprehensive_examples.rb +0 -508
- data/examples/redis_queue/02_pattern_routing.rb +0 -405
- data/examples/redis_queue/03_fluent_api.rb +0 -422
- data/examples/redis_queue/04_load_balancing.rb +0 -486
- data/examples/redis_queue/05_microservices.rb +0 -735
- data/examples/redis_queue/06_emergency_alerts.rb +0 -777
- data/examples/redis_queue/07_queue_management.rb +0 -587
- data/examples/redis_queue/README.md +0 -366
- data/examples/redis_queue/enhanced_01_basic_patterns.rb +0 -233
- data/examples/redis_queue/enhanced_02_fluent_api.rb +0 -331
- data/examples/redis_queue/enhanced_03_dual_publishing.rb +0 -281
- data/examples/redis_queue/enhanced_04_advanced_routing.rb +0 -419
- data/examples/redis_queue/redis_queue_architecture.svg +0 -148
- data/lib/smart_message/transport/redis_enhanced_transport.rb +0 -399
- data/lib/smart_message/transport/redis_queue_transport.rb +0 -555
@@ -1,573 +1,245 @@
|
|
1
|
-
#
|
1
|
+
# Transport-Based Serialization
|
2
2
|
|
3
|
-
|
3
|
+
In SmartMessage's architecture, serialization is handled at the transport level rather than being configured for individual messages. Each transport manages its own optimal serialization format, eliminating the need for separate serializer configuration.
|
4
4
|
|
5
5
|
## Overview
|
6
6
|
|
7
|
-
|
8
|
-
- **
|
9
|
-
- **
|
10
|
-
- **Format
|
11
|
-
- **
|
7
|
+
Transport-based serialization provides:
|
8
|
+
- **Automatic Format Selection**: Each transport chooses its optimal serialization format
|
9
|
+
- **Simplified Configuration**: No need to configure serializers separately
|
10
|
+
- **Format Optimization**: Transports can choose the best format for their medium
|
11
|
+
- **Consistent Behavior**: All messages using a transport share the same serialization format
|
12
12
|
|
13
|
-
##
|
13
|
+
## Transport Serialization Formats
|
14
14
|
|
15
|
-
###
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
**Features:**
|
20
|
-
- Human-readable output
|
21
|
-
- Wide compatibility
|
22
|
-
- Built on Ruby's standard JSON library
|
23
|
-
- Automatic property serialization
|
24
|
-
|
25
|
-
**Usage:**
|
15
|
+
### Memory Transport
|
16
|
+
- **Format**: No serialization (objects passed directly)
|
17
|
+
- **Use case**: Testing and development where no network transmission occurs
|
18
|
+
- **Performance**: Fastest possible (no encoding/decoding overhead)
|
26
19
|
|
27
20
|
```ruby
|
28
|
-
#
|
29
|
-
|
30
|
-
|
31
|
-
# Configure in message class
|
32
|
-
class UserMessage < SmartMessage::Base
|
33
|
-
property :user_id
|
34
|
-
property :email
|
35
|
-
property :preferences
|
36
|
-
|
37
|
-
config do
|
38
|
-
serializer SmartMessage::Serializer::Json.new
|
39
|
-
end
|
40
|
-
end
|
41
|
-
|
42
|
-
# Manual encoding/decoding
|
43
|
-
message = UserMessage.new(user_id: 123, email: "user@example.com")
|
44
|
-
encoded = serializer.encode(message)
|
45
|
-
# => '{"user_id":123,"email":"user@example.com","preferences":null}'
|
21
|
+
# Memory transport - no serialization needed
|
22
|
+
transport = SmartMessage::Transport::MemoryTransport.new
|
46
23
|
```
|
47
24
|
|
48
|
-
|
49
|
-
-
|
50
|
-
-
|
51
|
-
-
|
52
|
-
- Uses Ruby's `#to_json` method under the hood
|
53
|
-
|
54
|
-
## Serializer Interface
|
55
|
-
|
56
|
-
All serializers must implement the `SmartMessage::Serializer::Base` interface:
|
57
|
-
|
58
|
-
### Required Methods
|
25
|
+
### STDOUT Transport
|
26
|
+
- **Format**: JSON (human-readable)
|
27
|
+
- **Use case**: Debugging, development logging, message inspection
|
28
|
+
- **Features**: Pretty-printed output for easy reading
|
59
29
|
|
60
30
|
```ruby
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
end
|
66
|
-
|
67
|
-
# Convert SmartMessage instance to wire format
|
68
|
-
def encode(message_instance)
|
69
|
-
# Transform message_instance to your format
|
70
|
-
# Return string or binary data
|
71
|
-
end
|
72
|
-
|
73
|
-
# Convert wire format back to hash
|
74
|
-
def decode(payload)
|
75
|
-
# Transform payload string back to hash
|
76
|
-
# Return hash suitable for SmartMessage.new(hash)
|
77
|
-
end
|
78
|
-
end
|
31
|
+
# STDOUT transport - uses JSON for readability
|
32
|
+
transport = SmartMessage::Transport::StdoutTransport.new(
|
33
|
+
format: :pretty # or :json for compact format
|
34
|
+
)
|
79
35
|
```
|
80
36
|
|
81
|
-
###
|
37
|
+
### Redis Transport
|
38
|
+
- **Format**: MessagePack (primary), JSON (fallback)
|
39
|
+
- **Use case**: Production messaging where efficiency matters
|
40
|
+
- **Benefits**: Compact binary format reduces network overhead
|
82
41
|
|
83
42
|
```ruby
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
message_instance.to_h.to_msgpack
|
89
|
-
end
|
90
|
-
|
91
|
-
def decode(payload)
|
92
|
-
MessagePack.unpack(payload)
|
93
|
-
end
|
94
|
-
end
|
95
|
-
|
96
|
-
# Usage
|
97
|
-
class BinaryMessage < SmartMessage::Base
|
98
|
-
property :data
|
99
|
-
property :timestamp
|
100
|
-
|
101
|
-
config do
|
102
|
-
serializer MessagePackSerializer.new
|
103
|
-
end
|
104
|
-
end
|
43
|
+
# Redis transport - automatically uses MessagePack if available
|
44
|
+
transport = SmartMessage::Transport::RedisTransport.new(
|
45
|
+
url: 'redis://localhost:6379'
|
46
|
+
)
|
105
47
|
```
|
106
48
|
|
107
|
-
|
49
|
+
## How It Works
|
108
50
|
|
109
|
-
|
110
|
-
require 'nokogiri'
|
111
|
-
|
112
|
-
class XMLSerializer < SmartMessage::Serializer::Base
|
113
|
-
def encode(message_instance)
|
114
|
-
data = message_instance.to_h
|
115
|
-
builder = Nokogiri::XML::Builder.new do |xml|
|
116
|
-
xml.message do
|
117
|
-
data.each do |key, value|
|
118
|
-
xml.send(key, value)
|
119
|
-
end
|
120
|
-
end
|
121
|
-
end
|
122
|
-
builder.to_xml
|
123
|
-
end
|
124
|
-
|
125
|
-
def decode(payload)
|
126
|
-
doc = Nokogiri::XML(payload)
|
127
|
-
hash = {}
|
128
|
-
doc.xpath('//message/*').each do |node|
|
129
|
-
hash[node.name] = node.text
|
130
|
-
end
|
131
|
-
hash
|
132
|
-
end
|
133
|
-
end
|
134
|
-
```
|
51
|
+
### Transport Serialization Process
|
135
52
|
|
136
|
-
|
53
|
+
1. **Message Publishing**:
|
54
|
+
```ruby
|
55
|
+
message = OrderMessage.new(order_id: "123", amount: 99.99)
|
56
|
+
message.publish # Transport handles serialization automatically
|
57
|
+
```
|
137
58
|
|
138
|
-
|
59
|
+
2. **Automatic Encoding**: Transport calls its serializer internally
|
60
|
+
```ruby
|
61
|
+
# Inside transport.publish(message):
|
62
|
+
serialized = transport.serializer.encode(message.to_hash)
|
63
|
+
```
|
139
64
|
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
# Convert specific types
|
148
|
-
data.transform_values do |value|
|
149
|
-
case value
|
150
|
-
when Time
|
151
|
-
value.iso8601
|
152
|
-
when Date
|
153
|
-
value.to_s
|
154
|
-
when BigDecimal
|
155
|
-
value.to_f
|
156
|
-
else
|
157
|
-
value
|
158
|
-
end
|
159
|
-
end.to_json
|
160
|
-
end
|
161
|
-
|
162
|
-
def decode(payload)
|
163
|
-
data = JSON.parse(payload)
|
164
|
-
|
165
|
-
# Convert back from strings
|
166
|
-
data.transform_values do |value|
|
167
|
-
case value
|
168
|
-
when /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/
|
169
|
-
Time.parse(value)
|
170
|
-
else
|
171
|
-
value
|
172
|
-
end
|
173
|
-
end
|
174
|
-
end
|
175
|
-
end
|
176
|
-
```
|
65
|
+
3. **Message Receiving**: Transport deserializes automatically
|
66
|
+
```ruby
|
67
|
+
# Inside transport.receive(serialized_data):
|
68
|
+
data = transport.serializer.decode(serialized_data)
|
69
|
+
message = MessageClass.new(data)
|
70
|
+
```
|
177
71
|
|
178
|
-
###
|
72
|
+
### Message Structure
|
179
73
|
|
180
|
-
|
74
|
+
All messages are serialized as flat hashes with the `_sm_header` property containing routing metadata:
|
181
75
|
|
182
76
|
```ruby
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
def deep_serialize(obj)
|
197
|
-
case obj
|
198
|
-
when Hash
|
199
|
-
obj.transform_values { |v| deep_serialize(v) }
|
200
|
-
when Array
|
201
|
-
obj.map { |v| deep_serialize(v) }
|
202
|
-
when SmartMessage::Base
|
203
|
-
# Serialize nested messages
|
204
|
-
obj.to_h
|
205
|
-
else
|
206
|
-
obj
|
207
|
-
end
|
208
|
-
end
|
209
|
-
|
210
|
-
def deep_deserialize(obj)
|
211
|
-
case obj
|
212
|
-
when Hash
|
213
|
-
obj.transform_values { |v| deep_deserialize(v) }
|
214
|
-
when Array
|
215
|
-
obj.map { |v| deep_deserialize(v) }
|
216
|
-
else
|
217
|
-
obj
|
218
|
-
end
|
219
|
-
end
|
220
|
-
end
|
77
|
+
{
|
78
|
+
_sm_header: {
|
79
|
+
uuid: "...",
|
80
|
+
message_class: "OrderMessage",
|
81
|
+
published_at: "2025-01-09T...",
|
82
|
+
from: "order-service",
|
83
|
+
to: "fulfillment-service",
|
84
|
+
serializer: "SmartMessage::Serializer::Json"
|
85
|
+
},
|
86
|
+
order_id: "123",
|
87
|
+
amount: 99.99,
|
88
|
+
items: ["Widget A", "Widget B"]
|
89
|
+
}
|
221
90
|
```
|
222
91
|
|
223
|
-
##
|
92
|
+
## Custom Transport Serializers
|
224
93
|
|
225
|
-
|
94
|
+
You can specify a custom serializer when creating a transport:
|
226
95
|
|
227
96
|
```ruby
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
end
|
234
|
-
|
235
|
-
def encode(message_instance)
|
236
|
-
data = message_instance.to_h
|
237
|
-
|
238
|
-
# Remove nil values if requested
|
239
|
-
data = data.compact if @exclude_nil
|
240
|
-
|
241
|
-
# Format dates
|
242
|
-
data = format_dates(data)
|
243
|
-
|
244
|
-
# Generate JSON
|
245
|
-
if @pretty
|
246
|
-
JSON.pretty_generate(data)
|
247
|
-
else
|
248
|
-
JSON.generate(data)
|
249
|
-
end
|
97
|
+
# Custom serializer for a transport
|
98
|
+
class MyCustomSerializer
|
99
|
+
def encode(data_hash)
|
100
|
+
# Your encoding logic here
|
101
|
+
# Must return a string
|
250
102
|
end
|
251
|
-
|
252
|
-
private
|
253
|
-
|
254
|
-
def format_dates(data)
|
255
|
-
data.transform_values do |value|
|
256
|
-
case value
|
257
|
-
when Time, Date
|
258
|
-
case @date_format
|
259
|
-
when :iso8601
|
260
|
-
value.iso8601
|
261
|
-
when :unix
|
262
|
-
value.to_i
|
263
|
-
when :rfc2822
|
264
|
-
value.rfc2822
|
265
|
-
else
|
266
|
-
value.to_s
|
267
|
-
end
|
268
|
-
else
|
269
|
-
value
|
270
|
-
end
|
271
|
-
end
|
272
|
-
end
|
273
|
-
end
|
274
103
|
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
property :timestamp
|
279
|
-
|
280
|
-
config do
|
281
|
-
serializer ConfigurableJSONSerializer.new(
|
282
|
-
pretty: true,
|
283
|
-
exclude_nil: true,
|
284
|
-
date_format: :unix
|
285
|
-
)
|
104
|
+
def decode(serialized_string)
|
105
|
+
# Your decoding logic here
|
106
|
+
# Must return a hash
|
286
107
|
end
|
287
108
|
end
|
288
|
-
```
|
289
109
|
|
290
|
-
|
110
|
+
# Use custom serializer with transport
|
111
|
+
transport = SmartMessage::Transport::RedisTransport.new(
|
112
|
+
serializer: MyCustomSerializer.new,
|
113
|
+
url: 'redis://localhost:6379'
|
114
|
+
)
|
115
|
+
```
|
291
116
|
|
292
|
-
|
117
|
+
## Built-in Serializer Classes
|
293
118
|
|
294
|
-
|
119
|
+
SmartMessage includes these serializer implementations that transports use internally:
|
295
120
|
|
121
|
+
### JSON Serializer
|
296
122
|
```ruby
|
297
|
-
|
298
|
-
def encode(message_instance)
|
299
|
-
JSON.generate(message_instance.to_h)
|
300
|
-
rescue JSON::GeneratorError => e
|
301
|
-
# Log the error
|
302
|
-
puts "Serialization failed: #{e.message}"
|
303
|
-
|
304
|
-
# Fallback to simple string representation
|
305
|
-
message_instance.to_h.to_s
|
306
|
-
end
|
307
|
-
|
308
|
-
def decode(payload)
|
309
|
-
JSON.parse(payload)
|
310
|
-
rescue JSON::ParserError => e
|
311
|
-
# Log the error
|
312
|
-
puts "Deserialization failed: #{e.message}"
|
313
|
-
|
314
|
-
# Return error indicator or empty hash
|
315
|
-
{ "_error" => "Failed to deserialize: #{e.message}" }
|
316
|
-
end
|
317
|
-
end
|
123
|
+
SmartMessage::Serializer::Json.new
|
318
124
|
```
|
125
|
+
- Human-readable format
|
126
|
+
- Wide compatibility
|
127
|
+
- Used by STDOUT transport and as fallback
|
319
128
|
|
320
|
-
###
|
321
|
-
|
129
|
+
### MessagePack Serializer
|
322
130
|
```ruby
|
323
|
-
|
324
|
-
def encode(message_instance)
|
325
|
-
validate_before_encoding(message_instance)
|
326
|
-
JSON.generate(message_instance.to_h)
|
327
|
-
end
|
328
|
-
|
329
|
-
def decode(payload)
|
330
|
-
data = JSON.parse(payload)
|
331
|
-
validate_after_decoding(data)
|
332
|
-
data
|
333
|
-
end
|
334
|
-
|
335
|
-
private
|
336
|
-
|
337
|
-
def validate_before_encoding(message)
|
338
|
-
required_fields = message.class.properties.select do |prop|
|
339
|
-
message.class.required?(prop)
|
340
|
-
end
|
341
|
-
|
342
|
-
missing = required_fields.select { |field| message[field].nil? }
|
343
|
-
|
344
|
-
if missing.any?
|
345
|
-
raise "Missing required fields: #{missing.join(', ')}"
|
346
|
-
end
|
347
|
-
end
|
348
|
-
|
349
|
-
def validate_after_decoding(data)
|
350
|
-
unless data.is_a?(Hash)
|
351
|
-
raise "Expected hash, got #{data.class}"
|
352
|
-
end
|
353
|
-
|
354
|
-
# Additional validation logic
|
355
|
-
end
|
356
|
-
end
|
131
|
+
SmartMessage::Serializer::MessagePack.new
|
357
132
|
```
|
133
|
+
- Binary format for efficiency
|
134
|
+
- Smaller payload size
|
135
|
+
- Used by Redis transport when available
|
358
136
|
|
359
|
-
##
|
360
|
-
|
361
|
-
### Binary Serialization
|
137
|
+
## Migration from Message-Level Serializers
|
362
138
|
|
363
|
-
|
139
|
+
If you were previously configuring serializers at the message level, here's how to migrate:
|
364
140
|
|
141
|
+
### Before (Message-Level Configuration)
|
365
142
|
```ruby
|
366
|
-
class
|
367
|
-
|
368
|
-
|
369
|
-
end
|
370
|
-
|
371
|
-
def encode(message_instance)
|
372
|
-
proto_obj = @proto_class.new(message_instance.to_h)
|
373
|
-
proto_obj.serialize_to_string
|
374
|
-
end
|
375
|
-
|
376
|
-
def decode(payload)
|
377
|
-
proto_obj = @proto_class.parse(payload)
|
378
|
-
proto_obj.to_h
|
379
|
-
end
|
380
|
-
end
|
381
|
-
|
382
|
-
# Usage
|
383
|
-
UserProto = Google::Protobuf::DescriptorPool.generated_pool.lookup("User").msgclass
|
384
|
-
|
385
|
-
class UserMessage < SmartMessage::Base
|
386
|
-
property :user_id
|
387
|
-
property :name
|
143
|
+
class OrderMessage < SmartMessage::Base
|
144
|
+
property :order_id
|
145
|
+
property :amount
|
388
146
|
|
389
147
|
config do
|
390
|
-
|
391
|
-
|
392
|
-
end
|
393
|
-
```
|
394
|
-
|
395
|
-
### Streaming Serialization
|
396
|
-
|
397
|
-
For large messages, consider streaming:
|
398
|
-
|
399
|
-
```ruby
|
400
|
-
class StreamingSerializer < SmartMessage::Serializer::Base
|
401
|
-
def encode(message_instance)
|
402
|
-
StringIO.new.tap do |io|
|
403
|
-
JSON.dump(message_instance.to_h, io)
|
404
|
-
end.string
|
405
|
-
end
|
406
|
-
|
407
|
-
def decode(payload)
|
408
|
-
StringIO.new(payload).tap do |io|
|
409
|
-
JSON.load(io)
|
410
|
-
end
|
148
|
+
transport SmartMessage::Transport::RedisTransport.new
|
149
|
+
serializer SmartMessage::Serializer::Json.new # ❌ No longer needed
|
411
150
|
end
|
412
151
|
end
|
413
152
|
```
|
414
153
|
|
415
|
-
|
416
|
-
|
417
|
-
### Compressed Serialization
|
418
|
-
|
154
|
+
### After (Transport-Level Serialization)
|
419
155
|
```ruby
|
420
|
-
class
|
421
|
-
|
422
|
-
|
423
|
-
Zlib::Deflate.deflate(json_data)
|
424
|
-
end
|
156
|
+
class OrderMessage < SmartMessage::Base
|
157
|
+
property :order_id
|
158
|
+
property :amount
|
425
159
|
|
426
|
-
|
427
|
-
|
428
|
-
|
160
|
+
config do
|
161
|
+
# Transport automatically handles serialization
|
162
|
+
transport SmartMessage::Transport::RedisTransport.new
|
429
163
|
end
|
430
164
|
end
|
431
165
|
|
432
|
-
#
|
433
|
-
class
|
434
|
-
property :
|
435
|
-
property :
|
166
|
+
# Or specify custom serializer for transport
|
167
|
+
class OrderMessage < SmartMessage::Base
|
168
|
+
property :order_id
|
169
|
+
property :amount
|
436
170
|
|
437
171
|
config do
|
438
|
-
|
172
|
+
transport SmartMessage::Transport::RedisTransport.new(
|
173
|
+
serializer: MyCustomSerializer.new
|
174
|
+
)
|
439
175
|
end
|
440
176
|
end
|
441
177
|
```
|
442
178
|
|
443
|
-
##
|
179
|
+
## Serialization Best Practices
|
180
|
+
|
181
|
+
### 1. Let Transports Choose
|
182
|
+
Let each transport use its optimal format:
|
183
|
+
- Memory: No serialization
|
184
|
+
- STDOUT: JSON for readability
|
185
|
+
- Redis: MessagePack for efficiency
|
444
186
|
|
445
|
-
###
|
187
|
+
### 2. Custom Serializers
|
188
|
+
Only use custom serializers when you have specific requirements:
|
189
|
+
- Special data formats (XML, Protocol Buffers)
|
190
|
+
- Encryption/compression needs
|
191
|
+
- Legacy system compatibility
|
192
|
+
|
193
|
+
### 3. Testing
|
194
|
+
Test with actual transports to ensure serialization works correctly:
|
446
195
|
|
447
196
|
```ruby
|
448
|
-
RSpec.describe
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
user_id: 123,
|
453
|
-
email: "test@example.com",
|
454
|
-
created_at: Time.parse("2025-08-17T10:30:00Z")
|
455
|
-
)
|
456
|
-
end
|
457
|
-
|
458
|
-
describe "#encode" do
|
459
|
-
it "produces valid output" do
|
460
|
-
result = serializer.encode(message)
|
461
|
-
expect(result).to be_a(String)
|
462
|
-
expect(result).not_to be_empty
|
463
|
-
end
|
197
|
+
RSpec.describe OrderMessage do
|
198
|
+
it "serializes correctly with Redis transport" do
|
199
|
+
transport = SmartMessage::Transport::RedisTransport.new
|
200
|
+
message = OrderMessage.new(order_id: "123", amount: 99.99)
|
464
201
|
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
describe "#decode" do
|
472
|
-
it "roundtrips correctly" do
|
473
|
-
encoded = serializer.encode(message)
|
474
|
-
decoded = serializer.decode(encoded)
|
475
|
-
|
476
|
-
expect(decoded["user_id"]).to eq(123)
|
477
|
-
expect(decoded["email"]).to eq("test@example.com")
|
478
|
-
end
|
479
|
-
end
|
480
|
-
|
481
|
-
describe "error handling" do
|
482
|
-
it "handles invalid input gracefully" do
|
483
|
-
expect { serializer.decode("invalid") }.not_to raise_error
|
484
|
-
end
|
202
|
+
# Test roundtrip serialization
|
203
|
+
serialized = transport.encode_message(message)
|
204
|
+
deserialized = transport.decode_message(serialized)
|
205
|
+
|
206
|
+
expect(deserialized[:order_id]).to eq("123")
|
207
|
+
expect(deserialized[:amount]).to eq(99.99)
|
485
208
|
end
|
486
209
|
end
|
487
210
|
```
|
488
211
|
|
489
|
-
###
|
212
|
+
### 4. Error Handling
|
213
|
+
Transports handle serialization errors internally, but you can still catch them:
|
490
214
|
|
491
215
|
```ruby
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
@encoded_messages = []
|
497
|
-
@decoded_payloads = []
|
498
|
-
end
|
499
|
-
|
500
|
-
def encode(message_instance)
|
501
|
-
@encoded_messages << message_instance
|
502
|
-
"mock_encoded_#{message_instance.object_id}"
|
503
|
-
end
|
504
|
-
|
505
|
-
def decode(payload)
|
506
|
-
@decoded_payloads << payload
|
507
|
-
{ "mock" => "decoded", "payload" => payload }
|
508
|
-
end
|
509
|
-
|
510
|
-
def clear
|
511
|
-
@encoded_messages.clear
|
512
|
-
@decoded_payloads.clear
|
513
|
-
end
|
216
|
+
begin
|
217
|
+
message.publish
|
218
|
+
rescue SmartMessage::Errors::SerializationError => e
|
219
|
+
logger.error "Failed to serialize message: #{e.message}"
|
514
220
|
end
|
515
221
|
```
|
516
222
|
|
517
|
-
##
|
223
|
+
## Performance Considerations
|
518
224
|
|
519
|
-
###
|
225
|
+
### Format Efficiency
|
226
|
+
- **MessagePack**: 20-30% more compact than JSON
|
227
|
+
- **JSON**: Human-readable but larger payload
|
228
|
+
- **Memory**: No serialization overhead
|
520
229
|
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
JSON.generate(data)
|
526
|
-
end
|
527
|
-
|
528
|
-
private
|
529
|
-
|
530
|
-
def sanitize_for_json(obj)
|
531
|
-
case obj
|
532
|
-
when Hash
|
533
|
-
obj.transform_values { |v| sanitize_for_json(v) }
|
534
|
-
when Array
|
535
|
-
obj.map { |v| sanitize_for_json(v) }
|
536
|
-
when Float
|
537
|
-
return nil if obj.nan? || obj.infinite?
|
538
|
-
obj
|
539
|
-
when BigDecimal
|
540
|
-
obj.to_f
|
541
|
-
when Symbol
|
542
|
-
obj.to_s
|
543
|
-
when Complex, Rational
|
544
|
-
obj.to_f
|
545
|
-
else
|
546
|
-
obj
|
547
|
-
end
|
548
|
-
end
|
549
|
-
end
|
550
|
-
```
|
551
|
-
|
552
|
-
### Character Encoding
|
230
|
+
### Network Optimization
|
231
|
+
- Redis transport automatically uses MessagePack when available
|
232
|
+
- Falls back to JSON if MessagePack gem is not installed
|
233
|
+
- STDOUT uses JSON for debugging clarity
|
553
234
|
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
|
558
|
-
json = JSON.generate(data)
|
559
|
-
json.force_encoding('UTF-8')
|
560
|
-
end
|
561
|
-
|
562
|
-
def decode(payload)
|
563
|
-
# Ensure proper encoding
|
564
|
-
payload = payload.force_encoding('UTF-8')
|
565
|
-
JSON.parse(payload)
|
566
|
-
end
|
567
|
-
end
|
235
|
+
### Monitoring
|
236
|
+
Each transport logs its serializer choice:
|
237
|
+
```
|
238
|
+
[SmartMessage::Transport::RedisTransport] Using serializer: SmartMessage::Serializer::MessagePack
|
568
239
|
```
|
569
240
|
|
570
241
|
## Next Steps
|
571
242
|
|
572
|
-
- [Transports](transports.md) -
|
573
|
-
- [
|
243
|
+
- [Transports](transports.md) - Available transport implementations
|
244
|
+
- [Configuration](../getting-started/quick-start.md) - Setting up transports
|
245
|
+
- [Examples](../getting-started/examples.md) - Real-world usage patterns
|