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
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mimi
4
+ module Messaging
5
+ #
6
+ # Common namespace for all Mimi Messaging adapters
7
+ #
8
+ module Adapters
9
+ # Returns a Hash containing all registered adapters
10
+ #
11
+ # @return [Hash{String => Class < Mimi::Messaging::Adapters::Base}]
12
+ #
13
+ def self.registered_adapters
14
+ @registered_adapters ||= {}
15
+ end
16
+ end # module Adapters
17
+ end # module Messaging
18
+ end # module Mimi
19
+
20
+ require_relative "adapters/base"
21
+ require_relative "adapters/memory"
22
+ require_relative "adapters/test"
@@ -0,0 +1,233 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mimi
4
+ module Messaging
5
+ module Adapters
6
+ #
7
+ # An abstract messaging adapter.
8
+ #
9
+ # An adapter implementation must implement the following methods:
10
+ # * #start()
11
+ # * #stop()
12
+ # * #command(target, message, opts)
13
+ # * #query(target, message, opts)
14
+ # * #event(target, message, opts)
15
+ # * #start_request_processor(queue_name, processor, opts)
16
+ # * #start_event_processor(topic_name, processor, opts)
17
+ # * #start_event_processor_with_queue(topic_name, queue_name, processor, opts)
18
+ # * #stop_all_processors
19
+ #
20
+ # An adapter implementation must register itself using `.register_adapter_name` method.
21
+ #
22
+ class Base
23
+ attr_reader :serializer
24
+
25
+ # Registers adapter class with given adapter name
26
+ #
27
+ # @param adapter_name [String,Symbol]
28
+ #
29
+ def self.register_adapter_name(adapter_name)
30
+ adapter_name = adapter_name.to_s
31
+ if Mimi::Messaging::Adapters.registered_adapters.key?(adapter_name)
32
+ raise "Mimi::Messaging adapter '#{adapter_name}' is already registered"
33
+ end
34
+
35
+ Mimi::Messaging::Adapters.registered_adapters[adapter_name] = self
36
+ end
37
+
38
+ # Creates an Adapter instance
39
+ #
40
+ # @param params [Hash] adapter-specific configuration parameters
41
+ #
42
+ def initialize(params = {})
43
+ end
44
+
45
+ # Starts the adapter.
46
+ #
47
+ # All the message processors must be started after the adapter is started.
48
+ # Before the adapter is started it MAY respond with an error to an attempt
49
+ # to start a message processor.
50
+ #
51
+ # Serializer must be registered before any message is sent or received.
52
+ #
53
+ def start
54
+ raise "Method #start() is not implemented by #{self.class}"
55
+ end
56
+
57
+ # Stops all message processors and then stops the adapter.
58
+ #
59
+ def stop
60
+ raise "Method #stop() is not implemented by #{self.class}"
61
+ end
62
+
63
+ # Sends the command to the given target
64
+ #
65
+ # @param target [String] "<queue>/<method>"
66
+ # @param message [Mimi::Messaging::Message]
67
+ # @param opts [Hash] additional options
68
+ #
69
+ # @return nil
70
+ # @raise [SomeError]
71
+ #
72
+ def command(_target, _message, _opts = {})
73
+ raise "Method #command(target, message, opts) is not implemented by #{self.class}"
74
+ end
75
+
76
+ # Executes the query to the given target and returns response
77
+ #
78
+ # @param target [String] "<queue>/<method>"
79
+ # @param message [Mimi::Messaging::Message]
80
+ # @param opts [Hash] additional options, e.g. :timeout
81
+ #
82
+ # @return [Hash]
83
+ # @raise [SomeError,TimeoutError]
84
+ #
85
+ def query(_target, _message, _opts = {})
86
+ raise "Method #query(target, message, opts) is not implemented by #{self.class}"
87
+ end
88
+
89
+ # Broadcasts the event with the given target
90
+ #
91
+ # @param target [String] "<topic>#<event_type>", e.g. "customers#created"
92
+ # @param message [Mimi::Messaging::Message]
93
+ # @param opts [Hash] additional options
94
+ #
95
+ def event(_target, _message, _opts = {})
96
+ raise "Method #event(target, message, opts) is not implemented by #{self.class}"
97
+ end
98
+
99
+ # Starts a request (command/query) processor.
100
+ #
101
+ # Processor must respond to #call_command() AND #call_query()
102
+ # which accepts 3 arguments: (method, message, opts).
103
+ #
104
+ # TBD: It must #ack! or #nack! the message.
105
+ #
106
+ # If the processor raises an error, the message will be NACK-ed and accepted again
107
+ # at a later time.
108
+ #
109
+ # NOTE: Method must be overloaded by a subclass.
110
+ #
111
+ # @param queue_name [String] "<queue>"
112
+ # @param processor [#call_command(),#call_query()]
113
+ # @param opts [Hash] additional adapter-specific options
114
+ #
115
+ def start_request_processor(_queue_name, processor, _opts = {})
116
+ # validates processor
117
+ if (
118
+ processor.respond_to?(:call_command) && processor.method(:call_command).arity >= 3 &&
119
+ processor.respond_to?(:call_query) && processor.method(:call_query).arity >= 3
120
+ )
121
+ return
122
+ end
123
+
124
+ raise(
125
+ ArgumentError,
126
+ "Invalid request processor passed to #{self.class}##{__method__}(), " \
127
+ "expected to respond to #call_command(method_name, message, opts) AND #call_query(...)"
128
+ )
129
+ end
130
+
131
+ # Starts an event processor without a queue
132
+ #
133
+ # Processor must respond to #call_event() which accepts 3 arguments:
134
+ # (method, message, opts).
135
+ #
136
+ # TBD: It must #ack! or #nack! the message.
137
+ #
138
+ # If the processor raises an error, the message will be NACK-ed and accepted again
139
+ # at a later time.
140
+ #
141
+ # @param topic_name [String] "<topic>"
142
+ # @param processor [#call_event()]
143
+ # @param opts [Hash] additional adapter-specific options
144
+ #
145
+ def start_event_processor(_topic_name, processor, _opts = {})
146
+ # validates processor
147
+ return if processor.respond_to?(:call_event) && processor.method(:call_event).arity >= 3
148
+
149
+ raise(
150
+ ArgumentError,
151
+ "Invalid event processor passed to #{self.class}##{__method__}(), " \
152
+ "expected to respond to #call_event(event_type, message, opts)"
153
+ )
154
+ end
155
+
156
+ # Starts an event processor with a queue
157
+ #
158
+ # Processor must respond to #call_event() which accepts 3 arguments:
159
+ # (method, message, opts).
160
+ #
161
+ # TBD: It must #ack! or #nack! the message.
162
+ #
163
+ # If the processor raises an error, the message will be NACK-ed and accepted again
164
+ # at a later time.
165
+ #
166
+ # @param topic_name [String] "<topic>"
167
+ # @param queue_name [String] "<queue>"
168
+ # @param processor [#call_event()]
169
+ # @param opts [Hash] additional adapter-specific options
170
+ #
171
+ def start_event_processor_with_queue(_topic_name, _queue_name, processor, _opts = {})
172
+ # validates processor
173
+ return if processor.respond_to?(:call_event) && processor.method(:call_event).arity >= 3
174
+
175
+ raise(
176
+ ArgumentError,
177
+ "Invalid event processor passed to #{self.class}##{__method__}(), " \
178
+ "expected to respond to #call_event(event_type, message, opts)"
179
+ )
180
+ end
181
+
182
+ # Registers the message serializer
183
+ #
184
+ # Message serializer must implement methods #serialize(Hash) -> String
185
+ # and #deserialize(String) -> Hash
186
+ #
187
+ # @param serializer [#serialize(),#deserialize()]
188
+ #
189
+ def register_message_serializer(serializer)
190
+ raise "Message serializer is already registered in #{self.class}" if @serializer
191
+ if !serializer.respond_to?(:serialize) || !serializer.respond_to?(:deserialize)
192
+ raise "Invalid message serializer passed to #{self.class}"
193
+ end
194
+
195
+ @serializer = serializer
196
+ end
197
+
198
+ # Stops all message (command, query and event) processors.
199
+ #
200
+ # Stops currently registered processors and stops accepting new messages
201
+ # for processors.
202
+ #
203
+ def stop_all_processors
204
+ raise "Method #stop_all_processors() is not implemented by #{self.class}"
205
+ end
206
+
207
+ protected
208
+
209
+ # Serializes a message (Hash) to be sent on-the-wire using configured serializer
210
+ #
211
+ # @param message [Hash]
212
+ # @return [String]
213
+ #
214
+ def serialize(message)
215
+ raise "Message serializer is not registered in #{self.class}" unless @serializer
216
+
217
+ @serializer.serialize(message)
218
+ end
219
+
220
+ # Deserializes a message (String) received on-the-wire to a Hash
221
+ #
222
+ # @param message [String]
223
+ # @return [Hash]
224
+ #
225
+ def deserialize(message)
226
+ raise "Message serializer is not registered in #{self.class}" unless @serializer
227
+
228
+ @serializer.deserialize(message)
229
+ end
230
+ end # class Base
231
+ end # module Adapters
232
+ end # module Messaging
233
+ end # module Mimi
@@ -0,0 +1,147 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "timeout"
4
+
5
+ module Mimi
6
+ module Messaging
7
+ module Adapters
8
+ #
9
+ # A Memory is an in-memory implementation of a messaging adapter.
10
+ #
11
+ # All message dispatching happens within a single thread, the same as the caller's,
12
+ # so all ivocations are synchronous.
13
+ #
14
+ # The Memory purpose is only to use in tests and for the higher abstractions
15
+ # development.
16
+ #
17
+ class Memory < Base
18
+ register_adapter_name "memory"
19
+
20
+ def start
21
+ end
22
+
23
+ def stop
24
+ end
25
+
26
+ # Sends COMMAND to target
27
+ #
28
+ # @param target [String]
29
+ # @param message [Mimi::Messaging::Message]
30
+ # @param opts [Hash]
31
+ #
32
+ def command(target, message, opts = {})
33
+ raise ArgumentError, "Message is expected" unless message.is_a?(Mimi::Messaging::Message)
34
+ dispatch_command(target, message, opts)
35
+ nil
36
+ end
37
+
38
+ # Sends QUERY to target
39
+ #
40
+ # @param target [String]
41
+ # @param message [Mimi::Messaging::Message]
42
+ # @param opts [Hash]
43
+ #
44
+ def query(target, message, opts = {})
45
+ raise ArgumentError, "Message is expected" unless message.is_a?(Mimi::Messaging::Message)
46
+ response_serialized = dispatch_query(target, message, opts)
47
+ deserialize(response_serialized)
48
+ end
49
+
50
+ # Sends EVENT to target
51
+ #
52
+ # @param target [String]
53
+ # @param message [Mimi::Messaging::Message]
54
+ # @param opts [Hash]
55
+ #
56
+ def event(target, message, opts = {})
57
+ raise ArgumentError, "Message is expected" unless message.is_a?(Mimi::Messaging::Message)
58
+ dispatch_event(target, message, opts)
59
+ end
60
+
61
+ def start_request_processor(queue_name, processor, _opts = {})
62
+ super
63
+ request_processors[queue_name] ||= []
64
+ request_processors[queue_name] << processor
65
+ end
66
+
67
+ def start_event_processor(topic_name, processor, _opts = {})
68
+ super
69
+ event_processors[topic_name] ||= []
70
+ event_processors[topic_name] << processor
71
+ end
72
+
73
+ def start_event_processor_with_queue(topic_name, queue_name, processor, opts = {})
74
+ super
75
+ event_processors_with_queue[topic_name] ||= {}
76
+ event_processors_with_queue[topic_name][queue_name] ||= []
77
+ event_processors_with_queue[topic_name][queue_name] << processor
78
+ end
79
+
80
+ def stop_all_processors
81
+ @request_processors = {}
82
+ @event_processors = {}
83
+ @event_processors_with_queue = {}
84
+ end
85
+
86
+ private
87
+
88
+ # Simulates a transmitted message, following serialization/deserialization:
89
+ # message out -> message in
90
+ #
91
+ # @param message [Mimi::Messaging::Message]
92
+ # @return [Mimi::Messaging::Message]
93
+ #
94
+ def transmitted_message(message)
95
+ Mimi::Messaging::Message.new(
96
+ deserialize(serialize(message)),
97
+ message.headers
98
+ )
99
+ end
100
+
101
+ def dispatch_command(target, message, _opts = {})
102
+ queue_name, method_name = target.split("/")
103
+ return unless request_processors[queue_name]
104
+
105
+ # pick random processor serving the target
106
+ processor = request_processors[queue_name].sample
107
+ processor.call_command(method_name, transmitted_message(message), {})
108
+ end
109
+
110
+ def dispatch_query(target, message, _opts = {})
111
+ queue_name, method_name = target.split("/")
112
+ raise Timeout::Error unless request_processors[queue_name]
113
+
114
+ # pick random processor serving the target
115
+ processor = request_processors[queue_name].sample
116
+ response = processor.call_query(method_name, transmitted_message(message), {})
117
+ serialize(response)
118
+ end
119
+
120
+ def dispatch_event(target, message_serialized, _opts = {})
121
+ topic_name, event_type = target.split("#")
122
+ processors = event_processors[topic_name] || []
123
+ processor_queues = event_processors_with_queue[topic_name] || {}
124
+ processor_queues.values.each do |same_queue_processors|
125
+ processors << same_queue_processors.sample
126
+ end
127
+
128
+ processors.each do |processor|
129
+ processor.call_event(event_type, transmitted_message(message), {})
130
+ end
131
+ end
132
+
133
+ def request_processors
134
+ @request_processors ||= {}
135
+ end
136
+
137
+ def event_processors
138
+ @event_processors ||= {}
139
+ end
140
+
141
+ def event_processors_with_queue
142
+ @event_processors_with_queue ||= {}
143
+ end
144
+ end # class Memory
145
+ end # module Adapters
146
+ end # module Messaging
147
+ end # module Mimi
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "timeout"
4
+
5
+ module Mimi
6
+ module Messaging
7
+ module Adapters
8
+ #
9
+ # A Test is a mock adapter object for running tests.
10
+ #
11
+ class Test < Base
12
+ register_adapter_name "test"
13
+
14
+ def start
15
+ end
16
+
17
+ def stop
18
+ end
19
+
20
+ def command(target, message, opts = {})
21
+ end
22
+
23
+ def query(target, message, opts = {})
24
+ end
25
+
26
+ def event(target, message, opts = {})
27
+ end
28
+
29
+ def start_request_processor(queue_name, processor, _opts = {})
30
+ super
31
+ true
32
+ end
33
+
34
+ def start_event_processor(topic_name, processor, _opts = {})
35
+ super
36
+ true
37
+ end
38
+
39
+ def start_event_processor_with_queue(topic_name, queue_name, processor, opts = {})
40
+ super
41
+ true
42
+ end
43
+
44
+ def stop_all_processors
45
+ true
46
+ end
47
+ end # class Test
48
+ end # module Adapters
49
+ end # module Messaging
50
+ end # module Mimi