moleculer 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b336fe17152d5c0ebff7495c2fc7e5dd6ea4a5aa0a2cd29ee9f2f67f25fbe95f
4
- data.tar.gz: 1f99f72bae588fa8544cf9635891ed9bc51408ec656658090eeded5756f45be3
3
+ metadata.gz: 3a334a18ec4219854cfaf70d634adab755d0dc07cf4ed0e284e9762915e8518a
4
+ data.tar.gz: 59c11268ac5f85d9afb825d6f621f29aac1b793852621196645bc7a8150b38e5
5
5
  SHA512:
6
- metadata.gz: c9b52239d10c8aca09bae010e5193dfc56ebe3d4df72d8d5f70e497788c7274630951e0bfe1c641f82f57f26bc374772f7ff16d75af4ad093bb13f82b6333987
7
- data.tar.gz: 90fa10f008f7807bf729e993bd48a2dab8718144477fe5d340373ac74c0151811376f121af63127c543e4f2693b1bc042e3e4641065197b5c40b532b8bf5facd
6
+ metadata.gz: 7b32dad21bf85fba69d66e9c65296f6e39dab9f3d44eea5a473b48456fdeb362b9b0723456356ce6cf58dfa8c0a5ca0582b4ad7522d4e44f52eeddda1f242b58
7
+ data.tar.gz: 9bef8d267905857f6b3144ccd33dfa7e5335603d59993bc9654755fc0887b0832168d545494b951b0fb168a30878aff42b757dd8a06f63e3c1a5171fdd6ae7a0
@@ -1,7 +1,7 @@
1
1
  Layout/ExtraSpacing:
2
2
  ForceEqualSignAlignment: true
3
3
 
4
- Layout/IndentHash:
4
+ Layout/IndentFirstHashElement:
5
5
  EnforcedStyle: consistent
6
6
 
7
7
  Layout/IndentationWidth:
@@ -1,5 +1,34 @@
1
- # 0.2.0
1
+ ## 0.3.0
2
+ ### Breaking Changes
3
+ * `rescue_event` and `rescue_action` have been removed and replaced with a more generic `rescue_from`
4
+ handler
5
+
6
+ ### Features
7
+ * adds version support
8
+ * `log_level`, `transporter`, `heartbeat_interval`, `log_file` and `timeout` are now configurable
9
+ via environment variable prefixed with `MOLECULER_`
10
+ * added `rescue_from` configuration. this replaces `rescue_event` and `rescue_action`.
11
+ * updated json to 2.2.0
12
+ * updated oj to 3.7.12
13
+ * updated redis to 4.1.2
14
+ * updated yard to 0.9.20
15
+ * updated rspec-support to 3.8.2
16
+ * updated ruby-progressbar to 1.10.1
17
+ * updated rspec-mocks to 3.8.1
18
+ * updated simplecov to 0.17.0
19
+ * updated rspec-core to 3.8.2
20
+ * updated rubocop to 0.74.0
21
+
22
+ ### Bugfixes
23
+ * fix `concurrent_ruby` version requirement to ensure at least `1.1` is required.
24
+ * fixes issue where services don't recognize heartbeats by firing a DISCOVER packet
25
+ when a heartbeat is received from an unknown node.
26
+
27
+ ## 0.2.0
28
+ * add a fake transporter that can be used for testing without dependencies on an
29
+ * add `rescue_action` and `rescue_event` rescue handlers
2
30
 
31
+ ## 0.1.1
3
32
  ### Features
4
33
 
5
34
  * **actions:** ability to process errors that occur when executing actions
@@ -13,8 +42,3 @@
13
42
  * fix `concurrent_ruby` version requirement to ensure at least `1.1` is required
14
43
  * fix an issue where heartbeats back up in the queue and cause errors when consumed
15
44
 
16
- # 0.1.1
17
-
18
- ### Bugfixes
19
- * fixes bug where event publishing uses the wrong method name to look up local events
20
- * fixes condition where events may double publish when multiple events are registered
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- moleculer (0.2.0)
4
+ moleculer (0.3.0)
5
5
  awesome_print (~> 1.8)
6
6
  concurrent-ruby (~> 1.1)
7
7
  ougai (~> 1.7)
@@ -13,10 +13,10 @@ GEM
13
13
  awesome_print (1.8.0)
14
14
  concurrent-ruby (1.1.5)
15
15
  diff-lcs (1.3)
16
- docile (1.3.1)
17
- jaro_winkler (1.5.2)
18
- json (2.1.0)
19
- oj (3.7.12)
16
+ docile (1.3.2)
17
+ jaro_winkler (1.5.3)
18
+ json (2.2.0)
19
+ oj (3.8.1)
20
20
  ougai (1.7.1)
21
21
  oj (~> 3.4)
22
22
  parallel (1.17.0)
@@ -24,36 +24,36 @@ GEM
24
24
  ast (~> 2.4.0)
25
25
  rainbow (3.0.0)
26
26
  rake (10.5.0)
27
- redis (4.0.1)
27
+ redis (4.1.2)
28
28
  rspec (3.8.0)
29
29
  rspec-core (~> 3.8.0)
30
30
  rspec-expectations (~> 3.8.0)
31
31
  rspec-mocks (~> 3.8.0)
32
- rspec-core (3.8.0)
32
+ rspec-core (3.8.2)
33
33
  rspec-support (~> 3.8.0)
34
- rspec-expectations (3.8.2)
34
+ rspec-expectations (3.8.4)
35
35
  diff-lcs (>= 1.2.0, < 2.0)
36
36
  rspec-support (~> 3.8.0)
