mimi-messaging 0.1.12 → 1.2.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.rubocop.yml +66 -0
  4. data/README.md +69 -3
  5. data/TODO.md +8 -0
  6. data/docs/Messaging_Layer_Properties.md +141 -0
  7. data/docs/Why_HTTP_is_a_bad_choice.md +20 -0
  8. data/docs/diagrams/Pattern -- Command.drawio +1 -0
  9. data/docs/diagrams/Pattern -- Event direct.drawio +1 -0
  10. data/docs/diagrams/Pattern -- Event with Queue.drawio +1 -0
  11. data/docs/diagrams/Pattern -- Event.drawio +1 -0
  12. data/docs/diagrams/Pattern -- Query.drawio +1 -0
  13. data/docs/img/pattern--command.png +0 -0
  14. data/docs/img/pattern--event-direct.png +0 -0
  15. data/docs/img/pattern--event-using-queue.png +0 -0
  16. data/docs/img/pattern--event.png +0 -0
  17. data/docs/img/pattern--query.png +0 -0
  18. data/examples/basic_event_listener.rb +35 -0
  19. data/examples/basic_request_processor.rb +38 -0
  20. data/examples/using_messaging_low.rb +59 -0
  21. data/examples/using_pure_adapter.rb +62 -0
  22. data/lib/mimi/messaging.rb +441 -92
  23. data/lib/mimi/messaging/adapters.rb +22 -0
  24. data/lib/mimi/messaging/adapters/base.rb +233 -0
  25. data/lib/mimi/messaging/adapters/memory.rb +147 -0
  26. data/lib/mimi/messaging/adapters/test.rb +50 -0
  27. data/lib/mimi/messaging/errors.rb +24 -12
  28. data/lib/mimi/messaging/json_serializer.rb +45 -0
  29. data/lib/mimi/messaging/message.rb +25 -65
  30. data/lib/mimi/messaging/version.rb +3 -1
  31. data/mimi-messaging.gemspec +25 -23
  32. metadata +34 -77
  33. data/lib/mimi/messaging/connection.rb +0 -182
  34. data/lib/mimi/messaging/listener.rb +0 -72
  35. data/lib/mimi/messaging/mock.rb +0 -13
  36. data/lib/mimi/messaging/mock/connection.rb +0 -153
  37. data/lib/mimi/messaging/mock/request.rb +0 -19
  38. data/lib/mimi/messaging/mock/request_processor.rb +0 -92
  39. data/lib/mimi/messaging/model.rb +0 -27
  40. data/lib/mimi/messaging/model_provider.rb +0 -100
  41. data/lib/mimi/messaging/msgpack/msgpack_ext.rb +0 -14
  42. data/lib/mimi/messaging/msgpack/type_packer.rb +0 -104
  43. data/lib/mimi/messaging/notification.rb +0 -35
  44. data/lib/mimi/messaging/provider.rb +0 -48
  45. data/lib/mimi/messaging/request.rb +0 -56
  46. data/lib/mimi/messaging/request_processor.rb +0 -216
  47. data/lib/mimi/messaging/request_processor/context.rb +0 -39
  48. data/lib/mimi/messaging/request_processor/dsl.rb +0 -121
  49. data/lib/tasks/console_ext.rake +0 -6
  50. data/lib/tasks/console_helpers.rb +0 -116
