railway-ipc 0.1.4 → 1.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 +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