moleculer 0.1.0
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/.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
|