mimi-messaging-sqs_sns 0.1.4 → 0.4.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 799168326dcf4504b6ab6c62eb6da195015e615b
4
- data.tar.gz: 67c0507d5937e2160d87706706ac8d73dcb922bc
3
+ metadata.gz: f0919e38a4926e465f4b2017b98ce6b621a41f5b
4
+ data.tar.gz: f356e4552744757c4e6c77eb3eca359839d7d834
5
5
  SHA512:
6
- metadata.gz: 4db933f3182eacfa87a3e37ca56fb517d38f59acfa023de8750849b3f6d497fdac8e10e952e016bcc177ebae08139b5de52116231401e1908492f1ceeb5c104b
7
- data.tar.gz: 0ba15e0811746cfad55dfef7bd4e4136b3a217d1b5aa8f835cc595d236fb68c321fd68f78d0afd597bdd41f40d141e97fc6de160e58b06e6557645faffde7bba
6
+ metadata.gz: b45642dc502c2bf8e5debf230460594abef63fde87f4f029d5583fdb942965465581be65e0eb050781d2780453f1843644a9c0539849ef1e562062b3370cd47b
7
+ data.tar.gz: b5251902e1ac67cad8731f05fdd5a0fe1a7e32db70f4aa62a9f7a29bdb0a2de0c6ff56e82c0fa5b01fabb9d7438fb0411aa62390daf1afe0ceed1ce72c223562
data/README.md CHANGED
@@ -32,6 +32,7 @@ Mimi::Messaging.configure(
32
32
  mq_aws_access_key_id: nil,
33
33
  mq_aws_secret_access_key: nil,
34
34
  mq_aws_sqs_endpoint: nil,
35
+ mq_aws_sns_endpoint: nil,
35
36
 
36
37
  mq_aws_sqs_read_timeout: 10, # seconds
37
38
  mq_namespace: nil,
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "mimi/messaging/sqs_sns"
4
+
5
+ COUNT = 10
6
+ AWS_REGION = "eu-west-1"
7
+ AWS_SQS_ENDPOINT_URL = "http://localstack:4576"
8
+ AWS_SNS_ENDPOINT_URL = "http://localstack:4575"
9
+ AWS_ACCESS_KEY_ID = "foo"
10
+ AWS_SECRET_ACCESS_KEY = "bar"
11
+
12
+ logger = Logger.new(STDOUT)
13
+ logger.level = Logger::INFO
14
+ Mimi::Messaging.use(logger: logger, serializer: Mimi::Messaging::JsonSerializer)
15
+ Mimi::Messaging.configure(
16
+ mq_adapter: "sqs_sns",
17
+ mq_aws_access_key_id: AWS_ACCESS_KEY_ID,
18
+ mq_aws_secret_access_key: AWS_SECRET_ACCESS_KEY,
19
+ mq_aws_region: AWS_REGION,
20
+ mq_aws_sqs_endpoint: AWS_SQS_ENDPOINT_URL,
21
+ mq_aws_sns_endpoint: AWS_SNS_ENDPOINT_URL,
22
+ mq_log_at_level: :info
23
+ )
24
+ adapter = Mimi::Messaging.adapter
25
+
26
+ adapter.start
27
+
28
+ t_start = Time.now
29
+ COUNT.times do |i|
30
+ t = Time.now
31
+ puts "Publishing event: #{i}"
32
+ adapter.event("hello#tested", i: i) # rand(100))
33
+ sleep 1
34
+ end
@@ -5,6 +5,7 @@ require "mimi/messaging/sqs_sns"
5
5
  COUNT = 10
6
6
  AWS_REGION = "eu-west-1"
7
7
  AWS_SQS_ENDPOINT_URL = "http://localstack:4576"
8
+ AWS_SNS_ENDPOINT_URL = "http://localstack:4575"
8
9
  AWS_ACCESS_KEY_ID = "foo"
9
10
  AWS_SECRET_ACCESS_KEY = "bar"
10
11
 
@@ -16,7 +17,8 @@ Mimi::Messaging.configure(
16
17
  mq_aws_access_key_id: AWS_ACCESS_KEY_ID,
17
18
  mq_aws_secret_access_key: AWS_SECRET_ACCESS_KEY,
18
19
  mq_aws_region: AWS_REGION,
19
- mq_aws_sqs_endpoint: AWS_SQS_ENDPOINT_URL
20
+ mq_aws_sqs_endpoint: AWS_SQS_ENDPOINT_URL,
21
+ mq_aws_sns_endpoint: AWS_SNS_ENDPOINT_URL
20
22
  )
21
23
  adapter = Mimi::Messaging.adapter
22
24
 
@@ -4,6 +4,7 @@ require "mimi/messaging/sqs_sns"
4
4
 
5
5
  AWS_REGION = "eu-west-1"
6
6
  AWS_SQS_ENDPOINT_URL = "http://localstack:4576"
7
+ AWS_SNS_ENDPOINT_URL = "http://localstack:4575"
7
8
  AWS_ACCESS_KEY_ID = "foo"
8
9
  AWS_SECRET_ACCESS_KEY = "bar"
9
10
 
@@ -28,7 +29,8 @@ Mimi::Messaging.configure(
28
29
  mq_aws_access_key_id: AWS_ACCESS_KEY_ID,
29
30
  mq_aws_secret_access_key: AWS_SECRET_ACCESS_KEY,
30
31
  mq_aws_region: AWS_REGION,
31
- mq_aws_sqs_endpoint: AWS_SQS_ENDPOINT_URL
32
+ mq_aws_sqs_endpoint: AWS_SQS_ENDPOINT_URL,
33
+ mq_aws_sns_endpoint: AWS_SNS_ENDPOINT_URL
32
34
  )
33
35
  adapter = Mimi::Messaging.adapter
34
36
  queue_name = "test"
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "mimi/messaging/sqs_sns"
4
+
5
+ AWS_REGION = "eu-west-1"
6
+ AWS_SQS_ENDPOINT_URL = "http://localstack:4576"
7
+ AWS_SNS_ENDPOINT_URL = "http://localstack:4575"
8
+ AWS_ACCESS_KEY_ID = "foo"
9
+ AWS_SECRET_ACCESS_KEY = "bar"
10
+
11
+ class Processor
12
+ def self.call_command(method_name, message, opts)
13
+ puts "COMMAND: #{method_name}, #{message}, headers: #{message.headers}"
14
+ end
15
+
16
+ def self.call_query(method_name, message, opts)
17
+ puts "QUERY: #{method_name}, #{message}, headers: #{message.headers}"
18
+ {}
19
+ end
20
+
21
+ def self.call_event(event_type, message, opts)
22
+ puts "EVENT: #{event_type}, #{message}, headers: #{message.headers}"
23
+ end
24
+ end # class Processor
25
+
26
+
27
+ logger = Logger.new(STDOUT)
28
+ logger.level = Logger::INFO
29
+ Mimi::Messaging.use(logger: logger, serializer: Mimi::Messaging::JsonSerializer)
30
+ Mimi::Messaging.configure(
31
+ mq_adapter: "sqs_sns",
32
+ mq_aws_access_key_id: AWS_ACCESS_KEY_ID,
33
+ mq_aws_secret_access_key: AWS_SECRET_ACCESS_KEY,
34
+ mq_aws_region: AWS_REGION,
35
+ mq_aws_sqs_endpoint: AWS_SQS_ENDPOINT_URL,
36
+ mq_aws_sns_endpoint: AWS_SNS_ENDPOINT_URL,
37
+ mq_log_at_level: :info
38
+ )
39
+ adapter = Mimi::Messaging.adapter
40
+
41
+ topic_name = "hello"
42
+ queue_name = "listener.hello"
43
+ adapter.start
44
+ puts "Registering event processor on '#{topic_name}'->'#{queue_name}'"
45
+ adapter.start_event_processor_with_queue(topic_name, queue_name, Processor)
46
+
47
+ begin
48
+ loop do
49
+ sleep 1
50
+ end
51
+ ensure
52
+ puts "Stopping adapter"
53
+ adapter.stop
54
+ end
55
+
@@ -12,4 +12,4 @@ end # module Mimi
12
12
 
13
13
  require_relative "sqs_sns/adapter"
14
14
  require_relative "sqs_sns/consumer"
15
- require_relative "sqs_sns/reply_listener"
15
+ require_relative "sqs_sns/reply_consumer"
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "mimi/messaging"
4
4
  require "aws-sdk-sqs"
5
+ require "aws-sdk-sns"
5
6
  require "timeout"
6
7
  require "securerandom"
7
8
 
@@ -23,20 +24,37 @@ module Mimi
23
24
  # * #stop_all_processors
24
25
  #
25
26
  class Adapter < Mimi::Messaging::Adapters::Base
26
- attr_reader :options, :sqs_client
27
+ #
28
+ # NOTE: AWS SQS/SNS alphabet for queue and topic names
29
+ # is different from what mimi-messaging allows:
30
+ # '.' is not an allowed character.
31
+ #
32
+ # SQS_SNS_ALPHABET_MAP structure is used to convert
33
+ # names from mimi-messaging alphabet to SQS/SNS alphabet.
34
+ #
35
+ # Mimi::Messaging still accepts queue and topic names
36
+ # containing the '.', but the adapter will convert those
37
+ # to valid SQS/SNS names using this mapping.
38
+ #
39
+ SQS_SNS_ALPHABET_MAP = {
40
+ "." => "-"
41
+ }.freeze
42
+
43
+ attr_reader :options, :sqs_client, :sns_client
27
44
 
28
45
  register_adapter_name "sqs_sns"
29
46
 
30
47
  DEFAULT_OPTIONS = {
31
48
  mq_namespace: nil,
32
49
  mq_default_query_timeout: 15, # seconds,
33
- mq_reply_queue_prefix: "reply.",
50
+ mq_reply_queue_prefix: "reply-",
34
51
 
35
52
  # if nil, AWS SDK will guess values from environment
36
53
  mq_aws_region: nil,
37
54
  mq_aws_access_key_id: nil,
38
55
  mq_aws_secret_access_key: nil,
39
56
  mq_aws_sqs_endpoint: nil,
57
+ mq_aws_sns_endpoint: nil,
40
58
 
41
59
  mq_aws_sqs_read_timeout: 10, # seconds
42
60
  }.freeze
@@ -59,14 +77,26 @@ module Mimi
59
77
 
60
78
  def start
61
79
  @sqs_client = Aws::SQS::Client.new(sqs_client_config)
80
+ @sns_client = Aws::SNS::Client.new(sns_client_config)
81
+ check_availability!
62
82
  end
63
83
 
64
84
  def stop
85
+ stop_all_processors
86
+ @sqs_client = nil
87
+ @sns_client = nil
88
+ end
89
+
90
+ # Stops all message (command, query and event) processors.
91
+ #
92
+ # Stops currently registered processors and stops accepting new messages
93
+ # for processors.
94
+ #
95
+ def stop_all_processors
65
96
  @consumers&.each(&:stop)
66
97
  @consumers = nil
67
- @reply_listener&.stop
68
- @reply_listener = nil
69
- @sqs_client = nil
98
+ @reply_consumer&.stop
99
+ @reply_consumer = nil
70
100
  end
71
101
 
72
102
  # Sends the command to the given target
@@ -75,22 +105,22 @@ module Mimi
75
105
  # Mimi::Messaging.command("users/create", name: "John Smith")
76
106
  #
77
107
  # @param target [String] "<queue>/<method>"
78
- # @param message [Hash]
108
+ # @param message [Hash,Mimi::Messaging::Message]
79
109
  # @param opts [Hash] additional adapter-specific options
80
110
  #
81
111
  # @return nil
82
112
  #
83
113
  def command(target, message, _opts = {})
84
114
  queue_name, method_name = target.split("/")
85
- message_payload = serialize(message)
86
- queue_url = find_queue(queue_name)
87
- deliver_message(message_payload, queue_url, __method: method_name)
115
+ message = Mimi::Messaging::Message.new(message, __method: method_name)
116
+ queue_url = find_queue!(queue_name)
117
+ deliver_message_queue(queue_url, message)
88
118
  end
89
119
 
90
120
  # Executes the query to the given target and returns response
91
121
  #
92
122
  # @param target [String] "<queue>/<method>"
93
- # @param message [Hash]
123
+ # @param message [Hash,Mimi::Messaging::Message]
94
124
  # @param opts [Hash] additional options, e.g. :timeout
95
125
  #
96
126
  # @return [Hash]
@@ -98,18 +128,17 @@ module Mimi
98
128
  #
99
129
  def query(target, message, opts = {})
100
130
  queue_name, method_name = target.split("/")
101
- message_payload = serialize(message)
102
- queue_url = find_queue(queue_name)
131
+ queue_url = find_queue!(queue_name)
103
132
  request_id = SecureRandom.hex(8)
104
- reply_queue = reply_listener.register_request_id(request_id)
133
+ reply_queue = reply_consumer.register_request_id(request_id)
105
134
 
106
- deliver_message(
107
- message_payload,
108
- queue_url,
135
+ message = Mimi::Messaging::Message.new(
136
+ message,
109
137
  __method: method_name,
110
- __reply_queue_url: reply_listener.reply_queue_url,
138
+ __reply_queue_url: reply_consumer.reply_queue_url,
111
139
  __request_id: request_id
112
140
  )
141
+ deliver_message_queue(queue_url, message)
113
142
  timeout = opts[:timeout] || options[:mq_default_query_timeout]
114
143
  response = nil
115
144
  Timeout::timeout(timeout) do
@@ -118,8 +147,17 @@ module Mimi
118
147
  deserialize(response.body)
119
148
  end
120
149
 
150
+ # Broadcasts the event with the given target
151
+ #
152
+ # @param target [String] "<topic>#<event_type>", e.g. "customers#created"
153
+ # @param message [Mimi::Messaging::Message]
154
+ # @param opts [Hash] additional options
155
+ #
121
156
  def event(target, message, _opts = {})
122
- raise "Not implemented"
157
+ topic_name, event_type = target.split("#")
158
+ message = Mimi::Messaging::Message.new(message, __event_type: event_type)
159
+ topic_arn = find_or_create_topic(topic_name) # TODO: or find_topic!(...) ?
160
+ deliver_message_topic(topic_arn, message)
123
161
  end
124
162
 
125
163
  # Starts a request (command/query) processor.
@@ -140,39 +178,45 @@ module Mimi
140
178
  opts = opts.dup
141
179
  queue_url = find_or_create_queue(queue_name)
142
180
  @consumers << Consumer.new(self, queue_url) do |m|
143
- message = deserialize(m.body)
144
- headers = deserialize_headers(m)
145
- method_name = headers[:__method]
146
- reply_to = headers[:__reply_queue_url]
181
+ message = Mimi::Messaging::Message.new(
182
+ deserialize(m.body),
183
+ deserialize_headers(m)
184
+ )
185
+ method_name = message.headers[:__method]
186
+ reply_to = message.headers[:__reply_queue_url]
147
187
  if reply_to
148
- response = processor.call_query(method_name, message, headers: headers)
149
- deliver_message(serialize(response), reply_to, __request_id: headers[:__request_id])
188
+ response = processor.call_query(method_name, message, {})
189
+ response_message = Mimi::Messaging::Message.new(
190
+ response,
191
+ __request_id: message.headers[:__request_id]
192
+ )
193
+ deliver_message_queue(reply_to, response_message)
150
194
  else
151
- processor.call_command(method_name, message, headers: headers)
195
+ processor.call_command(method_name, message, {})
152
196
  end
153
197
  end
154
198
  end
155
199
 
156
200
  def start_event_processor(topic_name, processor, opts = {})
201
+ # NOTE: due to SQS/SNS limitations, implementing this will
202
+ # require creating a temporary queue and subscribing it to the topic
157
203
  raise "Not implemented"
158
204
  end
159
205
 
160
206
  def start_event_processor_with_queue(topic_name, queue_name, processor, opts = {})
161
- raise "Not implemented"
162
- end
163
-
164
- # Creates a new queue
165
- #
166
- # @param queue_name [String] name of the topic to be created
167
- # @return [String] a new queue URL
168
- #
169
- def create_queue(queue_name)
170
- fqn = full_queue_name(queue_name)
171
- Mimi::Messaging.log "Creating a queue: #{fqn}"
172
- result = sqs_client.create_queue(queue_name: fqn)
173
- result.queue_url
174
- rescue StandardError => e
175
- raise Mimi::Messaging::ConnectionError, "Failed to create queue '#{queue_name}': #{e}"
207
+ @consumers ||= []
208
+ opts = opts.dup
209
+ topic_arn = find_or_create_topic(topic_name) # TODO: or find_topic!(...) ?
210
+ queue_url = find_or_create_queue(queue_name)
211
+ subscribe_topic_queue(topic_arn, queue_url)
212
+ @consumers << Consumer.new(self, queue_url) do |m|
213
+ message = Mimi::Messaging::Message.new(
214
+ deserialize(m.body),
215
+ deserialize_headers(m)
216
+ )
217
+ event_type = message.headers[:__event_type]
218
+ processor.call_event(event_type, message, {})
219
+ end
176
220
  end
177
221
 
178
222
  private
@@ -191,19 +235,66 @@ module Mimi
191
235
  params.compact
192
236
  end
193
237
 
238
+ # Returns configuration parameters for AWS SNS client
239
+ #
240
+ # @return [Hash]
241
+ #
242
+ def sns_client_config
243
+ params = {
244
+ region: options[:mq_aws_region],
245
+ endpoint: options[:mq_aws_sns_endpoint],
246
+ access_key_id: options[:mq_aws_access_key_id],
247
+ secret_access_key: options[:mq_aws_secret_access_key]
248
+ }
249
+ params.compact
250
+ end
251
+
252
+ # Checks SQS and SNS clients availability
253
+ #
254
+ # @raise [Mimi::Messaging::ConnectionError]
255
+ #
256
+ def check_availability!
257
+ begin
258
+ queue_registry("test")
259
+ rescue StandardError => e
260
+ raise Mimi::Messaging::ConnectionError, "SQS connection is not available: #{e}"
261
+ end
262
+ begin
263
+ topic_registry("test")
264
+ rescue StandardError => e
265
+ raise Mimi::Messaging::ConnectionError, "SNS connection is not available: #{e}"
266
+ end
267
+ end
268
+
269
+ # Creates a new queue
270
+ #
271
+ # @param queue_name [String] name of the topic to be created
272
+ # @return [String] a new queue URL
273
+ #
274
+ def create_queue(queue_name)
275
+ fqn = sqs_sns_converted_full_name(queue_name)
276
+ Mimi::Messaging.log "Creating a queue: #{fqn}"
277
+ result = sqs_client.create_queue(queue_name: fqn)
278
+ result.queue_url
279
+ rescue StandardError => e
280
+ raise Mimi::Messaging::ConnectionError, "Failed to create queue '#{queue_name}': #{e}"
281
+ end
282
+
194
283
  # Delivers a message to a queue with given URL.
195
284
  #
196
- # @param message [String]
197
285
  # @param queue_url [String]
198
- # @param headers [Hash<Symbol,String>]
286
+ # @param message [Mimi::Messaging::Message]
199
287
  #
200
- def deliver_message(message, queue_url, headers = {})
288
+ def deliver_message_queue(queue_url, message)
201
289
  raise ArgumentError, "Non-empty queue URL is expected" unless queue_url
290
+ unless message.is_a?(Mimi::Messaging::Message)
291
+ raise ArgumentError, "Message is expected as argument"
292
+ end
202
293
  Mimi::Messaging.log "Delivering message to: #{queue_url}"
203
294
  sqs_client.send_message(
204
295
  queue_url: queue_url,
205
- message_body: message,
206
- message_attributes: headers.map do |k, v|
296
+ message_body: serialize(message),
297
+ message_attributes: message.headers.map do |k, v|
207
298
  [k.to_s, { data_type: "String", string_value: v.to_s }]
208
299
  end.to_h
209
300
  )
@@ -219,7 +310,7 @@ module Mimi
219
310
  # @return [String,nil] queue URL
220
311
  #
221
312
  def queue_registry(queue_name)
222
- fqn = full_queue_name(queue_name)
313
+ fqn = sqs_sns_converted_full_name(queue_name)
223
314
  @queue_registry ||= {}
224
315
  @queue_registry[fqn] ||= begin
225
316
  result = sqs_client.get_queue_url(queue_name: fqn)
@@ -227,15 +318,22 @@ module Mimi
227
318
  end
228
319
  rescue Aws::SQS::Errors::NonExistentQueue
229
320
  nil
321
+ rescue StandardError => e
322
+ raise Mimi::Messaging::ConnectionError, "Failed to get queue url '#{queue_name}': #{e}"
230
323
  end
231
324
 
232
- # Converts a queue name to a fully qualified queue name
325
+ # Converts a topic or queue name to a fully qualified (with namespace)
326
+ # and in a valid SQS/SNS alphabet.
233
327
  #
234
- # @param queue_name [String]
235
- # @return [String]
328
+ # @param name [String] a mimi-messaging valid name
329
+ # @return [String] an SQS/SNS valid name
236
330
  #
237
- def full_queue_name(queue_name)
238
- "#{options[:mq_namespace]}#{queue_name}"
331
+ def sqs_sns_converted_full_name(name)
332
+ name = "#{options[:mq_namespace]}#{name}"
333
+ SQS_SNS_ALPHABET_MAP.each do |from, to|
334
+ name = name.gsub(from, to)
335
+ end
336
+ name
239
337
  end
240
338
 
241
339
  # Finds a queue URL for a queue with a given name,
@@ -244,7 +342,7 @@ module Mimi
244
342
  # @param queue_name [String]
245
343
  # @return [String] a queue URL
246
344
  #
247
- def find_queue(queue_name)
345
+ def find_queue!(queue_name)
248
346
  queue_registry(queue_name) || (
249
347
  raise Mimi::Messaging::ConnectionError,
250
348
  "Failed to find a queue with given name: '#{queue_name}'"
@@ -265,12 +363,13 @@ module Mimi
265
363
 
266
364
  # Returns the configured reply listener for this process
267
365
  #
268
- # @return [ReplyListener]
366
+ # @return [ReplyConsumer]
269
367
  #
270
- def reply_listener
271
- @reply_listener ||= begin
368
+ def reply_consumer
369
+ @reply_consumer ||= begin
272
370
  reply_queue_name = options[:mq_reply_queue_prefix] + SecureRandom.hex(8)
273
- Mimi::Messaging::SQS_SNS::ReplyListener.new(self, reply_queue_name)
371
+ reply_queue_url = create_queue(reply_queue_name)
372
+ Mimi::Messaging::SQS_SNS::ReplyConsumer.new(self, reply_queue_url)
274
373
  end
275
374
  end
276
375
 
@@ -282,6 +381,125 @@ module Mimi
282
381
  def deserialize_headers(message)
283
382
  message.message_attributes.to_h.map { |k, v| [k.to_sym, v.string_value] }.to_h
284
383
  end
384
+
385
+ # Lists all SNS topics by their ARNs.
386
+ #
387
+ # NOTE: iterates over all topics at SNS every time
388
+ #
389
+ # @return [Array<String>] array of topic ARNs
390
+ #
391
+ def sns_list_topics
392
+ result = []
393
+ next_token = nil
394
+ loop do
395
+ response = sns_client.list_topics(next_token: next_token)
396
+ result += response.topics.map(&:topic_arn)
397
+ next_token = response.next_token
398
+ break unless next_token
399
+ end
400
+ result
401
+ rescue StandardError => e
402
+ raise Mimi::Messaging::ConnectionError, "Failed to list topics: #{e}"
403
+ end
404
+
405
+ # Returns ARN of a topic with a given name.
406
+ #
407
+ # If the topic with given name does not exist, returns nil
408
+ #
409
+ # @param topic_name [String]
410
+ # @return [String,nil] topic ARN or nil, if not found
411
+ #
412
+ def topic_registry(topic_name)
413
+ fqn = sqs_sns_converted_full_name(topic_name)
414
+ @topic_registry ||= {}
415
+ @topic_registry[fqn] ||= begin
416
+ sns_list_topics.find { |topic_arn| topic_arn.split(":").last == fqn }
417
+ end
418
+ end
419
+
420
+ # Finds a topic ARN for a topic with a given name,
421
+ # or raises an error if the topic is not found.
422
+ #
423
+ # @param topic_name [String]
424
+ # @return [String] a topic ARN
425
+ #
426
+ def find_topic!(topic_name)
427
+ topic_registry(topic_name) || (
428
+ raise Mimi::Messaging::ConnectionError,
429
+ "Failed to find a topic with given name: '#{topic_name}'"
430
+ )
431
+ end
432
+
433
+ # Finds a topic ARN for a topic with given name.
434
+ #
435
+ # If an existing topic with this name is not found,
436
+ # the method will try to create a new one.
437
+ #
438
+ # @param topic_name [String]
439
+ # @return [String] a topic ARN
440
+ #
441
+ def find_or_create_topic(topic_name)
442
+ topic_registry(topic_name) || create_topic(topic_name)
443
+ end
444
+
445
+ # Creates a new topic
446
+ #
447
+ # @param topic_name [String] name of the topic to be created
448
+ # @return [String] a new topic ARN
449
+ #
450
+ def create_topic(topic_name)
451
+ fqn = sqs_sns_converted_full_name(topic_name)
452
+ Mimi::Messaging.log "Creating a topic: #{fqn}"
453
+ result = sns_client.create_topic(name: fqn)
454
+ result.topic_arn
455
+ rescue StandardError => e
456
+ raise Mimi::Messaging::ConnectionError, "Failed to create topic '#{topic_name}': #{e}"
457
+ end
458
+
459
+ # Subscribes an existing queue to an existing topic
460
+ #
461
+ # @param topic_arn [String]
462
+ # @param queue_url [String]
463
+ #
464
+ def subscribe_topic_queue(topic_arn, queue_url)
465
+ result = sqs_client.get_queue_attributes(
466
+ queue_url: queue_url, attribute_names: ["QueueArn"]
467
+ )
468
+ queue_arn = result.attributes["QueueArn"]
469
+ Mimi::Messaging.log "Subscribing queue to a topic: '#{topic_arn}'->'#{queue_url}'"
470
+ result = sns_client.subscribe(
471
+ topic_arn: topic_arn,
472
+ protocol: "sqs",
473
+ endpoint: queue_arn,
474
+ attributes: { "RawMessageDelivery" => "true" }
475
+ )
476
+ true
477
+ rescue StandardError => e
478
+ raise Mimi::Messaging::ConnectionError,
479
+ "Failed to subscribe queue to topic '#{topic_arn}'->'#{queue_url}': #{e}"
480
+ end
481
+
482
+ # Delivers a message to a topic with given ARN.
483
+ #
484
+ # @param topic_arn [String]
485
+ # @param message [Mimi::Messaging::Message]
486
+ #
487
+ def deliver_message_topic(topic_arn, message)
488
+ raise ArgumentError, "Non-empty topic ARN is expected" unless topic_arn
489
+ unless message.is_a?(Mimi::Messaging::Message)
490
+ raise ArgumentError, "Message is expected as argument"
491
+ end
492
+ Mimi::Messaging.log "Delivering message to: #{topic_arn}"
493
+ sns_client.publish(
494
+ topic_arn: topic_arn,
495
+ message: serialize(message),
496
+ message_attributes: message.headers.map do |k, v|
497
+ [k.to_s, { data_type: "String", string_value: v.to_s }]
498
+ end.to_h
499
+ )
500
+ rescue StandardError => e
501
+ raise Mimi::Messaging::ConnectionError, "Failed to deliver message to '#{topic_arn}': #{e}"
502
+ end
285
503
  end # class Adapter
286
504
  end # module SQS_SNS
287
505
  end # module Messaging
@@ -4,18 +4,17 @@ module Mimi
4
4
  module Messaging
5
5
  module SQS_SNS
6
6
  #
7
- # ReplyListener listens on a particular SQS queue for replies
7
+ # ReplyConsumer listens on a particular SQS queue for replies
8
8
  # and passes them to registered Queues (see Ruby ::Queue class).
9
9
  #
10
- class ReplyListener
11
- attr_reader :reply_queue_name, :reply_queue_url
10
+ class ReplyConsumer
11
+ attr_reader :reply_queue_url
12
12
 
13
- def initialize(adapter, reply_queue_name)
13
+ def initialize(adapter, reply_queue_url)
14
14
  @mutex = Mutex.new
15
15
  @queues = {}
16
- @reply_queue_name = reply_queue_name
17
16
  @adapter = adapter
18
- @reply_queue_url = adapter.create_queue(reply_queue_name)
17
+ @reply_queue_url = reply_queue_url
19
18
  @consumer = Consumer.new(adapter, reply_queue_url) do |message|
20
19
  dispatch_message(message)
21
20
  end
@@ -73,7 +72,7 @@ module Mimi
73
72
  Mimi::Messaging.log "reply listener failed to process reply: #{e}"
74
73
  # TODO: propagate exception to main thread?
75
74
  end
76
- end # class ReplyListener
75
+ end # class ReplyConsumer
77
76
  end # module SQS_SNS
78
77
  end # module Messaging
79
78
  end # module Mimi
@@ -3,7 +3,7 @@
3
3
  module Mimi
4
4
  module Messaging
5
5
  module SQS_SNS
6
- VERSION = "0.1.4"
6
+ VERSION = "0.4.1"
7
7
  end
8
8
  end
9
9
  end
@@ -33,7 +33,7 @@ Gem::Specification.new do |spec|
33
33
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
34
34
  spec.require_paths = ["lib"]
35
35
 
36
- spec.add_dependency "mimi-messaging", "~> 1.0"
36
+ spec.add_dependency "mimi-messaging", "~> 1.2"
37
37
  spec.add_dependency "aws-sdk-sqs", "~> 1.22"
38
38
  spec.add_dependency "aws-sdk-sns", "~> 1.19"
39
39
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mimi-messaging-sqs_sns
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.4
4
+ version: 0.4.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alex Kukushkin
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-10-06 00:00:00.000000000 Z
11
+ date: 2019-10-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: mimi-messaging
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '1.0'
19
+ version: '1.2'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '1.0'
26
+ version: '1.2'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: aws-sdk-sqs
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -125,12 +125,14 @@ files:
125
125
  - Rakefile
126
126
  - bin/console
127
127
  - bin/setup
128
+ - examples/event.rb
128
129
  - examples/query.rb
129
130
  - examples/responder.rb
131
+ - examples/subscriber.rb
130
132
  - lib/mimi/messaging/sqs_sns.rb
131
133
  - lib/mimi/messaging/sqs_sns/adapter.rb
132
134
  - lib/mimi/messaging/sqs_sns/consumer.rb
133
- - lib/mimi/messaging/sqs_sns/reply_listener.rb
135
+ - lib/mimi/messaging/sqs_sns/reply_consumer.rb
134
136
  - lib/mimi/messaging/sqs_sns/version.rb
135
137
  - mimi-messaging-sqs_sns.gemspec
136
138
  homepage: https://github.com/kukushkin/mimi-messaging-sqs_sns