mimi-messaging 0.1.12 → 1.2.2

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.
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