sidekiq_sqs_processor 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 840d04b0ed53863a1d63d982a96adb4b44534151e2052dd508b6639e7a875f1d
4
+ data.tar.gz: 7392d9426507e8d20f9c932bedd1291ce86299a4606442602fa18b17c4ece62d
5
+ SHA512:
6
+ metadata.gz: 811fce502fecb1e1fed02d8e26f9836e3059091c583370f3864739df3509308df347a07b64c2be1d66ef63923f155531e7ab0e8cbdf9a68ab679171e09eb8816
7
+ data.tar.gz: 59cd2987302ed96f7004586b9a2cdc82c2669b7fce0b2fcefd5d47f2edcc4a83bb0007a5a177988370312515d5b198617601cda50d026f3f0f414f0b2aeaef52
data/README.md ADDED
@@ -0,0 +1,315 @@
1
+ # Sidekiq SQS Processor
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/sidekiq_sqs_processor.svg)](https://badge.fury.io/rb/sidekiq_sqs_processor)
4
+ [![Build Status](https://github.com/yourusername/sidekiq_sqs_processor/workflows/CI/badge.svg)](https://github.com/yourusername/sidekiq_sqs_processor/actions)
5
+
6
+ A Ruby gem that seamlessly integrates Amazon SQS with Sidekiq for efficient and scalable message processing. Process SQS messages asynchronously with the power and monitoring capabilities of Sidekiq.
7
+
8
+ ## Features
9
+
10
+ - **Automatic SQS polling** - Continuously polls SQS queues for new messages
11
+ - **Parallel processing** - Process messages across multiple Sidekiq workers
12
+ - **SNS integration** - Auto-unwraps SNS notifications
13
+ - **Configurable polling strategies** - Choose between continuous or scheduled polling
14
+ - **Automatic retries** - Leverage Sidekiq's retry mechanism with SQS visibility timeout
15
+ - **Easy message routing** - Route messages to different workers based on content
16
+ - **Rails integration** - Simple setup with generators for Rails applications
17
+ - **Comprehensive monitoring** - Track processing stats and worker health
18
+
19
+ ## Installation
20
+
21
+ Add this line to your application's Gemfile:
22
+
23
+ ```ruby
24
+ gem 'sidekiq_sqs_processor'
25
+ ```
26
+
27
+ And then execute:
28
+
29
+ ```bash
30
+ $ bundle install
31
+ ```
32
+
33
+ Or install it yourself:
34
+
35
+ ```bash
36
+ $ gem install sidekiq_sqs_processor
37
+ ```
38
+
39
+ ## Dependencies
40
+
41
+ This gem requires the following dependencies in your application:
42
+
43
+ - Sidekiq (~> 7.0)
44
+ - aws-sdk-sqs (~> 1.0)
45
+ - Rails (>= 6.0)
46
+
47
+ ### Rails Setup
48
+
49
+ If you're using Rails, run the built-in generator to create the initializer and example worker:
50
+
51
+ ```bash
52
+ $ rails generate sidekiq_sqs_processor:install
53
+ ```
54
+
55
+ ## Basic Configuration
56
+
57
+ ### Rails Applications
58
+
59
+ After running the installer, edit the generated initializer at `config/initializers/sidekiq_sqs_processor.rb`:
60
+
61
+ ```ruby
62
+ SidekiqSqsProcessor.configure do |config|
63
+ # AWS region for SQS queues
64
+ config.aws_region = 'us-east-1'
65
+
66
+ # SQS queue URLs to poll
67
+ config.queue_urls = [
68
+ 'https://sqs.us-east-1.amazonaws.com/123456789012/my-queue'
69
+ ]
70
+
71
+ # Polling configuration
72
+ config.polling_type = :continuous
73
+ end
74
+ ```
75
+
76
+ ### Non-Rails Applications
77
+
78
+ For non-Rails applications, add this to your initialization code:
79
+
80
+ ```ruby
81
+ require 'sidekiq_sqs_processor'
82
+
83
+ SidekiqSqsProcessor.configure do |config|
84
+ # AWS region for SQS queues
85
+ config.aws_region = 'us-east-1'
86
+
87
+ # SQS queue URLs to poll
88
+ config.queue_urls = [
89
+ 'https://sqs.us-east-1.amazonaws.com/123456789012/my-queue'
90
+ ]
91
+
92
+ # Start polling when Sidekiq starts
93
+ Sidekiq.configure_server do |sidekiq_config|
94
+ sidekiq_config.on(:startup) do
95
+ SidekiqSqsProcessor.start_continuous_poller
96
+ end
97
+
98
+ sidekiq_config.on(:shutdown) do
99
+ SidekiqSqsProcessor.stop_continuous_poller
100
+ end
101
+ end
102
+ end
103
+ ```
104
+
105
+ ## Creating Workers
106
+
107
+ ### Using the Generator (Rails)
108
+
109
+ ```bash
110
+ $ rails generate sidekiq_sqs_processor:worker UserNotification
111
+ ```
112
+
113
+ This will create:
114
+ - `app/workers/user_notification_worker.rb`
115
+ - `spec/workers/user_notification_worker_spec.rb` (if using RSpec)
116
+ - or `test/workers/user_notification_worker_test.rb` (if using Test::Unit)
117
+
118
+ ### Manual Worker Creation
119
+
120
+ Create a class that inherits from `SidekiqSqsProcessor::BaseWorker`:
121
+
122
+ ```ruby
123
+ class MyWorker < SidekiqSqsProcessor::BaseWorker
124
+ # Override the process_message method to handle your SQS messages
125
+ def process_message(message_body)
126
+ # message_body is already parsed from JSON if it was a JSON string
127
+
128
+ # Your processing logic here
129
+ User.find_by(id: message_body['user_id'])&.notify(message_body['message'])
130
+
131
+ # Return a result (optional)
132
+ { status: 'success' }
133
+ end
134
+ end
135
+ ```
136
+
137
+ ## Usage Examples
138
+
139
+ ### Basic Message Processing
140
+
141
+ ```ruby
142
+ class OrderProcessor < SidekiqSqsProcessor::BaseWorker
143
+ def process_message(message_body)
144
+ if message_body.is_a?(Hash) && message_body['order_id']
145
+ # Process an order
146
+ process_order(message_body['order_id'], message_body['items'])
147
+ else
148
+ logger.error("Invalid order message format: #{message_body.inspect}")
149
+ end
150
+ end
151
+
152
+ private
153
+
154
+ def process_order(order_id, items)
155
+ # Your order processing logic
156
+ Order.process(order_id, items)
157
+ end
158
+ end
159
+ ```
160
+
161
+ ### Handling Different Message Types
162
+
163
+ ```ruby
164
+ class EventProcessor < SidekiqSqsProcessor::BaseWorker
165
+ def process_message(message_body)
166
+ case message_body['event_type']
167
+ when 'user_created'
168
+ create_user(message_body['data'])
169
+ when 'order_placed'
170
+ process_order(message_body['data'])
171
+ when 'payment_received'
172
+ process_payment(message_body['data'])
173
+ else
174
+ logger.warn("Unknown event type: #{message_body['event_type']}")
175
+ end
176
+ end
177
+
178
+ # ... processing methods ...
179
+ end
180
+ ```
181
+
182
+ ### Handling SNS Messages
183
+
184
+ SNS messages are automatically detected and unwrapped, so the `message_body` will contain the inner message content:
185
+
186
+ ```ruby
187
+ class NotificationProcessor < SidekiqSqsProcessor::BaseWorker
188
+ def process_message(message_body)
189
+ # For an SNS message, the SNS envelope has been removed
190
+ # and message_body is the parsed content of the SNS Message field
191
+
192
+ logger.info("Processing notification: #{message_body.inspect}")
193
+ # ... your processing logic ...
194
+ end
195
+ end
196
+ ```
197
+
198
+ ### Direct Message Enqueueing
199
+
200
+ You can also enqueue messages directly to a worker without going through SQS:
201
+
202
+ ```ruby
203
+ # Enqueue a message to a specific worker
204
+ SidekiqSqsProcessor.enqueue_message(
205
+ MyWorker,
206
+ { order_id: 123, items: ['item1', 'item2'] }
207
+ )
208
+ ```
209
+
210
+ ## Configuration Options
211
+
212
+ ### AWS Configuration
213
+
214
+ ```ruby
215
+ config.aws_region = 'us-east-1' # AWS region
216
+ config.aws_access_key_id = 'YOUR_KEY' # Optional - uses env vars by default
217
+ config.aws_secret_access_key = 'YOUR_SECRET' # Optional - uses env vars by default
218
+ ```
219
+
220
+ ### SQS Configuration
221
+
222
+ ```ruby
223
+ config.queue_urls = ['https://sqs.region.amazonaws.com/account/queue']
224
+ config.visibility_timeout = 300 # 5 minutes
225
+ config.wait_time_seconds = 20 # 20 seconds (long polling)
226
+ config.max_number_of_messages = 10 # Max messages per receive call
227
+ ```
228
+
229
+ ### Polling Configuration
230
+
231
+ ```ruby
232
+ config.polling_type = :continuous # :continuous or :scheduled
233
+ config.polling_frequency = 60 # Only used for scheduled (seconds)
234
+ config.polling_enabled = true # Enable/disable polling
235
+ config.poll_on_startup = true # Start polling when app starts
236
+ ```
237
+
238
+ ### Sidekiq Configuration
239
+
240
+ ```ruby
241
+ config.worker_queue_name = 'sqs_default' # Default Sidekiq queue
242
+ config.worker_retry_count = 5 # Default retry count
243
+ ```
244
+
245
+ ### Error Handling
246
+
247
+ ```ruby
248
+ # Custom error handler
249
+ config.error_handler = ->(error, context) do
250
+ # Send to your error tracking service
251
+ Sentry.capture_exception(error, extra: context)
252
+ end
253
+ ```
254
+
255
+ ## Best Practices
256
+
257
+ ### Message Structure
258
+
259
+ For best routing and processing, use structured JSON messages:
260
+
261
+ ```json
262
+ {
263
+ "type": "order_placed",
264
+ "data": {
265
+ "order_id": "12345",
266
+ "customer_id": "67890",
267
+ "items": [
268
+ {"id": "item1", "quantity": 2},
269
+ {"id": "item2", "quantity": 1}
270
+ ]
271
+ },
272
+ "metadata": {
273
+ "source": "web",
274
+ "timestamp": "2025-04-21T10:15:30Z"
275
+ }
276
+ }
277
+ ```
278
+
279
+ ### Worker Organization
280
+
281
+ - Create separate workers for different message types or domains
282
+ - Use class inheritance for common processing logic
283
+ - Keep workers small and focused
284
+
285
+ ### Error Handling
286
+
287
+ - Use the `error_handler` config option for global error reporting
288
+ - Implement custom error handling in your workers for specific cases
289
+ - Let Sidekiq handle retries for transient failures
290
+
291
+ ### Visibility Timeout
292
+
293
+ - Set your SQS visibility timeout longer than your Sidekiq job timeout
294
+ - A good rule of thumb: visibility_timeout = (average_processing_time * 5) + max_retry_delay
295
+
296
+ ### Message Size Limits
297
+
298
+ Remember that SQS has a 256KB message size limit. For larger data:
299
+ - Store the data externally (e.g., S3) and include a reference in the message
300
+ - Split large datasets across multiple messages
301
+
302
+ ## Development
303
+
304
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
305
+
306
+ To install this gem onto your local machine, run `bundle exec rake install`.
307
+
308
+ ## Contributing
309
+
310
+ Bug reports and pull requests are welcome on GitHub at https://github.com/yourusername/sidekiq_sqs_processor. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/yourusername/sidekiq_sqs_processor/blob/master/CODE_OF_CONDUCT.md).
311
+
312
+ ## License
313
+
314
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
315
+
@@ -0,0 +1,71 @@
1
+ require 'rails/generators/base'
2
+
3
+ module SidekiqSqsProcessor
4
+ module Generators
5
+ class InstallGenerator < Rails::Generators::Base
6
+ source_root File.expand_path('templates', __dir__)
7
+
8
+ desc "Creates a SidekiqSqsProcessor initializer and configuration for your Rails application."
9
+
10
+ class_option :queue_urls, type: :array, default: [],
11
+ desc: "List of SQS queue URLs to process"
12
+
13
+ class_option :continuous, type: :boolean, default: true,
14
+ desc: "Use continuous polling (true) or scheduled polling (false)"
15
+
16
+ class_option :queue_name, type: :string, default: "default",
17
+ desc: "Default Sidekiq queue name for the worker"
18
+
19
+ class_option :aws_region, type: :string, default: 'us-east-1',
20
+ desc: "AWS region for SQS queues"
21
+
22
+ def create_initializer_file
23
+ template "initializer.rb.tt", "config/initializers/sidekiq_sqs_processor.rb"
24
+ end
25
+
26
+ def create_example_worker
27
+ template "worker.rb.tt", "app/workers/example_sqs_worker.rb"
28
+ end
29
+
30
+ def show_readme
31
+ readme "README.txt" if behavior == :invoke
32
+ end
33
+
34
+ private
35
+
36
+ def continuous_polling?
37
+ options[:continuous]
38
+ end
39
+
40
+ def polling_type
41
+ continuous_polling? ? ':continuous' : ':scheduled'
42
+ end
43
+
44
+ def queue_urls
45
+ urls = options[:queue_urls]
46
+ if urls.empty?
47
+ "[] # Add your queue URLs here, e.g. ['https://sqs.us-east-1.amazonaws.com/123456789012/my-queue']"
48
+ else
49
+ urls.inspect
50
+ end
51
+ end
52
+
53
+ def aws_region
54
+ options[:aws_region]
55
+ end
56
+
57
+ def worker_class_name
58
+ "ExampleSqsWorker"
59
+ end
60
+
61
+ def queue_name
62
+ options[:queue_name]
63
+ end
64
+
65
+ def retry_option
66
+ ", retry: 2" # Default Sidekiq retry behavior
67
+ end
68
+ end
69
+ end
70
+ end
71
+
@@ -0,0 +1,41 @@
1
+
2
+ === SidekiqSqsProcessor Installation ===
3
+
4
+ SidekiqSqsProcessor has been installed in your Rails application.
5
+
6
+ == Next Steps ==
7
+
8
+ 1. Review and update the configuration in:
9
+ config/initializers/sidekiq_sqs_processor.rb
10
+
11
+ Make sure to set your SQS queue URLs!
12
+
13
+ 2. Check the example worker:
14
+ app/workers/example_sqs_worker.rb
15
+
16
+ Update it to handle your specific message types.
17
+
18
+ 3. Run Sidekiq to start processing messages:
19
+ bundle exec sidekiq
20
+
21
+ == Worker Generation ==
22
+
23
+ To create additional SQS message workers:
24
+
25
+ $ rails generate sidekiq_sqs_processor:worker UserNotification
26
+
27
+ This will create:
28
+ app/workers/user_notification_worker.rb
29
+ spec/workers/user_notification_worker_spec.rb (if using RSpec)
30
+
31
+ == Advanced Configuration ==
32
+
33
+ * Set AWS credentials in your environment variables:
34
+ export AWS_ACCESS_KEY_ID=your_key
35
+ export AWS_SECRET_ACCESS_KEY=your_secret
36
+
37
+ * Configure Sidekiq as usual in config/sidekiq.yml
38
+
39
+ * Enable polling in development mode:
40
+ export ENABLE_SQS_POLLING_IN_
41
+
@@ -0,0 +1,66 @@
1
+
2
+ # SidekiqSqsProcessor configuration
3
+ SidekiqSqsProcessor.configure do |config|
4
+ # AWS credentials and region
5
+ # By default, SidekiqSqsProcessor will use the following credential sources (in order):
6
+ # 1. AWS SDK's default credential provider chain:
7
+ # - Environment variables (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY)
8
+ # - Shared credentials file (~/.aws/credentials)
9
+ # - EC2 instance profile or ECS task role
10
+ #
11
+ # Uncomment the lines below to explicitly set credentials
12
+ # config.aws_access_key_id = ENV['AWS_ACCESS_KEY_ID']
13
+ # config.aws_secret_access_key = ENV['AWS_SECRET_ACCESS_KEY']
14
+
15
+ # AWS region for SQS queues
16
+ config.aws_region = '<%= aws_region %>'
17
+
18
+ # SQS queue URLs (required)
19
+ # A list of SQS queue URLs to poll for messages
20
+ config.queue_urls = <%= queue_urls %>
21
+
22
+ # SQS message options
23
+ config.visibility_timeout = 300 # 5 minutes
24
+ config.wait_time_seconds = 20 # 20 seconds (long polling)
25
+ config.max_number_of_messages = 10 # Max messages per receive call
26
+
27
+ # Polling configuration
28
+ config.polling_type = <%= polling_type %> # :continuous or :scheduled
29
+ config.polling_frequency = 60 # Only used for scheduled polling (in seconds)
30
+
31
+ <% if Rails.env.development? %>
32
+ # Disable polling in development by default
33
+ # Set ENABLE_SQS_POLLING_IN_DEV=true to enable
34
+ config.polling_enabled = ENV['ENABLE_SQS_POLLING_IN_DEV'] == 'true'
35
+ <% else %>
36
+ config.polling_enabled = true # Enable polling in production
37
+ <% end %>
38
+
39
+ # Start polling when the application starts
40
+ config.poll_on_startup = true
41
+
42
+ # Sidekiq options
43
+ config.worker_queue_name = 'sqs_default' # Default queue name for SQS worker jobs
44
+ config.worker_retry_count = 5 # Number of retries for failed jobs
45
+
46
+ # Default message routing
47
+ # If set, all messages will be routed to this worker class
48
+ # If nil, will attempt to determine worker from message content
49
+ # config.default_worker_class = 'ExampleSqsWorker'
50
+
51
+ # Custom error handling (optional)
52
+ # Uncomment to add custom error reporting (e.g., to a monitoring service)
53
+ # config.error_handler = ->(error, context) do
54
+ # # Report to your error monitoring service
55
+ # Raven.capture_exception(error, extra: context) if defined?(Raven)
56
+ # Rails.logger.error("SQS Error: #{error.message}\nContext: #{context.inspect}")
57
+ # end
58
+
59
+ # Set logger - by default uses Rails.logger
60
+ config.logger = Rails.logger
61
+ config.log_level = Rails.env.production? ? Logger::INFO : Logger::DEBUG
62
+ end
63
+
64
+ # If using sidekiq-cron for scheduled polling, the Railtie will
65
+ # automatically set up the cron job based on your configuration.
66
+
@@ -0,0 +1,59 @@
1
+
2
+ class <%= worker_class_name %> < SidekiqSqsProcessor::BaseWorker
3
+ # Configure Sidekiq options if needed
4
+ sidekiq_options queue: '<%= queue_name %>'<%= retry_option %>
5
+
6
+ # Process an SQS message
7
+ # @param message_body [Hash, String] The parsed message body
8
+ # @return [Any] Result of processing (will be logged)
9
+ def process_message(message_body)
10
+ # Log the message content
11
+ logger.info("Processing message: #{message_body.inspect}")
12
+
13
+ # Your message processing logic goes here
14
+ #
15
+ # Example: Handle different message types based on content
16
+ if message_body.is_a?(Hash)
17
+ case message_body['type']
18
+ when 'user_created'
19
+ process_user_created(message_body['data'])
20
+ when 'order_placed'
21
+ process_order_placed(message_body['data'])
22
+ else
23
+ process_generic_hash(message_body)
24
+ end
25
+ else
26
+ process_generic_message(message_body)
27
+ end
28
+
29
+ # Return success result (optional)
30
+ { status: 'success' }
31
+ end
32
+
33
+ private
34
+
35
+ def process_user_created(user_data)
36
+ logger.info("Processing user created event")
37
+ # Your user creation logic here
38
+ # Example:
39
+ # User.create!(user_data)
40
+ end
41
+
42
+ def process_order_placed(order_data)
43
+ logger.info("Processing order placed event")
44
+ # Your order processing logic here
45
+ # Example:
46
+ # OrderProcessor.new(order_data).process
47
+ end
48
+
49
+ def process_generic_hash(data)
50
+ logger.info("Processing generic hash data with keys: #{data.keys.join(', ')}")
51
+ # Generic hash handling
52
+ end
53
+
54
+ def process_generic_message(message)
55
+ logger.info("Processing generic message: #{message}")
56
+ # Generic message handling
57
+ end
58
+ end
59
+
@@ -0,0 +1,54 @@
1
+
2
+ require 'rails_helper'
3
+
4
+ RSpec.describe <%= worker_class_name %> do
5
+ let(:worker) { described_class.new }
6
+
7
+ describe '#process_message' do
8
+ context 'with a hash message' do
9
+ let(:message) { { 'type' => 'user_created', 'data' => { 'name' => 'Test User' } } }
10
+
11
+ it 'processes the message correctly' do
12
+ expect(worker).to receive(:process_user_created).with(message['data'])
13
+ result = worker.process_message(message)
14
+ expect(result).to eq(status: 'success')
15
+ end
16
+ end
17
+
18
+ context 'with a string message' do
19
+ let(:message) { 'test message' }
20
+
21
+ it 'processes the message correctly' do
22
+ expect(worker).to receive(:process_generic_message).with(message)
23
+ result = worker.process_message(message)
24
+ expect(result).to eq(status: 'success')
25
+ end
26
+ end
27
+ end
28
+
29
+ describe 'SQS integration' do
30
+ it 'correctly deletes the message from SQS when processing succeeds' do
31
+ # Create a test message that simulates SQS format
32
+ message_data = {
33
+ 'receipt_handle' => 'test-receipt-handle',
34
+ 'queue_url' => 'https://sqs.us-east-1.amazonaws.com/123456789012/test-queue',
35
+ 'message_id' => 'test-message-id',
36
+ 'body' => '{"type":"test","data":{"id":123}}'
37
+ }
38
+
39
+ # Mock the SQS client
40
+ sqs_client = instance_double(Aws::SQS::Client)
41
+ allow(SidekiqSqsProcessor).to receive(:sqs_client).and_return(sqs_client)
42
+
43
+ # Expect delete_message to be called
44
+ expect(sqs_client).to receive(:delete_message).with(
45
+ queue_url: message_data['queue_url'],
46
+ receipt_handle: message_data['receipt_handle']
47
+ )
48
+
49
+ # Perform the job
50
+ worker.perform(message_data)
51
+ end
52
+ end
53
+ end
54
+
@@ -0,0 +1,50 @@
1
+
2
+ require 'test_helper'
3
+
4
+ class <%= worker_class_name %>Test < ActiveSupport::TestCase
5
+ setup do
6
+ @worker = <%= worker_class_name %>.new
7
+ end
8
+
9
+ test "processes a hash message correctly" do
10
+ message = { 'type' => 'user_created', 'data' => { 'name' => 'Test User' } }
11
+
12
+ @worker.expects(:process_user_created).with(message['data'])
13
+ result = @worker.process_message(message)
14
+
15
+ assert_equal({ status: 'success' }, result)
16
+ end
17
+
18
+ test "processes a string message correctly" do
19
+ message = 'test message'
20
+
21
+ @worker.expects(:process_generic_message).with(message)
22
+ result = @worker.process_message(message)
23
+
24
+ assert_equal({ status: 'success' }, result)
25
+ end
26
+
27
+ test "deletes the message from SQS when processing succeeds" do
28
+ # Create a test message that simulates SQS format
29
+ message_data = {
30
+ 'receipt_handle' => 'test-receipt-handle',
31
+ 'queue_url' => 'https://sqs.us-east-1.amazonaws.com/123456789012/test-queue',
32
+ 'message_id' => 'test-message-id',
33
+ 'body' => '{"type":"test","data":{"id":123}}'
34
+ }
35
+
36
+ # Mock the SQS client
37
+ sqs_client = mock()
38
+ SidekiqSqsProcessor.stubs(:sqs_client).returns(sqs_client)
39
+
40
+ # Expect delete_message to be called
41
+ sqs_client.expects(:delete_message).with(
42
+ queue_url: message_data['queue_url'],
43
+ receipt_handle: message_data['receipt_handle']
44
+ )
45
+
46
+ # Perform the job
47
+ @worker.perform(message_data)
48
+ end
49
+ end
50
+