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 +4 -4
- data/README.md +1 -0
- data/examples/event.rb +34 -0
- data/examples/query.rb +3 -1
- data/examples/responder.rb +3 -1
- data/examples/subscriber.rb +55 -0
- data/lib/mimi/messaging/sqs_sns.rb +1 -1
- data/lib/mimi/messaging/sqs_sns/adapter.rb +274 -56
- data/lib/mimi/messaging/sqs_sns/{reply_listener.rb → reply_consumer.rb} +6 -7
- data/lib/mimi/messaging/sqs_sns/version.rb +1 -1
- data/mimi-messaging-sqs_sns.gemspec +1 -1
- metadata +7 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f0919e38a4926e465f4b2017b98ce6b621a41f5b
|
4
|
+
data.tar.gz: f356e4552744757c4e6c77eb3eca359839d7d834
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b45642dc502c2bf8e5debf230460594abef63fde87f4f029d5583fdb942965465581be65e0eb050781d2780453f1843644a9c0539849ef1e562062b3370cd47b
|
7
|
+
data.tar.gz: b5251902e1ac67cad8731f05fdd5a0fe1a7e32db70f4aa62a9f7a29bdb0a2de0c6ff56e82c0fa5b01fabb9d7438fb0411aa62390daf1afe0ceed1ce72c223562
|
data/README.md
CHANGED
data/examples/event.rb
ADDED
@@ -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
|
data/examples/query.rb
CHANGED
@@ -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
|
|
data/examples/responder.rb
CHANGED
@@ -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
|
+
|
@@ -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
|
-
|
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
|
-
@
|
68
|
-
@
|
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
|
-
|
86
|
-
queue_url = find_queue(queue_name)
|
87
|
-
|
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
|
-
|
102
|
-
queue_url = find_queue(queue_name)
|
131
|
+
queue_url = find_queue!(queue_name)
|
103
132
|
request_id = SecureRandom.hex(8)
|
104
|
-
reply_queue =
|
133
|
+
reply_queue = reply_consumer.register_request_id(request_id)
|
105
134
|
|
106
|
-
|
107
|
-
|
108
|
-
queue_url,
|
135
|
+
message = Mimi::Messaging::Message.new(
|
136
|
+
message,
|
109
137
|
__method: method_name,
|
110
|
-
__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
|
-
|
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 =
|
144
|
-
|
145
|
-
|
146
|
-
|
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,
|
149
|
-
|
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,
|
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
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
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
|
286
|
+
# @param message [Mimi::Messaging::Message]
|
199
287
|
#
|
200
|
-
def
|
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 =
|
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
|
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
|
235
|
-
# @return [String]
|
328
|
+
# @param name [String] a mimi-messaging valid name
|
329
|
+
# @return [String] an SQS/SNS valid name
|
236
330
|
#
|
237
|
-
def
|
238
|
-
"#{options[:mq_namespace]}#{
|
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 [
|
366
|
+
# @return [ReplyConsumer]
|
269
367
|
#
|
270
|
-
def
|
271
|
-
@
|
368
|
+
def reply_consumer
|
369
|
+
@reply_consumer ||= begin
|
272
370
|
reply_queue_name = options[:mq_reply_queue_prefix] + SecureRandom.hex(8)
|
273
|
-
|
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
|
-
#
|
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
|
11
|
-
attr_reader :
|
10
|
+
class ReplyConsumer
|
11
|
+
attr_reader :reply_queue_url
|
12
12
|
|
13
|
-
def initialize(adapter,
|
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 =
|
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
|
75
|
+
end # class ReplyConsumer
|
77
76
|
end # module SQS_SNS
|
78
77
|
end # module Messaging
|
79
78
|
end # module Mimi
|
@@ -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.
|
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
|
+
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-
|
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.
|
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.
|
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/
|
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
|