railway-ipc 0.1.7 → 2.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +4 -3
- data/CHANGELOG.md +67 -0
- data/Gemfile +2 -2
- data/README.md +1 -1
- data/Rakefile +10 -4
- data/bin/console +3 -3
- data/bin/rspec +29 -0
- data/bin/rubocop +29 -0
- data/lib/railway_ipc.rb +9 -11
- data/lib/railway_ipc/Rakefile +2 -0
- data/lib/railway_ipc/consumer/consumer.rb +33 -73
- data/lib/railway_ipc/consumer/process_incoming_message.rb +111 -0
- data/lib/railway_ipc/errors.rb +9 -1
- data/lib/railway_ipc/handler.rb +17 -3
- data/lib/railway_ipc/handler_store.rb +5 -2
- data/lib/railway_ipc/incoming_message.rb +51 -0
- data/lib/railway_ipc/logger.rb +44 -26
- data/lib/railway_ipc/models/consumed_message.rb +40 -35
- data/lib/railway_ipc/models/published_message.rb +11 -9
- data/lib/railway_ipc/publisher.rb +67 -16
- data/lib/railway_ipc/rabbitmq/adapter.rb +23 -15
- data/lib/railway_ipc/rabbitmq/payload.rb +9 -4
- data/lib/railway_ipc/railtie.rb +2 -0
- data/lib/railway_ipc/responder.rb +10 -3
- data/lib/railway_ipc/response.rb +4 -1
- data/lib/railway_ipc/rpc/client/client.rb +43 -18
- data/lib/railway_ipc/rpc/client/client_response_handlers.rb +2 -0
- data/lib/railway_ipc/rpc/client/errors/timeout_error.rb +2 -0
- data/lib/railway_ipc/rpc/concerns/error_adapter_configurable.rb +2 -0
- data/lib/railway_ipc/rpc/concerns/message_observation_configurable.rb +2 -0
- data/lib/railway_ipc/rpc/concerns/publish_location_configurable.rb +2 -0
- data/lib/railway_ipc/rpc/rpc.rb +2 -0
- data/lib/railway_ipc/rpc/server/server.rb +25 -7
- data/lib/railway_ipc/rpc/server/server_response_handlers.rb +2 -0
- data/lib/railway_ipc/tasks/generate_migrations.rake +16 -16
- data/lib/railway_ipc/tasks/start_consumers.rake +3 -1
- data/lib/railway_ipc/tasks/start_servers.rake +3 -1
- data/lib/railway_ipc/unhandled_message_error.rb +2 -0
- data/lib/railway_ipc/unknown_message.pb.rb +18 -0
- data/lib/railway_ipc/version.rb +3 -1
- data/priv/migrations/add_railway_ipc_consumed_messages.rb +5 -3
- data/priv/migrations/add_railway_ipc_published_messages.rb +3 -1
- data/railway_ipc.gemspec +34 -30
- metadata +62 -64
- data/.rspec +0 -3
- data/.travis.yml +0 -7
- data/CHANGELOG.MD +0 -7
- data/Gemfile.lock +0 -186
- data/lib/railway_ipc/base_message.pb.rb +0 -21
- data/lib/railway_ipc/consumer/consumer_response_handlers.rb +0 -14
- data/lib/railway_ipc/handler_manifest.rb +0 -10
- data/lib/railway_ipc/null_handler.rb +0 -7
- data/lib/railway_ipc/null_message.rb +0 -7
@@ -1,10 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module RailwayIpc
|
2
4
|
module Rabbitmq
|
3
5
|
class Adapter
|
4
|
-
class TimeoutError < StandardError;
|
5
|
-
end
|
6
|
+
class TimeoutError < StandardError; end
|
6
7
|
extend Forwardable
|
7
8
|
attr_reader :connection, :exchange, :exchange_name, :queue, :queue_name, :channel
|
9
|
+
|
8
10
|
def_delegators :connection,
|
9
11
|
:automatically_recover?,
|
10
12
|
:connected?,
|
@@ -14,24 +16,26 @@ module RailwayIpc
|
|
14
16
|
:port,
|
15
17
|
:user
|
16
18
|
|
17
|
-
def initialize(amqp_url: ENV[
|
19
|
+
def initialize(amqp_url: ENV['RAILWAY_RABBITMQ_CONNECTION_URL'], exchange_name:, queue_name: '', options: {})
|
18
20
|
@queue_name = queue_name
|
19
21
|
@exchange_name = exchange_name
|
20
22
|
settings = AMQ::Settings.parse_amqp_url(amqp_url)
|
21
23
|
vhost = settings[:vhost] || '/'
|
22
24
|
@connection = Bunny.new({
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
25
|
+
host: settings[:host],
|
26
|
+
user: settings[:user],
|
27
|
+
pass: settings[:pass],
|
28
|
+
port: settings[:port],
|
29
|
+
vhost: vhost,
|
30
|
+
automatic_recovery: false,
|
31
|
+
logger: RailwayIpc.logger
|
32
|
+
}.merge(options))
|
31
33
|
end
|
32
34
|
|
33
|
-
def publish(message, options
|
35
|
+
def publish(message, options={})
|
36
|
+
# rubocop:disable Style/SafeNavigation
|
34
37
|
exchange.publish(message, options) if exchange
|
38
|
+
# rubocop:enable Style/SafeNavigation
|
35
39
|
end
|
36
40
|
|
37
41
|
def reply(message, from)
|
@@ -66,17 +70,19 @@ module RailwayIpc
|
|
66
70
|
self
|
67
71
|
end
|
68
72
|
|
69
|
-
def create_exchange(strategy: :fanout, options: {durable: true})
|
70
|
-
@exchange = Bunny::Exchange.new(connection.channel,
|
73
|
+
def create_exchange(strategy: :fanout, options: { durable: true })
|
74
|
+
@exchange = Bunny::Exchange.new(connection.channel, strategy, exchange_name, options)
|
71
75
|
self
|
72
76
|
end
|
73
77
|
|
74
78
|
def delete_exchange
|
79
|
+
# rubocop:disable Style/SafeNavigation
|
75
80
|
exchange.delete if exchange
|
81
|
+
# rubocop:enable Style/SafeNavigation
|
76
82
|
self
|
77
83
|
end
|
78
84
|
|
79
|
-
def create_queue(options
|
85
|
+
def create_queue(options={ durable: true })
|
80
86
|
@queue = @channel.queue(queue_name, options)
|
81
87
|
self
|
82
88
|
end
|
@@ -87,7 +93,9 @@ module RailwayIpc
|
|
87
93
|
end
|
88
94
|
|
89
95
|
def delete_queue
|
96
|
+
# rubocop:disable Style/SafeNavigation
|
90
97
|
queue.delete if queue
|
98
|
+
# rubocop:enable Style/SafeNavigation
|
91
99
|
self
|
92
100
|
end
|
93
101
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module RailwayIpc
|
2
4
|
module Rabbitmq
|
3
5
|
class Payload
|
@@ -7,6 +9,7 @@ module RailwayIpc
|
|
7
9
|
type = message.class.to_s
|
8
10
|
begin
|
9
11
|
message = Base64.encode64(message.class.encode(message))
|
12
|
+
# TODO: also need to rescue Google::Protobuf::TypeError
|
10
13
|
rescue NoMethodError
|
11
14
|
raise RailwayIpc::InvalidProtobuf.new("Message #{message} is not a valid protobuf")
|
12
15
|
end
|
@@ -15,8 +18,8 @@ module RailwayIpc
|
|
15
18
|
|
16
19
|
def self.decode(message)
|
17
20
|
message = JSON.parse(message)
|
18
|
-
type = message[
|
19
|
-
message = Base64.decode64(message[
|
21
|
+
type = message['type']
|
22
|
+
message = Base64.decode64(message['encoded_message'])
|
20
23
|
new(type, message)
|
21
24
|
end
|
22
25
|
|
@@ -25,12 +28,14 @@ module RailwayIpc
|
|
25
28
|
@message = message
|
26
29
|
end
|
27
30
|
|
31
|
+
# rubocop:disable Lint/ToJSON
|
28
32
|
def to_json
|
29
33
|
{
|
30
|
-
|
31
|
-
|
34
|
+
type: type,
|
35
|
+
encoded_message: message
|
32
36
|
}.to_json
|
33
37
|
end
|
38
|
+
# rubocop:enable Lint/ToJSON
|
34
39
|
end
|
35
40
|
end
|
36
41
|
end
|
data/lib/railway_ipc/railtie.rb
CHANGED
@@ -1,17 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module RailwayIpc
|
2
4
|
class Responder
|
3
5
|
def self.respond(&block)
|
4
6
|
@block = block
|
5
7
|
end
|
6
8
|
|
7
|
-
|
8
|
-
|
9
|
+
class << self
|
10
|
+
attr_reader :block
|
9
11
|
end
|
10
12
|
|
11
13
|
def respond(request)
|
12
|
-
RailwayIpc.logger.info(
|
14
|
+
RailwayIpc.logger.info(
|
15
|
+
'Responding to request',
|
16
|
+
protobuf: { type: request.class, data: request },
|
17
|
+
feature: 'railway_ipc_request'
|
18
|
+
)
|
13
19
|
response = self.class.block.call(request)
|
14
20
|
raise ResponseTypeError.new(response.class) unless response.is_a?(Google::Protobuf::MessageExts)
|
21
|
+
|
15
22
|
response
|
16
23
|
end
|
17
24
|
|
data/lib/railway_ipc/response.rb
CHANGED
@@ -1,12 +1,15 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require
|
4
|
-
require
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'railway_ipc/rpc/client/client_response_handlers'
|
4
|
+
require 'railway_ipc/rpc/concerns/publish_location_configurable'
|
5
|
+
require 'railway_ipc/rpc/concerns/error_adapter_configurable'
|
6
|
+
require 'railway_ipc/rpc/client/errors/timeout_error'
|
5
7
|
|
6
8
|
module RailwayIpc
|
7
9
|
class Client
|
8
10
|
attr_accessor :response_message, :request_message
|
9
11
|
attr_reader :rabbit_connection, :message
|
12
|
+
|
10
13
|
extend RailwayIpc::RPC::PublishLocationConfigurable
|
11
14
|
extend RailwayIpc::RPC::ErrorAdapterConfigurable
|
12
15
|
|
@@ -18,12 +21,12 @@ module RailwayIpc
|
|
18
21
|
RPC::ClientResponseHandlers.instance.register(response_type)
|
19
22
|
end
|
20
23
|
|
21
|
-
def initialize(request_message, opts
|
24
|
+
def initialize(request_message, opts={ automatic_recovery: false }, rabbit_adapter: RailwayIpc::Rabbitmq::Adapter)
|
22
25
|
@rabbit_connection = rabbit_adapter.new(exchange_name: self.class.exchange_name, options: opts)
|
23
26
|
@request_message = request_message
|
24
27
|
end
|
25
28
|
|
26
|
-
def request(timeout
|
29
|
+
def request(timeout=10)
|
27
30
|
setup_rabbit_connection
|
28
31
|
attach_reply_queue_to_message
|
29
32
|
publish_message
|
@@ -35,18 +38,28 @@ module RailwayIpc
|
|
35
38
|
RailwayIpc::RPC::ClientResponseHandlers.instance.registered
|
36
39
|
end
|
37
40
|
|
41
|
+
# rubocop:disable Metrics/AbcSize
|
42
|
+
# rubocop:disable Metrics/MethodLength
|
38
43
|
def process_payload(response)
|
39
44
|
decoded_payload = decode_payload(response)
|
40
45
|
case decoded_payload.type
|
41
46
|
when *registered_handlers
|
42
47
|
@message = get_message_class(decoded_payload).decode(decoded_payload.message)
|
43
|
-
RailwayIpc.logger.info(
|
48
|
+
RailwayIpc.logger.info(
|
49
|
+
'Handling response',
|
50
|
+
feature: 'railway_ipc_consumer',
|
51
|
+
exchange: self.class.exchange_name,
|
52
|
+
queue: self.class.queue_name,
|
53
|
+
protobuf: { type: message.class, data: message }
|
54
|
+
)
|
44
55
|
RailwayIpc::Response.new(message, success: true)
|
45
56
|
else
|
46
57
|
@message = LearnIpc::ErrorMessage.decode(decoded_payload.message)
|
47
|
-
raise RailwayIpc::UnhandledMessageError
|
58
|
+
raise RailwayIpc::UnhandledMessageError.new("#{self.class} does not know how to handle #{decoded_payload.type}")
|
48
59
|
end
|
49
60
|
end
|
61
|
+
# rubocop:enable Metrics/MethodLength
|
62
|
+
# rubocop:enable Metrics/AbcSize
|
50
63
|
|
51
64
|
def setup_rabbit_connection
|
52
65
|
rabbit_connection
|
@@ -60,7 +73,9 @@ module RailwayIpc
|
|
60
73
|
self.response_message = process_payload(payload)
|
61
74
|
end
|
62
75
|
rescue RailwayIpc::Rabbitmq::Adapter::TimeoutError
|
76
|
+
# rubocop:disable Style/RedundantSelf
|
63
77
|
error = self.class.rpc_error_adapter_class.error_message(TimeoutError.new, self.request_message)
|
78
|
+
# rubocop:enable Style/RedundantSelf
|
64
79
|
self.response_message = RailwayIpc::Response.new(error, success: false)
|
65
80
|
rescue StandardError
|
66
81
|
self.response_message = RailwayIpc::Response.new(message, success: false)
|
@@ -70,12 +85,14 @@ module RailwayIpc
|
|
70
85
|
|
71
86
|
private
|
72
87
|
|
73
|
-
def log_exception(
|
74
|
-
RailwayIpc.logger.
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
88
|
+
def log_exception(exception, payload)
|
89
|
+
RailwayIpc.logger.error(
|
90
|
+
exception.message,
|
91
|
+
feature: 'railway_ipc_consumer',
|
92
|
+
exchange: self.class.exchange_name,
|
93
|
+
queue: self.class.queue_name,
|
94
|
+
error: exception.class,
|
95
|
+
payload: decode_for_error(exception, payload)
|
79
96
|
)
|
80
97
|
end
|
81
98
|
|
@@ -92,13 +109,21 @@ module RailwayIpc
|
|
92
109
|
end
|
93
110
|
|
94
111
|
def publish_message
|
95
|
-
RailwayIpc.logger.info(
|
96
|
-
|
112
|
+
RailwayIpc.logger.info(
|
113
|
+
'Sending request',
|
114
|
+
feature: 'railway_ipc_publisher',
|
115
|
+
exchange: self.class.exchange_name,
|
116
|
+
protobuf: { type: request_message.class, data: request_message }
|
117
|
+
)
|
118
|
+
rabbit_connection.publish(RailwayIpc::Rabbitmq::Payload.encode(request_message), routing_key: '')
|
97
119
|
end
|
98
120
|
|
99
|
-
def decode_for_error(
|
100
|
-
return
|
121
|
+
def decode_for_error(exception, payload)
|
122
|
+
return exception.message unless payload
|
123
|
+
|
124
|
+
# rubocop:disable Style/RedundantSelf
|
101
125
|
self.class.rpc_error_adapter_class.error_message(payload, self.request_message)
|
126
|
+
# rubocop:enable Style/RedundantSelf
|
102
127
|
end
|
103
128
|
end
|
104
129
|
end
|
data/lib/railway_ipc/rpc/rpc.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'railway_ipc/rpc/server/server_response_handlers'
|
2
4
|
require 'railway_ipc/rpc/concerns/error_adapter_configurable'
|
3
5
|
require 'railway_ipc/rpc/concerns/message_observation_configurable'
|
@@ -12,7 +14,7 @@ module RailwayIpc
|
|
12
14
|
RailwayIpc::RPC::ServerResponseHandlers.instance.register(handler: with, message: message_type)
|
13
15
|
end
|
14
16
|
|
15
|
-
def initialize(opts
|
17
|
+
def initialize(opts={ automatic_recovery: true }, rabbit_adapter: RailwayIpc::Rabbitmq::Adapter)
|
16
18
|
@rabbit_connection = rabbit_adapter.new(
|
17
19
|
queue_name: self.class.queue_name,
|
18
20
|
exchange_name: self.class.exchange_name,
|
@@ -29,6 +31,8 @@ module RailwayIpc
|
|
29
31
|
subscribe_to_queue
|
30
32
|
end
|
31
33
|
|
34
|
+
# rubocop:disable Metrics/AbcSize
|
35
|
+
# rubocop:disable Metrics/MethodLength
|
32
36
|
def work(payload)
|
33
37
|
decoded_payload = RailwayIpc::Rabbitmq::Payload.decode(payload)
|
34
38
|
case decoded_payload.type
|
@@ -38,22 +42,35 @@ module RailwayIpc
|
|
38
42
|
responder.respond(message)
|
39
43
|
else
|
40
44
|
@message = LearnIpc::ErrorMessage.decode(decoded_payload.message)
|
41
|
-
raise RailwayIpc::UnhandledMessageError
|
45
|
+
raise RailwayIpc::UnhandledMessageError.new("#{self.class} does not know how to handle #{decoded_payload.type}")
|
42
46
|
end
|
43
47
|
rescue StandardError => e
|
44
|
-
RailwayIpc.logger.
|
45
|
-
|
48
|
+
RailwayIpc.logger.error(
|
49
|
+
e.message,
|
50
|
+
feature: 'railway_ipc_consumer',
|
51
|
+
exchange: self.class.exchange_name,
|
52
|
+
queue: self.class.queue_name,
|
46
53
|
error: e.class,
|
47
|
-
error_message: e.message,
|
48
54
|
payload: payload
|
49
55
|
)
|
50
56
|
raise e
|
51
57
|
end
|
58
|
+
# rubocop:enable Metrics/AbcSize
|
59
|
+
# rubocop:enable Metrics/MethodLength
|
52
60
|
|
61
|
+
# rubocop:disable Metrics/AbcSize
|
62
|
+
# rubocop:disable Metrics/MethodLength
|
53
63
|
def handle_request(payload)
|
54
64
|
response = work(payload)
|
55
65
|
rescue StandardError => e
|
56
|
-
RailwayIpc.logger.error(
|
66
|
+
RailwayIpc.logger.error(
|
67
|
+
'Error responding to message.',
|
68
|
+
exception: e,
|
69
|
+
feature: 'railway_ipc_consumer',
|
70
|
+
exchange: self.class.exchange_name,
|
71
|
+
queue: self.class.queue_name,
|
72
|
+
protobuf: { type: message.class, data: message }
|
73
|
+
)
|
57
74
|
response = self.class.rpc_error_adapter_class.error_message(e, message)
|
58
75
|
ensure
|
59
76
|
if response
|
@@ -62,6 +79,8 @@ module RailwayIpc
|
|
62
79
|
)
|
63
80
|
end
|
64
81
|
end
|
82
|
+
# rubocop:enable Metrics/AbcSize
|
83
|
+
# rubocop:enable Metrics/MethodLength
|
65
84
|
|
66
85
|
private
|
67
86
|
|
@@ -84,6 +103,5 @@ module RailwayIpc
|
|
84
103
|
handle_request(payload)
|
85
104
|
end
|
86
105
|
end
|
87
|
-
|
88
106
|
end
|
89
107
|
end
|
@@ -1,25 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'fileutils'
|
2
4
|
|
3
5
|
namespace :railway_ipc do
|
4
6
|
namespace :generate do
|
5
|
-
desc
|
7
|
+
desc 'Generates migrations to store Railway messages'
|
6
8
|
task :migrations do
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
9
|
+
raise 'Migration generation requires active record' unless defined?(ActiveRecord::Base)
|
10
|
+
|
11
|
+
puts 'generating Railway IPC table migrations'
|
12
|
+
seconds = 0
|
13
|
+
gem_path = Gem.loaded_specs['railway-ipc'].full_gem_path
|
14
|
+
folder_dest = "#{Rails.root}/db/migrate"
|
15
|
+
FileUtils.mkdir_p(folder_dest)
|
13
16
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
end
|
21
|
-
else
|
22
|
-
raise "Migration generation requires active record"
|
17
|
+
Dir.glob("#{gem_path}/priv/migrations/*.rb").each do |file_path|
|
18
|
+
file_name = File.basename(file_path)
|
19
|
+
migration_timestamp = (Time.now + seconds).utc.strftime('%Y%m%d%H%M%S') % '%.14d'
|
20
|
+
new_file_name = "#{migration_timestamp}_#{file_name}"
|
21
|
+
FileUtils.copy_file(file_path, "#{folder_dest}/#{new_file_name}")
|
22
|
+
seconds += 1
|
23
23
|
end
|
24
24
|
end
|
25
25
|
end
|