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,284 @@
|
|
1
|
+
# lib/smart_message/base.rb
|
2
|
+
# encoding: utf-8
|
3
|
+
# frozen_string_literal: true
|
4
|
+
|
5
|
+
require 'securerandom' # STDLIB
|
6
|
+
|
7
|
+
require_relative './wrapper.rb'
|
8
|
+
|
9
|
+
module SmartMessage
|
10
|
+
# The foundation class for the smart message
|
11
|
+
class Base < Hashie::Dash
|
12
|
+
|
13
|
+
# Supports multi-level plugins for transport, serializer and logger.
|
14
|
+
# Plugins can be made at the class level and at the instance level.
|
15
|
+
@@transport = nil
|
16
|
+
@@serializer = nil
|
17
|
+
@@logger = nil
|
18
|
+
|
19
|
+
include Hashie::Extensions::Dash::PropertyTranslation
|
20
|
+
|
21
|
+
include Hashie::Extensions::Coercion
|
22
|
+
include Hashie::Extensions::DeepMerge
|
23
|
+
include Hashie::Extensions::IgnoreUndeclared
|
24
|
+
include Hashie::Extensions::IndifferentAccess
|
25
|
+
include Hashie::Extensions::MergeInitializer
|
26
|
+
include Hashie::Extensions::MethodAccess
|
27
|
+
|
28
|
+
# Common attrubutes for all messages
|
29
|
+
# TODO: Need to change the SmartMessage::Header into a
|
30
|
+
# smartMessage::Wrapper concept where the message
|
31
|
+
# content is serialized into an element in the wrapper
|
32
|
+
# where the wrapper contains header/routing information
|
33
|
+
# in addition to the serialized message data.
|
34
|
+
property :_sm_header
|
35
|
+
|
36
|
+
# Constructor for a messsage definition that allows the
|
37
|
+
# setting of initial values.
|
38
|
+
def initialize(**props, &block)
|
39
|
+
# instance-level over ride of class plugins
|
40
|
+
@transport = nil
|
41
|
+
@serializer = nil
|
42
|
+
@logger = nil
|
43
|
+
|
44
|
+
attributes = {
|
45
|
+
_sm_header: SmartMessage::Header.new(
|
46
|
+
uuid: SecureRandom.uuid,
|
47
|
+
message_class: self.class.to_s,
|
48
|
+
published_at: 2,
|
49
|
+
publisher_pid: 3
|
50
|
+
)
|
51
|
+
}.merge(props)
|
52
|
+
|
53
|
+
super(attributes, &block)
|
54
|
+
end
|
55
|
+
|
56
|
+
|
57
|
+
###################################################
|
58
|
+
## Common instance methods
|
59
|
+
|
60
|
+
# SMELL: How does the transport know how to decode a message before
|
61
|
+
# it knows the message class? We need a wrapper around
|
62
|
+
# the entire message in a known serialization. That
|
63
|
+
# wrapper would contain two properties: _sm_header and
|
64
|
+
# _sm_payload
|
65
|
+
|
66
|
+
# NOTE: to publish a message it must first be encoded using a
|
67
|
+
# serializer. The receive a subscribed to message it must
|
68
|
+
# be decoded via a serializer from the transport to be processed.
|
69
|
+
def encode
|
70
|
+
raise Errors::SerializerNotConfigured if serializer_missing?
|
71
|
+
|
72
|
+
serializer.encode(self)
|
73
|
+
end
|
74
|
+
|
75
|
+
|
76
|
+
# NOTE: you publish instances; but, you subscribe/unsubscribe at
|
77
|
+
# the class-level
|
78
|
+
def publish
|
79
|
+
# TODO: move all of the _sm_ property processes into the wrapper
|
80
|
+
_sm_header.published_at = Time.now
|
81
|
+
_sm_header.publisher_pid = Process.pid
|
82
|
+
|
83
|
+
payload = encode
|
84
|
+
|
85
|
+
raise Errors::TransportNotConfigured if transport_missing?
|
86
|
+
transport.publish(_sm_header, payload)
|
87
|
+
|
88
|
+
SS.add(_sm_header.message_class, 'publish')
|
89
|
+
SS.get(_sm_header.message_class, 'publish')
|
90
|
+
end # def publish
|
91
|
+
|
92
|
+
|
93
|
+
|
94
|
+
#########################################################
|
95
|
+
## instance-level configuration
|
96
|
+
|
97
|
+
# Configure the plugins for transport, serializer and logger
|
98
|
+
def config(&block)
|
99
|
+
instance_eval(&block) if block_given?
|
100
|
+
end
|
101
|
+
|
102
|
+
|
103
|
+
#########################################################
|
104
|
+
## instance-level transport configuration
|
105
|
+
|
106
|
+
def transport(klass_or_instance = nil)
|
107
|
+
klass_or_instance.nil? ? @transport || @@transport : @transport = klass_or_instance
|
108
|
+
end
|
109
|
+
|
110
|
+
def transport_configured?; !transport.nil?; end
|
111
|
+
def transport_missing?; transport.nil?; end
|
112
|
+
def reset_transport; @transport = nil; end
|
113
|
+
|
114
|
+
|
115
|
+
#########################################################
|
116
|
+
## instance-level logger configuration
|
117
|
+
|
118
|
+
def logger(klass_or_instance = nil)
|
119
|
+
klass_or_instance.nil? ? @logger || @@logger : @logger = klass_or_instance
|
120
|
+
end
|
121
|
+
|
122
|
+
def logger_configured?; !logger.nil?; end
|
123
|
+
def logger_missing?; logger.nil?; end
|
124
|
+
def reset_logger; @logger = nil; end
|
125
|
+
|
126
|
+
|
127
|
+
#########################################################
|
128
|
+
## instance-level serializer configuration
|
129
|
+
|
130
|
+
def serializer(klass_or_instance = nil)
|
131
|
+
klass_or_instance.nil? ? @serializer || @@serializer : @serializer = klass_or_instance
|
132
|
+
end
|
133
|
+
|
134
|
+
def serializer_configured?; !serializer.nil?; end
|
135
|
+
def serializer_missing?; serializer.nil?; end
|
136
|
+
def reset_serializer; @serializer = nil; end
|
137
|
+
|
138
|
+
|
139
|
+
#########################################################
|
140
|
+
## instance-level utility methods
|
141
|
+
|
142
|
+
# return this class' name as a string
|
143
|
+
def whoami
|
144
|
+
self.class.to_s
|
145
|
+
end
|
146
|
+
|
147
|
+
|
148
|
+
# returns a collection of class Set that consists of
|
149
|
+
# the symbolized values of the property names of the message
|
150
|
+
# without the injected '_sm_' properties that support
|
151
|
+
# the behind-the-sceens operations of SmartMessage.
|
152
|
+
def fields
|
153
|
+
to_h.keys
|
154
|
+
.reject{|key| key.start_with?('_sm_')}
|
155
|
+
.map{|key| key.to_sym}
|
156
|
+
.to_set
|
157
|
+
end
|
158
|
+
|
159
|
+
|
160
|
+
###########################################################
|
161
|
+
## class methods
|
162
|
+
|
163
|
+
class << self
|
164
|
+
|
165
|
+
#########################################################
|
166
|
+
## class-level configuration
|
167
|
+
|
168
|
+
def config(&block)
|
169
|
+
class_eval(&block) if block_given?
|
170
|
+
end
|
171
|
+
|
172
|
+
|
173
|
+
#########################################################
|
174
|
+
## class-level transport configuration
|
175
|
+
|
176
|
+
def transport(klass_or_instance = nil)
|
177
|
+
klass_or_instance.nil? ? @@transport : @@transport = klass_or_instance
|
178
|
+
end
|
179
|
+
|
180
|
+
def transport_configured?; !transport.nil?; end
|
181
|
+
def transport_missing?; transport.nil?; end
|
182
|
+
def reset_transport; @@transport = nil; end
|
183
|
+
|
184
|
+
|
185
|
+
#########################################################
|
186
|
+
## class-level logger configuration
|
187
|
+
|
188
|
+
def logger(klass_or_instance = nil)
|
189
|
+
klass_or_instance.nil? ? @@logger : @@logger = klass_or_instance
|
190
|
+
end
|
191
|
+
|
192
|
+
def logger_configured?; !logger.nil?; end
|
193
|
+
def logger_missing?; logger.nil?; end
|
194
|
+
def reset_logger; @@logger = nil; end
|
195
|
+
|
196
|
+
|
197
|
+
#########################################################
|
198
|
+
## class-level serializer configuration
|
199
|
+
|
200
|
+
def serializer(klass_or_instance = nil)
|
201
|
+
klass_or_instance.nil? ? @@serializer : @@serializer = klass_or_instance
|
202
|
+
end
|
203
|
+
|
204
|
+
def serializer_configured?; !serializer.nil?; end
|
205
|
+
def serializer_missing?; serializer.nil?; end
|
206
|
+
def reset_serializer; @@serializer = nil; end
|
207
|
+
|
208
|
+
|
209
|
+
#########################################################
|
210
|
+
## class-level subscription management via the transport
|
211
|
+
|
212
|
+
# Add this message class to the transport's catalog of
|
213
|
+
# subscribed messages. If the transport is missing, raise
|
214
|
+
# an exception.
|
215
|
+
def subscribe(process_method = nil)
|
216
|
+
message_class = whoami
|
217
|
+
process_method = message_class + '.process' if process_method.nil?
|
218
|
+
|
219
|
+
# TODO: Add proper logging here
|
220
|
+
|
221
|
+
raise Errors::TransportNotConfigured if transport_missing?
|
222
|
+
transport.subscribe(message_class, process_method)
|
223
|
+
end
|
224
|
+
|
225
|
+
|
226
|
+
# Remove this process_method for this message class from the
|
227
|
+
# subscribers list.
|
228
|
+
def unsubscribe(process_method = nil)
|
229
|
+
message_class = whoami
|
230
|
+
process_method = message_class + '.process' if process_method.nil?
|
231
|
+
# TODO: Add proper logging here
|
232
|
+
|
233
|
+
transport.unsubscribe(message_class, process_method) if transport_configured?
|
234
|
+
end
|
235
|
+
|
236
|
+
|
237
|
+
# Remove this message class and all of its processing methods
|
238
|
+
# from the subscribers list.
|
239
|
+
def unsubscribe!
|
240
|
+
message_class = whoami
|
241
|
+
|
242
|
+
# TODO: Add proper logging here
|
243
|
+
|
244
|
+
transport.unsubscribe!(message_class) if transport_configured?
|
245
|
+
end
|
246
|
+
|
247
|
+
|
248
|
+
|
249
|
+
#########################################################
|
250
|
+
## class-level utility methods
|
251
|
+
|
252
|
+
# return this class' name as a string
|
253
|
+
def whoami
|
254
|
+
ancestors.first.to_s
|
255
|
+
end
|
256
|
+
|
257
|
+
# Return a Set of symbols representing each defined property of
|
258
|
+
# this message class.
|
259
|
+
def fields
|
260
|
+
@properties.dup.delete_if{|item| item.to_s.start_with?('_sm_')}
|
261
|
+
end
|
262
|
+
|
263
|
+
###################################################
|
264
|
+
## Business Logic resides in the #process method.
|
265
|
+
|
266
|
+
# When a transport receives a subscribed to message it
|
267
|
+
# creates an instance of the message and then calls
|
268
|
+
# the process method on that instance.
|
269
|
+
#
|
270
|
+
# It is expected that SmartMessage classes over ride
|
271
|
+
# the SmartMessage::Base#process method with appropriate
|
272
|
+
# business logic to handle the received message content.
|
273
|
+
def process(message_instance)
|
274
|
+
raise Errors::NotImplemented
|
275
|
+
end
|
276
|
+
|
277
|
+
end # class << self
|
278
|
+
end # class Base
|
279
|
+
end # module SmartMessage
|
280
|
+
|
281
|
+
require_relative 'header'
|
282
|
+
require_relative 'transport'
|
283
|
+
require_relative 'serializer'
|
284
|
+
require_relative 'logger'
|
File without changes
|
@@ -0,0 +1,146 @@
|
|
1
|
+
# lib/smart_message/dispatcher.rb
|
2
|
+
# encoding: utf-8
|
3
|
+
# frozen_string_literal: true
|
4
|
+
|
5
|
+
require 'concurrent'
|
6
|
+
|
7
|
+
module SmartMessage
|
8
|
+
|
9
|
+
# The disoatcher routes incoming messages to all of the methods that
|
10
|
+
# have been subscribed to the message.
|
11
|
+
class Dispatcher
|
12
|
+
|
13
|
+
# TODO: setup forwardable for some @router_pool methods
|
14
|
+
|
15
|
+
def initialize
|
16
|
+
@subscribers = Hash.new(Array.new)
|
17
|
+
@router_pool = Concurrent::CachedThreadPool.new
|
18
|
+
at_exit do
|
19
|
+
print "Shuttingdown down the dispatcher's @router_pool ..."
|
20
|
+
@router_pool.shutdown
|
21
|
+
while @router_pool.shuttingdown?
|
22
|
+
print '.'
|
23
|
+
sleep 1
|
24
|
+
end
|
25
|
+
puts " done."
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
|
30
|
+
def what_can_i_do?
|
31
|
+
# TODO: Return pool methods list for debugging
|
32
|
+
@router_pool.methods.sort
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
def status
|
37
|
+
# TODO: Return proper status hash
|
38
|
+
{
|
39
|
+
scheduled_task_count: @router_pool.scheduled_task_count,
|
40
|
+
completed_task_count: @router_pool.completed_task_count,
|
41
|
+
queue_length: @router_pool.queue_length,
|
42
|
+
length: @router_pool.length,
|
43
|
+
running: @router_pool.running?
|
44
|
+
}
|
45
|
+
rescue NoMethodError
|
46
|
+
what_can_i_do?
|
47
|
+
end
|
48
|
+
|
49
|
+
|
50
|
+
def pool
|
51
|
+
@router_pool.instance_variable_get('@pool'.to_sym)
|
52
|
+
end
|
53
|
+
|
54
|
+
def scheduled_task_count
|
55
|
+
@router_pool.scheduled_task_count
|
56
|
+
end
|
57
|
+
|
58
|
+
def worker_task_completed
|
59
|
+
@router_pool.worker_task_completed
|
60
|
+
end
|
61
|
+
|
62
|
+
def completed_task_count
|
63
|
+
@router_pool.completed_task_count
|
64
|
+
end
|
65
|
+
|
66
|
+
def queue_length
|
67
|
+
@router_pool.queue_length
|
68
|
+
end
|
69
|
+
|
70
|
+
|
71
|
+
def current_length
|
72
|
+
@router_pool.length
|
73
|
+
end
|
74
|
+
|
75
|
+
|
76
|
+
def running?
|
77
|
+
@router_pool.running?
|
78
|
+
end
|
79
|
+
|
80
|
+
|
81
|
+
def subscribers
|
82
|
+
@subscribers
|
83
|
+
end
|
84
|
+
|
85
|
+
|
86
|
+
def add(message_class, process_method_as_string)
|
87
|
+
klass = String(message_class)
|
88
|
+
unless @subscribers[klass].include? process_method_as_string
|
89
|
+
@subscribers[klass] += [process_method_as_string]
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
|
94
|
+
# drop a processer from a subscribed message
|
95
|
+
def drop(message_class, process_method_as_string)
|
96
|
+
@subscribers[String(message_class)].delete process_method_as_string
|
97
|
+
end
|
98
|
+
|
99
|
+
|
100
|
+
# drop all processer from a subscribed message
|
101
|
+
def drop_all(message_class)
|
102
|
+
@subscribers.delete String(message_class)
|
103
|
+
end
|
104
|
+
|
105
|
+
|
106
|
+
# complete reset all subscriptions
|
107
|
+
def drop_all!
|
108
|
+
@subscribers = Hash.new(Array.new)
|
109
|
+
end
|
110
|
+
|
111
|
+
|
112
|
+
# message_header is of class SmartMessage::Header
|
113
|
+
# message_payload is a string buffer that is a serialized
|
114
|
+
# SmartMessage
|
115
|
+
def route(message_header, message_payload)
|
116
|
+
message_klass = message_header.message_class
|
117
|
+
return nil if @subscribers[message_klass].empty?
|
118
|
+
@subscribers[message_klass].each do |message_processor|
|
119
|
+
SS.add(message_klass, message_processor, 'routed' )
|
120
|
+
@router_pool.post do
|
121
|
+
parts = message_processor.split('.')
|
122
|
+
target_klass = parts[0]
|
123
|
+
class_method = parts[1]
|
124
|
+
begin
|
125
|
+
target_klass.constantize
|
126
|
+
.method(class_method)
|
127
|
+
.call(message_header, message_payload)
|
128
|
+
rescue Exception => e
|
129
|
+
# TODO: Add proper exception logging
|
130
|
+
# Exception details: #{e.message}
|
131
|
+
# Processor: #{message_processor}
|
132
|
+
puts "Error processing message: #{e.message}" if $DEBUG
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
|
139
|
+
#######################################################
|
140
|
+
## Class methods
|
141
|
+
|
142
|
+
class << self
|
143
|
+
# TODO: may want a class-level config method
|
144
|
+
end # class << self
|
145
|
+
end # class Dispatcher
|
146
|
+
end # module SmartMessage
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# smart_message/lib/smart_message/errors.rb
|
2
|
+
# encoding: utf-8
|
3
|
+
# frozen_string_literal: true
|
4
|
+
|
5
|
+
module SmartMessage
|
6
|
+
module Errors
|
7
|
+
# A message can't be very smart if it does not know how to
|
8
|
+
# send and receive itself using a message transport
|
9
|
+
class TransportNotConfigured < RuntimeError; end
|
10
|
+
|
11
|
+
# A message can't be very smart if it does not know how to
|
12
|
+
# send and receive itself using a message transport
|
13
|
+
class TransportNotConfigured < RuntimeError; end
|
14
|
+
|
15
|
+
# A message can't be very smart if it does not know how to
|
16
|
+
# encode and decode itself using a message serializer
|
17
|
+
class SerializerNotConfigured < RuntimeError; end
|
18
|
+
|
19
|
+
# The functionality has not be implemented
|
20
|
+
class NotImplemented < RuntimeError; end
|
21
|
+
|
22
|
+
# A message was received to which there is no subscription
|
23
|
+
class ReceivedMessageNotSubscribed < RuntimeError; end
|
24
|
+
|
25
|
+
# A received message is of an unknown class
|
26
|
+
class UnknownMessageClass < RuntimeError; end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# lib/smart_message/header.rb
|
2
|
+
# encoding: utf-8
|
3
|
+
# frozen_string_literal: true
|
4
|
+
|
5
|
+
module SmartMessage
|
6
|
+
# Every smart message has a common header format that contains
|
7
|
+
# information used to support the dispatching of subscribed
|
8
|
+
# messages upon receipt from a transport.
|
9
|
+
class Header < Hashie::Dash
|
10
|
+
include Hashie::Extensions::IndifferentAccess
|
11
|
+
include Hashie::Extensions::MergeInitializer
|
12
|
+
include Hashie::Extensions::MethodAccess
|
13
|
+
|
14
|
+
# Common attributes of the smart message standard header
|
15
|
+
property :uuid
|
16
|
+
property :message_class
|
17
|
+
property :published_at
|
18
|
+
property :publisher_pid
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# lib/smart_message/serializer/base.rb
|
2
|
+
# encoding: utf-8
|
3
|
+
# frozen_string_literal: true
|
4
|
+
|
5
|
+
module SmartMessage::Serializer
|
6
|
+
# the standard super class
|
7
|
+
class Base
|
8
|
+
# provide basic configuration
|
9
|
+
def initialize
|
10
|
+
# TODO: write this
|
11
|
+
end
|
12
|
+
|
13
|
+
def encode(message_instance)
|
14
|
+
# TODO: Add proper logging here
|
15
|
+
raise ::SmartMessage::Errors::NotImplemented
|
16
|
+
end
|
17
|
+
|
18
|
+
def decode(payload)
|
19
|
+
# TODO: Add proper logging here
|
20
|
+
raise ::SmartMessage::Errors::NotImplemented
|
21
|
+
end
|
22
|
+
end # class Base
|
23
|
+
end # module SmartMessage::Serializer
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# lib/smart_message/serializer/json.rb
|
2
|
+
# encoding: utf-8
|
3
|
+
# frozen_string_literal: true
|
4
|
+
|
5
|
+
require 'json' # STDLIB
|
6
|
+
|
7
|
+
module SmartMessage::Serializer
|
8
|
+
class JSON < Base
|
9
|
+
def encode(message_instance)
|
10
|
+
# TODO: is this the right place to insert an automated-invisible
|
11
|
+
# message header?
|
12
|
+
message_instance.to_json
|
13
|
+
end
|
14
|
+
|
15
|
+
def decode(payload)
|
16
|
+
# TODO: so how do I know to which message class this payload
|
17
|
+
# belongs? The class needs to be in some kind of message
|
18
|
+
# header.
|
19
|
+
::JSON.parse payload
|
20
|
+
end
|
21
|
+
end # class JSON < Base
|
22
|
+
end # module SmartMessage::Serializer
|
@@ -0,0 +1,10 @@
|
|
1
|
+
# lib/smart_message/serializer.rb
|
2
|
+
# encoding: utf-8
|
3
|
+
# frozen_string_literal: true
|
4
|
+
|
5
|
+
require_relative 'serializer/base'
|
6
|
+
require_relative 'serializer/json'
|
7
|
+
|
8
|
+
module SmartMessage::Serializer
|
9
|
+
# Serializer module for message encoding/decoding
|
10
|
+
end # module SmartMessage::Serializer
|
@@ -0,0 +1,85 @@
|
|
1
|
+
# lib/smart_message/transport/base.rb
|
2
|
+
# encoding: utf-8
|
3
|
+
# frozen_string_literal: true
|
4
|
+
|
5
|
+
module SmartMessage
|
6
|
+
module Transport
|
7
|
+
# Base class for all transport implementations
|
8
|
+
# This defines the standard interface that all transports must implement
|
9
|
+
class Base
|
10
|
+
attr_reader :options, :dispatcher
|
11
|
+
|
12
|
+
def initialize(**options)
|
13
|
+
@options = default_options.merge(options)
|
14
|
+
@dispatcher = options[:dispatcher] || SmartMessage::Dispatcher.new
|
15
|
+
configure
|
16
|
+
end
|
17
|
+
|
18
|
+
# Transport-specific configuration
|
19
|
+
def configure
|
20
|
+
# Override in subclasses for specific setup
|
21
|
+
end
|
22
|
+
|
23
|
+
# Default options for this transport
|
24
|
+
def default_options
|
25
|
+
{}
|
26
|
+
end
|
27
|
+
|
28
|
+
# Publish a message
|
29
|
+
# @param message_header [SmartMessage::Header] Message routing information
|
30
|
+
# @param message_payload [String] Serialized message content
|
31
|
+
def publish(message_header, message_payload)
|
32
|
+
raise NotImplementedError, 'Transport must implement #publish'
|
33
|
+
end
|
34
|
+
|
35
|
+
# Subscribe to a message class
|
36
|
+
# @param message_class [String] The message class name
|
37
|
+
# @param process_method [String] The processing method identifier
|
38
|
+
def subscribe(message_class, process_method)
|
39
|
+
@dispatcher.add(message_class, process_method)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Unsubscribe from a specific message class and process method
|
43
|
+
# @param message_class [String] The message class name
|
44
|
+
# @param process_method [String] The processing method identifier
|
45
|
+
def unsubscribe(message_class, process_method)
|
46
|
+
@dispatcher.drop(message_class, process_method)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Unsubscribe from all process methods for a message class
|
50
|
+
# @param message_class [String] The message class name
|
51
|
+
def unsubscribe!(message_class)
|
52
|
+
@dispatcher.drop_all(message_class)
|
53
|
+
end
|
54
|
+
|
55
|
+
# Get current subscriptions
|
56
|
+
def subscribers
|
57
|
+
@dispatcher.subscribers
|
58
|
+
end
|
59
|
+
|
60
|
+
# Check if transport is connected/available
|
61
|
+
def connected?
|
62
|
+
true
|
63
|
+
end
|
64
|
+
|
65
|
+
# Connect to transport (if applicable)
|
66
|
+
def connect
|
67
|
+
# Override in subclasses if connection setup is needed
|
68
|
+
end
|
69
|
+
|
70
|
+
# Disconnect from transport (if applicable)
|
71
|
+
def disconnect
|
72
|
+
# Override in subclasses if cleanup is needed
|
73
|
+
end
|
74
|
+
|
75
|
+
# Receive and route a message (called by transport implementations)
|
76
|
+
# @param message_header [SmartMessage::Header] Message routing information
|
77
|
+
# @param message_payload [String] Serialized message content
|
78
|
+
protected
|
79
|
+
|
80
|
+
def receive(message_header, message_payload)
|
81
|
+
@dispatcher.route(message_header, message_payload)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|