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.
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