railway-ipc 0.1.3 → 0.1.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +8 -0
- data/.tool-versions +1 -0
- data/Gemfile.lock +125 -1
- data/README.md +5 -0
- data/lib/railway_ipc.rb +28 -21
- data/lib/railway_ipc/base_message.pb.rb +21 -0
- data/lib/railway_ipc/consumer/consumer.rb +112 -0
- data/lib/railway_ipc/consumer/consumer_response_handlers.rb +14 -0
- data/lib/railway_ipc/errors.rb +1 -0
- data/lib/railway_ipc/handler.rb +2 -0
- data/lib/railway_ipc/handler_manifest.rb +10 -0
- data/lib/railway_ipc/handler_store.rb +21 -0
- data/lib/railway_ipc/models/consumed_message.rb +48 -0
- data/lib/railway_ipc/models/published_message.rb +27 -0
- data/lib/railway_ipc/null_message.rb +1 -1
- data/lib/railway_ipc/publisher.rb +9 -4
- data/lib/railway_ipc/rabbitmq/adapter.rb +93 -0
- data/lib/railway_ipc/rabbitmq/payload.rb +7 -3
- data/lib/railway_ipc/rpc/client/client.rb +104 -0
- data/lib/railway_ipc/rpc/client/client_response_handlers.rb +25 -0
- data/lib/railway_ipc/rpc/client/errors/timeout_error.rb +5 -0
- data/lib/railway_ipc/rpc/concerns/error_adapter_configurable.rb +13 -0
- data/lib/railway_ipc/rpc/concerns/message_observation_configurable.rb +18 -0
- data/lib/railway_ipc/rpc/concerns/publish_location_configurable.rb +13 -0
- data/lib/railway_ipc/rpc/rpc.rb +2 -0
- data/lib/railway_ipc/rpc/server/server.rb +89 -0
- data/lib/railway_ipc/rpc/server/server_response_handlers.rb +17 -0
- data/lib/railway_ipc/tasks/generate_migrations.rake +26 -0
- data/lib/railway_ipc/version.rb +1 -1
- data/priv/migrations/add_railway_ipc_consumed_messages.rb +19 -0
- data/priv/migrations/add_railway_ipc_published_messages.rb +18 -0
- data/railway_ipc.gemspec +25 -16
- metadata +123 -8
- data/lib/railway_ipc/client.rb +0 -87
- data/lib/railway_ipc/concerns/message_handling.rb +0 -118
- data/lib/railway_ipc/consumer.rb +0 -26
- data/lib/railway_ipc/rabbitmq/connection.rb +0 -55
- data/lib/railway_ipc/server.rb +0 -50
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'railway_ipc/handler_manifest'
|
2
|
+
module RailwayIpc
|
3
|
+
class HandlerStore
|
4
|
+
attr_reader :handler_map
|
5
|
+
def initialize
|
6
|
+
@handler_map = {}
|
7
|
+
end
|
8
|
+
|
9
|
+
def registered
|
10
|
+
handler_map.keys
|
11
|
+
end
|
12
|
+
|
13
|
+
def register(message:, handler:)
|
14
|
+
handler_map[message.to_s] = HandlerManifest.new(message: message, handler: handler)
|
15
|
+
end
|
16
|
+
|
17
|
+
def get(response_message)
|
18
|
+
handler_map[response_message]
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module RailwayIpc
|
2
|
+
class ConsumedMessage < ActiveRecord::Base
|
3
|
+
STATUSES = {
|
4
|
+
success: 'success',
|
5
|
+
processing: 'processing',
|
6
|
+
unknown_message_type: 'unknown_message_type',
|
7
|
+
failed_to_process: 'failed_to_process'
|
8
|
+
}
|
9
|
+
|
10
|
+
attr_reader :decoded_message
|
11
|
+
self.table_name = 'railway_ipc_consumed_messages'
|
12
|
+
self.primary_key = 'uuid'
|
13
|
+
|
14
|
+
validates :uuid, :status, presence: true
|
15
|
+
validates :status, inclusion: { in: STATUSES.values }
|
16
|
+
|
17
|
+
def processed?
|
18
|
+
self.status == STATUSES[:success]
|
19
|
+
end
|
20
|
+
|
21
|
+
def encoded_protobuf=(encoded_protobuf)
|
22
|
+
self.encoded_message = Base64.encode64(encoded_protobuf)
|
23
|
+
end
|
24
|
+
|
25
|
+
def decoded_message
|
26
|
+
@decoded_message ||= decode_message
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def timestamp_attributes_for_create
|
32
|
+
super << :inserted_at
|
33
|
+
end
|
34
|
+
|
35
|
+
def decode_message
|
36
|
+
begin
|
37
|
+
message_class = Kernel.const_get(self.message_type)
|
38
|
+
rescue NameError
|
39
|
+
message_class = RailwayIpc::BaseMessage
|
40
|
+
end
|
41
|
+
message_class.decode(decoded_protobuf)
|
42
|
+
end
|
43
|
+
|
44
|
+
def decoded_protobuf
|
45
|
+
Base64.decode64(self.encoded_message)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module RailwayIpc
|
2
|
+
class PublishedMessage < ActiveRecord::Base
|
3
|
+
self.table_name = 'railway_ipc_published_messages'
|
4
|
+
self.primary_key = "uuid"
|
5
|
+
|
6
|
+
validates :uuid, :status, presence: true
|
7
|
+
|
8
|
+
def self.store_message(exchange_name, message)
|
9
|
+
encoded_message = RailwayIpc::Rabbitmq::Payload.encode(message)
|
10
|
+
self.create(
|
11
|
+
uuid: message.uuid,
|
12
|
+
message_type: message.class.to_s,
|
13
|
+
user_uuid: message.user_uuid,
|
14
|
+
correlation_id: message.correlation_id,
|
15
|
+
encoded_message: encoded_message,
|
16
|
+
status: "sent",
|
17
|
+
exchange: exchange_name
|
18
|
+
)
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def timestamp_attributes_for_create
|
24
|
+
super << :inserted_at
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'singleton'
|
2
|
+
|
2
3
|
module RailwayIpc
|
3
4
|
class Publisher < Sneakers::Publisher
|
4
5
|
include ::Singleton
|
@@ -12,14 +13,18 @@ module RailwayIpc
|
|
12
13
|
@exchange_name
|
13
14
|
end
|
14
15
|
|
15
|
-
|
16
16
|
def initialize
|
17
17
|
super(exchange: self.class.exchange_name, exchange_type: :fanout)
|
18
18
|
end
|
19
19
|
|
20
|
-
def publish(message)
|
21
|
-
RailwayIpc.logger.info(message,
|
22
|
-
super(RailwayIpc::Rabbitmq::Payload.encode(message))
|
20
|
+
def publish(message, published_message_store=RailwayIpc::PublishedMessage)
|
21
|
+
RailwayIpc.logger.info(message, 'Publishing message')
|
22
|
+
result = super(RailwayIpc::Rabbitmq::Payload.encode(message))
|
23
|
+
published_message_store.store_message(self.class.exchange_name, message)
|
24
|
+
result
|
25
|
+
rescue RailwayIpc::InvalidProtobuf => e
|
26
|
+
RailwayIpc.logger.error(message, 'Invalid protobuf')
|
27
|
+
raise e
|
23
28
|
end
|
24
29
|
end
|
25
30
|
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
module RailwayIpc
|
2
|
+
module Rabbitmq
|
3
|
+
class Adapter
|
4
|
+
class TimeoutError < StandardError;
|
5
|
+
end
|
6
|
+
extend Forwardable
|
7
|
+
attr_reader :connection, :exchange, :exchange_name, :queue, :queue_name, :channel
|
8
|
+
def_delegators :connection,
|
9
|
+
:automatically_recover?,
|
10
|
+
:connected?,
|
11
|
+
:host,
|
12
|
+
:logger,
|
13
|
+
:pass,
|
14
|
+
:port,
|
15
|
+
:user
|
16
|
+
|
17
|
+
def initialize(amqp_url: ENV["RAILWAY_RABBITMQ_CONNECTION_URL"], exchange_name:, queue_name: '', options: {})
|
18
|
+
@queue_name = queue_name
|
19
|
+
@exchange_name = exchange_name
|
20
|
+
settings = AMQ::Settings.parse_amqp_url(amqp_url)
|
21
|
+
@connection = Bunny.new({
|
22
|
+
host: settings[:host],
|
23
|
+
user: settings[:user],
|
24
|
+
pass: settings[:pass],
|
25
|
+
port: settings[:port],
|
26
|
+
automatic_recovery: false,
|
27
|
+
logger: RailwayIpc.bunny_logger}.merge(options)
|
28
|
+
)
|
29
|
+
end
|
30
|
+
|
31
|
+
def publish(message, options = {})
|
32
|
+
exchange.publish(message, options) if exchange
|
33
|
+
end
|
34
|
+
|
35
|
+
def reply(message, from)
|
36
|
+
channel.default_exchange.publish(message, routing_key: from)
|
37
|
+
end
|
38
|
+
|
39
|
+
def subscribe(&block)
|
40
|
+
queue.subscribe(&block)
|
41
|
+
end
|
42
|
+
|
43
|
+
def check_for_message(timeout: 10, time_elapsed: 0, &block)
|
44
|
+
raise TimeoutError.new if time_elapsed >= timeout
|
45
|
+
|
46
|
+
block ||= ->(result) { result }
|
47
|
+
|
48
|
+
result = queue.pop
|
49
|
+
return block.call(*result) if result.compact.any?
|
50
|
+
|
51
|
+
sleep 1
|
52
|
+
check_for_message(timeout: timeout, time_elapsed: time_elapsed + 1, &block)
|
53
|
+
end
|
54
|
+
|
55
|
+
def connect
|
56
|
+
connection.start
|
57
|
+
@channel = connection.channel
|
58
|
+
self
|
59
|
+
end
|
60
|
+
|
61
|
+
def disconnect
|
62
|
+
channel.close
|
63
|
+
connection.close
|
64
|
+
self
|
65
|
+
end
|
66
|
+
|
67
|
+
def create_exchange(strategy: :fanout, options: {durable: true})
|
68
|
+
@exchange = Bunny::Exchange.new(connection.channel, :fanout, exchange_name, options)
|
69
|
+
self
|
70
|
+
end
|
71
|
+
|
72
|
+
def delete_exchange
|
73
|
+
exchange.delete if exchange
|
74
|
+
self
|
75
|
+
end
|
76
|
+
|
77
|
+
def create_queue(options = {durable: true})
|
78
|
+
@queue = @channel.queue(queue_name, options)
|
79
|
+
self
|
80
|
+
end
|
81
|
+
|
82
|
+
def bind_queue_to_exchange
|
83
|
+
queue.bind(exchange)
|
84
|
+
self
|
85
|
+
end
|
86
|
+
|
87
|
+
def delete_queue
|
88
|
+
queue.delete if queue
|
89
|
+
self
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -5,7 +5,11 @@ module RailwayIpc
|
|
5
5
|
|
6
6
|
def self.encode(message)
|
7
7
|
type = message.class.to_s
|
8
|
-
|
8
|
+
begin
|
9
|
+
message = Base64.encode64(message.class.encode(message))
|
10
|
+
rescue NoMethodError
|
11
|
+
raise RailwayIpc::InvalidProtobuf.new("Message #{message} is not a valid protobuf")
|
12
|
+
end
|
9
13
|
new(type, message).to_json
|
10
14
|
end
|
11
15
|
|
@@ -23,8 +27,8 @@ module RailwayIpc
|
|
23
27
|
|
24
28
|
def to_json
|
25
29
|
{
|
26
|
-
|
27
|
-
|
30
|
+
type: type,
|
31
|
+
encoded_message: message
|
28
32
|
}.to_json
|
29
33
|
end
|
30
34
|
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
require "railway_ipc/rpc/client/client_response_handlers"
|
2
|
+
require "railway_ipc/rpc/concerns/publish_location_configurable"
|
3
|
+
require "railway_ipc/rpc/concerns/error_adapter_configurable"
|
4
|
+
require "railway_ipc/rpc/client/errors/timeout_error"
|
5
|
+
|
6
|
+
module RailwayIpc
|
7
|
+
class Client
|
8
|
+
attr_accessor :response_message, :request_message
|
9
|
+
attr_reader :rabbit_connection, :message
|
10
|
+
extend RailwayIpc::RPC::PublishLocationConfigurable
|
11
|
+
extend RailwayIpc::RPC::ErrorAdapterConfigurable
|
12
|
+
|
13
|
+
def self.request(message)
|
14
|
+
new(message).request
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.handle_response(response_type)
|
18
|
+
RPC::ClientResponseHandlers.instance.register(response_type)
|
19
|
+
end
|
20
|
+
|
21
|
+
def initialize(request_message, opts = { automatic_recovery: false }, rabbit_adapter: RailwayIpc::Rabbitmq::Adapter)
|
22
|
+
@rabbit_connection = rabbit_adapter.new(exchange_name: self.class.exchange_name, options: opts)
|
23
|
+
@request_message = request_message
|
24
|
+
end
|
25
|
+
|
26
|
+
def request(timeout = 10)
|
27
|
+
setup_rabbit_connection
|
28
|
+
attach_reply_queue_to_message
|
29
|
+
publish_message
|
30
|
+
await_response(timeout)
|
31
|
+
response_message
|
32
|
+
end
|
33
|
+
|
34
|
+
def registered_handlers
|
35
|
+
RailwayIpc::RPC::ClientResponseHandlers.instance.registered
|
36
|
+
end
|
37
|
+
|
38
|
+
def process_payload(response)
|
39
|
+
decoded_payload = decode_payload(response)
|
40
|
+
case decoded_payload.type
|
41
|
+
when *registered_handlers
|
42
|
+
@message = get_message_class(decoded_payload).decode(decoded_payload.message)
|
43
|
+
RailwayIpc.logger.info(message, "Handling response")
|
44
|
+
RailwayIpc::Response.new(message, success: true)
|
45
|
+
else
|
46
|
+
@message = LearnIpc::ErrorMessage.decode(decoded_payload.message)
|
47
|
+
raise RailwayIpc::UnhandledMessageError, "#{self.class} does not know how to handle #{decoded_payload.type}"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def setup_rabbit_connection
|
52
|
+
rabbit_connection
|
53
|
+
.connect
|
54
|
+
.create_exchange
|
55
|
+
.create_queue(auto_delete: true, exclusive: true)
|
56
|
+
end
|
57
|
+
|
58
|
+
def await_response(timeout)
|
59
|
+
rabbit_connection.check_for_message(timeout: timeout) do |_, _, payload|
|
60
|
+
self.response_message = process_payload(payload)
|
61
|
+
end
|
62
|
+
rescue RailwayIpc::Rabbitmq::Adapter::TimeoutError
|
63
|
+
error = self.class.rpc_error_adapter_class.error_message(TimeoutError.new, self.request_message)
|
64
|
+
self.response_message = RailwayIpc::Response.new(error, success: false)
|
65
|
+
rescue StandardError
|
66
|
+
self.response_message = RailwayIpc::Response.new(message, success: false)
|
67
|
+
ensure
|
68
|
+
rabbit_connection.disconnect
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
def log_exception(e, payload)
|
74
|
+
RailwayIpc.logger.log_exception(
|
75
|
+
feature: "railway_consumer",
|
76
|
+
error: e.class,
|
77
|
+
error_message: e.message,
|
78
|
+
payload: decode_for_error(e, payload),
|
79
|
+
)
|
80
|
+
end
|
81
|
+
|
82
|
+
def get_message_class(decoded_payload)
|
83
|
+
RailwayIpc::RPC::ClientResponseHandlers.instance.get(decoded_payload.type)
|
84
|
+
end
|
85
|
+
|
86
|
+
def decode_payload(response)
|
87
|
+
RailwayIpc::Rabbitmq::Payload.decode(response)
|
88
|
+
end
|
89
|
+
|
90
|
+
def attach_reply_queue_to_message
|
91
|
+
request_message.reply_to = rabbit_connection.queue.name
|
92
|
+
end
|
93
|
+
|
94
|
+
def publish_message
|
95
|
+
RailwayIpc.logger.info(request_message, "Sending request")
|
96
|
+
rabbit_connection.publish(RailwayIpc::Rabbitmq::Payload.encode(request_message), routing_key: "")
|
97
|
+
end
|
98
|
+
|
99
|
+
def decode_for_error(e, payload)
|
100
|
+
return e.message unless payload
|
101
|
+
self.class.rpc_error_adapter_class.error_message(payload, self.request_message)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module RailwayIpc
|
2
|
+
module RPC
|
3
|
+
class ClientResponseHandlers
|
4
|
+
include Singleton
|
5
|
+
|
6
|
+
def registered
|
7
|
+
handler_map.keys
|
8
|
+
end
|
9
|
+
|
10
|
+
def register(response_message)
|
11
|
+
handler_map[response_message.to_s] = response_message
|
12
|
+
end
|
13
|
+
|
14
|
+
def get(response_message)
|
15
|
+
handler_map[response_message]
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def handler_map
|
21
|
+
@handler_map ||= {}
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module RailwayIpc
|
2
|
+
module RPC
|
3
|
+
module MessageObservationConfigurable
|
4
|
+
def listen_to(exchange:, queue:)
|
5
|
+
@exchange_name = exchange
|
6
|
+
@queue_name = queue
|
7
|
+
end
|
8
|
+
|
9
|
+
def queue_name
|
10
|
+
@queue_name
|
11
|
+
end
|
12
|
+
|
13
|
+
def exchange_name
|
14
|
+
@exchange_name
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|