railway-ipc 0.1.5 → 1.1.0

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 +50 -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 +31 -83
  13. data/lib/railway_ipc/consumer/process_incoming_message.rb +103 -0
  14. data/lib/railway_ipc/errors.rb +9 -1
  15. data/lib/railway_ipc/handler.rb +5 -6
  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 +4 -3
  19. data/lib/railway_ipc/models/consumed_message.rb +41 -28
  20. data/lib/railway_ipc/models/published_message.rb +11 -9
  21. data/lib/railway_ipc/publisher.rb +58 -1
  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 +6 -3
  26. data/lib/railway_ipc/response.rb +4 -1
  27. data/lib/railway_ipc/rpc/client/client.rb +27 -17
  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 +8 -3
  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/.tool-versions +0 -1
  48. data/.travis.yml +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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ab62556fdb38e6d714ae0eeaf2210fc39c6611e5a193734b0edb44b18851edf1
4
- data.tar.gz: be864c9a46dcb21e120fab860781189fbfea59f72a9f563ac68146df84b23b69
3
+ metadata.gz: f2a31b80fc9d4f13598bdc2fbd14c84c78360bd4adeb588bb63b3bb20c33a2de
4
+ data.tar.gz: 2bfef85b0fbe56b0d66df365f8ffa5b7913b188658edbb63484aaa0138dfa6ba
5
5
  SHA512:
