moleculer 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +14 -0
  3. data/.rspec +3 -0
  4. data/.rubocop.yml +44 -0
  5. data/.travis.yml +23 -0
  6. data/.yardopts +1 -0
  7. data/CODE_OF_CONDUCT.md +74 -0
  8. data/Gemfile +6 -0
  9. data/Gemfile.lock +57 -0
  10. data/LICENSE.txt +21 -0
  11. data/README.md +59 -0
  12. data/Rakefile +6 -0
  13. data/bin/console +14 -0
  14. data/bin/setup +8 -0
  15. data/examples/benchmark_server.rb +21 -0
  16. data/examples/client-server/client.rb +13 -0
  17. data/examples/client-server/server.rb +25 -0
  18. data/lib/moleculer/broker.rb +318 -0
  19. data/lib/moleculer/configuration.rb +109 -0
  20. data/lib/moleculer/context.rb +24 -0
  21. data/lib/moleculer/errors/action_not_found.rb +6 -0
  22. data/lib/moleculer/errors/invalid_action_response.rb +11 -0
  23. data/lib/moleculer/errors/local_node_already_registered.rb +6 -0
  24. data/lib/moleculer/errors/node_not_found.rb +6 -0
  25. data/lib/moleculer/errors/transporter_already_started.rb +6 -0
  26. data/lib/moleculer/node.rb +105 -0
  27. data/lib/moleculer/packets/base.rb +47 -0
  28. data/lib/moleculer/packets/disconnect.rb +10 -0
  29. data/lib/moleculer/packets/discover.rb +10 -0
  30. data/lib/moleculer/packets/event.rb +37 -0
  31. data/lib/moleculer/packets/heartbeat.rb +18 -0
  32. data/lib/moleculer/packets/info.rb +97 -0
  33. data/lib/moleculer/packets/req.rb +57 -0
  34. data/lib/moleculer/packets/res.rb +43 -0
  35. data/lib/moleculer/packets.rb +25 -0
  36. data/lib/moleculer/registry.rb +325 -0
  37. data/lib/moleculer/serializers/json.rb +23 -0
  38. data/lib/moleculer/serializers.rb +10 -0
  39. data/lib/moleculer/service/action.rb +60 -0
  40. data/lib/moleculer/service/base.rb +117 -0
  41. data/lib/moleculer/service/event.rb +50 -0
  42. data/lib/moleculer/service/remote.rb +80 -0
  43. data/lib/moleculer/service.rb +2 -0
  44. data/lib/moleculer/support/hash_util.rb +48 -0
  45. data/lib/moleculer/support/log_proxy.rb +67 -0
  46. data/lib/moleculer/support/open_struct.rb +15 -0
  47. data/lib/moleculer/support/string_util.rb +25 -0
  48. data/lib/moleculer/support.rb +4 -0
  49. data/lib/moleculer/transporters/redis.rb +207 -0
  50. data/lib/moleculer/transporters.rb +22 -0
  51. data/lib/moleculer/version.rb +3 -0
  52. data/lib/moleculer.rb +103 -0
  53. data/moleculer.gemspec +50 -0
  54. metadata +238 -0
@@ -0,0 +1,18 @@
1
+ require_relative "base"
2
+
3
+ module Moleculer
4
+ module Packets
5
+ ##
6
+ # Represents a DISCOVER packet
7
+ class Heartbeat < Base
8
+
9
+ def initialize(data)
10
+ super(data)
11
+
12
+ @cpu = 0
13
+ end
14
+
15
+
16
+ end
17
+ end
18
+ end
@@ -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,10 @@
1
+ module Moleculer
2
+ module Serializers
3
+
4
+ def self.for(serializer)
5
+ require_relative("serializers/#{serializer}")
6
+ Serializers.const_get(serializer.to_s.split("_").map(&:capitalize).join)
7
+ end
8
+
9
+ end
10
+ 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