37
- rspec-mocks (3.8.0)
37
+ rspec-mocks (3.8.1)
38
38
  diff-lcs (>= 1.2.0, < 2.0)
39
39
  rspec-support (~> 3.8.0)
40
- rspec-support (3.8.0)
41
- rubocop (0.69.0)
40
+ rspec-support (3.8.2)
41
+ rubocop (0.74.0)
42
42
  jaro_winkler (~> 1.5.1)
43
43
  parallel (~> 1.10)
44
44
  parser (>= 2.6)
45
45
  rainbow (>= 2.2.2, < 4.0)
46
46
  ruby-progressbar (~> 1.7)
47
47
  unicode-display_width (>= 1.4.0, < 1.7)
48
- ruby-progressbar (1.10.0)
49
- simplecov (0.16.1)
48
+ ruby-progressbar (1.10.1)
49
+ simplecov (0.17.0)
50
50
  docile (~> 1.1)
51
51
  json (>= 1.8, < 3)
52
52
  simplecov-html (~> 0.10.0)
53
53
  simplecov-html (0.10.2)
54
54
  timecop (0.9.1)
55
55
  unicode-display_width (1.6.0)
56
- yard (0.9.18)
56
+ yard (0.9.20)
57
57
 
58
58
  PLATFORMS
59
59
  ruby
@@ -70,4 +70,4 @@ DEPENDENCIES
70
70
  yard (~> 0.9.11)
71
71
 
72
72
  BUNDLED WITH
73
- 1.17.2
73
+ 1.17.3
data/README.md CHANGED
@@ -20,7 +20,7 @@ gem install "moleculer-ruby"
20
20
  or add to your Gemfile:
21
21
 
22
22
  ```ruby
23
- gem "moleculer-ruby", "~>0.1"
23
+ gem "moleculer-ruby", "~>0.3"
24
24
  ```
25
25
 
26
26
  ### Create a Simple Service
@@ -55,17 +55,36 @@ Moleculer.configure do |c|
55
55
  end
