smart_message 0.0.1
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 +7 -0
- data/.envrc +3 -0
- data/.gitignore +8 -0
- data/.travis.yml +7 -0
- data/CHANGELOG.md +100 -0
- data/COMMITS.md +196 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +71 -0
- data/README.md +303 -0
- data/Rakefile +10 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/docs/README.md +52 -0
- data/docs/architecture.md +370 -0
- data/docs/dispatcher.md +593 -0
- data/docs/examples.md +808 -0
- data/docs/getting-started.md +235 -0
- data/docs/ideas_to_think_about.md +329 -0
- data/docs/serializers.md +575 -0
- data/docs/transports.md +501 -0
- data/docs/troubleshooting.md +582 -0
- data/examples/01_point_to_point_orders.rb +200 -0
- data/examples/02_publish_subscribe_events.rb +364 -0
- data/examples/03_many_to_many_chat.rb +608 -0
- data/examples/README.md +335 -0
- data/examples/tmux_chat/README.md +283 -0
- data/examples/tmux_chat/bot_agent.rb +272 -0
- data/examples/tmux_chat/human_agent.rb +197 -0
- data/examples/tmux_chat/room_monitor.rb +158 -0
- data/examples/tmux_chat/shared_chat_system.rb +295 -0
- data/examples/tmux_chat/start_chat_demo.sh +190 -0
- data/examples/tmux_chat/stop_chat_demo.sh +22 -0
- data/lib/simple_stats.rb +57 -0
- data/lib/smart_message/base.rb +284 -0
- data/lib/smart_message/dispatcher/.keep +0 -0
- data/lib/smart_message/dispatcher.rb +146 -0
- data/lib/smart_message/errors.rb +29 -0
- data/lib/smart_message/header.rb +20 -0
- data/lib/smart_message/logger/base.rb +8 -0
- data/lib/smart_message/logger.rb +7 -0
- data/lib/smart_message/serializer/base.rb +23 -0
- data/lib/smart_message/serializer/json.rb +22 -0
- data/lib/smart_message/serializer.rb +10 -0
- data/lib/smart_message/transport/base.rb +85 -0
- data/lib/smart_message/transport/memory_transport.rb +69 -0
- data/lib/smart_message/transport/registry.rb +59 -0
- data/lib/smart_message/transport/stdout_transport.rb +62 -0
- data/lib/smart_message/transport.rb +41 -0
- data/lib/smart_message/version.rb +7 -0
- data/lib/smart_message/wrapper.rb +43 -0
- data/lib/smart_message.rb +54 -0
- data/smart_message.gemspec +53 -0
- metadata +252 -0
@@ -0,0 +1,200 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# examples/01_point_to_point_orders.rb
|
3
|
+
#
|
4
|
+
# 1-to-1 Messaging Example: Order Processing System
|
5
|
+
#
|
6
|
+
# This example demonstrates point-to-point messaging between an OrderService
|
7
|
+
# and a PaymentService. Each order gets processed by exactly one payment processor.
|
8
|
+
|
9
|
+
require_relative '../lib/smart_message'
|
10
|
+
|
11
|
+
puts "=== SmartMessage Example: Point-to-Point Order Processing ==="
|
12
|
+
puts
|
13
|
+
|
14
|
+
# Define the Order Message
|
15
|
+
class OrderMessage < SmartMessage::Base
|
16
|
+
property :order_id
|
17
|
+
property :customer_id
|
18
|
+
property :amount
|
19
|
+
property :currency, default: 'USD'
|
20
|
+
property :payment_method
|
21
|
+
property :items
|
22
|
+
|
23
|
+
# Configure to use memory transport for this example
|
24
|
+
config do
|
25
|
+
transport SmartMessage::Transport::StdoutTransport.new(loopback: true)
|
26
|
+
serializer SmartMessage::Serializer::JSON.new
|
27
|
+
end
|
28
|
+
|
29
|
+
# Default processing - just logs the order
|
30
|
+
def self.process(message_header, message_payload)
|
31
|
+
order_data = JSON.parse(message_payload)
|
32
|
+
puts "š Order received: #{order_data['order_id']} for $#{order_data['amount']}"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Define the Payment Response Message
|
37
|
+
class PaymentResponseMessage < SmartMessage::Base
|
38
|
+
property :order_id
|
39
|
+
property :payment_id
|
40
|
+
property :status # 'success', 'failed', 'pending'
|
41
|
+
property :message
|
42
|
+
property :processed_at
|
43
|
+
|
44
|
+
config do
|
45
|
+
transport SmartMessage::Transport::StdoutTransport.new(loopback: true)
|
46
|
+
serializer SmartMessage::Serializer::JSON.new
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.process(message_header, message_payload)
|
50
|
+
response_data = JSON.parse(message_payload)
|
51
|
+
status_emoji = case response_data['status']
|
52
|
+
when 'success' then 'ā
'
|
53
|
+
when 'failed' then 'ā'
|
54
|
+
when 'pending' then 'ā³'
|
55
|
+
else 'ā'
|
56
|
+
end
|
57
|
+
|
58
|
+
puts "#{status_emoji} Payment #{response_data['status']}: Order #{response_data['order_id']} - #{response_data['message']}"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# Order Service - Creates and sends orders
|
63
|
+
class OrderService
|
64
|
+
def initialize
|
65
|
+
puts "šŖ OrderService: Starting up..."
|
66
|
+
@order_counter = 1000
|
67
|
+
end
|
68
|
+
|
69
|
+
def create_order(customer_id:, amount:, payment_method:, items:)
|
70
|
+
order_id = "ORD-#{@order_counter += 1}"
|
71
|
+
|
72
|
+
puts "\nšŖ OrderService: Creating order #{order_id}"
|
73
|
+
|
74
|
+
order = OrderMessage.new(
|
75
|
+
order_id: order_id,
|
76
|
+
customer_id: customer_id,
|
77
|
+
amount: amount,
|
78
|
+
payment_method: payment_method,
|
79
|
+
items: items
|
80
|
+
)
|
81
|
+
|
82
|
+
puts "šŖ OrderService: Sending order to payment processing..."
|
83
|
+
order.publish
|
84
|
+
|
85
|
+
order_id
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# Payment Service - Processes orders and sends responses
|
90
|
+
class PaymentService
|
91
|
+
def initialize
|
92
|
+
puts "š³ PaymentService: Starting up..."
|
93
|
+
@payment_counter = 5000
|
94
|
+
|
95
|
+
# Subscribe to order messages with custom processor
|
96
|
+
OrderMessage.subscribe('PaymentService.process_order')
|
97
|
+
end
|
98
|
+
|
99
|
+
def self.process_order(message_header, message_payload)
|
100
|
+
processor = new
|
101
|
+
processor.handle_order(message_header, message_payload)
|
102
|
+
end
|
103
|
+
|
104
|
+
def handle_order(message_header, message_payload)
|
105
|
+
order_data = JSON.parse(message_payload)
|
106
|
+
payment_id = "PAY-#{@payment_counter += 1}"
|
107
|
+
|
108
|
+
puts "š³ PaymentService: Processing payment for order #{order_data['order_id']}"
|
109
|
+
|
110
|
+
# Simulate payment processing logic
|
111
|
+
success = simulate_payment_processing(order_data)
|
112
|
+
|
113
|
+
# Send response back
|
114
|
+
response = PaymentResponseMessage.new(
|
115
|
+
order_id: order_data['order_id'],
|
116
|
+
payment_id: payment_id,
|
117
|
+
status: success ? 'success' : 'failed',
|
118
|
+
message: success ? 'Payment processed successfully' : 'Insufficient funds',
|
119
|
+
processed_at: Time.now.iso8601
|
120
|
+
)
|
121
|
+
|
122
|
+
puts "š³ PaymentService: Sending payment response..."
|
123
|
+
response.publish
|
124
|
+
end
|
125
|
+
|
126
|
+
private
|
127
|
+
|
128
|
+
def simulate_payment_processing(order_data)
|
129
|
+
# Simulate processing time
|
130
|
+
sleep(0.1)
|
131
|
+
|
132
|
+
# Simulate success/failure based on amount (fail large orders for demo)
|
133
|
+
order_data['amount'] < 1000
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
# Demo Runner
|
138
|
+
class OrderProcessingDemo
|
139
|
+
def run
|
140
|
+
puts "š Starting Order Processing Demo\n"
|
141
|
+
|
142
|
+
# Start services
|
143
|
+
order_service = OrderService.new
|
144
|
+
payment_service = PaymentService.new
|
145
|
+
|
146
|
+
# Subscribe to payment responses
|
147
|
+
PaymentResponseMessage.subscribe
|
148
|
+
|
149
|
+
puts "\n" + "="*60
|
150
|
+
puts "Processing Sample Orders"
|
151
|
+
puts "="*60
|
152
|
+
|
153
|
+
# Create some sample orders
|
154
|
+
orders = [
|
155
|
+
{
|
156
|
+
customer_id: "CUST-001",
|
157
|
+
amount: 99.99,
|
158
|
+
payment_method: "credit_card",
|
159
|
+
items: ["Widget A", "Widget B"]
|
160
|
+
},
|
161
|
+
{
|
162
|
+
customer_id: "CUST-002",
|
163
|
+
amount: 1299.99, # This will fail (too large)
|
164
|
+
payment_method: "debit_card",
|
165
|
+
items: ["Premium Widget", "Extended Warranty"]
|
166
|
+
},
|
167
|
+
{
|
168
|
+
customer_id: "CUST-003",
|
169
|
+
amount: 45.50,
|
170
|
+
payment_method: "paypal",
|
171
|
+
items: ["Small Widget"]
|
172
|
+
}
|
173
|
+
]
|
174
|
+
|
175
|
+
orders.each_with_index do |order_params, index|
|
176
|
+
puts "\n--- Order #{index + 1} ---"
|
177
|
+
order_id = order_service.create_order(**order_params)
|
178
|
+
|
179
|
+
# Brief pause between orders for clarity
|
180
|
+
sleep(0.5)
|
181
|
+
end
|
182
|
+
|
183
|
+
# Give time for all async processing to complete
|
184
|
+
puts "\nā³ Waiting for all payments to process..."
|
185
|
+
sleep(2)
|
186
|
+
|
187
|
+
puts "\n⨠Demo completed!"
|
188
|
+
puts "\nThis example demonstrated:"
|
189
|
+
puts "⢠Point-to-point messaging between OrderService and PaymentService"
|
190
|
+
puts "⢠Bidirectional communication with request/response pattern"
|
191
|
+
puts "⢠JSON serialization of complex message data"
|
192
|
+
puts "⢠STDOUT transport with loopback for local demonstration"
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
# Run the demo if this file is executed directly
|
197
|
+
if __FILE__ == $0
|
198
|
+
demo = OrderProcessingDemo.new
|
199
|
+
demo.run
|
200
|
+
end
|
@@ -0,0 +1,364 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# examples/02_publish_subscribe_events.rb
|
3
|
+
#
|
4
|
+
# 1-to-Many Messaging Example: Event Notification System
|
5
|
+
#
|
6
|
+
# This example demonstrates publish-subscribe messaging where one event publisher
|
7
|
+
# sends notifications to multiple subscribers (email service, SMS service, audit logger).
|
8
|
+
|
9
|
+
require_relative '../lib/smart_message'
|
10
|
+
|
11
|
+
puts "=== SmartMessage Example: Publish-Subscribe Event Notifications ==="
|
12
|
+
puts
|
13
|
+
|
14
|
+
# Define the User Event Message
|
15
|
+
class UserEventMessage < SmartMessage::Base
|
16
|
+
property :event_id
|
17
|
+
property :event_type # 'user_registered', 'user_login', 'password_changed', etc.
|
18
|
+
property :user_id
|
19
|
+
property :user_email
|
20
|
+
property :user_name
|
21
|
+
property :timestamp
|
22
|
+
property :metadata # Additional event-specific data
|
23
|
+
|
24
|
+
config do
|
25
|
+
transport SmartMessage::Transport::StdoutTransport.new(loopback: true)
|
26
|
+
serializer SmartMessage::Serializer::JSON.new
|
27
|
+
end
|
28
|
+
|
29
|
+
# Default processor - just logs the event
|
30
|
+
def self.process(message_header, message_payload)
|
31
|
+
event_data = JSON.parse(message_payload)
|
32
|
+
puts "š” Event broadcasted: #{event_data['event_type']} for user #{event_data['user_id']}"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Email Notification Service
|
37
|
+
class EmailService
|
38
|
+
def initialize
|
39
|
+
puts "š§ EmailService: Starting up..."
|
40
|
+
# Subscribe to user events with custom processor
|
41
|
+
UserEventMessage.subscribe('EmailService.handle_user_event')
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.handle_user_event(message_header, message_payload)
|
45
|
+
service = new
|
46
|
+
service.process_event(message_header, message_payload)
|
47
|
+
end
|
48
|
+
|
49
|
+
def process_event(message_header, message_payload)
|
50
|
+
event_data = JSON.parse(message_payload)
|
51
|
+
|
52
|
+
case event_data['event_type']
|
53
|
+
when 'user_registered'
|
54
|
+
send_welcome_email(event_data)
|
55
|
+
when 'password_changed'
|
56
|
+
send_security_alert(event_data)
|
57
|
+
when 'user_login'
|
58
|
+
# Could send login notifications for suspicious activity
|
59
|
+
log_email_activity("Login notification skipped for #{event_data['user_email']}")
|
60
|
+
else
|
61
|
+
log_email_activity("No email action for event: #{event_data['event_type']}")
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def send_welcome_email(event_data)
|
68
|
+
puts "š§ EmailService: Sending welcome email to #{event_data['user_email']}"
|
69
|
+
puts " Subject: Welcome to our platform, #{event_data['user_name']}!"
|
70
|
+
puts " Content: Thank you for registering..."
|
71
|
+
simulate_email_delivery
|
72
|
+
end
|
73
|
+
|
74
|
+
def send_security_alert(event_data)
|
75
|
+
puts "š§ EmailService: Sending security alert to #{event_data['user_email']}"
|
76
|
+
puts " Subject: Your password was changed"
|
77
|
+
puts " Content: If this wasn't you, please contact support..."
|
78
|
+
simulate_email_delivery
|
79
|
+
end
|
80
|
+
|
81
|
+
def log_email_activity(message)
|
82
|
+
puts "š§ EmailService: #{message}"
|
83
|
+
end
|
84
|
+
|
85
|
+
def simulate_email_delivery
|
86
|
+
# Simulate email sending delay
|
87
|
+
sleep(0.1)
|
88
|
+
puts " āļø Email queued for delivery"
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# SMS Notification Service
|
93
|
+
class SMSService
|
94
|
+
def initialize
|
95
|
+
puts "š± SMSService: Starting up..."
|
96
|
+
UserEventMessage.subscribe('SMSService.handle_user_event')
|
97
|
+
end
|
98
|
+
|
99
|
+
def self.handle_user_event(message_header, message_payload)
|
100
|
+
service = new
|
101
|
+
service.process_event(message_header, message_payload)
|
102
|
+
end
|
103
|
+
|
104
|
+
def process_event(message_header, message_payload)
|
105
|
+
event_data = JSON.parse(message_payload)
|
106
|
+
|
107
|
+
case event_data['event_type']
|
108
|
+
when 'password_changed'
|
109
|
+
send_security_sms(event_data)
|
110
|
+
when 'user_login'
|
111
|
+
if suspicious_login?(event_data)
|
112
|
+
send_login_alert(event_data)
|
113
|
+
else
|
114
|
+
log_sms_activity("Normal login, no SMS sent for #{event_data['user_id']}")
|
115
|
+
end
|
116
|
+
else
|
117
|
+
log_sms_activity("No SMS action for event: #{event_data['event_type']}")
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
private
|
122
|
+
|
123
|
+
def send_security_sms(event_data)
|
124
|
+
phone = get_user_phone(event_data['user_id'])
|
125
|
+
puts "š± SMSService: Sending security SMS to #{phone}"
|
126
|
+
puts " Message: Your password was changed. Contact support if this wasn't you."
|
127
|
+
simulate_sms_delivery
|
128
|
+
end
|
129
|
+
|
130
|
+
def send_login_alert(event_data)
|
131
|
+
phone = get_user_phone(event_data['user_id'])
|
132
|
+
puts "š± SMSService: Sending login alert SMS to #{phone}"
|
133
|
+
puts " Message: Suspicious login detected from new location."
|
134
|
+
simulate_sms_delivery
|
135
|
+
end
|
136
|
+
|
137
|
+
def suspicious_login?(event_data)
|
138
|
+
# Simulate detection logic - mark logins from 'unknown' locations as suspicious
|
139
|
+
event_data.dig('metadata', 'location') == 'unknown'
|
140
|
+
end
|
141
|
+
|
142
|
+
def get_user_phone(user_id)
|
143
|
+
"+1-555-0#{user_id.split('-').last}"
|
144
|
+
end
|
145
|
+
|
146
|
+
def log_sms_activity(message)
|
147
|
+
puts "š± SMSService: #{message}"
|
148
|
+
end
|
149
|
+
|
150
|
+
def simulate_sms_delivery
|
151
|
+
sleep(0.05)
|
152
|
+
puts " š¬ SMS sent"
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
# Audit Logging Service
|
157
|
+
class AuditService
|
158
|
+
def initialize
|
159
|
+
puts "š AuditService: Starting up..."
|
160
|
+
@audit_log = []
|
161
|
+
UserEventMessage.subscribe('AuditService.handle_user_event')
|
162
|
+
end
|
163
|
+
|
164
|
+
def self.handle_user_event(message_header, message_payload)
|
165
|
+
# Use a singleton pattern for persistent audit log
|
166
|
+
@@instance ||= new
|
167
|
+
@@instance.process_event(message_header, message_payload)
|
168
|
+
end
|
169
|
+
|
170
|
+
def self.get_summary
|
171
|
+
@@instance&.get_audit_summary || {}
|
172
|
+
end
|
173
|
+
|
174
|
+
def process_event(message_header, message_payload)
|
175
|
+
event_data = JSON.parse(message_payload)
|
176
|
+
|
177
|
+
audit_entry = {
|
178
|
+
timestamp: Time.now.iso8601,
|
179
|
+
event_id: event_data['event_id'],
|
180
|
+
event_type: event_data['event_type'],
|
181
|
+
user_id: event_data['user_id'],
|
182
|
+
processed_at: message_header.published_at
|
183
|
+
}
|
184
|
+
|
185
|
+
@audit_log << audit_entry
|
186
|
+
puts "š AuditService: Logged event #{event_data['event_id']} (#{event_data['event_type']})"
|
187
|
+
puts " Total events logged: #{@audit_log.size}"
|
188
|
+
end
|
189
|
+
|
190
|
+
def get_audit_summary
|
191
|
+
@audit_log.group_by { |entry| entry[:event_type] }
|
192
|
+
.transform_values(&:count)
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
# User Management System (Event Publisher)
|
197
|
+
class UserManager
|
198
|
+
def initialize
|
199
|
+
puts "š¤ UserManager: Starting up..."
|
200
|
+
@user_counter = 100
|
201
|
+
@event_counter = 1000
|
202
|
+
end
|
203
|
+
|
204
|
+
def register_user(name:, email:)
|
205
|
+
user_id = "USER-#{@user_counter += 1}"
|
206
|
+
|
207
|
+
puts "\nš¤ UserManager: Registering new user #{name} (#{user_id})"
|
208
|
+
|
209
|
+
# Simulate user creation in database
|
210
|
+
create_user_record(user_id, name, email)
|
211
|
+
|
212
|
+
# Publish user registration event
|
213
|
+
publish_event(
|
214
|
+
event_type: 'user_registered',
|
215
|
+
user_id: user_id,
|
216
|
+
user_email: email,
|
217
|
+
user_name: name,
|
218
|
+
metadata: { source: 'web_registration' }
|
219
|
+
)
|
220
|
+
|
221
|
+
user_id
|
222
|
+
end
|
223
|
+
|
224
|
+
def user_login(user_id:, email:, location: 'known')
|
225
|
+
puts "\nš¤ UserManager: User #{user_id} logging in from #{location}"
|
226
|
+
|
227
|
+
publish_event(
|
228
|
+
event_type: 'user_login',
|
229
|
+
user_id: user_id,
|
230
|
+
user_email: email,
|
231
|
+
user_name: get_user_name(user_id),
|
232
|
+
metadata: { location: location, ip: generate_fake_ip }
|
233
|
+
)
|
234
|
+
end
|
235
|
+
|
236
|
+
def change_password(user_id:, email:)
|
237
|
+
puts "\nš¤ UserManager: User #{user_id} changed password"
|
238
|
+
|
239
|
+
publish_event(
|
240
|
+
event_type: 'password_changed',
|
241
|
+
user_id: user_id,
|
242
|
+
user_email: email,
|
243
|
+
user_name: get_user_name(user_id),
|
244
|
+
metadata: { method: 'self_service' }
|
245
|
+
)
|
246
|
+
end
|
247
|
+
|
248
|
+
private
|
249
|
+
|
250
|
+
def create_user_record(user_id, name, email)
|
251
|
+
# Simulate database insertion
|
252
|
+
sleep(0.05)
|
253
|
+
puts "š¤ UserManager: User record created in database"
|
254
|
+
end
|
255
|
+
|
256
|
+
def publish_event(event_type:, user_id:, user_email:, user_name:, metadata: {})
|
257
|
+
event = UserEventMessage.new(
|
258
|
+
event_id: "EVT-#{@event_counter += 1}",
|
259
|
+
event_type: event_type,
|
260
|
+
user_id: user_id,
|
261
|
+
user_email: user_email,
|
262
|
+
user_name: user_name,
|
263
|
+
timestamp: Time.now.iso8601,
|
264
|
+
metadata: metadata
|
265
|
+
)
|
266
|
+
|
267
|
+
puts "š¤ UserManager: Publishing #{event_type} event..."
|
268
|
+
event.publish
|
269
|
+
end
|
270
|
+
|
271
|
+
def get_user_name(user_id)
|
272
|
+
# Simulate database lookup
|
273
|
+
case user_id
|
274
|
+
when /101/ then "Alice Johnson"
|
275
|
+
when /102/ then "Bob Smith"
|
276
|
+
when /103/ then "Carol Williams"
|
277
|
+
else "Unknown User"
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
def generate_fake_ip
|
282
|
+
"192.168.#{rand(1..254)}.#{rand(1..254)}"
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
# Demo Runner
|
287
|
+
class EventNotificationDemo
|
288
|
+
def run
|
289
|
+
puts "š Starting Event Notification Demo\n"
|
290
|
+
|
291
|
+
# Start all services (these become subscribers)
|
292
|
+
email_service = EmailService.new
|
293
|
+
sms_service = SMSService.new
|
294
|
+
audit_service = AuditService.new
|
295
|
+
|
296
|
+
# Start the publisher
|
297
|
+
user_manager = UserManager.new
|
298
|
+
|
299
|
+
puts "\n" + "="*70
|
300
|
+
puts "Simulating User Activities"
|
301
|
+
puts "="*70
|
302
|
+
|
303
|
+
# Simulate various user activities
|
304
|
+
puts "\n--- Scenario 1: New User Registration ---"
|
305
|
+
user_id_1 = user_manager.register_user(
|
306
|
+
name: "Alice Johnson",
|
307
|
+
email: "alice@example.com"
|
308
|
+
)
|
309
|
+
sleep(0.8) # Let all services process
|
310
|
+
|
311
|
+
puts "\n--- Scenario 2: Normal User Login ---"
|
312
|
+
user_manager.user_login(
|
313
|
+
user_id: user_id_1,
|
314
|
+
email: "alice@example.com",
|
315
|
+
location: "known"
|
316
|
+
)
|
317
|
+
sleep(0.8)
|
318
|
+
|
319
|
+
puts "\n--- Scenario 3: Another User Registration ---"
|
320
|
+
user_id_2 = user_manager.register_user(
|
321
|
+
name: "Bob Smith",
|
322
|
+
email: "bob@example.com"
|
323
|
+
)
|
324
|
+
sleep(0.8)
|
325
|
+
|
326
|
+
puts "\n--- Scenario 4: Suspicious Login ---"
|
327
|
+
user_manager.user_login(
|
328
|
+
user_id: user_id_2,
|
329
|
+
email: "bob@example.com",
|
330
|
+
location: "unknown"
|
331
|
+
)
|
332
|
+
sleep(0.8)
|
333
|
+
|
334
|
+
puts "\n--- Scenario 5: Password Change ---"
|
335
|
+
user_manager.change_password(
|
336
|
+
user_id: user_id_1,
|
337
|
+
email: "alice@example.com"
|
338
|
+
)
|
339
|
+
sleep(0.8)
|
340
|
+
|
341
|
+
# Show audit summary
|
342
|
+
puts "\n" + "="*70
|
343
|
+
puts "š Final Audit Summary"
|
344
|
+
puts "="*70
|
345
|
+
summary = AuditService.get_summary
|
346
|
+
summary.each do |event_type, count|
|
347
|
+
puts "#{event_type}: #{count} events"
|
348
|
+
end
|
349
|
+
|
350
|
+
puts "\n⨠Demo completed!"
|
351
|
+
puts "\nThis example demonstrated:"
|
352
|
+
puts "⢠One-to-many publish-subscribe messaging pattern"
|
353
|
+
puts "⢠Multiple services subscribing to the same event stream"
|
354
|
+
puts "⢠Different services handling events in their own specific ways"
|
355
|
+
puts "⢠Decoupled architecture where services can be added/removed independently"
|
356
|
+
puts "⢠Event-driven architecture with audit logging"
|
357
|
+
end
|
358
|
+
end
|
359
|
+
|
360
|
+
# Run the demo if this file is executed directly
|
361
|
+
if __FILE__ == $0
|
362
|
+
demo = EventNotificationDemo.new
|
363
|
+
demo.run
|
364
|
+
end
|