mimi-messaging 0.1.12 → 1.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.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.rubocop.yml +66 -0
- data/README.md +68 -3
- data/TODO.md +8 -0
- data/docs/Messaging_Layer_Properties.md +141 -0
- data/docs/Why_HTTP_is_a_bad_choice.md +20 -0
- data/docs/diagrams/Pattern -- Command.drawio +1 -0
- data/docs/diagrams/Pattern -- Event direct.drawio +1 -0
- data/docs/diagrams/Pattern -- Event with Queue.drawio +1 -0
- data/docs/diagrams/Pattern -- Event.drawio +1 -0
- data/docs/diagrams/Pattern -- Query.drawio +1 -0
- data/docs/img/pattern--command.png +0 -0
- data/docs/img/pattern--event-direct.png +0 -0
- data/docs/img/pattern--event-using-queue.png +0 -0
- data/docs/img/pattern--event.png +0 -0
- data/docs/img/pattern--query.png +0 -0
- data/examples/basic_event_listener.rb +35 -0
- data/examples/basic_request_processor.rb +38 -0
- data/examples/using_messaging_low.rb +59 -0
- data/examples/using_pure_adapter.rb +62 -0
- data/lib/mimi/messaging.rb +428 -92
- data/lib/mimi/messaging/adapters.rb +22 -0
- data/lib/mimi/messaging/adapters/base.rb +233 -0
- data/lib/mimi/messaging/adapters/memory.rb +119 -0
- data/lib/mimi/messaging/adapters/test.rb +50 -0
- data/lib/mimi/messaging/errors.rb +24 -12
- data/lib/mimi/messaging/json_serializer.rb +45 -0
- data/lib/mimi/messaging/version.rb +3 -1
- data/mimi-messaging.gemspec +25 -23
- metadata +34 -78
- data/lib/mimi/messaging/connection.rb +0 -182
- data/lib/mimi/messaging/listener.rb +0 -72
- data/lib/mimi/messaging/message.rb +0 -74
- data/lib/mimi/messaging/mock.rb +0 -13
- data/lib/mimi/messaging/mock/connection.rb +0 -153
- data/lib/mimi/messaging/mock/request.rb +0 -19
- data/lib/mimi/messaging/mock/request_processor.rb +0 -92
- data/lib/mimi/messaging/model.rb +0 -27
- data/lib/mimi/messaging/model_provider.rb +0 -100
- data/lib/mimi/messaging/msgpack/msgpack_ext.rb +0 -14
- data/lib/mimi/messaging/msgpack/type_packer.rb +0 -104
- data/lib/mimi/messaging/notification.rb +0 -35
- data/lib/mimi/messaging/provider.rb +0 -48
- data/lib/mimi/messaging/request.rb +0 -56
- data/lib/mimi/messaging/request_processor.rb +0 -216
- data/lib/mimi/messaging/request_processor/context.rb +0 -39
- data/lib/mimi/messaging/request_processor/dsl.rb +0 -121
- data/lib/tasks/console_ext.rake +0 -6
- 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 [Hash]
|
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 [Hash]
|
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 [Hash]
|
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(...) AND #call_query(method_name, request, opts)"
|
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(method_name, request, 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(method_name, request, 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,119 @@
|
|
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
|
+
def command(target, message, opts = {})
|
27
|
+
message_serialized = serialize(message)
|
28
|
+
dispatch_command(target, message_serialized, opts)
|
29
|
+
nil
|
30
|
+
end
|
31
|
+
|
32
|
+
def query(target, message, opts = {})
|
33
|
+
message_serialized = serialize(message)
|
34
|
+
response_serialized = dispatch_query(target, message_serialized, opts)
|
35
|
+
deserialize(response_serialized)
|
36
|
+
end
|
37
|
+
|
38
|
+
def event(target, message, opts = {})
|
39
|
+
message_serialized = serialize(message)
|
40
|
+
dispatch_event(target, message_serialized, opts)
|
41
|
+
end
|
42
|
+
|
43
|
+
def start_request_processor(queue_name, processor, _opts = {})
|
44
|
+
super
|
45
|
+
request_processors[queue_name] ||= []
|
46
|
+
request_processors[queue_name] << processor
|
47
|
+
end
|
48
|
+
|
49
|
+
def start_event_processor(topic_name, processor, _opts = {})
|
50
|
+
super
|
51
|
+
event_processors[topic_name] ||= []
|
52
|
+
event_processors[topic_name] << processor
|
53
|
+
end
|
54
|
+
|
55
|
+
def start_event_processor_with_queue(topic_name, queue_name, processor, opts = {})
|
56
|
+
super
|
57
|
+
event_processors_with_queue[topic_name] ||= {}
|
58
|
+
event_processors_with_queue[topic_name][queue_name] ||= []
|
59
|
+
event_processors_with_queue[topic_name][queue_name] << processor
|
60
|
+
end
|
61
|
+
|
62
|
+
def stop_all_processors
|
63
|
+
@request_processors = {}
|
64
|
+
@event_processors = {}
|
65
|
+
@event_processors_with_queue = {}
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def dispatch_command(target, message_serialized, _opts = {})
|
71
|
+
queue_name, method_name = target.split("/")
|
72
|
+
message = deserialize(message_serialized)
|
73
|
+
return unless request_processors[queue_name]
|
74
|
+
|
75
|
+
# pick random processor serving the target
|
76
|
+
processor = request_processors[queue_name].sample
|
77
|
+
processor.call_command(method_name, message, {})
|
78
|
+
end
|
79
|
+
|
80
|
+
def dispatch_query(target, message_serialized, _opts = {})
|
81
|
+
queue_name, method_name = target.split("/")
|
82
|
+
message = deserialize(message_serialized)
|
83
|
+
raise Timeout::Error unless request_processors[queue_name]
|
84
|
+
|
85
|
+
# pick random processor serving the target
|
86
|
+
processor = request_processors[queue_name].sample
|
87
|
+
response = processor.call_query(method_name, message, {})
|
88
|
+
serialize(response)
|
89
|
+
end
|
90
|
+
|
91
|
+
def dispatch_event(target, message_serialized, _opts = {})
|
92
|
+
topic_name, event_type = target.split("/")
|
93
|
+
processors = event_processors[topic_name] || []
|
94
|
+
processor_queues = event_processors_with_queue[topic_name] || {}
|
95
|
+
processor_queues.values.each do |same_queue_processors|
|
96
|
+
processors << same_queue_processors.sample
|
97
|
+
end
|
98
|
+
|
99
|
+
message = deserialize(message_serialized)
|
100
|
+
processors.each do |processor|
|
101
|
+
processor.call_event(event_type, message, {})
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def request_processors
|
106
|
+
@request_processors ||= {}
|
107
|
+
end
|
108
|
+
|
109
|
+
def event_processors
|
110
|
+
@event_processors ||= {}
|
111
|
+
end
|
112
|
+
|
113
|
+
def event_processors_with_queue
|
114
|
+
@event_processors_with_queue ||= {}
|
115
|
+
end
|
116
|
+
end # class Memory
|
117
|
+
end # module Adapters
|
118
|
+
end # module Messaging
|
119
|
+
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
|
@@ -1,19 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Mimi
|
2
4
|
module Messaging
|
3
|
-
|
4
|
-
|
5
|
+
#
|
6
|
+
# Error definitions
|
7
|
+
#
|
8
|
+
|
9
|
+
# Generic error in the Messaging layer
|
10
|
+
#
|
11
|
+
# Base class for other more specific errors raised by Messaging layer
|
12
|
+
#
|
13
|
+
class Error < StandardError; end
|
5
14
|
|
6
|
-
|
7
|
-
|
15
|
+
# Configuration related errors
|
16
|
+
#
|
17
|
+
class ConfigurationError < Error; end
|
8
18
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
19
|
+
# Connection level error
|
20
|
+
#
|
21
|
+
# Raised on errors related to network level, e.g. message broker host not reachable
|
22
|
+
# or authentication/authorization at message broker failed.
|
23
|
+
#
|
24
|
+
class ConnectionError < Error; end
|
13
25
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
26
|
+
# An error raised to indicate that the message should be NACK-ed, but
|
27
|
+
# no additional error logging or processing should happen.
|
28
|
+
#
|
29
|
+
class NACK < Error; end
|
18
30
|
end # module Messaging
|
19
31
|
end # module Mimi
|