56
56
  ```
57
57
 
58
+ Some Moleculer configuration values can also be set through environment variables.
59
+
58
60
  ### Configuration Options
59
61
 
60
- #### logger (default: Ougai::Logger)
61
- Sets teh Moleculer logger, this must be a Ruby `Logger` or `Ougai` compatible logger.
62
+ #### rescue_from
63
+ Sets an error handler that ties into the infrastructure of Moleculer instructing it how to handle errors of the specified
64
+ class. This allows things like Airbrake to easily tie in to exception handling. By default Moleculer rescues from
65
+ `StandardError` and simply logs it out.
66
+
67
+ #### log_file (default: STDOUT)
68
+ Sets the moleculer log_file. This value can also be set by setting the `MOLECULER_LOG_FILE` environment variable.
69
+
70
+ #### logger
71
+ Sets the moleculer logger. The logger must be an instance of `Moleculer::Support::LogProxy`. The log proxy supports any
72
+ ruby logger that supports the ruby `Logger` interface.
73
+
74
+ Example:
75
+ ```
76
+ c.logger = Moleculer::Support::Logger.new(Rails.logger)
77
+ ```
78
+
79
+ In the case that the logger is set to something other than the default, the log level set for moleculer is ignored, and the
80
+ level of the passed logger is used.
62
81
 
63
82
  #### log_level (default: debug)
64
83
  Sets the log level of the node. defaults to `:debug`. Can be one of `:trace`, `:debug`, `:info`, `:warn`, `:error`,
65
- `:fatal`.
84
+ `:fatal`. This value can also be set by setting the `MOLECULER_LOG_LEVEL` environment variable.
66
85
 
67
86
  #### heartbeat_interval (default: 5)
68
- The interval in which to send heartbeats.
87
+ The interval in which to send heartbeats. This value can also be set by setting the `MOLECULER_HEARTBEAT` environment variable.
69
88
 
70
89
  #### node_id (default: \<hostname\>-\<pid\>)
71
90
  The node id. Node IDs are required to be unique. In Moleculer-ruby all node ids are suffixed with the PID of the
@@ -82,10 +101,11 @@ service whose `service_name` is set to `users` would get the full `service_name`
82
101
 
83
102
  #### timeout (default: 5)
84
103
  The Moleculer system timeout. This is used to determine how long a moleculer `call` will wait for a response until it
85
- times out and throws an error.
104
+ times out and throws an error. This value can also be set by setting the `MOLECULER_TIMEOUT` environment variable.
86
105
 
87
106
  #### transporter (default: redis://localhost)
88
107
  The transporter Moleculer should use. For more information on transporters see [Transporters](https://moleculer.services/docs/0.13/networking.html#Transporters)
108
+ This value can also be set by setting the `MOLECULER_TRANSPORTER` environment variable.
89
109
 
90
110
 
91
111
  ## Roadmap
@@ -98,11 +118,22 @@ Initial release
98
118
  * Service registry & dynamic service discovery
99
119
  * JSON serializer
100
120
 
101
- ### 0.2 (IN PROGRESS)
121
+ ### 0.2 (COMPLETE)
102
122
  * Fake transporter (for testing)
103
123
  * Error handling, (ability to use Airbrake, etc.)
104
- * Event grouping
105
124
 
106
- ### 0.3 (PENDING)
107
- * NATS transporter
108
- * Built in caching solution (Redis, Memory)
125
+ ### 0.3 (COMPLETE)
126
+ * Service versioning
127
+ * Environment variable based configuration
128
+
129
+ ### 0.4 (IN PROGRESS)
130
+ * Protobuf serializer
131
+ * Add MessagePack serializer
132
+ * Circuit Breaker support
133
+ * Retry support
134
+
135
+ ### 0.5 (PLANNED)
136
+ * Bulkhead support
137
+ * Fallback support
138
+ * Nested call support
139
+
data/Rakefile CHANGED
@@ -3,4 +3,4 @@ require "rspec/core/rake_task"
3
3
 
4
4
  RSpec::Core::RakeTask.new(:spec)
5
5
 
6
- task :default => :spec
6
+ task default: :spec
@@ -14,7 +14,7 @@ module Moleculer
14
14
  class Broker
15
15
  include Moleculer::Support
16
16
  extend Forwardable
17
- attr_reader :config
17
+ attr_reader :config, :logger
18
18
 
19
19
  def_delegators :@config, :node_id, :heartbeat_interval, :services, :service_prefix
20
20
 
@@ -93,6 +93,7 @@ module Moleculer
93
93
 
94
94
  def start
95
95
  @logger.info "starting"
96
+ @logger.info "using transporter '#{@config.transporter}'"
96
97
  @transporter.start
97
98
  register_local_node
98
99
  start_subscribers
@@ -128,7 +129,7 @@ module Moleculer
128
129
  def process_message(channel, message)
129
130
  subscribers[channel] << Packets.for(channel.split(".")[1]).new(message) if subscribers[channel]
130
131
  rescue StandardError => e
131
- @logger.error e
132
+ config.handle_error(e)
132
133
  end
133
134
 
134
135
  def process_response(packet)
@@ -142,7 +143,7 @@ module Moleculer
142
143
 
143
144
  events.each { |e| e.execute(packet.data, self) }
144
145
  rescue StandardError => e
145
- @logger.error e
146
+ config.handle_error(e)
146
147
  end
147
148
 
148
149
  def process_request(packet)
@@ -194,7 +195,7 @@ module Moleculer
194
195
  end
195
196
 
196
197
  def publish(packet_type, message = {})
197
- packet = Packets.for(packet_type).new(message.merge(sender: @registry.local_node.id))
198
+ packet = Packets.for(packet_type).new(@config, message.merge(sender: @registry.local_node.id))
198
199
  @transporter.publish(packet)
199
200
  end
200
201
 
@@ -203,20 +204,36 @@ module Moleculer
203
204
  end
204
205
 
205
206
  def publish_heartbeat
207
+ @logger.trace "publishing hearbeat"
206
208
  publish(:heartbeat)
207
209
  end
208
210
 
209
211
  ##
210
212
  # Publishes the discover packet
211
213
  def publish_discover
214
+ @logger.trace "publishing discover request"
212
215
  publish(:discover)
213
216
  end
214
217
 
215
- def publish_info(node_id = nil)
216
- return publish(:info, @registry.local_node.as_json) unless node_id
218
+ ##
219
+ # Publish targeted discovery to node
220
+ def publish_discover_to_node_id(node_id)
221
+ publish_to_node_id(:discover, node_id)
222
+ end
217
223
 
218
- node = @registry.safe_fetch_node(node_id) || node_id
219
- publish_to_node(:info, node, @registry.local_node.as_json)
224
+ ##
225
+ # Publishes the info packet to either all nodes, or the given node
226
+ def publish_info(node_id = nil, force = false)
227
+ return publish(:info, @registry.local_node.to_h) unless node_id
228
+
229
+ node = @registry.safe_fetch_node(node_id)
230
+ if node
231
+ publish_to_node(:info, node, @registry.local_node.to_h)
232
+ elsif force
233
+ ## in rare cases there may be a lack of synchronization between brokers, if we can't find the node in the
234
+ # registry we will attempt to force publish it (if force is true)
235
+ publish_to_node_id(:info, node_id, @registry.local_node.to_h)
236
+ end
220
237
  end
221
238
 
222
239
  def publish_req(request_data)
@@ -228,7 +245,14 @@ module Moleculer
228
245
  end
229
246
 
230
247
  def publish_to_node(packet_type, node, message = {})
231
- packet = Packets.for(packet_type).new(message.merge(node: node))
248
+ packet = Packets.for(packet_type).new(@config, message.merge(node: node))
249
+ @transporter.publish(packet)
250
+ end
251
+
252
+ ##
253
+ # Publishes the provided packet directly to the given node_id
254
+ def publish_to_node_id(packet_type, node_id, message = {})
255
+ packet = Packets.for(packet_type).new(@config, message.merge(node_id: node_id))
232
256
  @transporter.publish(packet)
233
257
  end
234
258
 
@@ -259,6 +283,7 @@ module Moleculer
259
283
  end
260
284
 
261
285
  def start_heartbeat
286
+ @logger.trace "starting heartbeat timer"
262
287
  Concurrent::TimerTask.new(execution_interval: heartbeat_interval) do
263
288
  publish_heartbeat
264
289
  @registry.expire_nodes
@@ -276,12 +301,14 @@ module Moleculer
276
301
  end
277
302
 
278
303
  def subscribe_to_events
304
+ @logger.info "setting up 'EVENT' subscriber"
279
305
  subscribe("MOL.EVENT.#{node_id}") do |packet|
280
306
  process_event(packet)
281
307
  end
282
308
  end
283
309
 
284
310
  def subscribe_to_info
311
+ @logger.trace "setting up 'INFO' subscribers"
285
312
  subscribe("MOL.INFO.#{node_id}") do |packet|
286
313
  register_or_update_remote_node(packet)
287
314
  end
@@ -291,34 +318,48 @@ module Moleculer
291
318
  end
292
319
 
293
320
  def subscribe_to_res
321
+ @logger.trace "setting up 'RES' subscriber"
294
322
  subscribe("MOL.RES.#{node_id}") do |packet|
295
323
  process_response(packet)
296
324
  end
297
325
  end
298
326
 
299
327
  def subscribe_to_req
328
+ @logger.trace "setting up 'REQ' subscriber"
300
329
  subscribe("MOL.REQ.#{node_id}") do |packet|
301
330
  process_request(packet)
302
331
  end
303
332
  end
304
333
 
305
334
  def subscribe_to_discover
335
+ @logger.trace "setting up 'DISCOVER' subscriber"
306
336
  subscribe("MOL.DISCOVER") do |packet|
307
337
  publish_info(packet.sender) unless packet.sender == node_id
308
338
  end
309
339
  subscribe("MOL.DISCOVER.#{node_id}") do |packet|
310
- publish_info(packet.sender)
340
+ publish_info(packet.sender, true)
311
341
  end
312
342
  end
313
343
 
344
+ ##
345
+ # Subscribes to heartbeats from other services. If a node is not registered when it received a heartbeat the broker
346
+ # will send a discover packet directly to the node that published the beat.
314
347
  def subscribe_to_heartbeat
348
+ @logger.trace "setting up 'HEARTBEAT' subscriber"
315
349
  subscribe("MOL.HEARTBEAT") do |packet|
316
350
  node = @registry.safe_fetch_node(packet.sender)
317
- node.beat
351
+ if node
352
+ node.beat
353
+ else
354
+ # because the node is not registered with the broker, we have to assume that something broke down. we need to
355
+ # force a publish to the node we just received the heartbeat from
356
+ publish_discover_to_node_id(packet.sender)
357
+ end
318
358
  end
319
359
  end
320
360
 
321
361
  def subscribe_to_disconnect
362
+ @logger.trace "setting up 'DISCONNECT' subscriber"
322
363
  subscribe("MOL.DISCONNECT") do |packet|
323
364
  @registry.remove_node(packet.sender)
324
365
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Moleculer
2
4
  ##
3
5
  # Handles Moleculer configuration
@@ -48,11 +50,11 @@ module Moleculer
48
50
  @accessors ||= {}
49
51
  @accessors[attribute.to_sym] = { default: default, block: block }
50
52
 
51
- class_eval <<-method
53
+ class_eval <<-METHOD, __FILE__, __LINE__ + 1
52
54
  def #{attribute}
53
55
  @#{attribute} ||= default_for("#{attribute}".to_sym)
54
56
  end
55
- method
57
+ METHOD
56
58
 
57
59
  instance_eval do
58
60
  attr_writer attribute.to_sym
@@ -60,29 +62,34 @@ module Moleculer
60
62
  end
61
63
  end
62
64
 
63
- config_accessor :log_file
64
- config_accessor :log_level, :debug
65
65
  config_accessor :logger do |c|
66
66
  logger = Ougai::Logger.new(c.log_file || STDOUT)
67
67
  logger.formatter = Ougai::Formatters::Readable.new("MOL")
68
68
  logger.level = c.log_level
69
69
  Moleculer::Support::LogProxy.new(logger)
70
70
  end
71
- config_accessor :heartbeat_interval, 5
72
- config_accessor :timeout, 5
73
- config_accessor :transporter, "redis://localhost"
71
+ config_accessor :log_file, ENV["MOLECULER_LOG_FILE"]
72
+ config_accessor :log_level, ENV["MOLECULER_LOG_LEVEL"]&.to_sym || :debug
73
+ config_accessor :heartbeat_interval, ENV["MOLECULER_HEARTBEAT_INTERVAL"]&.to_i || 5
74
+ config_accessor :timeout, ENV["MOLECULER_TIMEOUT"]&.to_i || 5
75
+ config_accessor :transporter, ENV["MOLECULER_TRANSPORTER"] || "redis://localhost"
76
+
74
77
  config_accessor :serializer, :json
75
78
  config_accessor :node_id, "#{Socket.gethostname.downcase}-#{Process.pid}"
79
+
76
80
  config_accessor :service_prefix
77
- config_accessor :rescue_action
78
- config_accessor :rescue_event
79
81
 
80
82
  attr_accessor :broker
81
83
 
82
- def initialize(options={})
84
+ def initialize(options = {})
83
85
  options.each do |option, value|
84
86
  send("#{option}=".to_sym, value)
85
87
  end
88
+ @rescue_handlers = {}
89
+
90
+ rescue_from(StandardError) do |e|
91
+ logger.error(e)
92
+ end
86
93
  end
87
94
 
88
95
  def services
@@ -91,11 +98,60 @@ module Moleculer
91
98
 
92
99
  def services=(array)
93
100
  @services = ServiceList.new(self)
94
- array.each { |s| @services << s}
101
+ array.each { |s| @services << s }
102
+ end
103
+
104
+ def to_h
105
+ {
106
+ log_file: log_file,
107
+ log_level: log_level,
108
+ heartbeat_interval: heartbeat_interval,
109
+ timeout: timeout,
110
+ transporter: transporter,
111
+ serializer: serializer,
112
+ node_id: node_id,
113
+ service_prefix: service_prefix,
114
+ }
115
+ end
116
+
117
+ ##
118
+ # Add a rescue handler for a specific error. This allows libraries such as airbrake to hook into the error handling
119
+ # flow. When a rescue handler raises, it will look for a rescue handler for the parent class of the thrown error
120
+ # recursively until it reaches StandardError. If the block does not itself raise an error, the error may be
121
+ # swallowed.
122
+ #
123
+ # @param err [Class] the error class to handle
124
+ # @param block [Proc] the block to execute when the error is captured
125
+ def rescue_from(err, &block)
126
+ raise ArgumentError, "block required" unless block_given?
127
+ raise ArgumentError, "error must be a standard error" unless err.ancestors.include?(StandardError)
128
+
129
+ @rescue_handlers[err] = block
130
+ end
131
+
132
+ ##
133
+ # @private
134
+ def handle_error(error, parent = nil)
135
+ handler = select_rescue_handler_for(parent || error.class)
136
+ raise error unless handler
137
+
138
+ begin
139
+ handler.call(error)
140
+ rescue StandardError => e
141
+ # if the error was re-raised, and a new err was not raised then call the handler for the parent of the original
142
+ # error, otherwise, restart the chain
143
+ return handle_error(error, parent&.superclass || error.class.superclass) if error == e
144
+
145
+ handle_error(error)
146
+ end
95
147
  end
96
148
 
97
149
  private
98
150
 
151
+ def select_rescue_handler_for(error_class)
152
+ @rescue_handlers[error_class] || error_class.ancestors.map { |a| @rescue_handlers[a] }.compact.first
153
+ end
154
+
99
155
  def accessors
100
156
  self.class.accessors
101
157
  end
@@ -28,7 +28,7 @@ module Moleculer
28
28
  svcs = options.fetch(:services)
29
29
  # TODO: move this up to from_remote_info
30
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] }]
31
+ @services = Hash[svcs.map { |s| [s.full_name, s] }]
32
32
  end
33
33
 
34
34
  def register_service(service)
@@ -81,14 +81,14 @@ module Moleculer
81
81
  @local
82
82
  end
83
83
 
84
- def as_json
84
+ def to_h
85
85
  {
86
86
  sender: @id,
87
87
  config: {},
88
88
  seq: 1,
89
89
  ipList: [],
90
90
  hostname: @hostname,
91
- services: @services.values.map(&:as_json),
91
+ services: @services.values.map(&:to_h),
92
92
  client: client_attrubutes,
93
93
  }
94
94
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative "../support"
2
4
 
3
5
  module Moleculer
@@ -6,25 +8,55 @@ module Moleculer
6
8
  # @abstract Subclass for packet types.
7
9
  class Base
8
10
  include Support
11
+
12
+ class << self
13
+ # this ensures that the packets get the accessors from the parent
14
+ def inherited(other)
15
+ other.instance_variable_set(:@packet_accessors, other.packet_accessors.merge(packet_accessors))
16
+ end
17
+
18
+ def packet_accessors
19
+ @packet_accessors ||= {}
20
+ end
21
+
22
+ ##
23
+ # Sets an accessor that fetches @data attributes
24
+ def packet_attr(name, default = :__not_defined__)
25
+ class_eval <<-ATTR, __FILE__, __LINE__ + 1
26
+ def #{name}
27
+ default = self.class.packet_accessors[:#{name}]
28
+ if default != :__not_defined__
29
+ return HashUtil.fetch(@data, :#{name}, default) unless default.is_a? Proc
30
+ return HashUtil.fetch(@data, :#{name}, default.call(self))
31
+ end
32
+ return HashUtil.fetch(@data, :#{name})
33
+ end
34
+ ATTR
35
+ packet_accessors[name] = default
36
+ end
37
+
38
+ def packet_name
39
+ name.split("::").last.upcase
40
+ end
41
+ end
42
+
9
43
  ##
10
44
  # The protocol version
11
- attr_reader :ver
45
+ packet_attr :ver, "3"
12
46
 
13
47
  ##
14
48
  # The sender of the packet
15
- attr_reader :sender
49
+ packet_attr :sender, ->(packet) { packet.config.node_id }
16
50
 
17
- def self.packet_name
18
- name.split("::").last.upcase
19
- end
51
+ attr_reader :config
20
52
 
21
53
  ##
22
54
  # @param data [Hash] the raw packet data
23
55
  # @options data [String] :ver the protocol version, defaults to `'3'`
24
56
  # @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)
57
+ def initialize(config, data = {})
58
+ @data = data
59
+ @config = config
28
60
  end
29
61
 
30
62
  ##
@@ -36,7 +68,7 @@ module Moleculer
36
68
  "MOL.#{self.class.packet_name}"
37
69
  end
38
70
 
39
- def as_json
71
+ def to_h
40
72
  {
41
73
  ver: ver,
42
74
  sender: sender,
@@ -5,6 +5,13 @@ module Moleculer
5
5
  ##
6
6
  # Represents a DISCOVER packet
7
7
  class Discover < Base
8
+ packet_attr :node_id, nil
9
+
10
+ def topic
11
+ return "#{super}.#{node_id}" if node_id
12
+
13
+ super
14
+ end
8
15
  end
9
16
  end
10
17
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative "base"
2
4
 
3
5
  module Moleculer
@@ -10,8 +12,8 @@ module Moleculer
10
12
  :broadcast,
11
13
  :groups
12
14
 
13
- def initialize(data)
14
- super(data)
15
+ def initialize(config, data = {})
16
+ super(config, data)
15
17
 
16
18
  @event = HashUtil.fetch(data, :event)
17
19
  @data = HashUtil.fetch(data, :data)
@@ -20,7 +22,7 @@ module Moleculer
20
22
  @node = HashUtil.fetch(data, :node, nil)
21
23
  end
22
24
 
23
- def as_json
25
+ def to_h
24
26
  super.merge(
25
27
  event: @event,
26
28
  data: @data,
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative "base"
2
4
 
3
5
  module Moleculer
@@ -5,14 +7,11 @@ module Moleculer
5
7
  ##
6
8
  # Represents a DISCOVER packet
7
9
  class Heartbeat < Base
8
-
9
- def initialize(data)
10
- super(data)
10
+ def initialize(config, data = {})
11
+ super(config, data)
11
12
 
12
13
  @cpu = 0
13
14
  end
14
-
15
-
16
15
  end
17
16
  end
18
17
  end
@@ -24,14 +24,14 @@ module Moleculer
24
24
  :lang_version
25
25
 
26
26
  def initialize(data)
27
- @type = HashUtil.fetch(data, :type)
28
- @version = HashUtil.fetch(data, :version)
29
- @lang_version = HashUtil.fetch(data, :lang_version)
27
+ @type = HashUtil.fetch(data, :type, nil)
28
+ @version = HashUtil.fetch(data, :version, nil)
29
+ @lang_version = HashUtil.fetch(data, :lang_version, nil)
30
30
  end
31
31
 
32
32
  ##
33
33
  # @return [Hash] the object prepared for conversion to JSON for transmission
34
- def as_json
34
+ def to_h
35
35
  {
36
36
  type: @type,
37
37
  version: @version,
@@ -64,34 +64,37 @@ module Moleculer
64
64
  # @options data [Array<String>] ip_list the list of ip addresses for the node
65
65
  # @options data [String] hostname the hostname of the node
66
66
  # @options data [Hash] client the client data for the node
67
- def initialize(data)
68
- super(data)
67
+ def initialize(config, data = {})
68
+ super(config, data)
69
69
  @services = HashUtil.fetch(data, :services)
70
- @config = OpenStruct.new(Hash[HashUtil.fetch(data, :config).map { |i| [StringUtil.underscore(i[0]), i[1]] }])
71
70
  @ip_list = HashUtil.fetch(data, :ip_list)
72
71
  @hostname = HashUtil.fetch(data, :hostname)
73
- @client = Client.new(HashUtil.fetch(data, :client))
74
- @node = HashUtil.fetch(data, :node, nil)
72
+ @client = Client.new(HashUtil.fetch(data, :client, {}))
73
+ node = HashUtil.fetch(data, :node, nil)
74
+ @node_id = HashUtil.fetch(data, :node_id, node&.id)
75
75
  end
76
76
 
77
77
  def topic
78
- if @node
79
- return "#{super}.#{@node.id}" if @node.is_a? Moleculer::Node
78
+ return "#{super}.#{@node_id}" if @node_id
80
79
 
81
- return "#{super}.#{@node}"
82
- end
83
80
  super
84
81
  end
85
82
 
86
- def as_json
83
+ def to_h
87
84
  super.merge(
88
85
  services: @services,
89
- config: @config.to_h,
86
+ config: config_for_hash,
90
87
  ipList: @ip_list,
91
88
  hostname: @hostname,
92
- client: @client.as_json,
89
+ client: @client.to_h,
93
90
  )
94
91
  end
92
+
93
+ private
94
+
95
+ def config_for_hash
96
+ Hash[config.to_h.reject { |a, _| a == :log_file }]
97
+ end
95
98
  end
96
99
  end
97
100
  end
@@ -5,52 +5,35 @@ module Moleculer
5
5
  ##
6
6
  # Represents a REQ packet
7
7
  class Req < Base
8
- attr_reader :action,
9
- :params,
10
- :meta,
11
- :timeout,
12
- :level,
13
- :parent_id,
14
- :request_id,
15
- :stream,
16
- :metrics,
17
- :stream,
18
- :id,
19
- :node
8
+ packet_attr :action
9
+ packet_attr :params
10
+ packet_attr :meta
11
+ packet_attr :timeout, nil
12
+ packet_attr :level, 1
13
+ packet_attr :parent_id, nil
14
+ packet_attr :request_id, nil
15
+ packet_attr :stream, false
16
+ packet_attr :metrics, false
17
+ packet_attr :id
18
+ packet_attr :node, nil
20
19
 
21
- def initialize(data)
22
- super(data)
23
-
24
- @id = HashUtil.fetch(data, :id)
25
- @action = HashUtil.fetch(data, :action)
26
- @params = HashUtil.fetch(data, :params)
27
- @meta = HashUtil.fetch(data, :meta)
28
- @timeout = HashUtil.fetch(data, :timeout, nil)
29
- @level = HashUtil.fetch(data, :level, 1)
30
- @metrics = HashUtil.fetch(data, :metrics, false)
31
- @parent_id = HashUtil.fetch(data, :parent_id, nil)
32
- @request_id = HashUtil.fetch(data, :request_id, nil)
33
- @stream = false
34
- @node = HashUtil.fetch(data, :node, nil)
35
- end
36
-
37
- def as_json # rubocop:disable Metrics/MethodLength
20
+ def to_h # rubocop:disable Metrics/MethodLength
38
21
  super.merge(
39
- id: @id,
40
- action: @action,
41
- params: @params,
42
- meta: @meta,
43
- timeout: @timeout,
44
- level: @level,
45
- metrics: @metrics,
46
- parent_id: @parent_id,
47
- request_id: @request_id,
48
- stream: @stream,
22
+ id: id,
23
+ action: action,
24
+ params: params,
25
+ meta: meta,
26
+ timeout: timeout,
27
+ level: level,
28
+ metrics: metrics,
29
+ parent_id: parent_id,
30
+ request_id: request_id,
31
+ stream: stream,
49
32
  )
50
33
  end
51
34
 
52
35
  def topic
53
- "#{super}.#{@node.id}"
36
+ "#{super}.#{node.id}"
54
37
  end
55
38
  end
56
39
  end
@@ -12,8 +12,8 @@ module Moleculer
12
12
  :meta,
13
13
  :stream
14
14
 
15
- def initialize(data)
16
- super(data)
15
+ def initialize(config, data)
16
+ super(config, data)
17
17
 
18
18
  @id = HashUtil.fetch(data, :id)
19
19
  @success = HashUtil.fetch(data, :success)
@@ -28,7 +28,7 @@ module Moleculer
28
28
  "#{super}.#{@node.id}"
29
29
  end
30
30
 
31
- def as_json
31
+ def to_h
32
32
  super.merge(
33
33
  id: @id,
34
34
  success: @success,
@@ -42,11 +42,17 @@ module Moleculer
42
42
  end
43
43
 
44
44
  def active_nodes
45
- @nodes.values.select { |node| (Time.now - node[:node].last_heartbeat_at) < @heartbeat_interval * 3 }
45
+ @nodes.values.select { |node| (Time.now - node[:node].last_heartbeat_at) < expiration_interval }
46
46
  end
47
47
 
48
48
  def expired_nodes
49
- @nodes.values.select { |node| (Time.now - node[:node].last_heartbeat_at) > 600 }
49
+ @nodes.values.select { |node| (Time.now - node[:node].last_heartbeat_at) > expiration_interval }
50
+ end
51
+
52
+ private
53
+
54
+ def expiration_interval
55
+ @heartbeat_interval * 3
50
56
  end
51
57
  end
52
58
 
@@ -89,8 +95,8 @@ module Moleculer
89
95
  end
90
96
 
91
97
  def add_service(service)
92
- @services[service.service_name] ||= NodeList.new(@heartbeat_interval)
93
- @services[service.service_name].add_node(service.node)
98
+ @services[service.full_name] ||= NodeList.new(@heartbeat_interval)
99
+ @services[service.full_name].add_node(service.node)
94
100
  end
95
101
 
96
102
  def fetch_nodes
@@ -130,7 +136,7 @@ module Moleculer
130
136
  def fetch_events(event_name)
131
137
  return [] unless @events[event_name]
132
138
 
133
- @events[event_name].fetch_nodes.map { |n| n.events[event_name] }.flatten.uniq { |e| e.service.service_name }
139
+ @events[event_name].fetch_nodes.map { |n| n.events[event_name] }.flatten.uniq { |e| e.service.full_name }
134
140
  end
135
141
  end
136
142
 
@@ -317,8 +323,8 @@ module Moleculer
317
323
 
318
324
  def update_services(node)
319
325
  node.services.values.each do |service|
320
- @services[service.service_name] ||= NodeList.new(@heartbeat_interval)
321
- @services[service.service_name].add_node(node)
326
+ @services[service.full_name] ||= NodeList.new(@heartbeat_interval)
327
+ @services[service.full_name].add_node(node)
322
328
  end
323
329
  end
324
330
  end
@@ -7,16 +7,17 @@ module Moleculer
7
7
  class Json
8
8
  def initialize(config)
9
9
  @logger = config.logger.get_child("[SERIALIZER]")
10
+ @config = config
10
11
  end
11
12
 
12
13
  def serialize(message)
13
- message.as_json.to_json
14
+ message.to_h.to_json
14
15
  end
15
16
 
16
17
  def deserialize(message)
17
18
  JSON.parse(message)
18
19
  rescue StandardError => e
19
- @logger.error e
20
+ @config.handle_error(e)
20
21
  end
21
22
  end
22
23
  end
@@ -24,7 +24,6 @@ module Moleculer
24
24
  @name = name
25
25
  @service = service
26
26
  @method = method
27
- @service = service
28
27
  @options = options
29
28
  end
30
29
 
@@ -40,18 +39,16 @@ module Moleculer
40
39
 
41
40
  response
42
41
  rescue StandardError => e
43
- raise e unless broker.rescue_action
44
-
45
- broker.rescue_action.call(e)
42
+ broker.config.handle_error(e)
46
43
  end
47
44
 
48
45
  def node
49
46
  @service.node
50
47
  end
51
48
 
52
- def as_json
49
+ def to_h
53
50
  {
54
- name: "#{@service.service_name}.#{name}",
51
+ name: "#{@service.full_name}.#{name}",
55
52
  rawName: name,
56
53
  cache: HashUtil.fetch(@options, :cache, false),
57
54
  metrics: {
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative "action"
2
4
  require_relative "event"
3
5
 
@@ -66,6 +68,15 @@ module Moleculer
66
68
  events[name] = Event.new(name, self, method, options)
67
69
  end
68
70
 
71
+ ##
72
+ # Defines a version name or number on the service.
73
+ #
74
+ # @param ver [String|Number] the version of the service.
75
+ def version(ver = nil)
76
+ @version = ver if ver
77
+ @version
78
+ end
79
+
69
80
  def actions
70
81
  @actions ||= {}
71
82
  end
@@ -74,8 +85,26 @@ module Moleculer
74
85
  @events ||= {}
75
86
  end
76
87
 
88
+ ##
89
+ # @return [String] returns the full name of the service, including version and prefix
90
+ def full_name
91
+ return service_name unless @version
92
+
93
+ @full_name = service_name.dup
94
+ version = @version.to_s
95
+ version.prepend("v") if @version.is_a? Numeric
96
+
97
+ if @full_name.include?(".")
98
+ @full_name.sub!(".", ".#{version}.")
99
+ elsif version
100
+ @full_name.prepend("#{version}.")
101
+ end
102
+
103
+ @full_name
104
+ end
105
+
77
106
  def action_name_for(name)
78
- "#{service_name}.#{name}"
107
+ "#{full_name}.#{name}"
79
108
  end
80
109
  end
81
110
 
@@ -103,13 +132,14 @@ module Moleculer
103
132
  self.class.broker
104
133
  end
105
134
 
106
- def self.as_json
135
+ def self.to_h # rubocop:disable Metrics/AbcSize
107
136
  {
108
- name: service_name,
137
+ name: full_name,
138
+ version: version,
109
139
  settings: {},
110
140
  metadata: {},
111
- actions: Hash[actions.values.map { |a| [a.name.to_sym, a.as_json] }],
112
- events: Hash[events.values.map { |e| [e.name.to_sym, e.as_json] }],
141
+ actions: Hash[actions.values.map { |a| [a.name.to_sym, a.to_h] }],
142
+ events: Hash[events.values.map { |e| [e.name.to_sym, e.to_h] }],
113
143
  }
114
144
  end
115
145
  end
@@ -32,9 +32,7 @@ module Moleculer
32
32
  def execute(data, broker)
33
33
  @service.new(broker).public_send(@method, data)
34
34
  rescue StandardError => e
35
- raise e unless broker.rescue_event
36
-
37
- broker.rescue_event.call(e)
35
+ broker.config.handle_error(e)
38
36
  end
39
37
 
40
38
  ##
@@ -45,7 +43,7 @@ module Moleculer
45
43
 
46
44
  ##
47
45
  # @return [Hash] a hash representing this event as it would be in JSON
48
- def as_json
46
+ def to_h
49
47
  {
50
48
  name: name,
51
49
  }
@@ -7,8 +7,8 @@ module Moleculer
7
7
  class OpenStruct < ::OpenStruct
8
8
  ##
9
9
  # @return [Hash] the object prepared for conversion to JSON for transmission
10
- def as_json
11
- Hash[to_h.map { |item| [StringUtil.camelize(item[0]), item[1]] }]
10
+ def to_h
11
+ Hash[super.map { |item| [StringUtil.camelize(item[0]), item[1]] }]
12
12
  end
13
13
  end
14
14
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative "base"
2
4
 
3
5
  module Moleculer
@@ -11,14 +13,18 @@ module Moleculer
11
13
  # in this case we want to use a class var as this needs to behave like a singleton to mimic how a global
12
14
  # transporter functions
13
15
  @@subscriptions ||= {} # rubocop:disable Style/ClassVars
16
+ @logger = config.logger.get_child("[FAKE.TRANSPORTER]")
14
17
  end
15
18
 
16
19
  def subscribe(channel, &block)
17
- @@subscriptions[channel] = block
20
+ @@subscriptions[channel] ||= []
21
+ @@subscriptions[channel] << block
18
22
  end
19
23
 
20
24
  def publish(packet)
21
- @@subscriptions[packet.topic].call(packet)
25
+ @logger.debug "publishing packet to '#{packet.topic}'", packet.to_h
26
+ @logger.debug "processing #{@@subscriptions[packet.topic].length} callbacks for '#{packet.topic}'"
27
+ @@subscriptions[packet.topic].each { |c| c.call(packet) }
22
28
  end
23
29
 
24
30
  def start
@@ -22,7 +22,7 @@ module Moleculer
22
22
  # Publishes the packet to the packet's topic
23
23
  def publish(packet)
24
24
  topic = packet.topic
25
- @logger.debug "publishing packet to '#{topic}'", packet.as_json
25
+ @logger.debug "publishing packet to '#{topic}'", packet.to_h
26
26
  connection.publish(topic, @serializer.serialize(packet))
27
27
  end
28
28
 
@@ -61,6 +61,7 @@ module Moleculer
61
61
  @logger = config.logger.get_child("[REDIS.TRANSPORTER.SUBSCRIPTION.#{channel}]")
62
62
  @serializer = Serializers.for(config.serializer).new(config)
63
63
  @node_id = config.node_id
64
+ @config = config
64
65
 
65
66
  # it is necessary to send some sort of message to signal the subscriber to disconnect and shutdown
66
67
  # this is an internal message
@@ -121,11 +122,11 @@ module Moleculer
121
122
  def process_packet(packet)
122
123
  return @connection.unsubscribe if packet == :disconnect
123
124
 
124
- @logger.trace "received packet from #{packet.sender}:", packet.as_json
125
+ @logger.trace "received packet from #{packet.sender}:", packet.to_h
125
126
 
126
127
  @block.call(packet)
127
- rescue StandardError => error
128
- @logger.error error
128
+ rescue StandardError => e
129
+ @config.handle_error(e)
129
130
  end
130
131
 
131
132
  def process_message(message)
@@ -140,9 +141,9 @@ module Moleculer
140
141
  return nil unless parsed
141
142
 
142
143
 
143
- packet_type.new(parsed)
144
- rescue StandardError => error
145
- @logger.error error
144
+ packet_type.new(@config, parsed)
145
+ rescue StandardError => e
146
+ @config.handle_error(e)
146
147
  end
147
148
 
148
149
  def unsubscribe
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Moleculer
4
- VERSION = "0.2.0"
4
+ VERSION = "0.3.0"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: moleculer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - fugufish
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-06-06 00:00:00.000000000 Z
11
+ date: 2019-08-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby
@@ -249,7 +249,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
249
249
  - !ruby/object:Gem::Version
250
250
  version: '0'
251
251
  requirements: []
252
- rubygems_version: 3.0.3
252
+ rubygems_version: 3.0.4
253
253
  signing_key:
254
254
  specification_version: 4
255
255
  summary: This is a Ruby implementation of the Moleculer framework.