railway-ipc 0.1.7 → 2.0.1

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.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +4 -3
  3. data/CHANGELOG.md +67 -0
  4. data/Gemfile +2 -2
  5. data/README.md +1 -1
  6. data/Rakefile +10 -4
  7. data/bin/console +3 -3
  8. data/bin/rspec +29 -0
  9. data/bin/rubocop +29 -0
  10. data/lib/railway_ipc.rb +9 -11
  11. data/lib/railway_ipc/Rakefile +2 -0
  12. data/lib/railway_ipc/consumer/consumer.rb +33 -73
  13. data/lib/railway_ipc/consumer/process_incoming_message.rb +111 -0
  14. data/lib/railway_ipc/errors.rb +9 -1
  15. data/lib/railway_ipc/handler.rb +17 -3
  16. data/lib/railway_ipc/handler_store.rb +5 -2
  17. data/lib/railway_ipc/incoming_message.rb +51 -0
  18. data/lib/railway_ipc/logger.rb +44 -26
  19. data/lib/railway_ipc/models/consumed_message.rb +40 -35
  20. data/lib/railway_ipc/models/published_message.rb +11 -9
  21. data/lib/railway_ipc/publisher.rb +67 -16
  22. data/lib/railway_ipc/rabbitmq/adapter.rb +23 -15
  23. data/lib/railway_ipc/rabbitmq/payload.rb +9 -4
  24. data/lib/railway_ipc/railtie.rb +2 -0
  25. data/lib/railway_ipc/responder.rb +10 -3
  26. data/lib/railway_ipc/response.rb +4 -1
  27. data/lib/railway_ipc/rpc/client/client.rb +43 -18
  28. data/lib/railway_ipc/rpc/client/client_response_handlers.rb +2 -0
  29. data/lib/railway_ipc/rpc/client/errors/timeout_error.rb +2 -0
  30. data/lib/railway_ipc/rpc/concerns/error_adapter_configurable.rb +2 -0
  31. data/lib/railway_ipc/rpc/concerns/message_observation_configurable.rb +2 -0
  32. data/lib/railway_ipc/rpc/concerns/publish_location_configurable.rb +2 -0
  33. data/lib/railway_ipc/rpc/rpc.rb +2 -0
  34. data/lib/railway_ipc/rpc/server/server.rb +25 -7
  35. data/lib/railway_ipc/rpc/server/server_response_handlers.rb +2 -0
  36. data/lib/railway_ipc/tasks/generate_migrations.rake +16 -16
  37. data/lib/railway_ipc/tasks/start_consumers.rake +3 -1
  38. data/lib/railway_ipc/tasks/start_servers.rake +3 -1
  39. data/lib/railway_ipc/unhandled_message_error.rb +2 -0
  40. data/lib/railway_ipc/unknown_message.pb.rb +18 -0
  41. data/lib/railway_ipc/version.rb +3 -1
  42. data/priv/migrations/add_railway_ipc_consumed_messages.rb +5 -3
  43. data/priv/migrations/add_railway_ipc_published_messages.rb +3 -1
  44. data/railway_ipc.gemspec +34 -30
  45. metadata +62 -64
  46. data/.rspec +0 -3
  47. data/.travis.yml +0 -7
  48. data/CHANGELOG.MD +0 -7
  49. data/Gemfile.lock +0 -186
  50. data/lib/railway_ipc/base_message.pb.rb +0 -21
  51. data/lib/railway_ipc/consumer/consumer_response_handlers.rb +0 -14
  52. data/lib/railway_ipc/handler_manifest.rb +0 -10
  53. data/lib/railway_ipc/null_handler.rb +0 -7
  54. 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["RAILWAY_RABBITMQ_CONNECTION_URL"], exchange_name:, queue_name: '', options: {})
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
- host: settings[:host],
24
- user: settings[:user],
25
- pass: settings[:pass],
26
- port: settings[:port],
27
- vhost: vhost,
28
- automatic_recovery: false,
29
- logger: RailwayIpc.bunny_logger
30
- }.merge(options))
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, :fanout, exchange_name, options)
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 = {durable: true})
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["type"]
19
- message = Base64.decode64(message["encoded_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
- type: type,
31
- encoded_message: message
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
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module RailwayIpc
2
4
  class Railtie < Rails::Railtie
3
5
  railtie_name :railway_ipc
@@ -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
- def self.block
8
- @block
9
+ class << self
10
+ attr_reader :block
9
11
  end
10
12
 
11
13
  def respond(request)
12
- RailwayIpc.logger.info(request, "Responding to request")
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
 
@@ -1,7 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module RailwayIpc
2
4
  class Response
3
5
  attr_reader :body, :success
4
- def initialize(message, success:true)
6
+
7
+ def initialize(message, success: true)
5
8
  @body = message
6
9
  @success = success
7
10
  end
@@ -1,12 +1,15 @@
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"
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 = { automatic_recovery: false }, rabbit_adapter: RailwayIpc::Rabbitmq::Adapter)
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 = 10)
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(message, "Handling response")
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, "#{self.class} does not know how to handle #{decoded_payload.type}"
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(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),
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(request_message, "Sending request")
96
- rabbit_connection.publish(RailwayIpc::Rabbitmq::Payload.encode(request_message), routing_key: "")
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(e, payload)
100
- return e.message unless payload
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
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module RailwayIpc
2
4
  module RPC
3
5
  class ClientResponseHandlers
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module RailwayIpc
2
4
  class Client
3
5
  class TimeoutError < StandardError; end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module RailwayIpc
2
4
  module RPC
3
5
  module ErrorAdapterConfigurable
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module RailwayIpc
2
4
  module RPC
3
5
  module MessageObservationConfigurable
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module RailwayIpc
2
4
  module RPC
3
5
  module PublishLocationConfigurable
@@ -1,2 +1,4 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'railway_ipc/rpc/client/client'
2
4
  require 'railway_ipc/rpc/server/server'
@@ -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 = {automatic_recovery: true}, rabbit_adapter: RailwayIpc::Rabbitmq::Adapter)
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, "#{self.class} does not know how to handle #{decoded_payload.type}"
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.log_exception(
45
- feature: 'railway_consumer',
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(message, "Error responding to message. Error: #{e.class}, #{e.message}")
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,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'railway_ipc/handler_store'
2
4
  module RailwayIpc
3
5
  module RPC
@@ -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 "Generates migrations to store Railway messages"
7
+ desc 'Generates migrations to store Railway messages'
6
8
  task :migrations do
7
- if defined?(ActiveRecord::Base)
8
- puts "generating Railway IPC table migrations"
9
- seconds = 0
10
- gem_path = Gem.loaded_specs['railway-ipc'].full_gem_path
11
- folder_dest = "#{Rails.root.to_s}/db/migrate"
12
- FileUtils.mkdir_p(folder_dest)
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
- Dir.glob("#{gem_path}/priv/migrations/*.rb").each do |file_path|
15
- file_name = File.basename(file_path)
16
- migration_timestamp = (Time.now + seconds).utc.strftime("%Y%m%d%H%M%S") % "%.14d"
17
- new_file_name = "#{migration_timestamp}_#{file_name}"
18
- FileUtils.copy_file(file_path, "#{folder_dest}/#{new_file_name}")
19
- seconds += 1
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