rabbitmq-actors 2.0.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,64 @@
1
+ require_relative '../../base/producer'
2
+
3
+ module RabbitMQ
4
+ module Actors
5
+ # A producer of messages routed to certain queues bound based on message headers matching.
6
+ #
7
+ # @example
8
+ # RabbitMQ::Server.url = 'amqp://localhost'
9
+ #
10
+ # publisher = RabbitMQ::Actors::HeadersProducer.new(headers_name: 'reports', logger: Rails.logger)
11
+ # message = 'A report about USA economy'
12
+ # publisher.publish(message, message_id: '1234837633', headers: { 'type' => :economy, 'area' => 'USA'})
13
+ #
14
+ class HeadersProducer < Base::Producer
15
+ # @!attribute [r] headers_name
16
+ # @return [Bunny::Exchange] the headers exchange where to publish messages.
17
+ attr_reader :headers_name
18
+
19
+ # @param :headers_name [String] name of the headers exchange where to publish messages.
20
+ # @option opts [String] :reply_queue_name the name of the queue where a consumer should reply.
21
+ # @option opts [Logger] :logger the logger where to output info about this agent's activity.
22
+ def initialize(headers_name:, **opts)
23
+ super(opts.merge(headers_name: headers_name))
24
+ end
25
+
26
+ # Send a message to the RabbitMQ server.
27
+ # @param message [String] the message body to be sent.
28
+ # @param :message_id [String] user-defined id for replies to refer to this message using :correlation_id
29
+ # @param :headers [Hash] send the message only to queues bound to this exchange and matching any/all of these headers.
30
+ # @see Bunny::Exchange#publish for extra options:
31
+ # @option opts [Boolean] :persistent Should the message be persisted to disk?. Default true.
32
+ # @option opts [Boolean] :mandatory Should the message be returned if it cannot be routed to any queue?
33
+ # @option opts [Integer] :timestamp A timestamp associated with this message
34
+ # @option opts [Integer] :expiration Expiration time after which the message will be deleted
35
+ # @option opts [String] :type Message type, e.g. what type of event or command this message represents. Can be any string
36
+ # @option opts [String] :reply_to Queue name other apps should send the response to. Default to
37
+ # replay_queue_name if it was defined at creation time.
38
+ # @option opts [String] :content_type Message content type (e.g. application/json)
39
+ # @option opts [String] :content_encoding Message content encoding (e.g. gzip)
40
+ # @option opts [String] :correlation_id Message correlated to this one, e.g. what request this message is a reply for
41
+ # @option opts [Integer] :priority Message priority, 0 to 9. Not used by RabbitMQ, only applications
42
+ # @option opts [String] :user_id Optional user ID. Verified by RabbitMQ against the actual connection username
43
+ # @option opts [String] :app_id Optional application ID
44
+ def publish(message, message_id:, headers:, **opts)
45
+ super(message, opts.merge(message_id: message_id, headers: headers))
46
+ end
47
+
48
+ private
49
+
50
+ # Sets the exchange name to connect to.
51
+ # @see #initialize for the list of options that can be received.
52
+ def pre_initialize(**opts)
53
+ @headers_name = opts[:headers_name]
54
+ super
55
+ end
56
+
57
+ # The durable RabbitMQ headers exchange where to publish messages.
58
+ # @return [Bunny::Exchange]
59
+ def exchange
60
+ @exchange ||= channel.headers(headers_name, durable: true)
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,61 @@
1
+ require_relative '../../base/producer'
2
+
3
+ module RabbitMQ
4
+ module Actors
5
+
6
+ # A producer of messages routed (via a default exchange) to a given queue.
7
+ # Used to distribute tasks among several worker processes listening a shared queue.
8
+ #
9
+ # @example
10
+ # RabbitMQ::Server.url = 'amqp://localhost'
11
+ #
12
+ # master = RabbitMQ::Actors::MasterProducer.new(
13
+ # queue_name: 'purchases',
14
+ # auto_delete: false,
15
+ # reply_queue_name: 'confirmations',
16
+ # logger: Rails.logger)
17
+ #
18
+ # message = { stock: 'Apple', number: 1000 }.to_json
19
+ # master.publish(message, message_id: '1234837325', content_type: "application/json")
20
+ #
21
+ class MasterProducer < Base::Producer
22
+ # @param :queue_name [String] name of the durable queue where to publish messages.
23
+ # @option opts [Boolean] :auto_delete (true) if the queue will be deleted when
24
+ # there are no more consumers subscribed to it.
25
+ # @option opts [String] :reply_queue_name the name of the queue where a consumer should reply.
26
+ # @option opts [Logger] :logger the logger where to output info about this agent's activity.
27
+ def initialize(queue_name:, **opts)
28
+ super(opts.merge(queue_name: queue_name, exclusive: false))
29
+ end
30
+
31
+ # Send a message to the RabbitMQ server.
32
+ # @param message [String] the message body to be sent.
33
+ # @param :message_id [String] user-defined id for replies to refer to this message using :correlation_id
34
+ # @see Bunny::Exchange#publish for extra options:
35
+ # @option opts [Boolean] :persistent Should the message be persisted to disk?. Default true.
36
+ # @option opts [Boolean] :mandatory Should the message be returned if it cannot be routed to any queue?
37
+ # @option opts [Integer] :timestamp A timestamp associated with this message
38
+ # @option opts [Integer] :expiration Expiration time after which the message will be deleted
39
+ # @option opts [String] :type Message type, e.g. what type of event or command this message represents. Can be any string
40
+ # @option opts [String] :reply_to Queue name other apps should send the response to. Default to
41
+ # replay_queue_name if it was defined at creation time.
42
+ # @option opts [String] :content_type Message content type (e.g. application/json)
43
+ # @option opts [String] :content_encoding Message content encoding (e.g. gzip)
44
+ # @option opts [String] :correlation_id Message correlated to this one, e.g. what request this message is a reply for
45
+ # @option opts [Integer] :priority Message priority, 0 to 9. Not used by RabbitMQ, only applications
46
+ # @option opts [String] :user_id Optional user ID. Verified by RabbitMQ against the actual connection username
47
+ # @option opts [String] :app_id Optional application ID
48
+ def publish(message, message_id:, **opts)
49
+ super(message, opts.merge(message_id: message_id, routing_key: queue.name))
50
+ end
51
+
52
+ private
53
+
54
+ # The default RabbitMQ exchange where to publish messages
55
+ # @return [Bunny::Exchange]
56
+ def exchange
57
+ @exchange ||= channel.default_exchange
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,63 @@
1
+ require_relative '../../base/consumer'
2
+
3
+ module RabbitMQ
4
+ module Actors
5
+ # A consumer of messages from RabbitMQ based on queue name.
6
+ # @abstract Subclass and override #perform to define your customized worker class.
7
+ #
8
+ # @example
9
+ # class MyListener < RabbitMQ::Actors::Worker
10
+ # class_attribute :queue_name, instance_writer: false
11
+ #
12
+ # def initialize
13
+ # super(queue_name: queue_name,
14
+ # manual_ack: true
15
+ # logger: Rails.logger,
16
+ # on_cancellation: ->{ ActiveRecord::Base.connection.close }
17
+ # end
18
+ #
19
+ # private
20
+ #
21
+ # def perform(**task)
22
+ # answer, transaction_guid = JSON.parse(task[:body]), task[:properties][:correlation_id]
23
+ # transaction = referred_transaction(transaction_guid: transaction_guid, tid: answer['tid'])
24
+ # return _no_transaction!(message_id: transaction_guid, transaction: transaction) if transaction.errors.present?
25
+ # update_transaction(transaction, answer.symbolize_keys)
26
+ # end
27
+ #
28
+ # def referred_transaction(transaction_guid:, tid:)
29
+ # ...
30
+ # end
31
+ #
32
+ # def update_transaction(purchase, **attrs)
33
+ # ...
34
+ # end
35
+ #
36
+ # def _no_transaction!(message_id:, transaction:)
37
+ # log_event(type: 'Transaction',
38
+ # message_id: message_id,
39
+ # description: 'ERROR: answer does not identify any transaction',
40
+ # payload: { errors: transaction.errors.full_messages },
41
+ # severity: :high)
42
+ # transaction
43
+ # end
44
+ # end
45
+ #
46
+ # RabbitMQ::Server.url = 'amqp://localhost'
47
+ #
48
+ # MyListener.queue_name = "transactions"
49
+ # MyListener.new.start!
50
+ #
51
+ class Worker < Base::Consumer
52
+ # @param :queue_name [String] name of the durable queue where to receive messages from.
53
+ # @option opts [Boolean] :manual_ack to acknowledge messages to the RabbitMQ server
54
+ # once executed.
55
+ # @option opts [Proc] :on_cancellation to be executed before the worker is terminated
56
+ # @option opts [Logger] :logger the logger where to output info about this agent's activity.
57
+ # Rest of options required by your subclass.
58
+ def initialize(queue_name:, **opts)
59
+ super(opts.merge(queue_name: queue_name, exclusive: false))
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,72 @@
1
+ require_relative '../../base/producer'
2
+
3
+ module RabbitMQ
4
+ module Actors
5
+ # A producer of messages routed to all the consumers bound to the exchange.
6
+ #
7
+ # @example
8
+ # RabbitMQ::Server.url = 'amqp://localhost'
9
+ #
10
+ # publisher = RabbitMQ::Actors::Publisher.new(exchange_name: 'sports', logger: Rails.logger)
11
+ #
12
+ # message = {
13
+ # championship: 'Premier League',
14
+ # match: {
15
+ # team_1: 'Chelsea',
16
+ # team_2: 'Manchester City',
17
+ # date: '03-Sep-2016',
18
+ # score: '2-2'
19
+ # } }.to_json
20
+ #
21
+ # publisher.publish(message, message_id: '1234837633', content_type: "application/json")
22
+ #
23
+ class Publisher < Base::Producer
24
+ # @!attribute [r] exchange_name
25
+ # @return [Bunny::Exchange] the fanout exchange where to publish messages.
26
+ attr_reader :exchange_name
27
+
28
+ # @param :exchange_name [String] name of the exchange where to publish messages.
29
+ # @option opts [String] :reply_queue_name the name of the queue where a consumer should reply.
30
+ # @option opts [Logger] :logger the logger where to output info about this agent's activity.
31
+ def initialize(exchange_name:, **opts)
32
+ super(opts.merge(exchange_name: exchange_name))
33
+ end
34
+
35
+ # Send a message to the RabbitMQ server.
36
+ # @param message [String] the message body to be sent.
37
+ # @param :message_id [String] user-defined id for replies to refer to this message using :correlation_id
38
+ # @see Bunny::Exchange#publish for extra options:
39
+ # @option opts [Boolean] :persistent Should the message be persisted to disk?. Default true.
40
+ # @option opts [Boolean] :mandatory Should the message be returned if it cannot be routed to any queue?
41
+ # @option opts [Integer] :timestamp A timestamp associated with this message
42
+ # @option opts [Integer] :expiration Expiration time after which the message will be deleted
43
+ # @option opts [String] :type Message type, e.g. what type of event or command this message represents. Can be any string
44
+ # @option opts [String] :reply_to Queue name other apps should send the response to. Default to
45
+ # replay_queue_name if it was defined at creation time.
46
+ # @option opts [String] :content_type Message content type (e.g. application/json)
47
+ # @option opts [String] :content_encoding Message content encoding (e.g. gzip)
48
+ # @option opts [String] :correlation_id Message correlated to this one, e.g. what request this message is a reply for
49
+ # @option opts [Integer] :priority Message priority, 0 to 9. Not used by RabbitMQ, only applications
50
+ # @option opts [String] :user_id Optional user ID. Verified by RabbitMQ against the actual connection username
51
+ # @option opts [String] :app_id Optional application ID
52
+ def publish(message, message_id:, **opts)
53
+ super(message, opts.merge(message_id: message_id))
54
+ end
55
+
56
+ private
57
+
58
+ # Sets the exchange name to connect to.
59
+ # @see #initialize for the list of options that can be received.
60
+ def pre_initialize(**opts)
61
+ @exchange_name = opts[:exchange_name]
62
+ super
63
+ end
64
+
65
+ # The durable RabbitMQ fanout exchange where to publish messages.
66
+ # @return [Bunny::Exchange]
67
+ def exchange
68
+ @exchange ||= channel.fanout(exchange_name, durable: true)
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,70 @@
1
+ require_relative '../../base/consumer'
2
+
3
+ module RabbitMQ
4
+ module Actors
5
+ # A consumer of all messages produced by a fanout RabbitMQ exchange.
6
+ # @abstract Subclass and override #perform to define your customized subscriber class.
7
+ #
8
+ # @example
9
+ # class ScoresListener < RabbitMQ::Actors::Subscriber
10
+ # def initialize
11
+ # super(exchange_name: 'scores',
12
+ # logger: Rails.logger,
13
+ # on_cancellation: ->{ ActiveRecord::Base.connection.close })
14
+ # end
15
+ #
16
+ # private
17
+ #
18
+ # def perform(**task)
19
+ # match_data = JSON.parse(task[:body])
20
+ # process_match(match_data)
21
+ # end
22
+ # ...
23
+ # end
24
+ #
25
+ # RabbitMQ::Server.url = 'amqp://localhost'
26
+ #
27
+ # ScoresListener.new.start!
28
+ #
29
+ class Subscriber < Base::Consumer
30
+ # @!attribute [r] exchange_name
31
+ # @return [Bunny::Exchange] the exchange where to get messages from.
32
+ attr_reader :exchange_name
33
+
34
+ # @param :exchange_name [String] name of the exchange where to consume messages from.
35
+ # @option opts [Proc] :on_cancellation to be executed before the worker is terminated
36
+ # @option opts [Logger] :logger the logger where to output info about this agent's activity.
37
+ # Rest of options required by your subclass.
38
+ def initialize(exchange_name:, **opts)
39
+ super(opts.merge(exchange_name: exchange_name))
40
+ end
41
+
42
+ private
43
+
44
+ # Set exchange_name this worker is bound to.
45
+ # @see #initialize for the list of options that can be received.
46
+ def pre_initialize(**opts)
47
+ @exchange_name = opts[:exchange_name]
48
+ super
49
+ end
50
+
51
+ # Bind this worker's queue to the exchange
52
+ # @see #initialize for the list of options that can be received.
53
+ def post_initialize(**opts)
54
+ bind_queue_to_exchange
55
+ super
56
+ end
57
+
58
+ # The durable fanout RabbitMQ exchange from where messages are received
59
+ # @return [Bunny::Exchange]
60
+ def exchange
61
+ @exchange ||= channel.fanout(exchange_name, durable: true)
62
+ end
63
+
64
+ # Bind this worker's listening queue to the exchange to receive all messages produced.
65
+ def bind_queue_to_exchange
66
+ queue.bind(exchange)
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,99 @@
1
+ require_relative '../../base/consumer'
2
+
3
+ module RabbitMQ
4
+ module Actors
5
+ # A consumer of messages from RabbitMQ based on routing keys.
6
+ # @abstract Subclass and override #perform to define your customized routing worker class.
7
+ #
8
+ # @example
9
+ # class TennisListener < RabbitMQ::Actors::RoutingConsumer
10
+ # def initialize
11
+ # super(exchange_name: 'sports',
12
+ # binding_keys: ['tennis'],
13
+ # logger: Rails.logger,
14
+ # on_cancellation: ->{ ActiveRecord::Base.connection.close })
15
+ # end
16
+ #
17
+ # private
18
+ #
19
+ # def perform(**task)
20
+ # match_data = JSON.parse(task[:body])
21
+ # process_tennis_match(match_data)
22
+ # end
23
+ # ...
24
+ # end
25
+ #
26
+ # class FootballListener < RabbitMQ::Actors::RoutingConsumer
27
+ # def initialize
28
+ # super(exchange_name: 'sports',
29
+ # binding_keys: ['football', 'soccer'],
30
+ # logger: Rails.logger,
31
+ # on_cancellation: ->{ ActiveRecord::Base.connection.close })
32
+ # end
33
+ #
34
+ # private
35
+ #
36
+ # def perform(**task)
37
+ # match_data = JSON.parse(task[:body])
38
+ # process_footbal_match(match_data)
39
+ # end
40
+ # ...
41
+ # end
42
+ #
43
+ # RabbitMQ::Server.url = 'amqp://localhost'
44
+ #
45
+ # TennisListener.new.start!
46
+ # FootballListener.new.start!
47
+ #
48
+ class RoutingConsumer < Base::Consumer
49
+ # @!attribute [r] exchange_name
50
+ # @return [Bunny::Exchange] the exchange where to get messages from.
51
+ attr_reader :exchange_name
52
+
53
+ # @!attribute [r] binding_keys
54
+ # @return [String, Array] the routing keys this worker is interested in.
55
+ attr_reader :binding_keys
56
+
57
+ # @param :exchange_name [String] name of the exchange where to consume messages from.
58
+ # @param :binding_keys [String, Array] list of routing keys this worker is interested in.
59
+ # Default to all: '#'
60
+ # @option opts [Proc] :on_cancellation to be executed before the worker is terminated
61
+ # @option opts [Logger] :logger the logger where to output info about this agent's activity.
62
+ # Rest of options required by your subclass.
63
+ def initialize(exchange_name:, binding_keys: '#', **opts)
64
+ super(opts.merge(exchange_name: exchange_name, binding_keys: binding_keys))
65
+ end
66
+
67
+ private
68
+
69
+ # Set exchange_name and binding_keys this worker is bound to.
70
+ # @see #initialize for the list of options that can be received.
71
+ def pre_initialize(**opts)
72
+ @exchange_name = opts[:exchange_name]
73
+ @binding_keys = Array(opts[:binding_keys])
74
+ super
75
+ end
76
+
77
+ # Bind this worker's queue to the exchange and to the given binding_keys
78
+ # @see #initialize for the list of options that can be received.
79
+ def post_initialize(**opts)
80
+ bind_queue_to_exchange_routing_keys
81
+ super
82
+ end
83
+
84
+ # The durable direct RabbitMQ exchange from where messages are received
85
+ # @return [Bunny::Exchange]
86
+ def exchange
87
+ @exchange ||= channel.direct(exchange_name, durable: true)
88
+ end
89
+
90
+ # Bind this worker's listening queue to the exchange and receive only messages with routing key
91
+ # one of the given binding_keys.
92
+ def bind_queue_to_exchange_routing_keys
93
+ binding_keys.each do |key|
94
+ queue.bind(exchange, routing_key: key)
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,75 @@
1
+ require_relative '../../base/producer'
2
+
3
+ module RabbitMQ
4
+ module Actors
5
+ # A producer of messages routed to all the queues bound to the message's routing_key
6
+ #
7
+ # @example
8
+ # RabbitMQ::Server.url = 'amqp://localhost'
9
+ #
10
+ # publisher = RabbitMQ::Actors::RoutingProducer.new(
11
+ # exchange_name: 'sports',
12
+ # replay_queue_name: 'scores',
13
+ # logger: Rails.logger)
14
+ #
15
+ # message = {
16
+ # championship: 'Wimbledon',
17
+ # match: {
18
+ # player_1: 'Rafa Nadal',
19
+ # player_2: 'Roger Federed',
20
+ # date: '01-Jul-2016'
21
+ # } }.to_json
22
+ #
23
+ # publisher.publish(message, message_id: '1234837633', content_type: "application/json", routing_key: 'tennis')
24
+ #
25
+ class RoutingProducer < Base::Producer
26
+ # @!attribute [r] exchange_name
27
+ # @return [Bunny::Exchange] the routing exchange where to publish messages.
28
+ attr_reader :exchange_name
29
+
30
+ # @param :exchange_name [String] name of the exchange where to publish messages.
31
+ # @option opts [String] :reply_queue_name the name of the queue where a consumer should reply.
32
+ # @option opts [Logger] :logger the logger where to output info about this agent's activity.
33
+ def initialize(exchange_name:, **opts)
34
+ super(opts.merge(exchange_name: exchange_name))
35
+ end
36
+
37
+ # Send a message to the RabbitMQ server.
38
+ # @param message [String] the message body to be sent.
39
+ # @param :message_id [String] user-defined id for replies to refer to this message using :correlation_id
40
+ # @param :routing_key [String] send the message only to queues bound to this exchange and this routing_key
41
+ # @see Bunny::Exchange#publish for extra options:
42
+ # @option opts [Boolean] :persistent Should the message be persisted to disk?. Default true.
43
+ # @option opts [Boolean] :mandatory Should the message be returned if it cannot be routed to any queue?
44
+ # @option opts [Integer] :timestamp A timestamp associated with this message
45
+ # @option opts [Integer] :expiration Expiration time after which the message will be deleted
46
+ # @option opts [String] :type Message type, e.g. what type of event or command this message represents. Can be any string
47
+ # @option opts [String] :reply_to Queue name other apps should send the response to. Default to
48
+ # replay_queue_name if it was defined at creation time.
49
+ # @option opts [String] :content_type Message content type (e.g. application/json)
50
+ # @option opts [String] :content_encoding Message content encoding (e.g. gzip)
51
+ # @option opts [String] :correlation_id Message correlated to this one, e.g. what request this message is a reply for
52
+ # @option opts [Integer] :priority Message priority, 0 to 9. Not used by RabbitMQ, only applications
53
+ # @option opts [String] :user_id Optional user ID. Verified by RabbitMQ against the actual connection username
54
+ # @option opts [String] :app_id Optional application ID
55
+ def publish(message, message_id:, routing_key:, **opts)
56
+ super(message, opts.merge(message_id: message_id, routing_key: routing_key))
57
+ end
58
+
59
+ private
60
+
61
+ # Sets the exchange name to connect to.
62
+ # @see #initialize for the list of options that can be received.
63
+ def pre_initialize(**opts)
64
+ @exchange_name = opts[:exchange_name]
65
+ super
66
+ end
67
+
68
+ # The durable RabbitMQ direct exchange where to publish messages.
69
+ # @return [Bunny::Exchange]
70
+ def exchange
71
+ @exchange ||= channel.direct(exchange_name, durable: true)
72
+ end
73
+ end
74
+ end
75
+ end