Entity Addressing¶
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.
Overview¶
Entity addressing in SmartMessage provides:
- Sender Identification: Required
from
field identifies the sending entity - Recipient Targeting: Optional
to
field for point-to-point messaging - Response Routing: Optional
reply_to
field for request-reply patterns - Broadcast Support: Omitting
to
field enables broadcast to all subscribers - Dual-Level Configuration: Class and instance-level addressing configuration
Address Fields¶
FROM (Required)¶
The from
field identifies the entity sending the message. This is required for all messages.
class MyMessage < SmartMessage::Base
from 'order-service' # Required sender identity
property :data
end
TO (Optional)¶
The to
field specifies the intended recipient entity. When present, creates point-to-point messaging. When nil
, the message is broadcast to all subscribers.
# Point-to-point messaging
class DirectMessage < SmartMessage::Base
from 'sender-service'
to 'recipient-service' # Specific target
property :content
end
# Broadcast messaging
class AnnouncementMessage < SmartMessage::Base
from 'admin-service'
# No 'to' field = broadcast to all subscribers
property :announcement
end
REPLY_TO (Optional)¶
The reply_to
field specifies where responses should be sent. Defaults to the from
entity if not specified.
class RequestMessage < SmartMessage::Base
from 'client-service'
to 'api-service'
reply_to 'client-callback-service' # Responses go here
property :request_data
end
Configuration Patterns¶
Class-Level Configuration¶
Set default addressing for all instances of a message class using three different approaches:
Method 1: Direct Class Methods¶
class PaymentMessage < SmartMessage::Base
version 1
# Class-level addressing using direct methods
from 'payment-service'
to 'bank-gateway'
reply_to 'payment-service'
property :amount, required: true
property :account_id, required: true
end
Method 2: Header Block DSL¶
class PaymentMessage < SmartMessage::Base
version 1
# Class-level addressing using header block
header do
from 'payment-service'
to 'bank-gateway'
reply_to 'payment-service'
end
property :amount, required: true
property :account_id, required: true
end
Method 3: Mixed Approach¶
class PaymentMessage < SmartMessage::Base
version 1
# You can mix approaches if needed
header do
from 'payment-service'
to 'bank-gateway'
end
# Additional addressing outside the block
reply_to 'payment-service'
property :amount, required: true
property :account_id, required: true
end
# All instances inherit class addressing
payment = PaymentMessage.new(amount: 100.00, account_id: "ACCT-123")
puts payment._sm_header.from # => 'payment-service'
puts payment._sm_header.to # => 'bank-gateway'
puts payment._sm_header.reply_to # => 'payment-service'
Instance-Level Overrides¶
Override addressing for specific message instances using multiple approaches:
Method Chaining¶
class FlexibleMessage < SmartMessage::Base
header do
from 'service-a'
to 'service-b'
end
property :data
end
# Override addressing using method chaining
message = FlexibleMessage.new(data: "test")
message.from('different-sender')
.to('different-recipient')
.reply_to('different-reply-service')
puts message.from # => 'different-sender'
puts message.to # => 'different-recipient'
puts message.reply_to # => 'different-reply-service'
Setter Methods¶
# Override addressing using setter syntax
message = FlexibleMessage.new(data: "test")
message.from = 'different-sender'
message.to = 'different-recipient'
message.reply_to = 'different-reply-service'
Accessing Addressing Values¶
# Three ways to access addressing values
# 1. Direct shortcut methods
puts message.from # => 'different-sender'
puts message.to # => 'different-recipient'
puts message.reply_to # => 'different-reply-service'
# 2. Via header object
puts message._sm_header.from # => 'different-sender'
puts message._sm_header.to # => 'different-recipient'
puts message._sm_header.reply_to # => 'different-reply-service'
# 3. Class defaults remain unchanged
puts FlexibleMessage.from # => 'service-a'
puts FlexibleMessage.to # => 'service-b'
Messaging Patterns¶
Point-to-Point Messaging¶
Direct communication between two specific entities:
class OrderProcessingMessage < SmartMessage::Base
from 'order-service'
to 'inventory-service' # Direct target
reply_to 'order-service'
property :order_id, required: true
property :items, required: true
end
# Message goes directly to inventory-service
order = OrderProcessingMessage.new(
order_id: "ORD-123",
items: ["widget-1", "widget-2"]
)
order.publish # Only inventory-service receives this
Broadcast Messaging¶
Send message to all subscribers by omitting the to
field:
class SystemMaintenanceMessage < SmartMessage::Base
from 'admin-service'
# No 'to' field = broadcast
property :message, required: true
property :scheduled_time, required: true
end
# Message goes to all subscribers
maintenance = SystemMaintenanceMessage.new(
message: "System maintenance tonight at 2 AM",
scheduled_time: Time.parse("2024-01-15 02:00:00")
)
maintenance.publish # All subscribers receive this
Request-Reply Pattern¶
Structured request-response communication:
# Request message
class UserLookupRequest < SmartMessage::Base
from 'web-service'
to 'user-service'
reply_to 'web-service' # Responses come back here
property :user_id, required: true
property :request_id, required: true # For correlation
end
# Response message
class UserLookupResponse < SmartMessage::Base
from 'user-service'
# 'to' will be set to the original 'reply_to' value
property :user_id, required: true
property :request_id, required: true # Correlation ID
property :user_data
property :success, default: true
end
# Send request
request = UserLookupRequest.new(
user_id: "USER-123",
request_id: SecureRandom.uuid
)
request.publish
# In user-service handler:
class UserLookupRequest
def self.process(decoded_message)
# Process lookup...
user_data = UserService.find(decoded_message.user_id)
# Send response back to reply_to address
response = UserLookupResponse.new(
user_id: decoded_message.user_id,
request_id: decoded_message.request_id,
user_data: user_data
)
response.to(decoded_message._sm_header.reply_to) # Send to original reply_to
response.publish
end
end
Gateway Pattern¶
Forward messages between different transports/formats:
class GatewayMessage < SmartMessage::Base
from 'gateway-service'
property :original_message
property :source_format
property :target_format
end
# Receive from one transport/format
incoming = SomeMessage.new(data: "from system A")
# Forward to different system with different addressing
gateway_msg = GatewayMessage.new(
original_message: incoming.to_h,
source_format: 'json',
target_format: 'xml'
)
# Override transport and addressing for forwarding
gateway_msg.config do
transport DifferentTransport.new
serializer DifferentSerializer.new
end
gateway_msg.to('system-b')
gateway_msg.publish
Address Validation¶
The from
field is required and validated automatically:
class InvalidMessage < SmartMessage::Base
# No 'from' specified - will fail validation
property :data
end
message = InvalidMessage.new(data: "test")
message.publish # Raises SmartMessage::Errors::ValidationError
# => "The property 'from' From entity ID is required for message routing and replies"
Checking Address Configuration¶
class ValidatedMessage < SmartMessage::Base
header do
from 'my-service'
to 'target-service'
end
property :data
end
# Class-level checks
puts ValidatedMessage.from_configured? # => true
puts ValidatedMessage.to_configured? # => true
puts ValidatedMessage.reply_to_configured? # => false
puts ValidatedMessage.reply_to_missing? # => true
# Instance-level checks
message = ValidatedMessage.new(data: "test")
puts message.from_configured? # => true
puts message.to_missing? # => false
# Reset addressing
message.reset_from
puts message.from_configured? # => false
puts message.from # => nil
Header Access¶
Access addressing information from message headers:
class SampleMessage < SmartMessage::Base
# Using header block for configuration
header do
from 'sample-service'
to 'target-service'
reply_to 'callback-service'
end
property :content
end
message = SampleMessage.new(content: "Hello")
# Access via shortcut methods (recommended)
puts message.from # => 'sample-service'
puts message.to # => 'target-service'
puts message.reply_to # => 'callback-service'
# Access via header object
header = message._sm_header
puts header.from # => 'sample-service'
puts header.to # => 'target-service'
puts header.reply_to # => 'callback-service'
puts header.uuid # => Generated UUID
puts header.message_class # => 'SampleMessage'
# Headers automatically sync with instance changes
message.to = 'new-target'
puts message.to # => 'new-target'
puts header.to # => 'new-target' (automatically updated)
Integration with Dispatcher¶
The dispatcher can use addressing metadata for advanced routing logic:
# Future enhancement: Dispatcher filtering by recipient
# dispatcher.route(message_header, payload) do |header|
# if header.to.nil?
# # Broadcast to all subscribers
# route_to_all_subscribers(header.message_class)
# else
# # Route only to specific recipient
# route_to_entity(header.to, header.message_class)
# end
# end
Best Practices¶
Entity Naming¶
Use consistent, descriptive entity identifiers:
# Good: Descriptive service names
from 'order-management-service'
to 'inventory-tracking-service'
reply_to 'order-status-service'
# Avoid: Generic or unclear names
from 'service1'
to 'app'
Address Consistency¶
Maintain consistent addressing patterns across your application:
# Consistent pattern for microservices
class OrderMessage < SmartMessage::Base
from 'order-service'
to 'fulfillment-service'
reply_to 'order-service'
end
class PaymentMessage < SmartMessage::Base
from 'payment-service'
to 'billing-service'
reply_to 'payment-service'
end
Gateway Configuration¶
For gateway patterns, use instance-level overrides:
# Class defines default routing
class APIMessage < SmartMessage::Base
from 'api-gateway'
to 'internal-service'
end
# Override for external routing
message = APIMessage.new(data: "external request")
message.to('external-partner-service')
message.config do
transport ExternalTransport.new
serializer SecureSerializer.new
end
message.publish
Future Enhancements¶
The addressing system provides the foundation for advanced features:
- Dispatcher Filtering: Route messages based on recipient targeting
- Security Integration: Entity-based authentication and authorization
- Audit Trails: Track message flow between entities
- Load Balancing: Distribute messages across entity instances
- Circuit Breakers: Per-entity failure handling
Entity addressing enables sophisticated messaging architectures while maintaining the simplicity and flexibility that makes SmartMessage powerful.