railway-ipc 0.1.4 → 1.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 +43 -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 +6 -4
  11. data/lib/railway_ipc/Rakefile +2 -0
  12. data/lib/railway_ipc/consumer/consumer.rb +21 -81
  13. data/lib/railway_ipc/consumer/consumer_response_handlers.rb +2 -0
  14. data/lib/railway_ipc/consumer/process_incoming_message.rb +105 -0
  15. data/lib/railway_ipc/errors.rb +9 -1
  16. data/lib/railway_ipc/handler.rb +5 -6
  17. data/lib/railway_ipc/handler_manifest.rb +2 -0
  18. data/lib/railway_ipc/handler_store.rb +3 -0
  19. data/lib/railway_ipc/incoming_message.rb +51 -0
  20. data/lib/railway_ipc/logger.rb +4 -3
  21. data/lib/railway_ipc/models/consumed_message.rb +41 -27
  22. data/lib/railway_ipc/models/published_message.rb +11 -9
  23. data/lib/railway_ipc/publisher.rb +58 -1
  24. data/lib/railway_ipc/rabbitmq/adapter.rb +24 -14
  25. data/lib/railway_ipc/rabbitmq/payload.rb +9 -4
  26. data/lib/railway_ipc/railtie.rb +2 -0
  27. data/lib/railway_ipc/responder.rb +6 -3
  28. data/lib/railway_ipc/response.rb +4 -1
  29. data/lib/railway_ipc/rpc/client/client.rb +27 -17
  30. data/lib/railway_ipc/rpc/client/client_response_handlers.rb +2 -0
  31. data/lib/railway_ipc/rpc/client/errors/timeout_error.rb +2 -0
  32. data/lib/railway_ipc/rpc/concerns/error_adapter_configurable.rb +2 -0
  33. data/lib/railway_ipc/rpc/concerns/message_observation_configurable.rb +2 -0
  34. data/lib/railway_ipc/rpc/concerns/publish_location_configurable.rb +2 -0
  35. data/lib/railway_ipc/rpc/rpc.rb +2 -0
  36. data/lib/railway_ipc/rpc/server/server.rb +10 -3
  37. data/lib/railway_ipc/rpc/server/server_response_handlers.rb +2 -0
  38. data/lib/railway_ipc/tasks/generate_migrations.rake +16 -16
  39. data/lib/railway_ipc/tasks/start_consumers.rake +3 -1
  40. data/lib/railway_ipc/tasks/start_servers.rake +3 -1
  41. data/lib/railway_ipc/unhandled_message_error.rb +2 -0
  42. data/lib/railway_ipc/unknown_message.pb.rb +18 -0
  43. data/lib/railway_ipc/version.rb +3 -1
  44. data/priv/migrations/add_railway_ipc_consumed_messages.rb +3 -1
  45. data/priv/migrations/add_railway_ipc_published_messages.rb +3 -1
  46. data/railway_ipc.gemspec +33 -30
  47. metadata +64 -65
  48. data/.rspec +0 -3
  49. data/.tool-versions +0 -1
  50. data/.travis.yml +0 -7
  51. data/Gemfile.lock +0 -186
  52. data/lib/railway_ipc/base_message.pb.rb +0 -21
  53. data/lib/railway_ipc/null_handler.rb +0 -7
  54. data/lib/railway_ipc/null_message.rb +0 -7
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 945917b242216c009d199f73de3c559b16b005cdae28504273b2ee68d04ed8df
4
- data.tar.gz: 39bc88b82f608a2a74de630c334bd6a7021a92666e8843fc4ba81e3f1b3a2b03
3
+ metadata.gz: 6b0a9b840874946f0f7e80b0221e3df161eed5ce0926299356856cb5b81f76db
4
+ data.tar.gz: 252e1f3e84801e50cfccc47d6674c985a690e514cd2732b62c5f6cbbef71f0fc
5
5
  SHA512:
