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
data/lib/railway_ipc/errors.rb
CHANGED
@@ -1 +1,9 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# rubocop:disable Style/ClassAndModuleChildren
|
4
|
+
class RailwayIpc::Error < StandardError; end
|
5
|
+
class RailwayIpc::InvalidProtobuf < RailwayIpc::Error; end
|
6
|
+
class RailwayIpc::FailedToStoreOutgoingMessage < RailwayIpc::Error; end
|
7
|
+
class RailwayIpc::IncomingMessage::ParserError < RailwayIpc::Error; end
|
8
|
+
class RailwayIpc::IncomingMessage::InvalidMessage < RailwayIpc::Error; end
|
9
|
+
# rubocop:enable Style/ClassAndModuleChildren
|
data/lib/railway_ipc/handler.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module RailwayIpc
|
2
4
|
class Handler
|
3
5
|
class << self
|
@@ -9,15 +11,27 @@ module RailwayIpc
|
|
9
11
|
end
|
10
12
|
|
11
13
|
def handle(message)
|
12
|
-
RailwayIpc.logger.info(
|
14
|
+
RailwayIpc.logger.info('Handling message', log_message_options(message))
|
13
15
|
response = self.class.block.call(message)
|
14
16
|
if response.success?
|
15
|
-
RailwayIpc.logger.info(
|
17
|
+
RailwayIpc.logger.info('Successfully handled message', log_message_options(message))
|
16
18
|
else
|
17
|
-
RailwayIpc.logger.error(
|
19
|
+
RailwayIpc.logger.error('Failed to handle message', log_message_options(message))
|
18
20
|
end
|
19
21
|
|
20
22
|
response
|
21
23
|
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def log_message_options(message)
|
28
|
+
{
|
29
|
+
feature: 'railway_ipc_consumer',
|
30
|
+
protobuf: {
|
31
|
+
type: message.class.name,
|
32
|
+
data: message
|
33
|
+
}
|
34
|
+
}
|
35
|
+
end
|
22
36
|
end
|
23
37
|
end
|
@@ -1,7 +1,10 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module RailwayIpc
|
4
|
+
HandlerManifest = Struct.new(:message, :handler)
|
3
5
|
class HandlerStore
|
4
6
|
attr_reader :handler_map
|
7
|
+
|
5
8
|
def initialize
|
6
9
|
@handler_map = {}
|
7
10
|
end
|
@@ -11,7 +14,7 @@ module RailwayIpc
|
|
11
14
|
end
|
12
15
|
|
13
16
|
def register(message:, handler:)
|
14
|
-
handler_map[message.to_s] = HandlerManifest.new(message
|
17
|
+
handler_map[message.to_s] = HandlerManifest.new(message, handler)
|
15
18
|
end
|
16
19
|
|
17
20
|
def get(response_message)
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RailwayIpc
|
4
|
+
class IncomingMessage
|
5
|
+
attr_reader :type, :payload, :parsed_payload, :errors
|
6
|
+
|
7
|
+
def initialize(payload)
|
8
|
+
@parsed_payload = JSON.parse(payload)
|
9
|
+
@type = parsed_payload['type']
|
10
|
+
@payload = payload
|
11
|
+
@errors = {}
|
12
|
+
rescue JSON::ParserError => e
|
13
|
+
raise RailwayIpc::IncomingMessage::ParserError.new(e)
|
14
|
+
end
|
15
|
+
|
16
|
+
def uuid
|
17
|
+
decoded.uuid
|
18
|
+
end
|
19
|
+
|
20
|
+
def user_uuid
|
21
|
+
decoded.user_uuid
|
22
|
+
end
|
23
|
+
|
24
|
+
def correlation_id
|
25
|
+
decoded.correlation_id
|
26
|
+
end
|
27
|
+
|
28
|
+
def valid?
|
29
|
+
errors[:uuid] = 'uuid is required' unless uuid.present?
|
30
|
+
errors[:correlation_id] = 'correlation_id is required' unless correlation_id.present?
|
31
|
+
errors.none?
|
32
|
+
end
|
33
|
+
|
34
|
+
def decoded
|
35
|
+
@decoded ||=
|
36
|
+
begin
|
37
|
+
protobuf_msg = Base64.decode64(parsed_payload['encoded_message'])
|
38
|
+
decoder = Kernel.const_get(type)
|
39
|
+
decoder.decode(protobuf_msg)
|
40
|
+
rescue Google::Protobuf::ParseError => e
|
41
|
+
raise RailwayIpc::IncomingMessage::ParserError.new(e)
|
42
|
+
rescue NameError
|
43
|
+
RailwayIpc::Messages::Unknown.decode(protobuf_msg)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def stringify_errors
|
48
|
+
errors.values.join(', ')
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
data/lib/railway_ipc/logger.rb
CHANGED
@@ -1,35 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module RailwayIpc
|
4
|
+
# Custom logger that accepts a `device`, `level`, and `formatter`.
|
5
|
+
# `formatter` can be any object that responds to `call`; a
|
6
|
+
# `Logger::Formatter` is used if the argument is not provided.
|
7
|
+
#
|
8
|
+
# Here is an example formatter that uses `Oj` to format structured log
|
9
|
+
# messages:
|
10
|
+
#
|
11
|
+
# require 'oj'
|
12
|
+
# OjFormatter = proc do |severity, datetime, progname, data|
|
13
|
+
# data.merge!(
|
14
|
+
# name: progname,
|
15
|
+
# timestamp: datetime,
|
16
|
+
# severity: severity
|
17
|
+
# )
|
18
|
+
# Oj.dump(data, { mode: :compat, time_format: :xmlschema })
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# logger = RailwayIpc::Logger.new(STDOUT, Logger::INFO, OjFormatter)
|
22
|
+
#
|
2
23
|
class Logger
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
@logger = logger
|
24
|
+
def initialize(device=STDOUT, level=::Logger::INFO, formatter=nil)
|
25
|
+
@logger = ::Logger.new(device)
|
26
|
+
@logger.level = level
|
27
|
+
@logger.formatter = formatter if formatter
|
8
28
|
end
|
9
29
|
|
10
|
-
|
11
|
-
|
30
|
+
%w[fatal error warn info debug].each do |level|
|
31
|
+
define_method(level) do |message=nil, data={}, &block|
|
32
|
+
data.merge!(feature: 'railway_ipc') unless data.key?(:feature)
|
33
|
+
return logger.send(level, data.merge(message: message)) unless block
|
34
|
+
|
35
|
+
data = message.merge(data) if message&.is_a?(Hash)
|
36
|
+
data.merge!(message: block.call)
|
37
|
+
|
38
|
+
# This is for compatability w/ Ruby's Logger. Ruby's Logger class
|
39
|
+
# assumes that if both a `message` argument and a block are given,
|
40
|
+
# that the block contains the actual message. The `message` argument
|
41
|
+
# is assumed to be the `progname`.
|
42
|
+
#
|
43
|
+
# https://github.com/ruby/logger/blob/master/lib/logger.rb#L471
|
44
|
+
data.merge!(progname: message) if message&.is_a?(String)
|
45
|
+
logger.send(level, data)
|
46
|
+
end
|
12
47
|
end
|
13
48
|
|
14
|
-
|
15
|
-
logger.warn("[#{message_header(message)}] #{statement}")
|
16
|
-
end
|
17
|
-
|
18
|
-
def debug(message, statement)
|
19
|
-
logger.debug("[#{message_header(message)}] #{statement}")
|
20
|
-
end
|
49
|
+
private
|
21
50
|
|
22
|
-
|
23
|
-
logger.error("[#{message_header(message)}] #{statement}")
|
24
|
-
end
|
25
|
-
|
26
|
-
def log_exception(e)
|
27
|
-
logger.error(e)
|
28
|
-
end
|
29
|
-
|
30
|
-
def message_header(message)
|
31
|
-
log_statement = "message type: #{message.class}, uuid: #{message.uuid}, correlation_id: #{message.correlation_id}"
|
32
|
-
message.respond_to?(:user_uuid) ? "#{log_statement}, user_uuid: #{message.user_uuid}" : log_statement
|
33
|
-
end
|
51
|
+
attr_reader :logger
|
34
52
|
end
|
35
53
|
end
|
@@ -1,37 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module RailwayIpc
|
2
4
|
class ConsumedMessage < ActiveRecord::Base
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
5
|
+
STATUS_SUCCESS = 'success'
|
6
|
+
STATUS_PROCESSING = 'processing'
|
7
|
+
STATUS_IGNORED = 'ignored'
|
8
|
+
STATUS_UNKNOWN_MESSAGE_TYPE = 'unknown_message_type'
|
9
|
+
STATUS_FAILED_TO_PROCESS = 'failed_to_process'
|
10
|
+
|
11
|
+
VALID_STATUSES = [
|
12
|
+
STATUS_SUCCESS,
|
13
|
+
STATUS_PROCESSING,
|
14
|
+
STATUS_IGNORED,
|
15
|
+
STATUS_UNKNOWN_MESSAGE_TYPE,
|
16
|
+
STATUS_FAILED_TO_PROCESS
|
17
|
+
].freeze
|
9
18
|
|
10
19
|
attr_reader :decoded_message
|
20
|
+
|
11
21
|
self.table_name = 'railway_ipc_consumed_messages'
|
12
|
-
self.primary_key = 'uuid'
|
13
22
|
|
14
23
|
validates :uuid, :status, presence: true
|
15
|
-
validates :status, inclusion: { in:
|
24
|
+
validates :status, inclusion: { in: VALID_STATUSES }
|
25
|
+
|
26
|
+
def self.create_processing(consumer, incoming_message)
|
27
|
+
# rubocop:disable Style/RedundantSelf
|
28
|
+
self.create!(
|
29
|
+
uuid: incoming_message.uuid,
|
30
|
+
status: STATUS_PROCESSING,
|
31
|
+
message_type: incoming_message.type,
|
32
|
+
user_uuid: incoming_message.user_uuid,
|
33
|
+
correlation_id: incoming_message.correlation_id,
|
34
|
+
queue: consumer.queue_name,
|
35
|
+
exchange: consumer.exchange_name,
|
36
|
+
encoded_message: incoming_message.payload
|
37
|
+
)
|
38
|
+
# rubocop:enable Style/RedundantSelf
|
39
|
+
end
|
16
40
|
|
17
|
-
def
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
41
|
+
def update_with_lock(job)
|
42
|
+
with_lock('FOR UPDATE NOWAIT') do
|
43
|
+
job.run
|
44
|
+
self.status = job.status
|
45
|
+
save
|
22
46
|
end
|
23
47
|
end
|
24
48
|
|
25
49
|
def processed?
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
def encoded_protobuf=(encoded_protobuf)
|
30
|
-
self.encoded_message = Base64.encode64(encoded_protobuf)
|
31
|
-
end
|
32
|
-
|
33
|
-
def decoded_message
|
34
|
-
@decoded_message ||= decode_message
|
50
|
+
# rubocop:disable Style/RedundantSelf
|
51
|
+
self.status == STATUS_SUCCESS
|
52
|
+
# rubocop:enable Style/RedundantSelf
|
35
53
|
end
|
36
54
|
|
37
55
|
private
|
@@ -39,18 +57,5 @@ module RailwayIpc
|
|
39
57
|
def timestamp_attributes_for_create
|
40
58
|
super << :inserted_at
|
41
59
|
end
|
42
|
-
|
43
|
-
def decode_message
|
44
|
-
begin
|
45
|
-
message_class = Kernel.const_get(self.message_type)
|
46
|
-
rescue NameError
|
47
|
-
message_class = RailwayIpc::BaseMessage
|
48
|
-
end
|
49
|
-
message_class.decode(decoded_protobuf)
|
50
|
-
end
|
51
|
-
|
52
|
-
def decoded_protobuf
|
53
|
-
Base64.decode64(self.encoded_message)
|
54
|
-
end
|
55
60
|
end
|
56
61
|
end
|
@@ -1,20 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module RailwayIpc
|
2
4
|
class PublishedMessage < ActiveRecord::Base
|
3
5
|
self.table_name = 'railway_ipc_published_messages'
|
4
|
-
self.primary_key =
|
6
|
+
self.primary_key = 'uuid'
|
5
7
|
|
6
8
|
validates :uuid, :status, presence: true
|
7
9
|
|
8
10
|
def self.store_message(exchange_name, message)
|
9
11
|
encoded_message = RailwayIpc::Rabbitmq::Payload.encode(message)
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
12
|
+
create!(
|
13
|
+
uuid: message.uuid,
|
14
|
+
message_type: message.class.to_s,
|
15
|
+
user_uuid: message.user_uuid,
|
16
|
+
correlation_id: message.correlation_id,
|
17
|
+
encoded_message: encoded_message,
|
18
|
+
status: 'sent',
|
19
|
+
exchange: exchange_name
|
18
20
|
)
|
19
21
|
end
|
20
22
|
|
@@ -1,7 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'singleton'
|
2
4
|
|
3
5
|
module RailwayIpc
|
4
|
-
class
|
6
|
+
class SingletonPublisher < Sneakers::Publisher
|
5
7
|
include ::Singleton
|
6
8
|
|
7
9
|
def self.exchange(exchange)
|
@@ -10,6 +12,7 @@ module RailwayIpc
|
|
10
12
|
|
11
13
|
def self.exchange_name
|
12
14
|
raise 'Subclass must set the exchange' unless @exchange_name
|
15
|
+
|
13
16
|
@exchange_name
|
14
17
|
end
|
15
18
|
|
@@ -18,36 +21,84 @@ module RailwayIpc
|
|
18
21
|
end
|
19
22
|
|
20
23
|
def publish(message, published_message_store=RailwayIpc::PublishedMessage)
|
24
|
+
RailwayIpc.logger.warn('DEPRECATED: Use new PublisherInstance class', log_message_options)
|
21
25
|
ensure_message_uuid(message)
|
22
26
|
ensure_correlation_id(message)
|
23
|
-
RailwayIpc.logger.info(
|
24
|
-
|
27
|
+
RailwayIpc.logger.info('Publishing message', log_message_options(message))
|
25
28
|
result = super(RailwayIpc::Rabbitmq::Payload.encode(message))
|
26
29
|
published_message_store.store_message(self.class.exchange_name, message)
|
27
30
|
result
|
28
31
|
rescue RailwayIpc::InvalidProtobuf => e
|
29
|
-
RailwayIpc.logger.error(
|
32
|
+
RailwayIpc.logger.error('Invalid protobuf', log_message_options(message))
|
30
33
|
raise e
|
31
34
|
end
|
32
35
|
|
33
36
|
private
|
34
37
|
|
35
38
|
def ensure_message_uuid(message)
|
36
|
-
if message.uuid.blank?
|
37
|
-
|
38
|
-
message
|
39
|
-
else
|
40
|
-
message
|
41
|
-
end
|
39
|
+
message.uuid = SecureRandom.uuid if message.uuid.blank?
|
40
|
+
message
|
42
41
|
end
|
43
42
|
|
44
43
|
def ensure_correlation_id(message)
|
45
|
-
if message.correlation_id.blank?
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
44
|
+
message.correlation_id = SecureRandom.uuid if message.correlation_id.blank?
|
45
|
+
message
|
46
|
+
end
|
47
|
+
|
48
|
+
def log_message_options(message=nil)
|
49
|
+
options = { feature: 'railway_ipc_publisher', exchange: self.class.exchange_name }
|
50
|
+
message.nil? ? options : options.merge(protobuf: { type: message.class, data: message })
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
module RailwayIpc
|
56
|
+
class Publisher < Sneakers::Publisher
|
57
|
+
attr_reader :exchange_name, :message_store
|
58
|
+
|
59
|
+
def initialize(opts={})
|
60
|
+
@exchange_name = opts.fetch(:exchange_name)
|
61
|
+
@message_store = opts.fetch(:message_store, RailwayIpc::PublishedMessage)
|
62
|
+
connection = opts.fetch(:connection, nil)
|
63
|
+
options = {
|
64
|
+
exchange: exchange_name,
|
65
|
+
connection: connection,
|
66
|
+
exchange_type: :fanout
|
67
|
+
}.compact
|
68
|
+
super(options)
|
69
|
+
end
|
70
|
+
|
71
|
+
# rubocop:disable Metrics/AbcSize
|
72
|
+
def publish(message)
|
73
|
+
message.uuid = SecureRandom.uuid if message.uuid.blank?
|
74
|
+
message.correlation_id = SecureRandom.uuid if message.correlation_id.blank?
|
75
|
+
RailwayIpc.logger.info('Publishing message', log_message_options(message))
|
76
|
+
|
77
|
+
stored_message = message_store.store_message(exchange_name, message)
|
78
|
+
super(RailwayIpc::Rabbitmq::Payload.encode(message))
|
79
|
+
rescue RailwayIpc::InvalidProtobuf => e
|
80
|
+
RailwayIpc.logger.error('Invalid protobuf', log_message_options(message))
|
81
|
+
raise e
|
82
|
+
rescue ActiveRecord::RecordInvalid => e
|
83
|
+
RailwayIpc.logger.error('Failed to store outgoing message', log_message_options(message))
|
84
|
+
raise RailwayIpc::FailedToStoreOutgoingMessage.new(e)
|
85
|
+
rescue StandardError => e
|
86
|
+
stored_message&.destroy
|
87
|
+
raise e
|
88
|
+
end
|
89
|
+
# rubocop:enable Metrics/AbcSize
|
90
|
+
|
91
|
+
private
|
92
|
+
|
93
|
+
def log_message_options(message)
|
94
|
+
{
|
95
|
+
feature: 'railway_ipc_publisher',
|
96
|
+
exchange: exchange_name,
|
97
|
+
protobuf: {
|
98
|
+
type: message.class,
|
99
|
+
data: message
|
100
|
+
}
|
101
|
+
}
|
51
102
|
end
|
52
103
|
end
|
53
104
|
end
|