sqs_simplify 0.2.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 +19 -0
- data/.rspec +3 -0
- data/.travis.yml +6 -0
- data/CHANGELOG.md +14 -0
- data/Gemfile +14 -0
- data/LICENSE.txt +21 -0
- data/README.md +383 -0
- data/README.pt-br.md +384 -0
- data/Rakefile +8 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/lib/sqs_simplify/base.rb +212 -0
- data/lib/sqs_simplify/client.rb +34 -0
- data/lib/sqs_simplify/command.rb +89 -0
- data/lib/sqs_simplify/consumer.rb +154 -0
- data/lib/sqs_simplify/dead_queue.rb +65 -0
- data/lib/sqs_simplify/errors/execution_expired.rb +11 -0
- data/lib/sqs_simplify/errors/non_existent_queue.rb +11 -0
- data/lib/sqs_simplify/errors/reserved_method_name.rb +11 -0
- data/lib/sqs_simplify/execution_hook.rb +44 -0
- data/lib/sqs_simplify/faker_client.rb +78 -0
- data/lib/sqs_simplify/job.rb +193 -0
- data/lib/sqs_simplify/message.rb +24 -0
- data/lib/sqs_simplify/scheduler.rb +72 -0
- data/lib/sqs_simplify/version.rb +5 -0
- data/lib/sqs_simplify/worker.rb +99 -0
- data/lib/sqs_simplify.rb +68 -0
- data/rubocop.yml +3 -0
- data/sqs_simplify.gemspec +34 -0
- metadata +112 -0
data/README.pt-br.md
ADDED
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
# SqsSimplify
|
|
2
|
+
|
|
3
|
+
[](https://badge.fury.io/rb/sqs_simplify)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
[](https://www.ruby-lang.org)
|
|
6
|
+
|
|
7
|
+
Uma DSL Ruby de alto nível sobre o `aws-sdk-sqs` para produzir e consumir mensagens do AWS SQS com o mínimo de boilerplate.
|
|
8
|
+
|
|
9
|
+
Possui 3 papéis principais:
|
|
10
|
+
* **SqsSimplify::Scheduler**: Envia mensagens para uma fila.
|
|
11
|
+
* **SqsSimplify::Consumer**: Consome mensagens de uma fila.
|
|
12
|
+
* **SqsSimplify::Job**: Envia e consome mensagens de uma fila.
|
|
13
|
+
|
|
14
|
+
> 🇺🇸 An English version of this document is available at [README.md](README.md).
|
|
15
|
+
|
|
16
|
+
## Sumário
|
|
17
|
+
|
|
18
|
+
* [Requisitos](#requisitos)
|
|
19
|
+
* [Instalação](#instalação)
|
|
20
|
+
* [Como usar](#como-usar)
|
|
21
|
+
* [Configuração Inicial](#1-configuração-inicial)
|
|
22
|
+
* [Scheduler](#2-scheduler)
|
|
23
|
+
* [Consumer](#3-consumer)
|
|
24
|
+
* [Job](#4-job)
|
|
25
|
+
* [Configurações](#configurações)
|
|
26
|
+
* [Configuração Global](#1-configuração-global)
|
|
27
|
+
* [Hooks](#2-hooks)
|
|
28
|
+
* [Processo Background](#processo-background)
|
|
29
|
+
* [Recursos avançados](#recursos-avançados)
|
|
30
|
+
* [Desenvolvimento](#desenvolvimento)
|
|
31
|
+
* [Contribuição](#contribuição)
|
|
32
|
+
* [Licença](#licença)
|
|
33
|
+
|
|
34
|
+
## Requisitos
|
|
35
|
+
|
|
36
|
+
* Ruby `>= 3.0.0`
|
|
37
|
+
* Dependências de runtime (instaladas automaticamente junto com a gem):
|
|
38
|
+
* [`aws-sdk-sqs`](https://rubygems.org/gems/aws-sdk-sqs) `~> 1.116`
|
|
39
|
+
* [`parallel`](https://rubygems.org/gems/parallel) `~> 2.1`
|
|
40
|
+
|
|
41
|
+
Você também precisa de credenciais AWS válidas com acesso ao SQS.
|
|
42
|
+
|
|
43
|
+
## Instalação
|
|
44
|
+
|
|
45
|
+
Adicione esta linha na Gemfile da aplicação:
|
|
46
|
+
|
|
47
|
+
```ruby
|
|
48
|
+
gem 'sqs_simplify'
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
E execute:
|
|
52
|
+
|
|
53
|
+
$ bundle install
|
|
54
|
+
|
|
55
|
+
Ou instale você mesmo com:
|
|
56
|
+
|
|
57
|
+
$ gem install sqs_simplify
|
|
58
|
+
___
|
|
59
|
+
|
|
60
|
+
## Como usar
|
|
61
|
+
### 1. Configuração Inicial
|
|
62
|
+
|
|
63
|
+
Crie um arquivo de configuração para ser carregado na inicialização da aplicação.
|
|
64
|
+
|
|
65
|
+
Exemplo: *sqs_simplify.rb*
|
|
66
|
+
|
|
67
|
+
Se for uma aplicação **Rails**, crie em *config/initializers/sqs_simplify.rb*.
|
|
68
|
+
|
|
69
|
+
Neste arquivo você pode configurar as credenciais da AWS e outras customizações.
|
|
70
|
+
|
|
71
|
+
```ruby
|
|
72
|
+
# sqs_simplify.rb
|
|
73
|
+
|
|
74
|
+
SqsSimplify.configure do |config|
|
|
75
|
+
config.access_key_id = ENV['AWS_ACCESS_KEY_ID']
|
|
76
|
+
config.secret_access_key = ENV['AWS_SECRET_ACCESS_KEY']
|
|
77
|
+
config.region = 'us-east-1'
|
|
78
|
+
|
|
79
|
+
config.queue_prefix = Rails.env # optional
|
|
80
|
+
config.queue_suffix = 'my_application_name' # optional
|
|
81
|
+
end
|
|
82
|
+
```
|
|
83
|
+
___
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
### 2. Scheduler
|
|
87
|
+
|
|
88
|
+
O componente Scheduler é responsável por enviar mensagens para a fila SQS.
|
|
89
|
+
|
|
90
|
+
Seu foco principal é na utilização de uma fila como barramento entre duas aplicações distintas.
|
|
91
|
+
|
|
92
|
+
Por exemplo: a aplicação **A** tem um Scheduler que envia mensagem para a fila SQS, mas quem vai consumir esta mensagem será a aplicação **B**.
|
|
93
|
+
|
|
94
|
+
```ruby
|
|
95
|
+
# app/jobs/my_scheduler
|
|
96
|
+
|
|
97
|
+
class MyScheduler < SqsSimplify::Scheduler
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
# app/model/wheel_factory.rb
|
|
102
|
+
|
|
103
|
+
class WheelFactory
|
|
104
|
+
def send_now
|
|
105
|
+
message = { wheels: ['back_wheel', 'front_wheel'], type: 'motorcycle' }
|
|
106
|
+
MyScheduler.send_message(message: message)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def send_later(delay)
|
|
110
|
+
message = { wheels: ['back_wheel', 'front_wheel'], type: 'motorcycle' }
|
|
111
|
+
MyScheduler.send_message(message: message, after: delay)
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
wheel_factory = WheelFactory.new
|
|
117
|
+
|
|
118
|
+
# run now
|
|
119
|
+
wheel_factory.send_now # "8685d169-f4a0-476b-b970-39ee055f957b"
|
|
120
|
+
|
|
121
|
+
# run after 2 minutes
|
|
122
|
+
wheel_factory.send_later(120) # "5ebd6a74-8571-43e2-a9c8-7866b7598765"
|
|
123
|
+
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
#### `group_id`
|
|
127
|
+
|
|
128
|
+
O parâmetro `group_id` é enviado ao SQS como `message_group_id`. Informe-o em
|
|
129
|
+
`send_message`:
|
|
130
|
+
|
|
131
|
+
```ruby
|
|
132
|
+
MyScheduler.send_message(message: message, group_id: 'tenant-123')
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Seu significado depende do tipo de fila:
|
|
136
|
+
|
|
137
|
+
* **Filas FIFO**: o SQS exige `message_group_id`. Mensagens com o mesmo
|
|
138
|
+
`group_id` são processadas em ordem (FIFO).
|
|
139
|
+
* **Filas standard**: o `message_group_id` é usado como identificador de
|
|
140
|
+
*tenant* para o recurso de [fair queues](https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-fair-queues.html),
|
|
141
|
+
que mitiga o impacto de *noisy neighbor* em filas multi-tenant. Aqui ele
|
|
142
|
+
**não** garante ordenação — serve apenas para agrupar mensagens por tenant.
|
|
143
|
+
|
|
144
|
+
Se omitido (`nil`), nenhum `message_group_id` é enviado.
|
|
145
|
+
|
|
146
|
+
### 3. Consumer
|
|
147
|
+
|
|
148
|
+
O componente Consumer é responsável por consumir mensagens da fila SQS.
|
|
149
|
+
|
|
150
|
+
Seu foco principal também é na utilização de uma fila como barramento entre duas aplicações distintas.
|
|
151
|
+
|
|
152
|
+
Por exemplo: a aplicação **B** tem um Consumer que solicita mensagens da fila SQS que foram enviadas pela aplicação **A**.
|
|
153
|
+
|
|
154
|
+
```ruby
|
|
155
|
+
# app/jobs/motorcycle_assembler.rb
|
|
156
|
+
|
|
157
|
+
class MotorcycleAssembler < SqsSimplify::Consumer
|
|
158
|
+
|
|
159
|
+
def perform
|
|
160
|
+
# your logic here
|
|
161
|
+
# your object has "message" method with data of sqs_message
|
|
162
|
+
p message # {:wheels=>["back_wheel", "front_wheel"], :type=>"motorcycle"}
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
### 4. Job
|
|
171
|
+
|
|
172
|
+
O componente Job é responsável por enviar e consumir mensagens da fila SQS dentro da mesma aplicação.
|
|
173
|
+
|
|
174
|
+
Diferente dos outros componentes, seu foco **não** é na utilização de uma fila como barramento.
|
|
175
|
+
|
|
176
|
+
Por exemplo: sua aplicação tem uma classe **Report** que gera um relatório.
|
|
177
|
+
Este processamento demora muito para ser executado.
|
|
178
|
+
Então você pode agendar a execução para mais tarde.
|
|
179
|
+
|
|
180
|
+
O método de negócio **deve** se chamar `perform`.
|
|
181
|
+
|
|
182
|
+
```ruby
|
|
183
|
+
# app/jobs/report.rb
|
|
184
|
+
|
|
185
|
+
class Report < SqsSimplify::Job
|
|
186
|
+
|
|
187
|
+
def perform(list)
|
|
188
|
+
# your logic here
|
|
189
|
+
PersistReport.save(list)
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
list = # many data
|
|
195
|
+
|
|
196
|
+
# enfileira para execução posterior
|
|
197
|
+
Report.perform_later(list) # "76107a55-43d9-4f2e-b449-02c329a51692"
|
|
198
|
+
|
|
199
|
+
# enfileira com atraso de 180 segundos
|
|
200
|
+
Report.new_job(after: 180).perform_later(list) # "be6837d5-c11f-495c-a03e-cb093011f1d0"
|
|
201
|
+
|
|
202
|
+
# executa inline, sem enfileirar
|
|
203
|
+
# atenção, não será agendado
|
|
204
|
+
Report.perform(list) # :executed
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
___
|
|
208
|
+
|
|
209
|
+
## Configurações
|
|
210
|
+
|
|
211
|
+
### 1. Configuração Global
|
|
212
|
+
|
|
213
|
+
#### SqsSimplify
|
|
214
|
+
Toda configuração feita na classe SqsSimplify será aplicada em todos os componentes.
|
|
215
|
+
|
|
216
|
+
Exemplo:
|
|
217
|
+
|
|
218
|
+
```ruby
|
|
219
|
+
# sqs_simplify.rb
|
|
220
|
+
|
|
221
|
+
SqsSimplify.configure do |config|
|
|
222
|
+
config.access_key_id = ENV['AWS_ACCESS_KEY_ID']
|
|
223
|
+
config.secret_access_key = ENV['AWS_SECRET_ACCESS_KEY']
|
|
224
|
+
config.region = 'us-east-2'
|
|
225
|
+
|
|
226
|
+
config.queue_prefix = 'production'
|
|
227
|
+
end
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
Com esta configuração todos os componentes — Scheduler, Consumer e Job — terão a mesma configuração.
|
|
231
|
+
|
|
232
|
+
Todos terão acesso às filas SQS de *us-east-2*
|
|
233
|
+
|
|
234
|
+
Todos terão o prefixo de *production*
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
```ruby
|
|
238
|
+
|
|
239
|
+
class MyScheduler < SqsSimplify::Scheduler
|
|
240
|
+
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
class MyConsumer < SqsSimplify::Consumer
|
|
244
|
+
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
class MyJob < SqsSimplify::Job
|
|
248
|
+
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
# same prefix
|
|
252
|
+
MyScheduler.queue_name # "production_my_scheduler"
|
|
253
|
+
MyConsumer.queue_name # "production_my_consumer"
|
|
254
|
+
MyJob.queue_name # "production_my_job"
|
|
255
|
+
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### 2. Hooks
|
|
259
|
+
|
|
260
|
+
**resolver_exception:** É invocado sempre que ocorrer uma **Exception** na sua aplicação.
|
|
261
|
+
Disponibiliza dois parâmetros:
|
|
262
|
+
* o **primeiro parâmetro** é uma **Exception**.
|
|
263
|
+
* o **segundo parâmetro** pode variar de acordo com o componente e onde ocorreu a **Exception**.
|
|
264
|
+
|
|
265
|
+
**message_not_deleted:** É invocado quando um Job ou Consumer pega uma mensagem da fila SQS e não consegue apagá-la.
|
|
266
|
+
Há dois principais motivos para isso ocorrer:
|
|
267
|
+
* **Exception**: quando ocorre uma **Exception**. Obs: também será invocado o hook **resolver_exception**.
|
|
268
|
+
* **Default visibility timeout**: a mensagem não foi processada no tempo definido.
|
|
269
|
+
|
|
270
|
+
```ruby
|
|
271
|
+
# sqs_simplify.rb
|
|
272
|
+
|
|
273
|
+
SqsSimplify.configure do |config|
|
|
274
|
+
config.hooks.resolver_exception do |exception, args|
|
|
275
|
+
logger.info "Exception => #{exception.message}, args => #{args}"
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
config.hooks.message_not_deleted do |consumer|
|
|
279
|
+
logger.info "Consumer => #{consumer}"
|
|
280
|
+
end
|
|
281
|
+
end
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
___
|
|
285
|
+
|
|
286
|
+
## Processo Background
|
|
287
|
+
### 1. Configuração
|
|
288
|
+
Para rodar o processo de consumo deve criar um arquivo de script.
|
|
289
|
+
O método `run` roda um loop em foreground que consome as filas até receber
|
|
290
|
+
`SIGINT` (Ctrl+C) — não daemoniza o processo.
|
|
291
|
+
Exemplo de um arquivo de script nomeado de *sqs_simplify*
|
|
292
|
+
|
|
293
|
+
```ruby
|
|
294
|
+
#!/usr/bin/env ruby
|
|
295
|
+
|
|
296
|
+
require 'sqs_simplify/command'
|
|
297
|
+
SqsSimplify::Command.new(ARGV).run
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
Para projeto Rails você pode carregar a aplicação antes da invocação da GEM.
|
|
301
|
+
Exemplo de um arquivo de script nomeado de *bin/sqs_simplify*
|
|
302
|
+
```ruby
|
|
303
|
+
#!/usr/bin/env ruby
|
|
304
|
+
|
|
305
|
+
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'config', 'environment'))
|
|
306
|
+
require 'sqs_simplify/command'
|
|
307
|
+
SqsSimplify::Command.new(ARGV).run
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
E deve dar permissão de execução.
|
|
311
|
+
````bash
|
|
312
|
+
$ chmod +x sqs_simplify
|
|
313
|
+
````
|
|
314
|
+
|
|
315
|
+
### 2. Comandos
|
|
316
|
+
Para ver as opções do comando, execute no diretório do arquivo:
|
|
317
|
+
|
|
318
|
+
````bash
|
|
319
|
+
$ sqs_simplify -h
|
|
320
|
+
Usage: sqs_simplify [options]
|
|
321
|
+
-h, --help Show help
|
|
322
|
+
-n, --number_of_workers=workers Number of unique workers to spawn
|
|
323
|
+
-e, --environment=environment Environment
|
|
324
|
+
--queues=queues queues that will be consumed
|
|
325
|
+
--priority with priority in the queues
|
|
326
|
+
-f, --fork parallel in processes
|
|
327
|
+
-t, --thread parallel in threads
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
````
|
|
331
|
+
|
|
332
|
+
___
|
|
333
|
+
|
|
334
|
+
## Recursos avançados
|
|
335
|
+
|
|
336
|
+
Além do básico apresentado acima, a gem também oferece:
|
|
337
|
+
|
|
338
|
+
* **`map_queue(nickname, &block)`** — roteia um único Scheduler para filas
|
|
339
|
+
alternativas dinamicamente (`SqsSimplify::Scheduler`).
|
|
340
|
+
* **Sobrescrita de destino por chamada** — passe `queue_url:` para `send_message`
|
|
341
|
+
para enviar uma mensagem a uma fila específica no momento da chamada.
|
|
342
|
+
* **DSL `set` por classe** — sobrescreve configurações por classe, como o nome da
|
|
343
|
+
fila, o visibility timeout, a serialização (`dump_message`/`load_message`) e mais,
|
|
344
|
+
por exemplo `set :queue_name, 'custom_name'`.
|
|
345
|
+
* **Dead-letter queues automáticas** — cada fila ganha uma `<nome>_dead`
|
|
346
|
+
pareada como dead-letter queue.
|
|
347
|
+
* **Hooks adicionais** — além de `resolver_exception` e `message_not_deleted`, o
|
|
348
|
+
pipeline também suporta os hooks `before`/`after` (`:each` / `:all`) e `around`.
|
|
349
|
+
* **Testes sem AWS** — defina `config.faker = true` para usar o `FakerClient` em
|
|
350
|
+
memória, ou `config.stub_responses = true` para stubar o cliente `aws-sdk-sqs`.
|
|
351
|
+
|
|
352
|
+
## Desenvolvimento
|
|
353
|
+
|
|
354
|
+
Após clonar o repositório, instale as dependências e rode a suíte de testes:
|
|
355
|
+
|
|
356
|
+
```bash
|
|
357
|
+
bin/setup # instala as dependências
|
|
358
|
+
bundle exec rake spec # roda a suíte de testes completa
|
|
359
|
+
bundle exec rubocop # lint
|
|
360
|
+
bin/console # prompt interativo para experimentar
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
Os testes rodam contra um cliente SQS falso em memória, então nenhum acesso real à
|
|
364
|
+
AWS é necessário.
|
|
365
|
+
|
|
366
|
+
Para instalar esta gem na sua máquina local, rode `bundle exec rake install`. Para
|
|
367
|
+
publicar uma nova versão, atualize o número da versão em `lib/sqs_simplify/version.rb`
|
|
368
|
+
e então rode `bundle exec rake release`.
|
|
369
|
+
|
|
370
|
+
## Contribuição
|
|
371
|
+
|
|
372
|
+
Relatos de bugs e pull requests são bem-vindos no GitHub em
|
|
373
|
+
https://github.com/ralphsbaesso/sqs_simplify. Para contribuir:
|
|
374
|
+
|
|
375
|
+
1. Faça um fork do repositório.
|
|
376
|
+
2. Crie um branch de feature (`git checkout -b minha-feature`).
|
|
377
|
+
3. Faça commit das suas alterações e garanta que os testes (`bundle exec rake spec`)
|
|
378
|
+
e o linter (`bundle exec rubocop`) passem.
|
|
379
|
+
4. Abra um pull request.
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
## Licença
|
|
383
|
+
|
|
384
|
+
A gem está disponível como código aberto sob os termos da [Licença MIT](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
data/bin/console
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require 'bundler/setup'
|
|
5
|
+
require 'sqs_simplify'
|
|
6
|
+
|
|
7
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
|
8
|
+
# with your gem easier. You can also use a different console, if you like.
|
|
9
|
+
|
|
10
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
|
11
|
+
# require "pry"
|
|
12
|
+
# Pry.start
|
|
13
|
+
|
|
14
|
+
require 'irb'
|
|
15
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'date'
|
|
4
|
+
|
|
5
|
+
module SqsSimplify
|
|
6
|
+
class Base
|
|
7
|
+
include SqsSimplify::Client
|
|
8
|
+
|
|
9
|
+
VISIBILITY_TIMEOUT = 10 * 60
|
|
10
|
+
MESSAGE_RETENTION_PERIOD = 14 * 24 * 60 * 60
|
|
11
|
+
DELAY_SECONDS = 0
|
|
12
|
+
|
|
13
|
+
protected
|
|
14
|
+
|
|
15
|
+
def dump_message(value)
|
|
16
|
+
self.class.dump_message value
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def load_message(value)
|
|
20
|
+
self.class.load_message value
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
class << self
|
|
24
|
+
def set(key, value)
|
|
25
|
+
settings[key.to_sym] = value
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def queue_name
|
|
29
|
+
(settings[:queue_name] || to_underscore(name)).to_s
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def queue_full_name
|
|
33
|
+
build_queue_full_name
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def queue_url
|
|
37
|
+
@queue_url ||= client.get_queue_url(queue_name: queue_full_name).queue_url
|
|
38
|
+
rescue Aws::SQS::Errors::NonExistentQueue
|
|
39
|
+
raise SqsSimplify::Errors::NonExistentQueue, queue_full_name
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def dead_queue
|
|
43
|
+
@dead_queue ||= build_dead_queue_class
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def count_messages
|
|
47
|
+
approximate_number_of_messages(load: true)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def approximate_number_of_messages(load: false)
|
|
51
|
+
queue_info(load: load)['ApproximateNumberOfMessages'].to_i
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def queue_info(load: false)
|
|
55
|
+
@queue_info = nil if load
|
|
56
|
+
@queue_info ||= client.get_queue_attributes(queue_url: queue_url, attribute_names: ['All']).attributes
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def queue_arn(load: false)
|
|
60
|
+
queue_info(load: load)['QueueArn']
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def approximate_number_of_messages_not_visible(load: false)
|
|
64
|
+
queue_info(load: load)['ApproximateNumberOfMessagesNotVisible'].to_i
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def created_at(load: false)
|
|
68
|
+
DateTime.strptime(queue_info(load: load)['CreatedTimestamp'], '%s')
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def updated_at(load: false)
|
|
72
|
+
DateTime.strptime(queue_info(load: load)['LastModifiedTimestamp'], '%s')
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def visibility_timeout(load: false)
|
|
76
|
+
queue_info(load: load)['VisibilityTimeout'].to_i
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def message_retention_period(load: false)
|
|
80
|
+
queue_info(load: load)['MessageRetentionPeriod'].to_i
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def max_receive_count
|
|
84
|
+
@max_receive_count ||= build_max_receive_count
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def dump_message(value)
|
|
88
|
+
dump_message = settings[:dump_message] || :to_json
|
|
89
|
+
|
|
90
|
+
case dump_message
|
|
91
|
+
when Proc
|
|
92
|
+
dump_message.call value
|
|
93
|
+
when :to_json
|
|
94
|
+
value.to_json
|
|
95
|
+
else
|
|
96
|
+
value.to_s
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def load_message(message)
|
|
101
|
+
body = message.body
|
|
102
|
+
load_message = settings[:load_message] || :to_json
|
|
103
|
+
|
|
104
|
+
case load_message
|
|
105
|
+
when Proc
|
|
106
|
+
load_message.call body
|
|
107
|
+
when :to_json
|
|
108
|
+
JSON.parse(body)
|
|
109
|
+
else
|
|
110
|
+
body
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def settings
|
|
115
|
+
@settings ||= {}
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def create_queue_and_dead_queue
|
|
119
|
+
create_queue
|
|
120
|
+
dead_queue.create_queue
|
|
121
|
+
true
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def create_queue
|
|
125
|
+
queue = find_queue_by_name(queue_full_name)
|
|
126
|
+
return queue if queue
|
|
127
|
+
|
|
128
|
+
client.create_queue(
|
|
129
|
+
queue_name: queue_full_name,
|
|
130
|
+
attributes: default_attributes
|
|
131
|
+
)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def find_queue_by_name(name)
|
|
135
|
+
client.get_queue_url(queue_name: name)
|
|
136
|
+
rescue Aws::SQS::Errors::NonExistentQueue
|
|
137
|
+
nil
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def update_attributes(**attributes)
|
|
141
|
+
attributes = attributes.map { |att| att.map(&:to_s) }.to_h
|
|
142
|
+
client.set_queue_attributes(
|
|
143
|
+
queue_url: queue_url,
|
|
144
|
+
attributes: attributes
|
|
145
|
+
)
|
|
146
|
+
true
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
private
|
|
150
|
+
|
|
151
|
+
def build_queue_full_name
|
|
152
|
+
[
|
|
153
|
+
settings[:queue_prefix] || SqsSimplify.settings.queue_prefix,
|
|
154
|
+
queue_name,
|
|
155
|
+
settings[:queue_suffix] || SqsSimplify.settings.queue_suffix
|
|
156
|
+
].reject { |value| value.nil? || value.empty? }
|
|
157
|
+
.join('_')
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def build_dead_queue_class
|
|
161
|
+
class_name = "::#{name}::DeadQueue"
|
|
162
|
+
|
|
163
|
+
class_eval <<~M, __FILE__, __LINE__ + 1
|
|
164
|
+
class #{class_name} < SqsSimplify::DeadQueue; end
|
|
165
|
+
M
|
|
166
|
+
|
|
167
|
+
dead_queue_class = const_get class_name
|
|
168
|
+
dead_queue_class.set :queue_name, "#{queue_name}_dead"
|
|
169
|
+
dead_queue_class
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def build_max_receive_count
|
|
173
|
+
value = settings[:max_receive_count].to_i
|
|
174
|
+
value.positive? ? value : 1
|
|
175
|
+
rescue StandardError
|
|
176
|
+
1
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def default_attributes
|
|
180
|
+
visibility_timeout = settings[:visibility_timeout] || self::VISIBILITY_TIMEOUT # Should be between 0 seconds and 12 hours.
|
|
181
|
+
message_retention_period = settings[:message_retention_period] || self::MESSAGE_RETENTION_PERIOD # retention, Should be between 1 minute and 14 days.
|
|
182
|
+
delay_seconds = settings[:delay_seconds] || self::DELAY_SECONDS
|
|
183
|
+
|
|
184
|
+
{
|
|
185
|
+
VisibilityTimeout: visibility_timeout.to_s,
|
|
186
|
+
MessageRetentionPeriod: message_retention_period.to_s,
|
|
187
|
+
DelaySeconds: delay_seconds.to_s
|
|
188
|
+
}
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def to_underscore(value)
|
|
192
|
+
value.gsub(/::/, '_')
|
|
193
|
+
.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
|
194
|
+
.gsub(/([a-z\d])([A-Z])/, '\1_\2')
|
|
195
|
+
.tr('-', '_')
|
|
196
|
+
.downcase
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def inherited(sub)
|
|
200
|
+
super
|
|
201
|
+
return if sub.name.nil?
|
|
202
|
+
return if %w[SqsSimplify::Job SqsSimplify::Consumer SqsSimplify::Scheduler].include? sub.name
|
|
203
|
+
|
|
204
|
+
if sub < SqsSimplify::Consumer || sub < SqsSimplify::Job
|
|
205
|
+
SqsSimplify.consumers << sub
|
|
206
|
+
elsif sub < SqsSimplify::Scheduler
|
|
207
|
+
SqsSimplify.schedulers << sub
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SqsSimplify
|
|
4
|
+
module Client
|
|
5
|
+
protected
|
|
6
|
+
|
|
7
|
+
def client
|
|
8
|
+
self.class.client
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
module ClassMethods
|
|
12
|
+
def client
|
|
13
|
+
@client ||= build_client
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
def build_client
|
|
19
|
+
args = {
|
|
20
|
+
access_key_id: settings[:access_key_id] || SqsSimplify.settings.access_key_id,
|
|
21
|
+
secret_access_key: settings[:secret_access_key] || SqsSimplify.settings.secret_access_key,
|
|
22
|
+
region: settings[:region] || SqsSimplify.settings.region,
|
|
23
|
+
stub_responses: SqsSimplify.settings.stub_responses
|
|
24
|
+
}.select { |_k, v| v }
|
|
25
|
+
|
|
26
|
+
SqsSimplify.settings.faker ? SqsSimplify::FakerClient.new : Aws::SQS::Client.new(args)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def self.included(base)
|
|
31
|
+
base.extend ClassMethods
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|