railway-ipc 0.1.5 → 1.1.0

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