shoryuken-template 0.3.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.
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'shoryuken'
4
+
5
+ module Shoryuken
6
+ module Template
7
+ module Workers
8
+ # Worker base com funcionalidades comuns
9
+ module BaseWorker
10
+ def self.included(base)
11
+ base.include Shoryuken::Worker
12
+ base.extend ClassMethods
13
+ end
14
+
15
+ module ClassMethods
16
+ # Define configurações do worker
17
+ def worker_options(queue:, auto_delete: true, body_parser: :json, **options)
18
+ shoryuken_options(
19
+ queue: queue,
20
+ auto_delete: auto_delete,
21
+ body_parser: body_parser,
22
+ **options
23
+ )
24
+ end
25
+ end
26
+
27
+ # Nome do worker para logs e métricas
28
+ def worker_name
29
+ self.class.name
30
+ end
31
+
32
+ protected
33
+
34
+ # Extrai informações da mensagem SQS
35
+ def message_attributes(sqs_msg)
36
+ return {} unless sqs_msg.respond_to?(:message_attributes)
37
+
38
+ sqs_msg.message_attributes.transform_values do |attr|
39
+ attr.respond_to?(:string_value) ? attr.string_value : attr
40
+ end
41
+ end
42
+
43
+ # Deleta mensagem manualmente
44
+ def delete_message(sqs_msg)
45
+ sqs_msg.delete if sqs_msg.respond_to?(:delete)
46
+ end
47
+
48
+ # Muda visibilidade da mensagem
49
+ def change_message_visibility(sqs_msg, timeout)
50
+ sqs_msg.change_visibility(visibility_timeout: timeout) if sqs_msg.respond_to?(:change_visibility)
51
+ end
52
+
53
+ # Log estruturado com contexto do worker
54
+ def log_info(message, **context)
55
+ Shoryuken::Template.logger.info(message, worker: worker_name, **context)
56
+ end
57
+
58
+ def log_error(message, **context)
59
+ Shoryuken::Template.logger.error(message, worker: worker_name, **context)
60
+ end
61
+
62
+ def log_warn(message, **context)
63
+ Shoryuken::Template.logger.warn(message, worker: worker_name, **context)
64
+ end
65
+
66
+ def log_debug(message, **context)
67
+ Shoryuken::Template.logger.debug(message, worker: worker_name, **context)
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shoryuken
4
+ module Template
5
+ module Workers
6
+ # Worker FIFO - processa mensagens sequencialmente de filas FIFO
7
+ # Garante processamento ordenado e sem concorrência
8
+ #
9
+ # Exemplo de uso:
10
+ # class MyFifoWorker
11
+ # include Shoryuken::Template::Workers::FifoWorker
12
+ #
13
+ # worker_options queue: 'my-queue.fifo'
14
+ #
15
+ # def process_message(body, message_group_id)
16
+ # # Processamento sequencial garantido por message_group_id
17
+ # puts "Processando sequencialmente: #{body}"
18
+ # end
19
+ # end
20
+ module FifoWorker
21
+ def self.included(base)
22
+ base.include BaseWorker
23
+ base.class_eval do
24
+ # FIFO queues devem processar uma mensagem por vez
25
+ shoryuken_options auto_delete: true
26
+ end
27
+ end
28
+
29
+ # Método perform do Shoryuken
30
+ def perform(sqs_msg, body)
31
+ message_group_id = extract_message_group_id(sqs_msg)
32
+ deduplication_id = extract_deduplication_id(sqs_msg)
33
+
34
+ log_info(
35
+ "Iniciando processamento FIFO",
36
+ message_id: sqs_msg.message_id,
37
+ message_group_id: message_group_id,
38
+ deduplication_id: deduplication_id,
39
+ queue: sqs_msg.queue_url
40
+ )
41
+
42
+ # Valida se é realmente uma fila FIFO
43
+ validate_fifo_queue(sqs_msg)
44
+
45
+ ErrorHandler.handle_error(worker_name, sqs_msg) do
46
+ process_message(body, message_group_id)
47
+ end
48
+ end
49
+
50
+ private
51
+
52
+ # Extrai o Message Group ID da mensagem
53
+ def extract_message_group_id(sqs_msg)
54
+ if sqs_msg.respond_to?(:attributes)
55
+ sqs_msg.attributes['MessageGroupId']
56
+ end
57
+ end
58
+
59
+ # Extrai o Deduplication ID da mensagem
60
+ def extract_deduplication_id(sqs_msg)
61
+ if sqs_msg.respond_to?(:attributes)
62
+ sqs_msg.attributes['MessageDeduplicationId']
63
+ end
64
+ end
65
+
66
+ # Valida se a fila é FIFO
67
+ def validate_fifo_queue(sqs_msg)
68
+ queue_url = sqs_msg.queue_url
69
+ unless queue_url.end_with?('.fifo')
70
+ log_warn(
71
+ "Worker FIFO sendo usado em fila não-FIFO",
72
+ queue_url: queue_url
73
+ )
74
+ end
75
+ end
76
+
77
+ # Método que deve ser implementado pela classe filha
78
+ def process_message(body, message_group_id)
79
+ raise NotImplementedError, "Você deve implementar o método #process_message(body, message_group_id)"
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,120 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'concurrent'
4
+
5
+ module Shoryuken
6
+ module Template
7
+ module Workers
8
+ # Worker paralelo - processa mensagens em batch usando threads
9
+ #
10
+ # Exemplo de uso:
11
+ # class MyParallelWorker
12
+ # include Shoryuken::Template::Workers::ParallelWorker
13
+ #
14
+ # worker_options queue: 'my-queue', batch: true
15
+ #
16
+ # def process_message(body)
17
+ # # Cada mensagem será processada em uma thread separada
18
+ # puts "Processando em thread: #{body}"
19
+ # end
20
+ # end
21
+ module ParallelWorker
22
+ def self.included(base)
23
+ base.include BaseWorker
24
+ end
25
+
26
+ # Método perform do Shoryuken para batch
27
+ def perform(sqs_msg, body)
28
+ # Para batch, sqs_msg e body são arrays
29
+ messages = sqs_msg.is_a?(Array) ? sqs_msg : [sqs_msg]
30
+ bodies = body.is_a?(Array) ? body : [body]
31
+
32
+ log_info(
33
+ "Iniciando processamento paralelo de batch",
34
+ batch_size: messages.size,
35
+ queue: messages.first&.queue_url
36
+ )
37
+
38
+ start_time = Time.now
39
+ process_batch_parallel(messages, bodies)
40
+ duration = Time.now - start_time
41
+
42
+ log_info(
43
+ "Batch processado com sucesso",
44
+ batch_size: messages.size,
45
+ duration: duration.round(3)
46
+ )
47
+ end
48
+
49
+ private
50
+
51
+ def process_batch_parallel(messages, bodies)
52
+ thread_pool = Shoryuken::Template.configuration.thread_pool_for(worker_name)
53
+ timeout = Shoryuken::Template.configuration.thread_timeout_for(worker_name)
54
+
55
+ # Cria promises para cada mensagem
56
+ promises = messages.each_with_index.map do |sqs_msg, index|
57
+ Concurrent::Promise.execute(executor: thread_pool) do
58
+ process_single_message_with_timeout(sqs_msg, bodies[index], timeout)
59
+ end
60
+ end
61
+
62
+ # Aguarda todas as promises completarem
63
+ results = promises.map do |promise|
64
+ begin
65
+ promise.value
66
+ rescue => e
67
+ log_error(
68
+ "Erro em thread de processamento",
69
+ error: e.message,
70
+ error_class: e.class.name
71
+ )
72
+ { success: false, error: e }
73
+ end
74
+ end
75
+
76
+ # Verifica se alguma mensagem falhou
77
+ failed_count = results.count { |r| r.is_a?(Hash) && !r[:success] }
78
+ if failed_count > 0
79
+ log_warn(
80
+ "Algumas mensagens falharam no processamento paralelo",
81
+ failed_count: failed_count,
82
+ total_count: messages.size
83
+ )
84
+ end
85
+
86
+ results
87
+ end
88
+
89
+ def process_single_message_with_timeout(sqs_msg, body, timeout)
90
+ ErrorHandler.with_timeout(timeout) do
91
+ ErrorHandler.handle_error(worker_name, sqs_msg) do
92
+ log_debug(
93
+ "Processando mensagem em thread",
94
+ message_id: sqs_msg.message_id,
95
+ thread_id: Thread.current.object_id
96
+ )
97
+
98
+ process_message(body)
99
+ { success: true, message_id: sqs_msg.message_id }
100
+ end
101
+ end
102
+ rescue Shoryuken::Template::TimeoutError => e
103
+ log_error(
104
+ "Timeout ao processar mensagem",
105
+ message_id: sqs_msg.message_id,
106
+ timeout: timeout
107
+ )
108
+ { success: false, error: e, message_id: sqs_msg.message_id }
109
+ rescue => e
110
+ { success: false, error: e, message_id: sqs_msg.message_id }
111
+ end
112
+
113
+ # Método que deve ser implementado pela classe filha
114
+ def process_message(body)
115
+ raise NotImplementedError, "Você deve implementar o método #process_message"
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shoryuken
4
+ module Template
5
+ module Workers
6
+ # Worker padrão - processa uma mensagem por vez
7
+ #
8
+ # Exemplo de uso:
9
+ # class MyWorker
10
+ # include Shoryuken::Template::Workers::StandardWorker
11
+ #
12
+ # worker_options queue: 'my-queue'
13
+ #
14
+ # def process_message(body)
15
+ # # Sua lógica aqui
16
+ # puts "Processando: #{body}"
17
+ # end
18
+ # end
19
+ module StandardWorker
20
+ def self.included(base)
21
+ base.include BaseWorker
22
+ end
23
+
24
+ # Método perform do Shoryuken
25
+ def perform(sqs_msg, body)
26
+ log_info(
27
+ "Iniciando processamento de mensagem",
28
+ message_id: sqs_msg.message_id,
29
+ queue: sqs_msg.queue_url
30
+ )
31
+
32
+ ErrorHandler.handle_error(worker_name, sqs_msg) do
33
+ process_message(body)
34
+ end
35
+ end
36
+
37
+ # Método que deve ser implementado pela classe filha
38
+ def process_message(body)
39
+ raise NotImplementedError, "Você deve implementar o método #process_message"
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shoryuken
4
+ module Template
5
+ class Error < StandardError; end
6
+ end
7
+ end
8
+
9
+ require_relative "template/version"
10
+ require_relative "template/configuration"
11
+ require_relative "template/logger"
12
+ require_relative "template/error_handler"
13
+ require_relative "template/workers/base_worker"
14
+ require_relative "template/workers/standard_worker"
15
+ require_relative "template/workers/parallel_worker"
16
+ require_relative "template/workers/fifo_worker"
@@ -0,0 +1,43 @@
1
+ # Arquivo de configuração do Shoryuken
2
+ # Copie para shoryuken.yml e configure de acordo com suas necessidades
3
+
4
+ # AWS Credentials (ou use IAM roles no Heroku)
5
+ aws:
6
+ region: us-east-1
7
+ # access_key_id: <%= ENV['AWS_ACCESS_KEY_ID'] %>
8
+ # secret_access_key: <%= ENV['AWS_SECRET_ACCESS_KEY'] %>
9
+
10
+ # Concurrency
11
+ concurrency: 10
12
+
13
+ # Delay between polls (em segundos)
14
+ delay: 0
15
+
16
+ # Filas
17
+ queues:
18
+ # Worker Padrão
19
+ - [orders-queue, 2]
20
+
21
+ # Worker Paralelo (batch)
22
+ - [images-queue, 1]
23
+
24
+ # Worker FIFO
25
+ - [transactions-queue.fifo, 1]
26
+
27
+ # Timeout (em segundos)
28
+ timeout: 25
29
+
30
+ # Lifecycle events
31
+ lifecycle_events_logger: structured
32
+
33
+ # Log level
34
+ logfile: log/shoryuken.log
35
+ verbose: false
36
+
37
+ # Workers
38
+ # Certifique-se de que seus workers estão carregados
39
+ require:
40
+ - ./config/environment.rb
41
+ - ./app/workers/order_processor_worker.rb
42
+ - ./app/workers/image_processor_worker.rb
43
+ - ./app/workers/transaction_worker.rb
@@ -0,0 +1,6 @@
1
+ module Shoryuken
2
+ module Template
3
+ VERSION: String
4
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
5
+ end
6
+ end
metadata ADDED
@@ -0,0 +1,192 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: shoryuken-template
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.3.0
5
+ platform: ruby
6
+ authors:
7
+ - Ramon Andrade
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: shoryuken
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '6.0'
19
+ - - "<"
20
+ - !ruby/object:Gem::Version
21
+ version: '8.0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ version: '6.0'
29
+ - - "<"
30
+ - !ruby/object:Gem::Version
31
+ version: '8.0'
32
+ - !ruby/object:Gem::Dependency
33
+ name: concurrent-ruby
34
+ requirement: !ruby/object:Gem::Requirement
35
+ requirements:
36
+ - - "~>"
37
+ - !ruby/object:Gem::Version
38
+ version: '1.3'
39
+ type: :runtime
40
+ prerelease: false
41
+ version_requirements: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - "~>"
44
+ - !ruby/object:Gem::Version
45
+ version: '1.3'
46
+ - !ruby/object:Gem::Dependency
47
+ name: rake
48
+ requirement: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - "~>"
51
+ - !ruby/object:Gem::Version
52
+ version: '13.2'
53
+ type: :development
54
+ prerelease: false
55
+ version_requirements: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - "~>"
58
+ - !ruby/object:Gem::Version
59
+ version: '13.2'
60
+ - !ruby/object:Gem::Dependency
61
+ name: minitest
62
+ requirement: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - "~>"
65
+ - !ruby/object:Gem::Version
66
+ version: '5.22'
67
+ type: :development
68
+ prerelease: false
69
+ version_requirements: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - "~>"
72
+ - !ruby/object:Gem::Version
73
+ version: '5.22'
74
+ - !ruby/object:Gem::Dependency
75
+ name: minitest-reporters
76
+ requirement: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - "~>"
79
+ - !ruby/object:Gem::Version
80
+ version: '1.7'
81
+ type: :development
82
+ prerelease: false
83
+ version_requirements: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - "~>"
86
+ - !ruby/object:Gem::Version
87
+ version: '1.7'
88
+ - !ruby/object:Gem::Dependency
89
+ name: rubocop
90
+ requirement: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - "~>"
93
+ - !ruby/object:Gem::Version
94
+ version: '1.60'
95
+ type: :development
96
+ prerelease: false
97
+ version_requirements: !ruby/object:Gem::Requirement
98
+ requirements:
99
+ - - "~>"
100
+ - !ruby/object:Gem::Version
101
+ version: '1.60'
102
+ - !ruby/object:Gem::Dependency
103
+ name: rubocop-rake
104
+ requirement: !ruby/object:Gem::Requirement
105
+ requirements:
106
+ - - "~>"
107
+ - !ruby/object:Gem::Version
108
+ version: '0.6'
109
+ type: :development
110
+ prerelease: false
111
+ version_requirements: !ruby/object:Gem::Requirement
112
+ requirements:
113
+ - - "~>"
114
+ - !ruby/object:Gem::Version
115
+ version: '0.6'
116
+ - !ruby/object:Gem::Dependency
117
+ name: rubocop-minitest
118
+ requirement: !ruby/object:Gem::Requirement
119
+ requirements:
120
+ - - "~>"
121
+ - !ruby/object:Gem::Version
122
+ version: '0.35'
123
+ type: :development
124
+ prerelease: false
125
+ version_requirements: !ruby/object:Gem::Requirement
126
+ requirements:
127
+ - - "~>"
128
+ - !ruby/object:Gem::Version
129
+ version: '0.35'
130
+ description: Gem template para projetos Shoryuken com suporte a workers padrão, paralelos
131
+ e FIFO, incluindo pool de threads configurável, logging estruturado e métricas
132
+ email:
133
+ - andrade.rmn@gmail.com
134
+ executables: []
135
+ extensions: []
136
+ extra_rdoc_files: []
137
+ files:
138
+ - ".rubocop.yml"
139
+ - CHANGELOG.md
140
+ - CONTRIBUTING.md
141
+ - INSTALLATION.md
142
+ - LICENSE.txt
143
+ - PUBLISHING.md
144
+ - Procfile.example
145
+ - QUICKSTART.md
146
+ - README.md
147
+ - RUBY_3.4_FEATURES.md
148
+ - Rakefile
149
+ - UPGRADE_GUIDE.md
150
+ - examples/configuration_example.rb
151
+ - examples/fifo_worker_example.rb
152
+ - examples/heroku_setup.rb
153
+ - examples/parallel_worker_example.rb
154
+ - examples/standard_worker_example.rb
155
+ - examples/worker_specific_config_example.rb
156
+ - lib/shoryuken/template.rb
157
+ - lib/shoryuken/template/configuration.rb
158
+ - lib/shoryuken/template/error_handler.rb
159
+ - lib/shoryuken/template/logger.rb
160
+ - lib/shoryuken/template/version.rb
161
+ - lib/shoryuken/template/workers/base_worker.rb
162
+ - lib/shoryuken/template/workers/fifo_worker.rb
163
+ - lib/shoryuken/template/workers/parallel_worker.rb
164
+ - lib/shoryuken/template/workers/standard_worker.rb
165
+ - shoryuken.yml.example
166
+ - sig/shoryuken/template.rbs
167
+ homepage: https://github.com/sousmile/shoryuken-template
168
+ licenses:
169
+ - MIT
170
+ metadata:
171
+ homepage_uri: https://github.com/sousmile/shoryuken-template
172
+ source_code_uri: https://github.com/sousmile/shoryuken-template
173
+ changelog_uri: https://github.com/sousmile/shoryuken-template/blob/main/CHANGELOG.md
174
+ rdoc_options: []
175
+ require_paths:
176
+ - lib
177
+ required_ruby_version: !ruby/object:Gem::Requirement
178
+ requirements:
179
+ - - ">="
180
+ - !ruby/object:Gem::Version
181
+ version: 3.4.0
182
+ required_rubygems_version: !ruby/object:Gem::Requirement
183
+ requirements:
184
+ - - ">="
185
+ - !ruby/object:Gem::Version
186
+ version: '0'
187
+ requirements: []
188
+ rubygems_version: 4.0.4
189
+ specification_version: 4
190
+ summary: Template estruturado para workers Shoryuken com processamento paralelo e
191
+ sequencial
192
+ test_files: []