6
- metadata.gz: 225b0aded5a0880fbae40da74823707fea29741d98c13b4fedb9e80e7df9acc0e00c8a9d711df6a949b5bf7ac8898a5e3f0be7897c30a5068326fdc5d2ce9015
7
- data.tar.gz: 235bb3d12d988761fffa79128d6cb4e4f68bec15856ca6fcc4262312d025ea840cf1f5a948c34ac83607981ff6f63857260e071a5edac240aaa88c4dd5e56b28
6
+ metadata.gz: a27c830370df223add103179c9e8b60d5259abbb5b21b664f0b2642d96e5736926325c07e271b08822401c312ef532ec8a6f3c03dba30ce5a637edcbbba1ed12
7
+ data.tar.gz: 555cb81425239fba2c5752e238bd541db554f48eb7d46d1336122d730781e40fdd0b339a8266a49a42ace8b73909d7a26f145b9e3ea5d9602fbfcefb9db1a022
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,50 @@
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.1.0] - 2020-08-07
14
+ ### Changed
15
+ * allow multiple consumers to handle the same message
16
+ * consumed messages table requires its own primary key due to ActiveRecord not having support for composite primary keys
17
+
18
+ ## [1.0.1] - 2020-07-23
19
+ ### Fixed
20
+ * Fix publisher connection by using default connection if one isn't provided
21
+
22
+ ## [1.0.0] - 2020-07-20
23
+ ### Added
24
+ * CircleCI build that runs the specs
25
+ * Rubocop (also ran by CircleCI)
26
+ * New error types for incoming messages
27
+ * RailwayIpc::Messages::Unknown protobuf
28
+
29
+ ### Changed
30
+ * Refactored worker to use ProcessIncomingMessage and IncomingMessage abstractions
31
+ * Moved decoding logic from ConsumedMessage to IncomingMessage
32
+ * Removed STATUSES constant from ConsumedMessage
33
+ * Publisher is no longer a Singleton; kept a Singleton version of the Publisher for backwards compatibility that gives a "deprecated" warning
34
+
35
+ ### Removed
36
+ * Removed `BaseMessage` protobuf
37
+ * NullMessage and NullHandler were removed
38
+
39
+ ### Fixed
40
+ * Fixed all Rubocop warnings and errors
41
+
42
+ ## [0.1.7] - 2020-06-29
43
+ ### Added
44
+ - 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)
45
+
46
+ [Unreleased]: https://github.com/learn-co/railway_ipc_gem/compare/v1.1.0...HEAD
47
+ [1.1.0]: https://github.com/learn-co/railway_ipc_gem/compare/v1.0.1...v1.1.0
48
+ [1.0.1]: https://github.com/learn-co/railway_ipc_gem/compare/v1.0.0...v1.0.1
49
+ [1.0.0]: https://github.com/learn-co/railway_ipc_gem/compare/v0.1.7...v1.0.0
50
+ [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,15 @@
1
- require "json"
2
- require "base64"
3
- require "railway_ipc/consumer/consumer_response_handlers"
1
+ # frozen_string_literal: true
4
2
 
5
3
  module RailwayIpc
6
4
  class Consumer
7
5
  include Sneakers::Worker
8
- attr_reader :message, :handler, :protobuf_message, :delivery_info, :decoded_payload, :encoded_message
6
+ def self.inherited(base)
7
+ base.instance_eval do
8
+ def handlers
9
+ @handlers ||= RailwayIpc::HandlerStore.new
10
+ end
11
+ end
12
+ end
9
13
 
10
14
  def self.listen_to(queue:, exchange:)
11
15
  from_queue queue,
@@ -16,98 +20,42 @@ module RailwayIpc
16
20
  end
17
21
 
18
22
  def self.handle(message_type, with:)
19
- ConsumerResponseHandlers.instance.register(message: message_type, handler: with)
20
- end
21
-
22
- def registered_handlers
23
- ConsumerResponseHandlers.instance.registered
23
+ handlers.register(message: message_type, handler: with)
24
24
  end
25
25
 
26
- def work_with_params(payload, delivery_info, _metadata)
27
- @delivery_info = delivery_info
28
- @decoded_payload = RailwayIpc::Rabbitmq::Payload.decode(payload)
29
- @encoded_message = payload
30
-
31
- case decoded_payload.type
32
- when *registered_handlers
33
- @handler = handler_for(decoded_payload)
34
- message_klass = message_handler_for(decoded_payload)
35
- @protobuf_message = message_klass.decode(decoded_payload.message)
36
- process_known_message_type
37
- else
38
- @handler = RailwayIpc::NullHandler.new
39
- @protobuf_message = RailwayIpc::BaseMessage.decode(decoded_payload.message)
40
- process_unknown_message_type
41
- end
42
-
43
- rescue StandardError => e
44
- RailwayIpc.logger.log_exception(
45
- feature: "railway_consumer",
46
- error: e.class,
47
- error_message: e.message,
48
- payload: payload,
49
- )
50
- raise e
26
+ def handlers
27
+ self.class.handlers
51
28
  end
52
29
 
53
- private
54
-
55
- def process_protobuf!(message)
56
- if handler.handle(protobuf_message).success?
57
- message.status = RailwayIpc::ConsumedMessage::STATUSES[:success]
58
- else
59
- message.status = RailwayIpc::ConsumedMessage::STATUSES[:failed_to_process]
60
- end
61
-
62
- message.save!
30
+ def registered_handlers
31
+ handlers.registered
63
32
  end
64
33
 
65
- def process_known_message_type
66
- message = RailwayIpc::ConsumedMessage.find_by(uuid: protobuf_message.uuid)
67
-
68
- if message && message.processed?
69
- handler.ack!
70
- elsif message && !message.processed?
71
- message.with_lock("FOR UPDATE NOWAIT") { process_protobuf!(message) }
72
- else
73
- message = create_message_with_status!(RailwayIpc::ConsumedMessage::STATUSES[:processing])
74
- message.with_lock("FOR UPDATE NOWAIT") { process_protobuf!(message) }
75
- end
76
-
77
- nil
34
+ def queue_name
35
+ queue.name
78
36
  end
79
37
 
80
- def process_unknown_message_type
81
- handler.ack!
82
-
83
- if RailwayIpc::ConsumedMessage.exists?(uuid: protobuf_message.uuid)
84
- return
85
- else
86
- create_message_with_status!(RailwayIpc::ConsumedMessage::STATUSES[:unknown_message_type])
87
- end
88
-
89
- nil
38
+ def exchange_name
39
+ queue.opts[:exchange]
90
40
  end
91
41
 
92
- def create_message_with_status!(status)
93
- RailwayIpc::ConsumedMessage.create!(
94
- uuid: protobuf_message.uuid,
95
- status: status,
96
- message_type: decoded_payload.type,
97
- user_uuid: protobuf_message.user_uuid,
98
- correlation_id: protobuf_message.correlation_id,
99
- queue: delivery_info.consumer.queue.name,
100
- exchange: delivery_info.exchange,
101
- encoded_message: encoded_message
42
+ def work(payload)
43
+ message = RailwayIpc::IncomingMessage.new(payload)
44
+ RailwayIpc::ProcessIncomingMessage.call(self, message)
45
+ ack!
46
+ rescue StandardError => e
47
+ RailwayIpc.logger.log_exception(
48
+ feature: 'railway_consumer',
49
+ error: e.class,
50
+ error_message: e.message,
51
+ payload: payload
102
52
  )
53
+ raise e
103
54
  end
104
55
 
105
- def message_handler_for(decoded_payload)
106
- ConsumerResponseHandlers.instance.get(decoded_payload.type).message
107
- end
108
-
109
- def handler_for(decoded_payload)
110
- ConsumerResponseHandlers.instance.get(decoded_payload.type).handler.new
56
+ def get_handler(type)
57
+ manifest = handlers.get(type)
58
+ manifest ? manifest.handler.new : nil
111
59
  end
112
60
  end
113
61
  end
@@ -0,0 +1,103 @@
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, queue: consumer.queue_name) ||
90
+ RailwayIpc::ConsumedMessage.create_processing(consumer, incoming_message)
91
+ end
92
+
93
+ def classify_message
94
+ if incoming_message.decoded.is_a?(RailwayIpc::Messages::Unknown)
95
+ UnknownMessageJob.new(incoming_message, logger)
96
+ elsif (handler = consumer.get_handler(incoming_message.type))
97
+ NormalMessageJob.new(incoming_message, handler)
98
+ else
99
+ IgnoredMessageJob.new(incoming_message, logger)
100
+ end
101
+ end
102
+ end
103
+ end