rabbitmq-actors 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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