@@ -1,182 +0,0 @@
1
- require 'bunny'
2
-
3
- module Mimi
4
- module Messaging
5
- class Connection
6
- attr_reader :queue_prefix
7
-
8
- # Creates a Connection with given connection params
9
- #
10
- # @param params [Hash] Connection params as accepted by Bunny
11
- # @param params[:queue_prefix] [String] (optional) Use this connection for all communication
12
- # related to queues, having names starting with given
13
- # prefix
14
- #
15
- def initialize(params = {})
16
- @queue_prefix = params[:queue_prefix]
17
- @channel_pool = {}
18
- bunny_params = {
19
- host: params[:mq_host],
20
- port: params[:mq_port],
21
- username: params[:mq_username],
22
- password: params[:mq_password],
23
- vhost: params[:mq_vhost],
24
- logger: Mimi::Messaging.logger
25
- }
26
- @connection = Bunny.new(bunny_params)
27
- end
28
-
29
- # Starts the connection, opening actual connection to RabbitMQ
30
- #
31
- def start
32
- @connection.start
33
- end
34
-
35
- # Stops the connection
36
- #
37
- def stop
38
- @connection.close
39
- @channel_pool = {}
40
- end
41
-
42
- def started?
43
- @connection.status == :open
44
- end
45
-
46
- def channel
47
- raise ConnectionError unless started?
48
- @channel_pool[Thread.current.object_id] ||= create_channel
49
- end
50
-
51
- def create_channel(opts = {})
52
- Channel.new(@connection, opts)
53
- end
54
-
55
- def reply_queue
56
- raise ConnectionError unless started?
57
- channel.reply_queue
58
- end
59
-
60
- def post(queue_name, raw_message, params = {})
61
- channel.post(queue_name, raw_message, params)
62
- end
63
-
64
- def get(queue_name, raw_message, params = {})
65
- channel.get(queue_name, raw_message, params)
66
- end
67
-
68
- def broadcast(queue_name, raw_message, params = {})
69
- channel.broadcast(queue_name, raw_message, params)
70
- end
71
-
72
- class Channel
73
- attr_reader :options, :connection
74
-
75
- DEFAULT_OPTIONS = {
76
- concurrency: 1
77
- }
78
- DEFAULT_GET_TIMEOUT = 60 # seconds
79
-
80
- def initialize(connection, opts = {})
81
- @connection = connection
82
- @options = DEFAULT_OPTIONS.merge(opts)
83
- @channel = @connection.create_channel(nil, options[:concurrency])
84
- @mutex = Mutex.new
85
- end
86
-
87
- def create_queue(name, opts = {})
88
- @channel.queue(name, opts)
89
- end
90
-
91
- def reply_queue
92
- @reply_queue ||= create_queue('', exclusive: true)
93
- end
94
-
95
- def ack(tag)
96
- @channel.ack(tag)
97
- end
98
-
99
- def fanout(name)
100
- @channel.fanout(name)
101
- end
102
-
103
- def active?
104
- @channel && @channel.active
105
- end
106
-
107
- # Sends a raw RabbitMQ message to a given direct exchange
108
- #
109
- # @param queue_name [String] Queue name to send the message to
110
- # @param raw_message [String]
111
- # @param params [Hash] Message params (metadata)
112
- #
113
- def post(queue_name, raw_message, params = {})
114
- x = @channel.default_exchange
115
- params = { routing_key: queue_name }.merge(params.dup)
116
- publish(x, raw_message, params)
117
- end
118
-
119
- # Sends a raw RabbitMQ message to a given direct exchange and listens for response
120
- #
121
- # @param queue_name [String] Queue name to send the message to
122
- # @param raw_message [String]
123
- # @param params [Hash] Message params (metadata)
124
- #
125
- # @param params[:timeout] [Integer] (optional) Timeout in seconds
126
- #
127
- # @return [nil,Array]
128
- #
129
- def get(queue_name, raw_message, params = {})
130
- correlation_id = Time.now.utc.to_f.to_s
131
- params = params.dup.merge(
132
- reply_to: reply_queue.name,
133
- correlation_id: correlation_id
134
- )
135
- post(queue_name, raw_message, params)
136
- response = nil
137
- begin
138
- Timeout.timeout(params[:timeout] || DEFAULT_GET_TIMEOUT) do
139
- loop do
140
- d, m, p = reply_queue.pop
141
- next if d && m.correlation_id != correlation_id
142
- response = [d, m, p] if d
143
- break if response
144
- sleep 0.001 # s
145
- end
146
- end
147
- rescue Timeout::Error
148
- # respond with nil
149
- end
150
- response
151
- end
152
-
153
- # Sends a raw RabbitMQ message to a given fanout exchange
154
- #
155
- # @param fanout_name [String] Fanout exchange name to send the message to
156
- # @param raw_message [String]
157
- # @param params [Hash] Message params (metadata)
158
- #
159
- def broadcast(fanout_name, raw_message, params = {})
160
- x = @channel.fanout(fanout_name)
161
- publish(x, raw_message, params)
162
- end
163
-
164
- private
165
-
166
- def publish(exchange, raw_message, params = {})
167
- # HACK: Connection-level mutex reduces throughoutput, hopefully improves stability (ku)
168
- @mutex.synchronize do
169
- # TODO: may be make publishing an atomic operation using a separate thread? (ku)
170
- exchange.publish(raw_message, params)
171
- end
172
- rescue StandardError => e
173
- # Raising fatal error:
174
- unless Thread.main == Thread.current
175
- Thread.main.raise ConnectionError, "failed to publish message in a child thread: #{e}"
176
- end
177
- raise ConnectionError, "failed to publish message: #{e}"
178
- end
179
- end # class Channel
180
- end # class Connection
181
- end # module Messaging
182
- end # module Mimi
@@ -1,72 +0,0 @@
1
- module Mimi
2
- module Messaging
3
- class Listener < RequestProcessor
4
- DEFAULT_DURABLE_QUEUE_OPTIONS = {
5
- exclusive: false,
6
- durable: true,
7
- auto_delete: false
8
- }
9
-
10
- DEFAULT_TEMPORARY_QUEUE_OPTIONS = {
11
- exclusive: true,
12
- durable: false,
13
- auto_delete: true
14
- }
15
-
16
- abstract!
17
-
18
- queue_options DEFAULT_TEMPORARY_QUEUE_OPTIONS
19
-
20
- def self.queue(name = nil, opts = {})
21
- return super unless name
22
- queue_name(name)
23
- if name && name != ''
24
- queue_options(DEFAULT_DURABLE_QUEUE_OPTIONS.merge(opts))
25
- else
26
- queue_options(DEFAULT_TEMPORARY_QUEUE_OPTIONS.merge(opts))
27
- end
28
- end
29
-
30
- def self.default_queue_name
31
- ''
32
- end
33
-
34
- # Sets or gets notification resource name
35
- #
36
- def self.notification(name = nil, _opts = {})
37
- notification_name name
38
- true
39
- end
40
-
41
- # Sets or gets queue name
42
- #
43
- def self.notification_name(name = nil)
44
- if name && @notification_name
45
- raise "#{self} has already registered '#{@notification_name}' as notification name"
46
- end
47
- (@notification_name ||= name) || default_notification_name
48
- end
49
-
50
- # Default (inferred) notification name
51
- #
52
- def self.default_notification_name
53
- class_name_to_resource_name(name, 'listener')
54
- end
55
-
56
- def self.resource_name
57
- notification_name
58
- end
59
-
60
- def self.request_type(_d, _m, _p)
61
- :broadcast
62
- end
63
-
64
- def self.construct_queue
65
- exchange = channel.fanout(resource_name)
66
- q = channel.create_queue(queue_name, queue_options)
67
- q.bind(exchange)
68
- q
69
- end
70
- end # class Listener
71
- end # module Messaging
72
- end # module Mimi
@@ -1,13 +0,0 @@
1
- #
2
- # Replaces real Connection and Channel with MockConnection and MockChannel
3
- # that stubs connections to RabbitMQ.
4
- #
5
- # Replaces RequestProcessor with MockRequestProcessor
6
- #
7
-
8
- require_relative 'mock/connection'
9
- require_relative 'mock/request_processor'
10
- require_relative 'mock/request'
11
-
12
- Mimi::Messaging.send :remove_const, :Connection
13
- Mimi::Messaging::Connection = Mimi::Messaging::MockConnection
@@ -1,153 +0,0 @@
1
- require 'bunny'
2
-
3
- module Mimi
4
- module Messaging
5
- class MockConnection
6
- attr_reader :queue_prefix
7
-
8
- # Creates a Connection with given connection params
9
- #
10
- # @param params [Hash] Connection params as accepted by Bunny
11
- # @param params[:queue_prefix] [String] (optional) Use this connection for all communication
12
- # related to queues, having names starting with given
13
- # prefix
14
- #
15
- def initialize(params = {})
16
- @queue_prefix = params[:queue_prefix]
17
- @channel_pool = {}
18
- bunny_params = {
19
- host: params[:mq_host],
20
- port: params[:mq_port],
21
- username: params[:mq_username],
22
- password: params[:mq_password],
23
- vhost: params[:mq_vhost]
24
- }
25
- @connection = { mock_connection: bunny_params }
26
- end
27
-
28
- # Starts the connection, opening actual connection to RabbitMQ
29
- #
30
- def start
31
- @started = true
32
- end
33
-
34
- # Stops the connection
35
- #
36
- def stop
37
- @channel_pool = {}
38
- @started = false
39
- end
40
-
41
- def started?
42
- @started
43
- end
44
-
45
- def channel
46
- raise ConnectionError unless started?
47
- @channel_pool[Thread.current.object_id] ||= create_channel
48
- end
49
-
50
- def create_channel(opts = {})
51
- MockChannel.new(@connection, opts)
52
- end
53
-
54
- def reply_queue
55
- raise ConnectionError unless started?
56
- channel.reply_queue
57
- end
58
-
59
- def post(queue_name, raw_message, params = {})
60
- channel.post(queue_name, raw_message, params)
61
- end
62
-
63
- def get(queue_name, raw_message, params = {})
64
- channel.get(queue_name, raw_message, params)
65
- end
66
-
67
- def broadcast(queue_name, raw_message, params = {})
68
- channel.broadcast(queue_name, raw_message, params)
69
- end
70
-
71
- class MockQueue
72
- def initialize(*)
73
- end
74
- end
75
-
76
- class MockChannel
77
- attr_reader :options, :connection
78
-
79
- DEFAULT_OPTIONS = {
80
- concurrency: 1
81
- }
82
- DEFAULT_GET_TIMEOUT = 60 # seconds
83
-
84
- def initialize(connection, opts = {})
85
- @connection = connection
86
- @options = DEFAULT_OPTIONS.merge(opts)
87
- @channel = { mock_channel: @connection, opts: options[:concurrency] }
88
- @mutex = Mutex.new
89
- end
90
-
91
- def create_queue(name, opts = {})
92
- MockQueue.new(name, opts)
93
- end
94
-
95
- def reply_queue
96
- @reply_queue ||= create_queue('', exclusive: true)
97
- end
98
-
99
- def ack(tag)
100
- raise "Not implemented"
101
- end
102
-
103
- def fanout(name)
104
- raise "Not implemented"
105
- end
106
-
107
- def active?
108
- true
109
- end
110
-
111
- # Sends a raw RabbitMQ message to a given direct exchange
112
- #
113
- # @param queue_name [String] Queue name to send the message to
114
- # @param raw_message [String]
115
- # @param params [Hash] Message params (metadata)
116
- #
117
- def post(queue_name, raw_message, params = {})
118
- true
119
- end
120
-
121
- # Sends a raw RabbitMQ message to a given direct exchange and listens for response
122
- #
123
- # @param queue_name [String] Queue name to send the message to
124
- # @param raw_message [String]
125
- # @param params [Hash] Message params (metadata)
126
- #
127
- # @param params[:timeout] [Integer] (optional) Timeout in seconds
128
- #
129
- # @return [nil,Array]
130
- #
131
- def get(queue_name, raw_message, params = {})
132
- nil
133
- end
134
-
135
- # Sends a raw RabbitMQ message to a given fanout exchange
136
- #
137
- # @param fanout_name [String] Fanout exchange name to send the message to
138
- # @param raw_message [String]
139
- # @param params [Hash] Message params (metadata)
140
- #
141
- def broadcast(fanout_name, raw_message, params = {})
142
- true
143
- end
144
-
145
- private
146
-
147
- def publish(exchange, raw_message, params = {})
148
- true
149
- end
150
- end # class Channel
151
- end # class Connection
152
- end # module Messaging
153
- end # module Mimi
@@ -1,19 +0,0 @@
1
- module Mimi
2
- module Messaging
3
- class Request
4
- attr_reader :response
5
-
6
- def send_response(data = {})
7
- return if !get? || replied?
8
- raise ArgumentError, 'Invalid response format, Hash is expected' unless data.is_a?(Hash)
9
- reply_to_queue_name = metadata[:reply_to]
10
- raw_message = Mimi::Messaging::Message.encode(data)
11
- @response = Mimi::Messaging::Message.new(Mimi::Messaging::Message.decode(raw_message))
12
- request_processor.connection.post(
13
- reply_to_queue_name, raw_message, correlation_id: metadata[:correlation_id]
14
- )
15
- @replied = true
16
- end
17
- end # class Request
18
- end # module Messaging
19
- end # module Mimi
@@ -1,92 +0,0 @@
1
- module Mimi
2
- module Messaging
3
- class RequestProcessor
4
- attr_reader :result, :request
5
-
6
- def self.started?
7
- !@consumer.nil?
8
- end
9
-
10
- # Mock start
11
- #
12
- def self.start
13
- return if abstract?
14
- raise "#{name} already started" if started?
15
- logger.debug "#{self} starting to serve '#{resource_name}' (#{exposed_methods})"
16
- @queue = construct_queue
17
- @consumer_mutex = Mutex.new
18
- @consumer_mutex.synchronize do
19
- @consumer = true
20
- end
21
- # consumer created, mutex released
22
- end
23
-
24
- # Mock stop
25
- #
26
- def self.stop
27
- return if abstract?
28
- raise "#{name} already stopped" unless started?
29
- @consumer_mutex.synchronize do
30
- @consumer = nil
31
- end
32
- @consumer = nil
33
- @queue = nil
34
- @channel = nil
35
- @connection = nil
36
- end
37
-
38
- # MockRequestProcessor helper GET methods
39
- #
40
- # @param method_name [Symbol]
41
- # @param message [Hash]
42
- #
43
- # @return [Mimi::Messaging::Message]
44
- #
45
- def self.get(method_name, message = {})
46
- metadata = Mimi::Messaging::Message.new(
47
- correlation_id: 1,
48
- reply_to: 'mock_client',
49
- headers: {
50
- 'method_name' => method_name.to_s,
51
- 'c' => 'mock-context'
52
- }
53
- )
54
- d = Mimi::Messaging::Message.new()
55
- raw_message = Mimi::Messaging::Message.new(message).to_msgpack
56
- request_processor = new(d, metadata, raw_message)
57
- request_processor.request.response
58
- end
59
-
60
- # MockRequestProcessor helper POST method
61
- #
62
- # @param method_name [Symbol]
63
- # @param message [Hash]
64
- #
65
- # @return [Mimi::Messaging::Message]
66
- #
67
- def self.post(method_name, message)
68
- metadata = Mimi::Messaging::Message.new(
69
- headers: {
70
- 'method_name' => method_name.to_s,
71
- 'c' => 'context-id'
72
- }
73
- )
74
- d = Mimi::Messaging::Message.new()
75
- raw_message = Mimi::Messaging::Message.new(message)
76
- request_processor = new(d, metadata, raw_message)
77
- nil
78
- end
79
-
80
- # MockRequestProcessor helper BROADCAST method
81
- #
82
- # @param method_name [Symbol]
83
- # @param message [Hash]
84
- #
85
- # @return [Mimi::Messaging::Message]
86
- #
87
- def self.broadcast(method_name, message)
88
- post(method_name, message)
89
- end
90
- end # class RequestProcessor
91
- end # module Messaging
92
- end # module Mimi