6
- metadata.gz: 42425f432289124825d92ec02bc3350875070da6554c824fc4cb9f802d961817ae65980b6011566be24696abbecf6613e48166373e5d6476e1cf6498ae64057a
7
- data.tar.gz: 9d09e840a91d94a02e4551fa44f2d653e40e716e4606288649320709d54c47cccc8240268b38a550466b92ebd0882f4718bd271256ca520b735d283cbbcd3294
6
+ metadata.gz: 69cde1dbbfcabb1ed5f3a27ace4a126f7cf231096aa8b8761c6ac3f19f0c36a4a79183a44140367117eab13f01e1507e53ef9a484abb89510df17ff040395407
7
+ data.tar.gz: a2a85998d976faa746820474bb9e5597964bb92f379b95b7b55cc5936a72af532d3b5321d3228194f4209dbdab3b76dc430dbd2d357df33e9929ff3748465b6a
data/.gitignore CHANGED
@@ -6,14 +6,15 @@
6
6
  /pkg/
7
7
  /spec/reports/
8
8
  /tmp/
9
+ Gemfile.lock
9
10
 
10
11
  # rspec failure tracking
11
12
  .rspec_status
12
13
 
13
14
  # rails support app files
14
- /spec/support/rails_app/.bundle
15
- /spec/support/rails_app/log/*
16
- /spec/support/rails_app/tmp/*
17
15
  /spec/support/rails_app!/log/.keep
18
16
  /spec/support/rails_app!/tmp/.keep
19
17
  /spec/support/rails_app.byebug_history
18
+ /spec/support/rails_app/.bundle
19
+ /spec/support/rails_app/log/*
20
+ /spec/support/rails_app/tmp/*
@@ -0,0 +1,43 @@
1
+ # Changelog
2
+ All notable changes to this project will be documented in this file.
3
+
4
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## [Unreleased]
8
+ ### Added
9
+ ### Changed
10
+ ### Removed
11
+ ### Fixed
12
+
13
+ ## [1.0.1] - 2020-07-23
14
+ ### Fixed
15
+ * Fix publisher connection by using default connection if one isn't provided
16
+
17
+ ## [1.0.0] - 2020-07-20
18
+ ### Added
19
+ * CircleCI build that runs the specs
20
+ * Rubocop (also ran by CircleCI)
21
+ * New error types for incoming messages
22
+ * RailwayIpc::Messages::Unknown protobuf
23
+
24
+ ### Changed
25
+ * Refactored worker to use ProcessIncomingMessage and IncomingMessage abstractions
26
+ * Moved decoding logic from ConsumedMessage to IncomingMessage
27
+ * Removed STATUSES constant from ConsumedMessage
28
+ * Publisher is no longer a Singleton; kept a Singleton version of the Publisher for backwards compatibility that gives a "deprecated" warning
29
+
30
+ ### Removed
31
+ * Removed `BaseMessage` protobuf
32
+ * NullMessage and NullHandler were removed
33
+
34
+ ### Fixed
35
+ * Fixed all Rubocop warnings and errors
36
+
37
+ ## [0.1.7] - 2020-06-29
38
+ ### Added
39
+ - Correlation ID and message UUID are auto generated for messages for IDs are not passed in [#23](https://github.com/learn-co/railway_ipc_gem/pull/23)
40
+
41
+ [Unreleased]: https://github.com/learn-co/railway_ipc_gem/compare/v1.0.0...HEAD
42
+ [1.0.0]: https://github.com/learn-co/railway_ipc_gem/compare/v0.1.7...v1.0.0
43
+ [0.1.7]: https://github.com/learn-co/railway_ipc_gem/releases/tag/v0.1.7
data/Gemfile CHANGED
@@ -1,5 +1,5 @@
1
- source "https://rubygems.org"
1
+ source 'https://rubygems.org'
2
2
 
3
- git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
3
+ git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
4
4
 
5
5
  gemspec
data/README.md CHANGED
@@ -32,7 +32,7 @@ require "railway_ipc"
32
32
  * Create RabbitMQ connection credentials for Railway and set the environment variable:
33
33
 
34
34
  ```
35
- RABBITMQ_CONNECTION_URL=amqp://<railway_user>:<railway_password>@localhost:5672
35
+ RAILWAY_RABBITMQ_CONNECTION_URL=amqp://<railway_user>:<railway_password>@localhost:5672
36
36
  ```
37
37
 
38
38
  * Load table migrations and migrate by executing:
data/Rakefile CHANGED
@@ -1,7 +1,13 @@
1
- require "bundler/gem_tasks"
2
- require "rspec/core/rake_task"
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
3
5
 
4
6
  RSpec::Core::RakeTask.new(:spec)
5
7
 
6
- task :default => :spec
7
- task :test => :spec
8
+ task default: %i[spec lint]
9
+
10
+ desc 'Lint code with Rubocop'
11
+ task :lint do
12
+ exec('./bin/rubocop .')
13
+ end
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require "bundler/setup"
4
- require "railway_ipc"
3
+ require 'bundler/setup'
4
+ require 'railway_ipc'
5
5
 
6
6
  # You can add fixtures and/or initialization code here to make experimenting
7
7
  # with your gem easier. You can also use a different console, if you like.
8
8
 
9
- require "pry"
9
+ require 'pry'
10
10
  Pry.start
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'rspec' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ require "pathname"
12
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13
+ Pathname.new(__FILE__).realpath)
14
+
15
+ bundle_binstub = File.expand_path("../bundle", __FILE__)
16
+
17
+ if File.file?(bundle_binstub)
18
+ if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19
+ load(bundle_binstub)
20
+ else
21
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22
+ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23
+ end
24
+ end
25
+
26
+ require "rubygems"
27
+ require "bundler/setup"
28
+
29
+ load Gem.bin_path("rspec-core", "rspec")
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'rubocop' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ require "pathname"
12
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13
+ Pathname.new(__FILE__).realpath)
14
+
15
+ bundle_binstub = File.expand_path("../bundle", __FILE__)
16
+
17
+ if File.file?(bundle_binstub)
18
+ if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19
+ load(bundle_binstub)
20
+ else
21
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22
+ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23
+ end
24
+ end
25
+
26
+ require "rubygems"
27
+ require "bundler/setup"
28
+
29
+ load Gem.bin_path("rubocop", "rubocop")
@@ -1,26 +1,28 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'railway_ipc/version'
2
4
  require 'sneakers'
3
5
  require 'bunny'
4
6
  require 'active_record'
5
7
  require 'railway_ipc/version'
6
- require 'railway_ipc/errors'
7
8
  require 'railway_ipc/logger'
8
9
  require 'railway_ipc/unhandled_message_error'
9
10
  require 'railway_ipc/response'
10
11
  require 'railway_ipc/rabbitmq/payload'
11
- require 'railway_ipc/null_message'
12
- require 'railway_ipc/base_message.pb'
12
+ require 'railway_ipc/unknown_message.pb'
13
13
  require 'railway_ipc/rabbitmq/adapter'
14
14
  require 'railway_ipc/handler'
15
15
  require 'railway_ipc/handler_store'
16
+ require 'railway_ipc/incoming_message'
16
17
  require 'railway_ipc/publisher'
17
- require 'railway_ipc/null_handler'
18
18
  require 'railway_ipc/responder'
19
19
  require 'railway_ipc/rpc/rpc'
20
20
  require 'railway_ipc/consumer/consumer'
21
+ require 'railway_ipc/consumer/process_incoming_message'
21
22
  require 'railway_ipc/models/published_message'
22
23
  require 'railway_ipc/models/consumed_message'
23
24
  require 'railway_ipc/railtie' if defined?(Rails)
25
+ require 'railway_ipc/errors'
24
26
 
25
27
  module RailwayIpc
26
28
  def self.start
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'railway_ipc'
2
4
 
3
5
  path = File.expand_path(__dir__)
@@ -1,11 +1,10 @@
1
- require "json"
2
- require "base64"
3
- require "railway_ipc/consumer/consumer_response_handlers"
1
+ # frozen_string_literal: true
2
+
3
+ require 'railway_ipc/consumer/consumer_response_handlers'
4
4
 
5
5
  module RailwayIpc
6
6
  class Consumer
7
7
  include Sneakers::Worker
8
- attr_reader :message, :handler, :protobuff_message, :delivery_info, :decoded_payload
9
8
 
10
9
  def self.listen_to(queue:, exchange:)
11
10
  from_queue queue,
@@ -23,90 +22,31 @@ module RailwayIpc
23
22
  ConsumerResponseHandlers.instance.registered
24
23
  end
25
24
 
26
- def work_with_params(payload, delivery_info, _metadata)
27
- @delivery_info = delivery_info
28
- @decoded_payload = RailwayIpc::Rabbitmq::Payload.decode(payload)
29
-
30
- case decoded_payload.type
31
- when *registered_handlers
32
- @handler = handler_for(decoded_payload)
33
- message_klass = message_handler_for(decoded_payload)
34
- @protobuff_message = message_klass.decode(decoded_payload.message)
35
- process_known_message_type
36
- else
37
- @handler = RailwayIpc::NullHandler.new
38
- @protobuff_message = RailwayIpc::BaseMessage.decode(decoded_payload.message)
39
- process_unknown_message_type
40
- end
41
-
42
- rescue StandardError => e
43
- RailwayIpc.logger.log_exception(
44
- feature: "railway_consumer",
45
- error: e.class,
46
- error_message: e.message,
47
- payload: payload,
48
- )
49
- raise e
25
+ def queue_name
26
+ queue.name
50
27
  end
51
28
 
52
- private
53
-
54
- def process_protobuff!(message)
55
- if handler.handle(protobuff_message).success?
56
- message.status = RailwayIpc::ConsumedMessage::STATUSES[:success]
57
- else
58
- message.status = RailwayIpc::ConsumedMessage::STATUSES[:failed_to_process]
59
- end
60
-
61
- message.save!
29
+ def exchange_name
30
+ queue.opts[:exchange]
62
31
  end
63
32
 
64
- def process_known_message_type
65
- message = RailwayIpc::ConsumedMessage.find_by(uuid: protobuff_message.uuid)
66
-
67
- if message && message.processed?
68
- handler.ack!
69
- elsif message && !message.processed?
70
- message.with_lock("FOR UPDATE NOWAIT") { process_protobuff!(message) }
71
- else
72
- message = create_message_with_status!(RailwayIpc::ConsumedMessage::STATUSES[:processing])
73
- message.with_lock("FOR UPDATE NOWAIT") { process_protobuff!(message) }
74
- end
75
-
76
- nil
77
- end
78
-
79
- def process_unknown_message_type
80
- handler.ack!
81
-
82
- if RailwayIpc::ConsumedMessage.exists?(uuid: protobuff_message.uuid)
83
- return
84
- else
85
- create_message_with_status!(RailwayIpc::ConsumedMessage::STATUSES[:unknown_message_type])
86
- end
87
-
88
- nil
89
- end
90
-
91
- def create_message_with_status!(status)
92
- RailwayIpc::ConsumedMessage.create!(
93
- uuid: protobuff_message.uuid,
94
- status: status,
95
- message_type: decoded_payload.type,
96
- user_uuid: protobuff_message.user_uuid,
97
- correlation_id: protobuff_message.correlation_id,
98
- queue: delivery_info.consumer.queue.name,
99
- exchange: delivery_info.exchange,
100
- encoded_message: decoded_payload.message
33
+ def work(payload)
34
+ message = RailwayIpc::IncomingMessage.new(payload)
35
+ RailwayIpc::ProcessIncomingMessage.call(self, message)
36
+ ack!
37
+ rescue StandardError => e
38
+ RailwayIpc.logger.log_exception(
39
+ feature: 'railway_consumer',
40
+ error: e.class,
41
+ error_message: e.message,
42
+ payload: payload
101
43
  )
44
+ raise e
102
45
  end
103
46
 
104
- def message_handler_for(decoded_payload)
105
- ConsumerResponseHandlers.instance.get(decoded_payload.type).message
106
- end
107
-
108
- def handler_for(decoded_payload)
109
- ConsumerResponseHandlers.instance.get(decoded_payload.type).handler.new
47
+ def get_handler(type)
48
+ manifest = ConsumerResponseHandlers.instance.get(type)
49
+ manifest ? manifest.handler.new : nil
110
50
  end
111
51
  end
112
52
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'railway_ipc/handler_store'
2
4
  module RailwayIpc
3
5
  class ConsumerResponseHandlers
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailwayIpc
4
+ class ProcessIncomingMessage
5
+ class UnknownMessageJob
6
+ attr_reader :incoming_message, :logger
7
+
8
+ def initialize(incoming_message, logger)
9
+ @incoming_message = incoming_message
10
+ @logger = logger
11
+ end
12
+
13
+ def status
14
+ 'unknown_message_type'
15
+ end
16
+
17
+ def run
18
+ logger.warn(
19
+ incoming_message.decoded,
20
+ "Ignoring unknown message of type '#{incoming_message.type}'"
21
+ )
22
+ end
23
+ end
24
+
25
+ class IgnoredMessageJob
26
+ attr_reader :incoming_message, :logger
27
+
28
+ def initialize(incoming_message, logger)
29
+ @incoming_message = incoming_message
30
+ @logger = logger
31
+ end
32
+
33
+ def status
34
+ 'ignored'
35
+ end
36
+
37
+ def run
38
+ logger.warn(
39
+ incoming_message.decoded,
40
+ "Ignoring message, no registered handler for '#{incoming_message.type}'"
41
+ )
42
+ end
43
+ end
44
+
45
+ class NormalMessageJob
46
+ attr_reader :incoming_message, :handler, :status
47
+
48
+ def initialize(incoming_message, handler)
49
+ @incoming_message = incoming_message
50
+ @handler = handler
51
+ @status = 'not_processed'
52
+ end
53
+
54
+ def run
55
+ result = handler.handle(incoming_message.decoded)
56
+ @status = result.success? ? RailwayIpc::ConsumedMessage::STATUS_SUCCESS : RailwayIpc::ConsumedMessage::STATUS_FAILED_TO_PROCESS
57
+ end
58
+ end
59
+
60
+ attr_reader :consumer, :incoming_message, :logger
61
+
62
+ def self.call(consumer, incoming_message)
63
+ new(consumer, incoming_message).call
64
+ end
65
+
66
+ def initialize(consumer, incoming_message, logger: RailwayIpc.logger)
67
+ @consumer = consumer
68
+ @incoming_message = incoming_message
69
+ @logger = logger
70
+ end
71
+
72
+ def call
73
+ raise_message_invalid_error unless incoming_message.valid?
74
+ message = find_or_create_consumed_message
75
+ return if message.processed?
76
+
77
+ message.update_with_lock(classify_message)
78
+ end
79
+
80
+ private
81
+
82
+ def raise_message_invalid_error
83
+ error = "Message is invalid: #{incoming_message.stringify_errors}."
84
+ logger.error(incoming_message.decoded, error)
85
+ raise RailwayIpc::IncomingMessage::InvalidMessage.new(error)
86
+ end
87
+
88
+ def find_or_create_consumed_message
89
+ RailwayIpc::ConsumedMessage.find_by(uuid: incoming_message.uuid) ||
90
+ RailwayIpc::ConsumedMessage.create_processing(consumer, incoming_message)
91
+ end
92
+
93
+ # rubocop:disable Metrics/AbcSize
94
+ def classify_message
95
+ if incoming_message.decoded.is_a?(RailwayIpc::Messages::Unknown)
96
+ UnknownMessageJob.new(incoming_message, logger)
97
+ elsif (handler = consumer.get_handler(incoming_message.type))
98
+ NormalMessageJob.new(incoming_message, handler)
99
+ else
100
+ IgnoredMessageJob.new(incoming_message, logger)
101
+ end
102
+ end
103
+ # rubocop:enable Metrics/AbcSize
104
+ end
105
+ end