moleculer 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +14 -0
- data/.rspec +3 -0
- data/.rubocop.yml +44 -0
- data/.travis.yml +23 -0
- data/.yardopts +1 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +57 -0
- data/LICENSE.txt +21 -0
- data/README.md +59 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/examples/benchmark_server.rb +21 -0
- data/examples/client-server/client.rb +13 -0
- data/examples/client-server/server.rb +25 -0
- data/lib/moleculer/broker.rb +318 -0
- data/lib/moleculer/configuration.rb +109 -0
- data/lib/moleculer/context.rb +24 -0
- data/lib/moleculer/errors/action_not_found.rb +6 -0
- data/lib/moleculer/errors/invalid_action_response.rb +11 -0
- data/lib/moleculer/errors/local_node_already_registered.rb +6 -0
- data/lib/moleculer/errors/node_not_found.rb +6 -0
- data/lib/moleculer/errors/transporter_already_started.rb +6 -0
- data/lib/moleculer/node.rb +105 -0
- data/lib/moleculer/packets/base.rb +47 -0
- data/lib/moleculer/packets/disconnect.rb +10 -0
- data/lib/moleculer/packets/discover.rb +10 -0
- data/lib/moleculer/packets/event.rb +37 -0
- data/lib/moleculer/packets/heartbeat.rb +18 -0
- data/lib/moleculer/packets/info.rb +97 -0
- data/lib/moleculer/packets/req.rb +57 -0
- data/lib/moleculer/packets/res.rb +43 -0
- data/lib/moleculer/packets.rb +25 -0
- data/lib/moleculer/registry.rb +325 -0
- data/lib/moleculer/serializers/json.rb +23 -0
- data/lib/moleculer/serializers.rb +10 -0
- data/lib/moleculer/service/action.rb +60 -0
- data/lib/moleculer/service/base.rb +117 -0
- data/lib/moleculer/service/event.rb +50 -0
- data/lib/moleculer/service/remote.rb +80 -0
- data/lib/moleculer/service.rb +2 -0
- data/lib/moleculer/support/hash_util.rb +48 -0
- data/lib/moleculer/support/log_proxy.rb +67 -0
- data/lib/moleculer/support/open_struct.rb +15 -0
- data/lib/moleculer/support/string_util.rb +25 -0
- data/lib/moleculer/support.rb +4 -0
- data/lib/moleculer/transporters/redis.rb +207 -0
- data/lib/moleculer/transporters.rb +22 -0
- data/lib/moleculer/version.rb +3 -0
- data/lib/moleculer.rb +103 -0
- data/moleculer.gemspec +50 -0
- metadata +238 -0
@@ -0,0 +1,318 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "forwardable"
|
4
|
+
|
5
|
+
require_relative "registry"
|
6
|
+
require_relative "transporters"
|
7
|
+
require_relative "support"
|
8
|
+
|
9
|
+
module Moleculer
|
10
|
+
##
|
11
|
+
# The Broker is the primary component of Moleculer. It handles action, events, and communication with remote nodes.
|
12
|
+
# Only a single broker should be run for any given process, and it is automatically started when Moleculer::start or
|
13
|
+
# Moleculer::run is called.
|
14
|
+
class Broker
|
15
|
+
include Moleculer::Support
|
16
|
+
extend Forwardable
|
17
|
+
attr_reader :config
|
18
|
+
|
19
|
+
def_delegators :@config, :node_id, :heartbeat_interval, :services, :service_prefix
|
20
|
+
|
21
|
+
##
|
22
|
+
# @param config [Moleculer::Config] the broker configuration
|
23
|
+
def initialize(config)
|
24
|
+
@config = config
|
25
|
+
|
26
|
+
@config.broker = self
|
27
|
+
|
28
|
+
@logger = @config.logger.get_child("[BROKER]")
|
29
|
+
@registry = Registry.new(@config)
|
30
|
+
@transporter = Transporters.for(@config.transporter).new(@config)
|
31
|
+
@contexts = Concurrent::Map.new
|
32
|
+
end
|
33
|
+
|
34
|
+
##
|
35
|
+
# Call the provided action.
|
36
|
+
#
|
37
|
+
# @param action_name [String] the action to call.
|
38
|
+
# @param params [Hash] the params with which to call the action
|
39
|
+
# @param meta [Hash] the metadata of the request
|
40
|
+
#
|
41
|
+
# @return [Hash] the return result of the action call
|
42
|
+
def call(action_name, params, meta: {}, node_id: nil, timeout: Moleculer.config.timeout)
|
43
|
+
action = node_id ? @registry.fetch_action_for_node_id(action_name, node_id) : @registry.fetch_action(action_name)
|
44
|
+
|
45
|
+
context = Context.new(
|
46
|
+
broker: self,
|
47
|
+
action: action,
|
48
|
+
params: params,
|
49
|
+
meta: meta,
|
50
|
+
timeout: timeout,
|
51
|
+
)
|
52
|
+
|
53
|
+
future = Concurrent::Promises.resolvable_future
|
54
|
+
|
55
|
+
@contexts[context.id] = {
|
56
|
+
context: context,
|
57
|
+
called_at: Time.now,
|
58
|
+
future: future,
|
59
|
+
}
|
60
|
+
|
61
|
+
action.execute(context, self)
|
62
|
+
|
63
|
+
future.value!(context.timeout)
|
64
|
+
end
|
65
|
+
|
66
|
+
def emit(event_name, payload)
|
67
|
+
@logger.debug("emitting event '#{event_name}'")
|
68
|
+
events = @registry.fetch_events_for_emit(event_name)
|
69
|
+
|
70
|
+
events.each { |e| e.execute(payload, self) }
|
71
|
+
end
|
72
|
+
|
73
|
+
def run
|
74
|
+
self_read, self_write = IO.pipe
|
75
|
+
|
76
|
+
%w[INT TERM].each do |sig|
|
77
|
+
trap sig do
|
78
|
+
self_write.puts(sig)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
begin
|
83
|
+
start
|
84
|
+
|
85
|
+
while (readable_io = IO.select([self_read]))
|
86
|
+
signal = readable_io.first[0].gets.strip
|
87
|
+
handle_signal(signal)
|
88
|
+
end
|
89
|
+
rescue Interrupt
|
90
|
+
stop
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def start
|
95
|
+
@logger.info "starting"
|
96
|
+
@transporter.connect
|
97
|
+
register_local_node
|
98
|
+
start_subscribers
|
99
|
+
publish_discover
|
100
|
+
publish_info
|
101
|
+
start_heartbeat
|
102
|
+
self
|
103
|
+
end
|
104
|
+
|
105
|
+
def stop
|
106
|
+
@logger.info "stopping"
|
107
|
+
publish(:disconnect)
|
108
|
+
@transporter.disconnect
|
109
|
+
exit 0
|
110
|
+
end
|
111
|
+
|
112
|
+
def wait_for_services(*services)
|
113
|
+
until (services = @registry.missing_services(*services)) && services.empty?
|
114
|
+
@logger.info "waiting for services '#{services.join(', ')}'"
|
115
|
+
sleep 0.1
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def local_node
|
120
|
+
@registry.local_node
|
121
|
+
end
|
122
|
+
|
123
|
+
##
|
124
|
+
# Processes an incoming message and passes it to the appropriate channel for handling
|
125
|
+
#
|
126
|
+
# @param [String] channel the channel in which the message came in on
|
127
|
+
# @param [Hash] message the raw deserialized message
|
128
|
+
def process_message(channel, message)
|
129
|
+
subscribers[channel] << Packets.for(channel.split(".")[1]).new(message) if subscribers[channel]
|
130
|
+
rescue StandardError => e
|
131
|
+
@logger.error e
|
132
|
+
end
|
133
|
+
|
134
|
+
def process_response(packet)
|
135
|
+
context = @contexts.delete(packet.id)
|
136
|
+
context[:future].fulfill(packet.data)
|
137
|
+
end
|
138
|
+
|
139
|
+
def process_event(packet)
|
140
|
+
@logger.debug("processing event '#{packet.event}'")
|
141
|
+
events = @registry.fetch_events_for_node_id(packet.event, node_id)
|
142
|
+
|
143
|
+
events.each { |e| e.execute(packet.data) }
|
144
|
+
rescue StandardError => e
|
145
|
+
@logger.error e
|
146
|
+
end
|
147
|
+
|
148
|
+
def process_request(packet)
|
149
|
+
@logger.debug "processing request #{packet.id}"
|
150
|
+
action = @registry.fetch_action_for_node_id(packet.action, node_id)
|
151
|
+
node = @registry.fetch_node(packet.sender)
|
152
|
+
|
153
|
+
context = Context.new(
|
154
|
+
id: packet.id,
|
155
|
+
broker: self,
|
156
|
+
action: action,
|
157
|
+
params: packet.params,
|
158
|
+
meta: packet.meta,
|
159
|
+
timeout: @config.timeout,
|
160
|
+
)
|
161
|
+
|
162
|
+
response = action.execute(context, self)
|
163
|
+
|
164
|
+
publish_res(
|
165
|
+
id: context.id,
|
166
|
+
success: true,
|
167
|
+
data: response,
|
168
|
+
error: {},
|
169
|
+
meta: context.meta,
|
170
|
+
stream: false,
|
171
|
+
node: node,
|
172
|
+
)
|
173
|
+
end
|
174
|
+
|
175
|
+
private
|
176
|
+
|
177
|
+
def handle_signal(sig)
|
178
|
+
case sig
|
179
|
+
when "INT", "TERM"
|
180
|
+
raise Interrupt
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
def publish(packet_type, message = {})
|
185
|
+
packet = Packets.for(packet_type).new(message)
|
186
|
+
@transporter.publish(packet)
|
187
|
+
end
|
188
|
+
|
189
|
+
def publish_event(event_data)
|
190
|
+
publish_to_node(:event, event_data.delete(:node), event_data)
|
191
|
+
end
|
192
|
+
|
193
|
+
def publish_heartbeat
|
194
|
+
publish(:heartbeat)
|
195
|
+
end
|
196
|
+
|
197
|
+
##
|
198
|
+
# Publishes the discover packet
|
199
|
+
def publish_discover
|
200
|
+
publish(:discover)
|
201
|
+
end
|
202
|
+
|
203
|
+
def publish_info(node_id = nil)
|
204
|
+
return publish(:info, @registry.local_node.as_json) unless node_id
|
205
|
+
|
206
|
+
node = @registry.safe_fetch_node(node_id) || node_id
|
207
|
+
publish_to_node(:info, node, @registry.local_node.as_json)
|
208
|
+
end
|
209
|
+
|
210
|
+
def publish_req(request_data)
|
211
|
+
publish_to_node(:req, request_data.delete(:node), request_data)
|
212
|
+
end
|
213
|
+
|
214
|
+
def publish_res(response_data)
|
215
|
+
publish_to_node(:res, response_data.delete(:node), response_data)
|
216
|
+
end
|
217
|
+
|
218
|
+
def publish_to_node(packet_type, node, message = {})
|
219
|
+
packet = Packets.for(packet_type).new(message.merge(node: node))
|
220
|
+
@transporter.publish(packet)
|
221
|
+
end
|
222
|
+
|
223
|
+
def register_local_node
|
224
|
+
@logger.info "registering #{services.length} local services"
|
225
|
+
node = Node.new(
|
226
|
+
node_id: node_id,
|
227
|
+
services: services,
|
228
|
+
local: true,
|
229
|
+
)
|
230
|
+
@registry.register_node(node)
|
231
|
+
end
|
232
|
+
|
233
|
+
def register_or_update_remote_node(info_packet)
|
234
|
+
node = Node.from_remote_info(info_packet)
|
235
|
+
@registry.register_node(node)
|
236
|
+
end
|
237
|
+
|
238
|
+
def register_local_services
|
239
|
+
services.each do |service|
|
240
|
+
register_service(service)
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
def register_service(service)
|
245
|
+
@registry.register_local_service(service)
|
246
|
+
end
|
247
|
+
|
248
|
+
def start_heartbeat
|
249
|
+
Concurrent::TimerTask.new(execution_interval: heartbeat_interval) do
|
250
|
+
publish_heartbeat
|
251
|
+
@registry.expire_nodes
|
252
|
+
end.execute
|
253
|
+
end
|
254
|
+
|
255
|
+
def start_subscribers
|
256
|
+
subscribe_to_info
|
257
|
+
subscribe_to_res
|
258
|
+
subscribe_to_req
|
259
|
+
subscribe_to_events
|
260
|
+
subscribe_to_discover
|
261
|
+
subscribe_to_disconnect
|
262
|
+
subscribe_to_heartbeat
|
263
|
+
end
|
264
|
+
|
265
|
+
def subscribe_to_events
|
266
|
+
subscribe("MOL.EVENT.#{node_id}") do |packet|
|
267
|
+
process_event(packet)
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
def subscribe_to_info
|
272
|
+
subscribe("MOL.INFO.#{node_id}") do |packet|
|
273
|
+
register_or_update_remote_node(packet)
|
274
|
+
end
|
275
|
+
subscribe("MOL.INFO") do |packet|
|
276
|
+
register_or_update_remote_node(packet)
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
280
|
+
def subscribe_to_res
|
281
|
+
subscribe("MOL.RES.#{node_id}") do |packet|
|
282
|
+
process_response(packet)
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
def subscribe_to_req
|
287
|
+
subscribe("MOL.REQ.#{node_id}") do |packet|
|
288
|
+
process_request(packet)
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
def subscribe_to_discover
|
293
|
+
subscribe("MOL.DISCOVER") do |packet|
|
294
|
+
publish_info(packet.sender) unless packet.sender == node_id
|
295
|
+
end
|
296
|
+
subscribe("MOL.DISCOVER.#{node_id}") do |packet|
|
297
|
+
publish_info(packet.sender)
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|
301
|
+
def subscribe_to_heartbeat
|
302
|
+
subscribe("MOL.HEARTBEAT") do |packet|
|
303
|
+
node = @registry.safe_fetch_node(packet.sender)
|
304
|
+
node.beat
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
308
|
+
def subscribe_to_disconnect
|
309
|
+
subscribe("MOL.DISCONNECT") do |packet|
|
310
|
+
@registry.remove_node(packet.sender)
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
def subscribe(channel, &block)
|
315
|
+
@transporter.subscribe(channel, &block)
|
316
|
+
end
|
317
|
+
end
|
318
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
module Moleculer
|
2
|
+
##
|
3
|
+
# Handles Moleculer configuration
|
4
|
+
# @private
|
5
|
+
class Configuration
|
6
|
+
##
|
7
|
+
# @private
|
8
|
+
class ServiceList
|
9
|
+
def initialize(configuration)
|
10
|
+
@configuration = configuration
|
11
|
+
@services = []
|
12
|
+
end
|
13
|
+
|
14
|
+
def <<(service)
|
15
|
+
service.broker = @configuration.broker
|
16
|
+
@services << service
|
17
|
+
end
|
18
|
+
|
19
|
+
def length
|
20
|
+
@services.length
|
21
|
+
end
|
22
|
+
|
23
|
+
def first
|
24
|
+
@services.first
|
25
|
+
end
|
26
|
+
|
27
|
+
def map(&block)
|
28
|
+
@services.map(&block)
|
29
|
+
end
|
30
|
+
|
31
|
+
def include?(service)
|
32
|
+
@services.include?(service)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
private_constant :ServiceList
|
37
|
+
|
38
|
+
class << self
|
39
|
+
attr_reader :accessors
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def config_accessor(attribute, default = nil, &block)
|
44
|
+
@accessors ||= {}
|
45
|
+
@accessors[attribute.to_sym] = { default: default, block: block }
|
46
|
+
|
47
|
+
class_eval <<-method
|
48
|
+
def #{attribute}
|
49
|
+
@#{attribute} ||= default_for("#{attribute}".to_sym)
|
50
|
+
end
|
51
|
+
method
|
52
|
+
|
53
|
+
instance_eval do
|
54
|
+
attr_writer attribute.to_sym
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
config_accessor :log_file
|
60
|
+
config_accessor :log_level, :debug
|
61
|
+
config_accessor :logger do |c|
|
62
|
+
logger = Ougai::Logger.new(c.log_file || STDOUT)
|
63
|
+
logger.formatter = Ougai::Formatters::Readable.new("MOL")
|
64
|
+
logger.level = c.log_level
|
65
|
+
Moleculer::Support::LogProxy.new(logger)
|
66
|
+
end
|
67
|
+
config_accessor :heartbeat_interval, 5
|
68
|
+
config_accessor :timeout, 5
|
69
|
+
config_accessor :transporter, "redis://localhost"
|
70
|
+
config_accessor :serializer, :json
|
71
|
+
config_accessor :node_id, "#{Socket.gethostname.downcase}-#{Process.pid}"
|
72
|
+
config_accessor :service_prefix
|
73
|
+
|
74
|
+
attr_accessor :broker
|
75
|
+
|
76
|
+
def initialize(options={})
|
77
|
+
options.each do |option, value|
|
78
|
+
send("#{option}=".to_sym, value)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def services
|
83
|
+
@services ||= ServiceList.new(self)
|
84
|
+
end
|
85
|
+
|
86
|
+
def services=(array)
|
87
|
+
@services = ServiceList.new(self)
|
88
|
+
array.each { |s| @services << s}
|
89
|
+
end
|
90
|
+
|
91
|
+
private
|
92
|
+
|
93
|
+
def accessors
|
94
|
+
self.class.accessors
|
95
|
+
end
|
96
|
+
|
97
|
+
def default_for(attribute)
|
98
|
+
accessors[attribute][:default] || (has_block_for?(attribute) ? block_for(attribute).call(self) : nil)
|
99
|
+
end
|
100
|
+
|
101
|
+
def block_for(attribute)
|
102
|
+
accessors[attribute][:block]
|
103
|
+
end
|
104
|
+
|
105
|
+
def has_block_for?(attribute)
|
106
|
+
accessors[attribute][:block] && true
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Moleculer
|
2
|
+
class Context
|
3
|
+
attr_reader :request_id,
|
4
|
+
:action,
|
5
|
+
:params,
|
6
|
+
:meta,
|
7
|
+
:level,
|
8
|
+
:timeout,
|
9
|
+
:id
|
10
|
+
|
11
|
+
def initialize(broker:, action:, params:, meta:, parent_id: nil, level: 1, timeout:, id: nil)
|
12
|
+
@id = id ? id : SecureRandom.uuid
|
13
|
+
@broker = broker
|
14
|
+
@action = action
|
15
|
+
@request_id = SecureRandom.uuid
|
16
|
+
@parent_id = parent_id
|
17
|
+
@params = params
|
18
|
+
@meta = meta
|
19
|
+
@level = level
|
20
|
+
@timeout = timeout
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module Moleculer
|
2
|
+
module Errors
|
3
|
+
##
|
4
|
+
# Raised when an action does not return a hash
|
5
|
+
class InvalidActionResponse < StandardError
|
6
|
+
def initialize(response)
|
7
|
+
super "Action must return a Hash, instead it returned a #{response.class.name}"
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "socket"
|
4
|
+
|
5
|
+
module Moleculer
|
6
|
+
##
|
7
|
+
# Nodes are a representation of communicating apps within the same event bus.
|
8
|
+
# A node is something that emits/listens to events within the bus and
|
9
|
+
# communicates accordingly.
|
10
|
+
class Node
|
11
|
+
class << self
|
12
|
+
def from_remote_info(info_packet)
|
13
|
+
new(
|
14
|
+
services: info_packet.services,
|
15
|
+
node_id: info_packet.sender,
|
16
|
+
)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
attr_reader :id,
|
21
|
+
:services
|
22
|
+
|
23
|
+
def initialize(options = {})
|
24
|
+
@id = options.fetch(:node_id)
|
25
|
+
@local = options.fetch(:local, false)
|
26
|
+
@hostname = options.fetch(:hostname, Socket.gethostname)
|
27
|
+
|
28
|
+
svcs = options.fetch(:services)
|
29
|
+
# TODO: move this up to from_remote_info
|
30
|
+
svcs.map! { |service| Service.from_remote_info(service, self) } if svcs.first.is_a? Hash
|
31
|
+
@services = Hash[svcs.map { |s| [s.service_name, s] }]
|
32
|
+
end
|
33
|
+
|
34
|
+
def register_service(service)
|
35
|
+
@services[service.name] = service
|
36
|
+
end
|
37
|
+
|
38
|
+
##
|
39
|
+
# @return [Hash] returns a key, value mapping of available actions on this node
|
40
|
+
# TODO: refactor this into a list object
|
41
|
+
def actions
|
42
|
+
unless @actions
|
43
|
+
@actions = {}
|
44
|
+
|
45
|
+
@services.each_value { |s| s.actions.each { |key, value| @actions[key] = value } }
|
46
|
+
end
|
47
|
+
@actions
|
48
|
+
end
|
49
|
+
|
50
|
+
##
|
51
|
+
# @return [Hash] returns a key value mapping of events on this node
|
52
|
+
# TODO: refactor this into a list object
|
53
|
+
def events
|
54
|
+
unless @events
|
55
|
+
@events = {}
|
56
|
+
@services.each_value do |s|
|
57
|
+
s.events.each do |key, value|
|
58
|
+
@events[key] ||= []
|
59
|
+
@events[key] << value
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
@events
|
64
|
+
end
|
65
|
+
|
66
|
+
##
|
67
|
+
# @return [Time] the time of the last heartbeat, or Time#now if the node is local.
|
68
|
+
def last_heartbeat_at
|
69
|
+
return Time.now if local?
|
70
|
+
|
71
|
+
@last_heartbeat_at || Time.now
|
72
|
+
end
|
73
|
+
|
74
|
+
##
|
75
|
+
# Updates the last heartbeat to Time#now
|
76
|
+
def beat
|
77
|
+
@last_heartbeat_at = Time.now
|
78
|
+
end
|
79
|
+
|
80
|
+
def local?
|
81
|
+
@local
|
82
|
+
end
|
83
|
+
|
84
|
+
def as_json
|
85
|
+
{
|
86
|
+
config: {},
|
87
|
+
seq: 1,
|
88
|
+
ipList: [],
|
89
|
+
hostname: @hostname,
|
90
|
+
services: @services.values.map(&:as_json),
|
91
|
+
client: client_attrubutes
|
92
|
+
}
|
93
|
+
end
|
94
|
+
|
95
|
+
private
|
96
|
+
|
97
|
+
def client_attrubutes
|
98
|
+
{
|
99
|
+
type: "Ruby",
|
100
|
+
version: Moleculer::VERSION,
|
101
|
+
lang_version: RUBY_VERSION
|
102
|
+
}
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require_relative "../support"
|
2
|
+
|
3
|
+
module Moleculer
|
4
|
+
module Packets
|
5
|
+
##
|
6
|
+
# @abstract Subclass for packet types.
|
7
|
+
class Base
|
8
|
+
include Support
|
9
|
+
##
|
10
|
+
# The protocol version
|
11
|
+
attr_reader :ver
|
12
|
+
|
13
|
+
##
|
14
|
+
# The sender of the packet
|
15
|
+
attr_reader :sender
|
16
|
+
|
17
|
+
def self.packet_name
|
18
|
+
name.split("::").last.upcase
|
19
|
+
end
|
20
|
+
|
21
|
+
##
|
22
|
+
# @param data [Hash] the raw packet data
|
23
|
+
# @options data [String] :ver the protocol version, defaults to `'3'`
|
24
|
+
# @options data [String] :sender the packet sender, defaults to `Moleculer#node_id`
|
25
|
+
def initialize(data = {})
|
26
|
+
@ver = HashUtil.fetch(data, :ver, "3")
|
27
|
+
@sender = HashUtil.fetch(data, :sender, Moleculer.config.node_id)
|
28
|
+
end
|
29
|
+
|
30
|
+
##
|
31
|
+
# The publishing topic for the packet. This is used to publish packets to the moleculer network. Override as
|
32
|
+
# needed.
|
33
|
+
#
|
34
|
+
# @return [String] the pub/sub topic to publish to
|
35
|
+
def topic
|
36
|
+
"MOL.#{self.class.packet_name}"
|
37
|
+
end
|
38
|
+
|
39
|
+
def as_json
|
40
|
+
{
|
41
|
+
ver: ver,
|
42
|
+
sender: sender,
|
43
|
+
}
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require_relative "base"
|
2
|
+
|
3
|
+
module Moleculer
|
4
|
+
module Packets
|
5
|
+
##
|
6
|
+
# Represents a EVENT packet
|
7
|
+
class Event < Base
|
8
|
+
attr_reader :event,
|
9
|
+
:data,
|
10
|
+
:broadcast,
|
11
|
+
:groups
|
12
|
+
|
13
|
+
def initialize(data)
|
14
|
+
super(data)
|
15
|
+
|
16
|
+
@event = HashUtil.fetch(data, :event)
|
17
|
+
@data = HashUtil.fetch(data, :data)
|
18
|
+
@broadcast = HashUtil.fetch(data, :broadcast)
|
19
|
+
@groups = HashUtil.fetch(data, :groups, [])
|
20
|
+
@node = HashUtil.fetch(data, :node, nil)
|
21
|
+
end
|
22
|
+
|
23
|
+
def as_json
|
24
|
+
super.merge(
|
25
|
+
event: @event,
|
26
|
+
data: @data,
|
27
|
+
broadcast: @broadcast,
|
28
|
+
groups: @groups,
|
29
|
+
)
|
30
|
+
end
|
31
|
+
|
32
|
+
def topic
|
33
|
+
"#{super}.#{@node.id}"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|