smart_message 0.0.4 → 0.0.6
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 +118 -0
- data/Gemfile.lock +1 -1
- data/README.md +299 -10
- data/docs/README.md +1 -0
- data/docs/addressing.md +364 -0
- data/docs/architecture.md +2 -0
- data/docs/examples.md +2 -0
- data/docs/getting-started.md +49 -0
- data/docs/properties.md +213 -7
- data/examples/01_point_to_point_orders.rb +27 -11
- data/examples/02_publish_subscribe_events.rb +16 -7
- data/examples/03_many_to_many_chat.rb +56 -22
- data/examples/04_redis_smart_home_iot.rb +48 -21
- data/examples/05_proc_handlers.rb +12 -5
- data/examples/06_custom_logger_example.rb +34 -13
- data/examples/07_error_handling_scenarios.rb +477 -0
- data/examples/08_entity_addressing_basic.rb +366 -0
- data/examples/08_entity_addressing_with_filtering.rb +418 -0
- data/examples/README.md +68 -0
- data/examples/tmux_chat/bot_agent.rb +4 -1
- data/examples/tmux_chat/shared_chat_system.rb +50 -22
- data/lib/smart_message/base.rb +306 -20
- data/lib/smart_message/dispatcher.rb +53 -5
- data/lib/smart_message/errors.rb +3 -0
- data/lib/smart_message/header.rb +46 -5
- data/lib/smart_message/property_descriptions.rb +5 -4
- data/lib/smart_message/property_validations.rb +141 -0
- data/lib/smart_message/transport/base.rb +3 -2
- data/lib/smart_message/transport/redis_transport.rb +2 -2
- data/lib/smart_message/version.rb +1 -1
- metadata +6 -1
data/docs/addressing.md
ADDED
@@ -0,0 +1,364 @@
|
|
1
|
+
# Entity Addressing
|
2
|
+
|
3
|
+
SmartMessage supports entity-to-entity addressing through built-in FROM/TO/REPLY_TO fields in message headers. This enables sophisticated messaging patterns including point-to-point communication, broadcast messaging, and request-reply workflows.
|
4
|
+
|
5
|
+
## Overview
|
6
|
+
|
7
|
+
Entity addressing in SmartMessage provides:
|
8
|
+
|
9
|
+
- **Sender Identification**: Required `from` field identifies the sending entity
|
10
|
+
- **Recipient Targeting**: Optional `to` field for point-to-point messaging
|
11
|
+
- **Response Routing**: Optional `reply_to` field for request-reply patterns
|
12
|
+
- **Broadcast Support**: Omitting `to` field enables broadcast to all subscribers
|
13
|
+
- **Dual-Level Configuration**: Class and instance-level addressing configuration
|
14
|
+
|
15
|
+
## Address Fields
|
16
|
+
|
17
|
+
### FROM (Required)
|
18
|
+
The `from` field identifies the entity sending the message. This is required for all messages.
|
19
|
+
|
20
|
+
```ruby
|
21
|
+
class MyMessage < SmartMessage::Base
|
22
|
+
from 'order-service' # Required sender identity
|
23
|
+
|
24
|
+
property :data
|
25
|
+
end
|
26
|
+
```
|
27
|
+
|
28
|
+
### TO (Optional)
|
29
|
+
The `to` field specifies the intended recipient entity. When present, creates point-to-point messaging. When `nil`, the message is broadcast to all subscribers.
|
30
|
+
|
31
|
+
```ruby
|
32
|
+
# Point-to-point messaging
|
33
|
+
class DirectMessage < SmartMessage::Base
|
34
|
+
from 'sender-service'
|
35
|
+
to 'recipient-service' # Specific target
|
36
|
+
|
37
|
+
property :content
|
38
|
+
end
|
39
|
+
|
40
|
+
# Broadcast messaging
|
41
|
+
class AnnouncementMessage < SmartMessage::Base
|
42
|
+
from 'admin-service'
|
43
|
+
# No 'to' field = broadcast to all subscribers
|
44
|
+
|
45
|
+
property :announcement
|
46
|
+
end
|
47
|
+
```
|
48
|
+
|
49
|
+
### REPLY_TO (Optional)
|
50
|
+
The `reply_to` field specifies where responses should be sent. Defaults to the `from` entity if not specified.
|
51
|
+
|
52
|
+
```ruby
|
53
|
+
class RequestMessage < SmartMessage::Base
|
54
|
+
from 'client-service'
|
55
|
+
to 'api-service'
|
56
|
+
reply_to 'client-callback-service' # Responses go here
|
57
|
+
|
58
|
+
property :request_data
|
59
|
+
end
|
60
|
+
```
|
61
|
+
|
62
|
+
## Configuration Patterns
|
63
|
+
|
64
|
+
### Class-Level Configuration
|
65
|
+
|
66
|
+
Set default addressing for all instances of a message class:
|
67
|
+
|
68
|
+
```ruby
|
69
|
+
class PaymentMessage < SmartMessage::Base
|
70
|
+
version 1
|
71
|
+
|
72
|
+
# Class-level addressing
|
73
|
+
from 'payment-service'
|
74
|
+
to 'bank-gateway'
|
75
|
+
reply_to 'payment-service'
|
76
|
+
|
77
|
+
property :amount, required: true
|
78
|
+
property :account_id, required: true
|
79
|
+
end
|
80
|
+
|
81
|
+
# All instances inherit class addressing
|
82
|
+
payment = PaymentMessage.new(amount: 100.00, account_id: "ACCT-123")
|
83
|
+
puts payment._sm_header.from # => 'payment-service'
|
84
|
+
puts payment._sm_header.to # => 'bank-gateway'
|
85
|
+
puts payment._sm_header.reply_to # => 'payment-service'
|
86
|
+
```
|
87
|
+
|
88
|
+
### Instance-Level Overrides
|
89
|
+
|
90
|
+
Override addressing for specific message instances:
|
91
|
+
|
92
|
+
```ruby
|
93
|
+
class FlexibleMessage < SmartMessage::Base
|
94
|
+
from 'service-a'
|
95
|
+
to 'service-b'
|
96
|
+
|
97
|
+
property :data
|
98
|
+
end
|
99
|
+
|
100
|
+
# Override addressing for this instance
|
101
|
+
message = FlexibleMessage.new(data: "test")
|
102
|
+
message.from('different-sender')
|
103
|
+
message.to('different-recipient')
|
104
|
+
message.reply_to('different-reply-service')
|
105
|
+
|
106
|
+
puts message.from # => 'different-sender'
|
107
|
+
puts message.to # => 'different-recipient'
|
108
|
+
puts message.reply_to # => 'different-reply-service'
|
109
|
+
|
110
|
+
# Class defaults remain unchanged
|
111
|
+
puts FlexibleMessage.from # => 'service-a'
|
112
|
+
puts FlexibleMessage.to # => 'service-b'
|
113
|
+
```
|
114
|
+
|
115
|
+
## Messaging Patterns
|
116
|
+
|
117
|
+
### Point-to-Point Messaging
|
118
|
+
|
119
|
+
Direct communication between two specific entities:
|
120
|
+
|
121
|
+
```ruby
|
122
|
+
class OrderProcessingMessage < SmartMessage::Base
|
123
|
+
from 'order-service'
|
124
|
+
to 'inventory-service' # Direct target
|
125
|
+
reply_to 'order-service'
|
126
|
+
|
127
|
+
property :order_id, required: true
|
128
|
+
property :items, required: true
|
129
|
+
end
|
130
|
+
|
131
|
+
# Message goes directly to inventory-service
|
132
|
+
order = OrderProcessingMessage.new(
|
133
|
+
order_id: "ORD-123",
|
134
|
+
items: ["widget-1", "widget-2"]
|
135
|
+
)
|
136
|
+
order.publish # Only inventory-service receives this
|
137
|
+
```
|
138
|
+
|
139
|
+
### Broadcast Messaging
|
140
|
+
|
141
|
+
Send message to all subscribers by omitting the `to` field:
|
142
|
+
|
143
|
+
```ruby
|
144
|
+
class SystemMaintenanceMessage < SmartMessage::Base
|
145
|
+
from 'admin-service'
|
146
|
+
# No 'to' field = broadcast
|
147
|
+
|
148
|
+
property :message, required: true
|
149
|
+
property :scheduled_time, required: true
|
150
|
+
end
|
151
|
+
|
152
|
+
# Message goes to all subscribers
|
153
|
+
maintenance = SystemMaintenanceMessage.new(
|
154
|
+
message: "System maintenance tonight at 2 AM",
|
155
|
+
scheduled_time: Time.parse("2024-01-15 02:00:00")
|
156
|
+
)
|
157
|
+
maintenance.publish # All subscribers receive this
|
158
|
+
```
|
159
|
+
|
160
|
+
### Request-Reply Pattern
|
161
|
+
|
162
|
+
Structured request-response communication:
|
163
|
+
|
164
|
+
```ruby
|
165
|
+
# Request message
|
166
|
+
class UserLookupRequest < SmartMessage::Base
|
167
|
+
from 'web-service'
|
168
|
+
to 'user-service'
|
169
|
+
reply_to 'web-service' # Responses come back here
|
170
|
+
|
171
|
+
property :user_id, required: true
|
172
|
+
property :request_id, required: true # For correlation
|
173
|
+
end
|
174
|
+
|
175
|
+
# Response message
|
176
|
+
class UserLookupResponse < SmartMessage::Base
|
177
|
+
from 'user-service'
|
178
|
+
# 'to' will be set to the original 'reply_to' value
|
179
|
+
|
180
|
+
property :user_id, required: true
|
181
|
+
property :request_id, required: true # Correlation ID
|
182
|
+
property :user_data
|
183
|
+
property :success, default: true
|
184
|
+
end
|
185
|
+
|
186
|
+
# Send request
|
187
|
+
request = UserLookupRequest.new(
|
188
|
+
user_id: "USER-123",
|
189
|
+
request_id: SecureRandom.uuid
|
190
|
+
)
|
191
|
+
request.publish
|
192
|
+
|
193
|
+
# In user-service handler:
|
194
|
+
class UserLookupRequest
|
195
|
+
def self.process(header, payload)
|
196
|
+
request_data = JSON.parse(payload)
|
197
|
+
|
198
|
+
# Process lookup...
|
199
|
+
user_data = UserService.find(request_data['user_id'])
|
200
|
+
|
201
|
+
# Send response back to reply_to address
|
202
|
+
response = UserLookupResponse.new(
|
203
|
+
user_id: request_data['user_id'],
|
204
|
+
request_id: request_data['request_id'],
|
205
|
+
user_data: user_data
|
206
|
+
)
|
207
|
+
response.to(header.reply_to) # Send to original reply_to
|
208
|
+
response.publish
|
209
|
+
end
|
210
|
+
end
|
211
|
+
```
|
212
|
+
|
213
|
+
### Gateway Pattern
|
214
|
+
|
215
|
+
Forward messages between different transports/formats:
|
216
|
+
|
217
|
+
```ruby
|
218
|
+
class GatewayMessage < SmartMessage::Base
|
219
|
+
from 'gateway-service'
|
220
|
+
|
221
|
+
property :original_message
|
222
|
+
property :source_format
|
223
|
+
property :target_format
|
224
|
+
end
|
225
|
+
|
226
|
+
# Receive from one transport/format
|
227
|
+
incoming = SomeMessage.new(data: "from system A")
|
228
|
+
|
229
|
+
# Forward to different system with different addressing
|
230
|
+
gateway_msg = GatewayMessage.new(
|
231
|
+
original_message: incoming.to_h,
|
232
|
+
source_format: 'json',
|
233
|
+
target_format: 'xml'
|
234
|
+
)
|
235
|
+
|
236
|
+
# Override transport and addressing for forwarding
|
237
|
+
gateway_msg.config do
|
238
|
+
transport DifferentTransport.new
|
239
|
+
serializer DifferentSerializer.new
|
240
|
+
end
|
241
|
+
gateway_msg.to('system-b')
|
242
|
+
gateway_msg.publish
|
243
|
+
```
|
244
|
+
|
245
|
+
## Address Validation
|
246
|
+
|
247
|
+
The `from` field is required and validated automatically:
|
248
|
+
|
249
|
+
```ruby
|
250
|
+
class InvalidMessage < SmartMessage::Base
|
251
|
+
# No 'from' specified - will fail validation
|
252
|
+
property :data
|
253
|
+
end
|
254
|
+
|
255
|
+
message = InvalidMessage.new(data: "test")
|
256
|
+
message.publish # Raises SmartMessage::Errors::ValidationError
|
257
|
+
# => "The property 'from' From entity ID is required for message routing and replies"
|
258
|
+
```
|
259
|
+
|
260
|
+
## Header Access
|
261
|
+
|
262
|
+
Access addressing information from message headers:
|
263
|
+
|
264
|
+
```ruby
|
265
|
+
class SampleMessage < SmartMessage::Base
|
266
|
+
from 'sample-service'
|
267
|
+
to 'target-service'
|
268
|
+
reply_to 'callback-service'
|
269
|
+
|
270
|
+
property :content
|
271
|
+
end
|
272
|
+
|
273
|
+
message = SampleMessage.new(content: "Hello")
|
274
|
+
header = message._sm_header
|
275
|
+
|
276
|
+
puts header.from # => 'sample-service'
|
277
|
+
puts header.to # => 'target-service'
|
278
|
+
puts header.reply_to # => 'callback-service'
|
279
|
+
puts header.uuid # => Generated UUID
|
280
|
+
puts header.message_class # => 'SampleMessage'
|
281
|
+
```
|
282
|
+
|
283
|
+
## Integration with Dispatcher
|
284
|
+
|
285
|
+
The dispatcher can use addressing metadata for advanced routing logic:
|
286
|
+
|
287
|
+
```ruby
|
288
|
+
# Future enhancement: Dispatcher filtering by recipient
|
289
|
+
# dispatcher.route(message_header, payload) do |header|
|
290
|
+
# if header.to.nil?
|
291
|
+
# # Broadcast to all subscribers
|
292
|
+
# route_to_all_subscribers(header.message_class)
|
293
|
+
# else
|
294
|
+
# # Route only to specific recipient
|
295
|
+
# route_to_entity(header.to, header.message_class)
|
296
|
+
# end
|
297
|
+
# end
|
298
|
+
```
|
299
|
+
|
300
|
+
## Best Practices
|
301
|
+
|
302
|
+
### Entity Naming
|
303
|
+
Use consistent, descriptive entity identifiers:
|
304
|
+
|
305
|
+
```ruby
|
306
|
+
# Good: Descriptive service names
|
307
|
+
from 'order-management-service'
|
308
|
+
to 'inventory-tracking-service'
|
309
|
+
reply_to 'order-status-service'
|
310
|
+
|
311
|
+
# Avoid: Generic or unclear names
|
312
|
+
from 'service1'
|
313
|
+
to 'app'
|
314
|
+
```
|
315
|
+
|
316
|
+
### Address Consistency
|
317
|
+
Maintain consistent addressing patterns across your application:
|
318
|
+
|
319
|
+
```ruby
|
320
|
+
# Consistent pattern for microservices
|
321
|
+
class OrderMessage < SmartMessage::Base
|
322
|
+
from 'order-service'
|
323
|
+
to 'fulfillment-service'
|
324
|
+
reply_to 'order-service'
|
325
|
+
end
|
326
|
+
|
327
|
+
class PaymentMessage < SmartMessage::Base
|
328
|
+
from 'payment-service'
|
329
|
+
to 'billing-service'
|
330
|
+
reply_to 'payment-service'
|
331
|
+
end
|
332
|
+
```
|
333
|
+
|
334
|
+
### Gateway Configuration
|
335
|
+
For gateway patterns, use instance-level overrides:
|
336
|
+
|
337
|
+
```ruby
|
338
|
+
# Class defines default routing
|
339
|
+
class APIMessage < SmartMessage::Base
|
340
|
+
from 'api-gateway'
|
341
|
+
to 'internal-service'
|
342
|
+
end
|
343
|
+
|
344
|
+
# Override for external routing
|
345
|
+
message = APIMessage.new(data: "external request")
|
346
|
+
message.to('external-partner-service')
|
347
|
+
message.config do
|
348
|
+
transport ExternalTransport.new
|
349
|
+
serializer SecureSerializer.new
|
350
|
+
end
|
351
|
+
message.publish
|
352
|
+
```
|
353
|
+
|
354
|
+
## Future Enhancements
|
355
|
+
|
356
|
+
The addressing system provides the foundation for advanced features:
|
357
|
+
|
358
|
+
- **Dispatcher Filtering**: Route messages based on recipient targeting
|
359
|
+
- **Security Integration**: Entity-based authentication and authorization
|
360
|
+
- **Audit Trails**: Track message flow between entities
|
361
|
+
- **Load Balancing**: Distribute messages across entity instances
|
362
|
+
- **Circuit Breakers**: Per-entity failure handling
|
363
|
+
|
364
|
+
Entity addressing enables sophisticated messaging architectures while maintaining the simplicity and flexibility that makes SmartMessage powerful.
|
data/docs/architecture.md
CHANGED
data/docs/examples.md
CHANGED
@@ -10,6 +10,8 @@ This document provides practical examples of using SmartMessage in real-world sc
|
|
10
10
|
require 'smart_message'
|
11
11
|
|
12
12
|
class NotificationMessage < SmartMessage::Base
|
13
|
+
description "Sends notifications to users via multiple channels"
|
14
|
+
|
13
15
|
property :recipient
|
14
16
|
property :subject
|
15
17
|
property :body
|
data/docs/getting-started.md
CHANGED
@@ -37,6 +37,14 @@ Let's create a simple message class and see it in action:
|
|
37
37
|
require 'smart_message'
|
38
38
|
|
39
39
|
class WelcomeMessage < SmartMessage::Base
|
40
|
+
# Add a description for the message class
|
41
|
+
description "Welcomes new users after successful signup"
|
42
|
+
|
43
|
+
# Configure entity addressing
|
44
|
+
from 'user-service' # Required: identifies sender
|
45
|
+
to 'notification-service' # Optional: specific recipient
|
46
|
+
reply_to 'user-service' # Optional: where responses go
|
47
|
+
|
40
48
|
# Define message properties
|
41
49
|
property :user_name
|
42
50
|
property :email
|
@@ -132,6 +140,8 @@ Messages use Hashie::Dash properties for type-safe attributes:
|
|
132
140
|
|
133
141
|
```ruby
|
134
142
|
class OrderMessage < SmartMessage::Base
|
143
|
+
description "Represents customer orders for processing and fulfillment"
|
144
|
+
|
135
145
|
property :order_id, required: true
|
136
146
|
property :amount, transform_with: ->(v) { BigDecimal(v.to_s) }
|
137
147
|
property :items, default: []
|
@@ -148,6 +158,9 @@ puts message._sm_header.uuid # Unique identifier
|
|
148
158
|
puts message._sm_header.message_class # "WelcomeMessage"
|
149
159
|
puts message._sm_header.published_at # Timestamp when published
|
150
160
|
puts message._sm_header.publisher_pid # Process ID of publisher
|
161
|
+
puts message._sm_header.from # 'user-service'
|
162
|
+
puts message._sm_header.to # 'notification-service'
|
163
|
+
puts message._sm_header.reply_to # 'user-service'
|
151
164
|
```
|
152
165
|
|
153
166
|
### Transports
|
@@ -175,6 +188,8 @@ SmartMessage supports four types of message handlers to give you flexibility in
|
|
175
188
|
|
176
189
|
```ruby
|
177
190
|
class OrderMessage < SmartMessage::Base
|
191
|
+
description "Handles customer order processing and fulfillment"
|
192
|
+
|
178
193
|
# Define your message properties
|
179
194
|
property :order_id
|
180
195
|
property :amount
|
@@ -215,6 +230,36 @@ OrderMessage.subscribe("OrderService.process_order")
|
|
215
230
|
- **Proc**: Reusable handlers that work across multiple message types
|
216
231
|
- **Method**: Complex business logic organized in service classes
|
217
232
|
|
233
|
+
### Entity Addressing
|
234
|
+
|
235
|
+
SmartMessage supports entity-to-entity addressing for sophisticated messaging patterns:
|
236
|
+
|
237
|
+
```ruby
|
238
|
+
# Point-to-point messaging
|
239
|
+
class PaymentMessage < SmartMessage::Base
|
240
|
+
from 'payment-service' # Required: sender identity
|
241
|
+
to 'bank-gateway' # Optional: specific recipient
|
242
|
+
reply_to 'payment-service' # Optional: where responses go
|
243
|
+
|
244
|
+
property :amount, required: true
|
245
|
+
end
|
246
|
+
|
247
|
+
# Broadcast messaging (no 'to' field)
|
248
|
+
class AnnouncementMessage < SmartMessage::Base
|
249
|
+
from 'admin-service' # Required sender
|
250
|
+
# No 'to' = broadcast to all subscribers
|
251
|
+
|
252
|
+
property :message, required: true
|
253
|
+
end
|
254
|
+
|
255
|
+
# Instance-level addressing override
|
256
|
+
payment = PaymentMessage.new(amount: 100.00)
|
257
|
+
payment.to('backup-gateway') # Override destination
|
258
|
+
payment.publish
|
259
|
+
```
|
260
|
+
|
261
|
+
For more details, see [Entity Addressing](addressing.md).
|
262
|
+
|
218
263
|
## Next Steps
|
219
264
|
|
220
265
|
Now that you have the basics working, explore:
|
@@ -230,6 +275,8 @@ Now that you have the basics working, explore:
|
|
230
275
|
|
231
276
|
```ruby
|
232
277
|
class NotificationMessage < SmartMessage::Base
|
278
|
+
description "Sends notifications to users via email, SMS, or push"
|
279
|
+
|
233
280
|
property :recipient
|
234
281
|
property :subject
|
235
282
|
property :body
|
@@ -271,6 +318,8 @@ NotificationMessage.new(
|
|
271
318
|
|
272
319
|
```ruby
|
273
320
|
class EventMessage < SmartMessage::Base
|
321
|
+
description "Logs application events for monitoring and analytics"
|
322
|
+
|
274
323
|
property :event_type
|
275
324
|
property :user_id
|
276
325
|
property :data
|