railway-ipc 0.1.3 → 0.1.4
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 +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
|