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,97 @@
|
|
1
|
+
require "socket"
|
2
|
+
require_relative "base"
|
3
|
+
|
4
|
+
module Moleculer
|
5
|
+
module Packets
|
6
|
+
##
|
7
|
+
# Represents an INFO packet
|
8
|
+
class Info < Base
|
9
|
+
include Support
|
10
|
+
|
11
|
+
##
|
12
|
+
# Represents the client information for a given node
|
13
|
+
class Client
|
14
|
+
include Support
|
15
|
+
|
16
|
+
# @!attribute [r] type
|
17
|
+
# @return [String] type of client implementation (nodejs, java, ruby etc.)
|
18
|
+
# @!attribute [r] version
|
19
|
+
# @return [String] the version of the moleculer client
|
20
|
+
# @!attribute [r] lang_version
|
21
|
+
# @return [String] the client type version
|
22
|
+
attr_reader :type,
|
23
|
+
:version,
|
24
|
+
:lang_version
|
25
|
+
|
26
|
+
def initialize(data)
|
27
|
+
@type = HashUtil.fetch(data, :type)
|
28
|
+
@version = HashUtil.fetch(data, :version)
|
29
|
+
@lang_version = HashUtil.fetch(data, :lang_version)
|
30
|
+
end
|
31
|
+
|
32
|
+
##
|
33
|
+
# @return [Hash] the object prepared for conversion to JSON for transmission
|
34
|
+
def as_json
|
35
|
+
{
|
36
|
+
type: @type,
|
37
|
+
version: @version,
|
38
|
+
langVersion: @lang_version,
|
39
|
+
}
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# @!attribute [r] services
|
44
|
+
# @return [Array<Moleculer::Services::Remote|Moleculer::Services::Base>] an array of the services the endpoint
|
45
|
+
# provides
|
46
|
+
# @!attribute [r] config
|
47
|
+
# @return [Moleculer::Support::OpenStruct] the configuration of the node
|
48
|
+
# @!attribute [r] ip_list
|
49
|
+
# @return [Array<String>] a list of the node's used IP addresses
|
50
|
+
# @!attribute [r] hostname
|
51
|
+
# @return [String] the hostname of the node
|
52
|
+
# @!attribute [r] client
|
53
|
+
# @return [Moleculer::Packets::Info::Client] the client data for the node
|
54
|
+
attr_reader :services,
|
55
|
+
:config,
|
56
|
+
:ip_list,
|
57
|
+
:hostname,
|
58
|
+
:client
|
59
|
+
|
60
|
+
##
|
61
|
+
# @param data [Hash] the packet data
|
62
|
+
# @options data [Array<Hash>|Moleculer::Services::Base] services the services information
|
63
|
+
# @options data [Hash] config the configuration data for the node
|
64
|
+
# @options data [Array<String>] ip_list the list of ip addresses for the node
|
65
|
+
# @options data [String] hostname the hostname of the node
|
66
|
+
# @options data [Hash] client the client data for the node
|
67
|
+
def initialize(data)
|
68
|
+
super(data)
|
69
|
+
@services = HashUtil.fetch(data, :services)
|
70
|
+
@config = OpenStruct.new(Hash[HashUtil.fetch(data, :config).map { |i| [StringUtil.underscore(i[0]), i[1]] }])
|
71
|
+
@ip_list = HashUtil.fetch(data, :ip_list)
|
72
|
+
@hostname = HashUtil.fetch(data, :hostname)
|
73
|
+
@client = Client.new(HashUtil.fetch(data, :client))
|
74
|
+
@node = HashUtil.fetch(data, :node, nil)
|
75
|
+
end
|
76
|
+
|
77
|
+
def topic
|
78
|
+
if @node
|
79
|
+
return "#{super}.#{@node.id}" if @node.is_a? Moleculer::Node
|
80
|
+
|
81
|
+
return "#{super}.#{@node}"
|
82
|
+
end
|
83
|
+
super
|
84
|
+
end
|
85
|
+
|
86
|
+
def as_json
|
87
|
+
super.merge(
|
88
|
+
services: @services,
|
89
|
+
config: @config.to_h,
|
90
|
+
ipList: @ip_list,
|
91
|
+
hostname: @hostname,
|
92
|
+
client: @client.as_json,
|
93
|
+
)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require_relative "base"
|
2
|
+
|
3
|
+
module Moleculer
|
4
|
+
module Packets
|
5
|
+
##
|
6
|
+
# Represents a REQ packet
|
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
|
20
|
+
|
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
|
38
|
+
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,
|
49
|
+
)
|
50
|
+
end
|
51
|
+
|
52
|
+
def topic
|
53
|
+
"#{super}.#{@node.id}"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require_relative "base"
|
2
|
+
|
3
|
+
module Moleculer
|
4
|
+
module Packets
|
5
|
+
##
|
6
|
+
# Represents a RES packet
|
7
|
+
class Res < Base
|
8
|
+
attr_reader :id,
|
9
|
+
:success,
|
10
|
+
:data,
|
11
|
+
:error,
|
12
|
+
:meta,
|
13
|
+
:stream
|
14
|
+
|
15
|
+
def initialize(data)
|
16
|
+
super(data)
|
17
|
+
|
18
|
+
@id = HashUtil.fetch(data, :id)
|
19
|
+
@success = HashUtil.fetch(data, :success)
|
20
|
+
@data = HashUtil.fetch(data, :data)
|
21
|
+
@error = HashUtil.fetch(data, :error, nil)
|
22
|
+
@meta = HashUtil.fetch(data, :meta)
|
23
|
+
@stream = HashUtil.fetch(data, :stream, false)
|
24
|
+
@node = HashUtil.fetch(data, :node, nil)
|
25
|
+
end
|
26
|
+
|
27
|
+
def topic
|
28
|
+
"#{super}.#{@node.id}"
|
29
|
+
end
|
30
|
+
|
31
|
+
def as_json
|
32
|
+
super.merge(
|
33
|
+
id: @id,
|
34
|
+
success: @success,
|
35
|
+
data: @data,
|
36
|
+
error: @error,
|
37
|
+
meta: @meta,
|
38
|
+
stream: @stream,
|
39
|
+
)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require_relative "packets/disconnect"
|
2
|
+
require_relative "packets/discover"
|
3
|
+
require_relative "packets/event"
|
4
|
+
require_relative "packets/heartbeat"
|
5
|
+
require_relative "packets/info"
|
6
|
+
require_relative "packets/req"
|
7
|
+
require_relative "packets/res"
|
8
|
+
|
9
|
+
module Moleculer
|
10
|
+
module Packets
|
11
|
+
TYPES = {
|
12
|
+
Discover.packet_name => Discover,
|
13
|
+
Info.packet_name => Info,
|
14
|
+
Req.packet_name => Req,
|
15
|
+
Res.packet_name => Res,
|
16
|
+
Heartbeat.packet_name => Heartbeat,
|
17
|
+
Event.packet_name => Event,
|
18
|
+
Disconnect.packet_name => Disconnect,
|
19
|
+
}.freeze
|
20
|
+
|
21
|
+
def self.for(type)
|
22
|
+
TYPES[type.to_s.upcase]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,325 @@
|
|
1
|
+
require_relative "errors/local_node_already_registered"
|
2
|
+
require_relative "errors/action_not_found"
|
3
|
+
require_relative "errors/node_not_found"
|
4
|
+
require_relative "support"
|
5
|
+
|
6
|
+
module Moleculer
|
7
|
+
##
|
8
|
+
# The Registry manages the available services on the network
|
9
|
+
class Registry
|
10
|
+
##
|
11
|
+
# @private
|
12
|
+
class NodeList
|
13
|
+
def initialize(heartbeat_interval)
|
14
|
+
@nodes = Concurrent::Hash.new
|
15
|
+
@heartbeat_interval = heartbeat_interval
|
16
|
+
end
|
17
|
+
|
18
|
+
def add_node(node)
|
19
|
+
if @nodes[node.id]
|
20
|
+
@nodes[node.id][:node] = node
|
21
|
+
else
|
22
|
+
@nodes[node.id] = { node: node, last_requested_at: Time.now }
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def remove_node(node_id)
|
27
|
+
@nodes.delete(node_id)
|
28
|
+
end
|
29
|
+
|
30
|
+
def fetch_next_node
|
31
|
+
node = active_nodes.min_by { |a| a[:last_requested_at] }[:node]
|
32
|
+
@nodes[node.id][:last_requested_at] = Time.now
|
33
|
+
node
|
34
|
+
end
|
35
|
+
|
36
|
+
def fetch_node(node_id)
|
37
|
+
@nodes.fetch(node_id)[:node]
|
38
|
+
end
|
39
|
+
|
40
|
+
def length
|
41
|
+
@nodes.length
|
42
|
+
end
|
43
|
+
|
44
|
+
def active_nodes
|
45
|
+
@nodes.values.select { |node| (Time.now - node[:node].last_heartbeat_at) < @heartbeat_interval * 3 }
|
46
|
+
end
|
47
|
+
|
48
|
+
def expired_nodes
|
49
|
+
@nodes.values.select { |node| (Time.now - node[:node].last_heartbeat_at) > 600 }
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
##
|
54
|
+
# @private
|
55
|
+
class ActionList
|
56
|
+
def initialize(heartbeat_interval)
|
57
|
+
@heartbeat_interval = heartbeat_interval
|
58
|
+
@actions = Concurrent::Hash.new
|
59
|
+
end
|
60
|
+
|
61
|
+
def add(name, action)
|
62
|
+
@actions[name] ||= NodeList.new(@heartbeat_interval)
|
63
|
+
@actions[name].add_node(action.node)
|
64
|
+
end
|
65
|
+
|
66
|
+
def remove_node(node_id)
|
67
|
+
@actions.each do |k, a|
|
68
|
+
a.remove_node(node_id)
|
69
|
+
@actions.delete(k) if a.length.zero?
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def fetch_action(action_name)
|
74
|
+
raise Errors::ActionNotFound, "The action '#{action_name}' was not found." unless @actions[action_name]
|
75
|
+
|
76
|
+
@actions[action_name].fetch_next_node.actions[action_name]
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
##
|
81
|
+
# @private
|
82
|
+
class EventList
|
83
|
+
##
|
84
|
+
# @private
|
85
|
+
class Item
|
86
|
+
def initialize(heartbeat_interval)
|
87
|
+
@heartbeat_interval = heartbeat_interval
|
88
|
+
@services = Concurrent::Hash.new
|
89
|
+
end
|
90
|
+
|
91
|
+
def add_service(service)
|
92
|
+
@services[service.service_name] ||= NodeList.new(@heartbeat_interval)
|
93
|
+
@services[service.service_name].add_node(service.node)
|
94
|
+
end
|
95
|
+
|
96
|
+
def fetch_nodes
|
97
|
+
@services.values.map(&:fetch_next_node)
|
98
|
+
end
|
99
|
+
|
100
|
+
def remove_node(node_id)
|
101
|
+
@services.each do |k, list|
|
102
|
+
list.remove_node(node_id)
|
103
|
+
@services.delete(k) if list.length.zero?
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def length
|
108
|
+
@services.map(&:length).inject(0) { |s, a| s + a }
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def initialize(heartbeat_interval)
|
113
|
+
@events = Concurrent::Hash.new
|
114
|
+
@heartbeat_interval = heartbeat_interval
|
115
|
+
end
|
116
|
+
|
117
|
+
def add(event)
|
118
|
+
name = event.name
|
119
|
+
@events[name] ||= Item.new(@heartbeat_interval)
|
120
|
+
@events[name].add_service(event.service)
|
121
|
+
end
|
122
|
+
|
123
|
+
def remove_node(node_id)
|
124
|
+
@events.each do |k, item|
|
125
|
+
item.remove_node(node_id)
|
126
|
+
@events.delete(k) if item.length.zero?
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def fetch_events(event_name)
|
131
|
+
return [] unless @events[event_name]
|
132
|
+
|
133
|
+
@events[event_name].fetch_nodes.map { |n| n.events[event_name] }.flatten
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
private_constant :ActionList
|
138
|
+
private_constant :EventList
|
139
|
+
private_constant :NodeList
|
140
|
+
|
141
|
+
include Support
|
142
|
+
|
143
|
+
attr_reader :local_node
|
144
|
+
|
145
|
+
##
|
146
|
+
# @param config [Moleculer::Config] the moleculer configuration
|
147
|
+
def initialize(config)
|
148
|
+
@config = config
|
149
|
+
@nodes = NodeList.new(@config.heartbeat_interval)
|
150
|
+
@actions = ActionList.new(@config.heartbeat_interval)
|
151
|
+
@events = EventList.new(@config.heartbeat_interval)
|
152
|
+
@services = Concurrent::Hash.new
|
153
|
+
@logger = @config.logger.get_child("[REGISTRY]")
|
154
|
+
@remove_semaphore = Concurrent::Semaphore.new(1)
|
155
|
+
end
|
156
|
+
|
157
|
+
##
|
158
|
+
# Registers the node with the registry and updates the action/event handler lists.
|
159
|
+
#
|
160
|
+
# @param [Moleculer::Node] node the node to register
|
161
|
+
#
|
162
|
+
# @return [Moleculer::Node] the node that has been registered
|
163
|
+
def register_node(node)
|
164
|
+
return local_node if @local_node && node.id == @local_node.id
|
165
|
+
|
166
|
+
if node.local?
|
167
|
+
raise Errors::LocalNodeAlreadyRegistered, "A LOCAL node has already been registered" if @local_node
|
168
|
+
|
169
|
+
@logger.info "registering LOCAL node '#{node.id}'"
|
170
|
+
@local_node = node
|
171
|
+
end
|
172
|
+
@logger.info "registering node #{node.id}" unless node.local?
|
173
|
+
@nodes.add_node(node)
|
174
|
+
update_actions(node)
|
175
|
+
update_events(node)
|
176
|
+
# always call this last, as it will immediately cause processes waiting for services to show it as ready even if
|
177
|
+
# the actions or events lists have not been updated.
|
178
|
+
update_services(node)
|
179
|
+
node
|
180
|
+
end
|
181
|
+
|
182
|
+
##
|
183
|
+
# Gets the named action from the registry. If a local action exists it will return the local one instead of a
|
184
|
+
# remote action.
|
185
|
+
#
|
186
|
+
# @param action_name [String] the name of the action
|
187
|
+
#
|
188
|
+
# @return [Moleculer::Service::Action|Moleculer::RemoteService::Action]
|
189
|
+
def fetch_action(action_name)
|
190
|
+
@actions.fetch_action(action_name)
|
191
|
+
end
|
192
|
+
|
193
|
+
##
|
194
|
+
# Fetches all the events for the given event name that should be used to emit an event. This is load balanced, and
|
195
|
+
# will fetch a different node for nodes that duplicate the service/event
|
196
|
+
#
|
197
|
+
# @param event_name [String] the name of the even to fetch
|
198
|
+
#
|
199
|
+
# @return [Array<Moleculer::Service::Event>] the events that that should be emitted to
|
200
|
+
def fetch_events_for_emit(event_name)
|
201
|
+
@events.fetch_events(event_name)
|
202
|
+
end
|
203
|
+
|
204
|
+
##
|
205
|
+
# It fetches the given node, and raises an exception if the node does not exist
|
206
|
+
#
|
207
|
+
# @param node_id [String] the id of the node to fetch
|
208
|
+
#
|
209
|
+
# @return [Moleculer::Node] the moleculer node with the given id
|
210
|
+
# @raise [Moleculer::Errors::NodeNotFound] if the node was not found
|
211
|
+
def fetch_node(node_id)
|
212
|
+
@nodes.fetch_node(node_id)
|
213
|
+
rescue KeyError
|
214
|
+
raise Errors::NodeNotFound, "The node with the id '#{node_id}' was not found."
|
215
|
+
end
|
216
|
+
|
217
|
+
##
|
218
|
+
# Fetches the given node, and returns nil if the node was not found
|
219
|
+
#
|
220
|
+
# @param node_id [String] the id of the node to fetch
|
221
|
+
#
|
222
|
+
# @return [Moleculer::Node] the moleculer node with the given id
|
223
|
+
def safe_fetch_node(node_id)
|
224
|
+
fetch_node(node_id)
|
225
|
+
rescue Errors::NodeNotFound
|
226
|
+
nil
|
227
|
+
end
|
228
|
+
|
229
|
+
##
|
230
|
+
# Gets the named action from the registry for the given node. Raises an error if the node does not exist or the node
|
231
|
+
# does not have the specified action.
|
232
|
+
#
|
233
|
+
# @param action_name [String] the name of the action
|
234
|
+
# @param node_id [String] the id of the node from which to get the action
|
235
|
+
#
|
236
|
+
# @return [Moleculer::Service::Action] the action from the specified node_id
|
237
|
+
# @raise [Moleculer::NodeNotFound] raised when the specified node_id was not found
|
238
|
+
# @raise [Moleculer::ActionNotFound] raised when the specified action was not found on the specified node
|
239
|
+
def fetch_action_for_node_id(action_name, node_id)
|
240
|
+
node = fetch_node(node_id)
|
241
|
+
fetch_action_from_node(action_name, node)
|
242
|
+
end
|
243
|
+
|
244
|
+
def missing_services(*services)
|
245
|
+
services - @services.keys
|
246
|
+
end
|
247
|
+
|
248
|
+
##
|
249
|
+
# Removes the node with the given id. Because this must act across multiple indexes this action uses a semaphore to
|
250
|
+
# reduce the chance of race conditions.
|
251
|
+
#
|
252
|
+
# @param node_id [String] the node to remove
|
253
|
+
def remove_node(node_id, reason = nil)
|
254
|
+
@remove_semaphore.acquire
|
255
|
+
@logger.info "removing node '#{node_id}'" unless reason
|
256
|
+
@logger.info "removing node '#{node_id}' because #{reason}"
|
257
|
+
@nodes.remove_node(node_id)
|
258
|
+
@actions.remove_node(node_id)
|
259
|
+
@events.remove_node(node_id)
|
260
|
+
@remove_semaphore.release
|
261
|
+
end
|
262
|
+
|
263
|
+
##
|
264
|
+
# Looks for nodes that have stopped reporting heartbeats and removes them from the registry
|
265
|
+
def expire_nodes
|
266
|
+
@nodes.expired_nodes.each { |node| remove_node(node[:node].id, "it expired after 10 minutes") }
|
267
|
+
end
|
268
|
+
|
269
|
+
private
|
270
|
+
|
271
|
+
def get_local_action(name)
|
272
|
+
@local_node.actions.select { |a| a.name == name.to_s }.first
|
273
|
+
end
|
274
|
+
|
275
|
+
def update_node_for_load_balancer(node)
|
276
|
+
@nodes[node.id][:last_called_at] = Time.now
|
277
|
+
end
|
278
|
+
|
279
|
+
def fetch_event_from_node(event_name, node)
|
280
|
+
node.events.fetch(event_name)
|
281
|
+
rescue KeyError
|
282
|
+
raise Errors::EventNotFound, "The event '#{event_name}' was not found on the node id with id '#{node.id}'"
|
283
|
+
end
|
284
|
+
|
285
|
+
def fetch_action_from_node(action_name, node)
|
286
|
+
node.actions.fetch(action_name)
|
287
|
+
end
|
288
|
+
|
289
|
+
def fetch_next_nodes_for_event(event_name)
|
290
|
+
service_names = HashUtil.fetch(@events, event_name, {}).keys
|
291
|
+
node_names = service_names.map { |s| @services[s] }
|
292
|
+
nodes = node_names.map { |names| names.map { |name| @nodes[name] } }
|
293
|
+
nodes.map { |node_list| node_list.min_by { |a| a[:last_called_at] }[:node] }
|
294
|
+
end
|
295
|
+
|
296
|
+
def remove_node_from_events(node_id)
|
297
|
+
@events.values.each do |event|
|
298
|
+
event.values.each do |list|
|
299
|
+
list.reject! { |id| id == node_id }
|
300
|
+
end
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
304
|
+
def update_actions(node)
|
305
|
+
node.actions.each do |name, action|
|
306
|
+
@actions.add(name, action)
|
307
|
+
end
|
308
|
+
@logger.debug "registered #{node.actions.length} action(s) for node '#{node.id}'"
|
309
|
+
end
|
310
|
+
|
311
|
+
def update_events(node)
|
312
|
+
node.events.values.each do |events|
|
313
|
+
events.each { |e| @events.add(e) }
|
314
|
+
end
|
315
|
+
@logger.debug "registered #{node.events.length} event(s) for node '#{node.id}'"
|
316
|
+
end
|
317
|
+
|
318
|
+
def update_services(node)
|
319
|
+
node.services.values.each do |service|
|
320
|
+
@services[service.service_name] ||= NodeList.new(@heartbeat_interval)
|
321
|
+
@services[service.service_name].add_node(node)
|
322
|
+
end
|
323
|
+
end
|
324
|
+
end
|
325
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require "json"
|
2
|
+
|
3
|
+
module Moleculer
|
4
|
+
module Serializers
|
5
|
+
##
|
6
|
+
# Serializes data packets to and from JSON
|
7
|
+
class Json
|
8
|
+
def initialize(config)
|
9
|
+
@logger = config.logger.get_child("[SERIALIZER]")
|
10
|
+
end
|
11
|
+
|
12
|
+
def serialize(message)
|
13
|
+
message.as_json.to_json
|
14
|
+
end
|
15
|
+
|
16
|
+
def deserialize(message)
|
17
|
+
JSON.parse(message)
|
18
|
+
rescue StandardError => e
|
19
|
+
@logger.error e
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require_relative "../errors/invalid_action_response"
|
2
|
+
require_relative "../support"
|
3
|
+
module Moleculer
|
4
|
+
module Service
|
5
|
+
##
|
6
|
+
# Represents an action
|
7
|
+
class Action
|
8
|
+
include Support
|
9
|
+
|
10
|
+
# @!attribute [r] name
|
11
|
+
# @return [String] the name of the action
|
12
|
+
attr_reader :name, :service
|
13
|
+
|
14
|
+
##
|
15
|
+
# @param name [String|Symbol] the name of the action.
|
16
|
+
# @param method [Symbol] the service method to be called.
|
17
|
+
# @param service [Moleculer::Service::Base] the moleculer service class
|
18
|
+
# @param options [Hash] action options.
|
19
|
+
# @option options [Boolean|Hash] :cache if true, will use default caching options, if a hash is provided caching
|
20
|
+
# options will reflect the hash.
|
21
|
+
# @option options [Hash] params list of param and param types. Can be used to coerce specific params to the
|
22
|
+
# provided type.
|
23
|
+
def initialize(name, service, method, options = {})
|
24
|
+
@name = name
|
25
|
+
@service = service
|
26
|
+
@method = method
|
27
|
+
@service = service
|
28
|
+
@options = options
|
29
|
+
end
|
30
|
+
|
31
|
+
##
|
32
|
+
# @param context [Moleculer::Context] the execution contextd
|
33
|
+
#
|
34
|
+
# @return [Moleculer::Support::Hash] returns a hash which will be converted into json for the response.
|
35
|
+
def execute(context, broker)
|
36
|
+
response = @service.new(broker).public_send(@method, context)
|
37
|
+
# rubocop:disable Style/RaiseArgs
|
38
|
+
raise Errors::InvalidActionResponse.new(response) unless response.is_a? Hash
|
39
|
+
|
40
|
+
response
|
41
|
+
end
|
42
|
+
|
43
|
+
def node
|
44
|
+
@service.node
|
45
|
+
end
|
46
|
+
|
47
|
+
def as_json
|
48
|
+
{
|
49
|
+
name: "#{@service.service_name}.#{name}",
|
50
|
+
rawName: name,
|
51
|
+
cache: HashUtil.fetch(@options, :cache, false),
|
52
|
+
metrics: {
|
53
|
+
params: false,
|
54
|
+
meta: true,
|
55
|
+
},
|
56
|
+
}
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|