railway-ipc 0.1.7 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +4 -3
  3. data/CHANGELOG.MD +35 -3
  4. data/Gemfile +2 -2
  5. data/Rakefile +10 -4
  6. data/bin/console +3 -3
  7. data/bin/rspec +29 -0
  8. data/bin/rubocop +29 -0
  9. data/lib/railway_ipc.rb +6 -4
  10. data/lib/railway_ipc/Rakefile +2 -0
  11. data/lib/railway_ipc/consumer/consumer.rb +18 -69
  12. data/lib/railway_ipc/consumer/consumer_response_handlers.rb +2 -0
  13. data/lib/railway_ipc/consumer/process_incoming_message.rb +105 -0
  14. data/lib/railway_ipc/errors.rb +9 -1
  15. data/lib/railway_ipc/handler.rb +2 -0
  16. data/lib/railway_ipc/handler_manifest.rb +2 -0
  17. data/lib/railway_ipc/handler_store.rb +3 -0
  18. data/lib/railway_ipc/incoming_message.rb +51 -0
  19. data/lib/railway_ipc/logger.rb +4 -3
  20. data/lib/railway_ipc/models/consumed_message.rb +40 -34
  21. data/lib/railway_ipc/models/published_message.rb +11 -9
  22. data/lib/railway_ipc/publisher.rb +41 -13
  23. data/lib/railway_ipc/rabbitmq/adapter.rb +23 -15
  24. data/lib/railway_ipc/rabbitmq/payload.rb +9 -4
  25. data/lib/railway_ipc/railtie.rb +2 -0
  26. data/lib/railway_ipc/responder.rb +6 -3
  27. data/lib/railway_ipc/response.rb +4 -1
  28. data/lib/railway_ipc/rpc/client/client.rb +27 -17
  29. data/lib/railway_ipc/rpc/client/client_response_handlers.rb +2 -0
  30. data/lib/railway_ipc/rpc/client/errors/timeout_error.rb +2 -0
  31. data/lib/railway_ipc/rpc/concerns/error_adapter_configurable.rb +2 -0
  32. data/lib/railway_ipc/rpc/concerns/message_observation_configurable.rb +2 -0
  33. data/lib/railway_ipc/rpc/concerns/publish_location_configurable.rb +2 -0
  34. data/lib/railway_ipc/rpc/rpc.rb +2 -0
  35. data/lib/railway_ipc/rpc/server/server.rb +10 -3
  36. data/lib/railway_ipc/rpc/server/server_response_handlers.rb +2 -0
  37. data/lib/railway_ipc/tasks/generate_migrations.rake +16 -16
  38. data/lib/railway_ipc/tasks/start_consumers.rake +3 -1
  39. data/lib/railway_ipc/tasks/start_servers.rake +3 -1
  40. data/lib/railway_ipc/unhandled_message_error.rb +2 -0
  41. data/lib/railway_ipc/unknown_message.pb.rb +18 -0
  42. data/lib/railway_ipc/version.rb +3 -1
  43. data/priv/migrations/add_railway_ipc_consumed_messages.rb +3 -1
  44. data/priv/migrations/add_railway_ipc_published_messages.rb +3 -1
  45. data/railway_ipc.gemspec +33 -30
  46. metadata +60 -60
  47. data/.rspec +0 -3
  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/null_handler.rb +0 -7
  52. data/lib/railway_ipc/null_message.rb +0 -7
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2d54b5628ec775d68214605d423ffaea0cd56296efce288d9f6d225a3fe5b68a
4
- data.tar.gz: b8fce8f52051eb667daa678b931ec24c3fa5de54783918849f618407e5b81863
3
+ metadata.gz: 39c4398fe9a2406dea3747efe2f8cb83710b0a2f58f56e153f2df57625613445
4
+ data.tar.gz: 5e6b9f14dfa42fe89078f6a1d95941eb133e113a1e90ff1e48b6804e2e8adec4
5
5
  SHA512:
6
- metadata.gz: 68e7d82e728b4c3ace10b7c1926672c301bcd5090aa0bfc80b80485936c476fbf59c97493fe7d1eaaf7874620cf967b9b6ee2f95923cbeefff498248732c47e1
7
- data.tar.gz: 2780cc3cf0d29b4abfda4fecc2d28a119e08d5592a87b6cba3ebf3c868bdea208c9f3c00ddf4708f35aa6f6b9e21defbd25baf2708006c725c6aec164038563d
6
+ metadata.gz: dbdfdebec3010f66e63ed12965b3e82135b0599f4bce0a5fa771ce8e28dbed042149b2391c925a614079c0799249fa2cd3f2a114de3527450746420c40b20578
7
+ data.tar.gz: c02cbdcb2cdc1cb6b609f818fb844c3e8accb6a54821d75b3afbb5197eff4c5ffa49cabb520bec8cfe3989052cbd38456d68e5e532e58244e1ea69e35c8677f4
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/*
@@ -1,7 +1,39 @@
1
- # Changelog for v0.x
1
+ # Changelog
2
+ All notable changes to this project will be documented in this file.
2
3
 
3
- ## v0.1.7 (2020-06-29)
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).
4
6
 
5
- ### Enhancements
7
+ ## [Unreleased]
8
+ ### Added
9
+ ### Changed
10
+ ### Removed
11
+ ### Fixed
6
12
 
13
+ ## [1.0.0] - 2020-07-20
14
+ ### Added
15
+ * CircleCI build that runs the specs
16
+ * Rubocop (also ran by CircleCI)
17
+ * New error types for incoming messages
18
+ * RailwayIpc::Messages::Unknown protobuf
19
+
20
+ ### Changed
21
+ * Refactored worker to use ProcessIncomingMessage and IncomingMessage abstractions
22
+ * Moved decoding logic from ConsumedMessage to IncomingMessage
23
+ * Removed STATUSES constant from ConsumedMessage
24
+ * Publisher is no longer a Singleton; kept a Singleton version of the Publisher for backwards compatibility that gives a "deprecated" warning
25
+
26
+ ### Removed
27
+ * Removed `BaseMessage` protobuf
28
+ * NullMessage and NullHandler were removed
29
+
30
+ ### Fixed
31
+ * Fixed all Rubocop warnings and errors
32
+
33
+ ## [0.1.7] - 2020-06-29
34
+ ### Added
7
35
  - 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)
36
+
37
+ [Unreleased]: https://github.com/learn-co/railway_ipc_gem/compare/v1.0.0...HEAD
38
+ [1.0.0]: https://github.com/learn-co/railway_ipc_gem/compare/v0.1.7...v1.0.0
39
+ [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/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, :protobuf_message, :delivery_info, :decoded_payload, :encoded_message
9
8
 
10
9
  def self.listen_to(queue:, exchange:)
11
10
  from_queue queue,
@@ -23,81 +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
- @encoded_message = payload
25
+ def queue_name
26
+ queue.name
27
+ end
30
28
 
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
- @protobuf_message = RailwayIpc::BaseMessage.decode(decoded_payload.message)
39
- process_unknown_message_type
40
- end
29
+ def exchange_name
30
+ queue.opts[:exchange]
31
+ end
32
+
33
+ def work(payload)
34
+ message = RailwayIpc::IncomingMessage.new(payload)
35
+ RailwayIpc::ProcessIncomingMessage.call(self, message)
41
36
  ack!
42
37
  rescue StandardError => e
43
38
  RailwayIpc.logger.log_exception(
44
- feature: "railway_consumer",
39
+ feature: 'railway_consumer',
45
40
  error: e.class,
46
41
  error_message: e.message,
47
- payload: payload,
42
+ payload: payload
48
43
  )
49
44
  raise e
50
45
  end
51
46
 
52
- private
53
-
54
- def process_protobuf!(message)
55
- response = handler.handle(protobuf_message)
56
- message.status = RailwayIpc::ConsumedMessage.response_to_status(response)
57
-
58
- message.save!
59
- end
60
-
61
- def process_known_message_type
62
- message = RailwayIpc::ConsumedMessage.find_by(uuid: protobuf_message.uuid)
63
-
64
- return if message && message.processed?
65
-
66
- if message && !message.processed?
67
- message.with_lock("FOR UPDATE NOWAIT") { process_protobuf!(message) }
68
- else
69
- message = create_message_with_status!(RailwayIpc::ConsumedMessage::STATUSES[:processing])
70
- message.with_lock("FOR UPDATE NOWAIT") { process_protobuf!(message) }
71
- end
72
- end
73
-
74
- def process_unknown_message_type
75
- if RailwayIpc::ConsumedMessage.exists?(uuid: protobuf_message.uuid)
76
- return
77
- else
78
- create_message_with_status!(RailwayIpc::ConsumedMessage::STATUSES[:unknown_message_type])
79
- end
80
- end
81
-
82
- def create_message_with_status!(status)
83
- RailwayIpc::ConsumedMessage.create!(
84
- uuid: protobuf_message.uuid,
85
- status: status,
86
- message_type: decoded_payload.type,
87
- user_uuid: protobuf_message.user_uuid,
88
- correlation_id: protobuf_message.correlation_id,
89
- queue: delivery_info.consumer.queue.name,
90
- exchange: delivery_info.exchange,
91
- encoded_message: encoded_message
92
- )
93
- end
94
-
95
- def message_handler_for(decoded_payload)
96
- ConsumerResponseHandlers.instance.get(decoded_payload.type).message
97
- end
98
-
99
- def handler_for(decoded_payload)
100
- 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
101
50
  end
102
51
  end
103
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
@@ -1 +1,9 @@
1
- class RailwayIpc::InvalidProtobuf < StandardError; end
1
+ # frozen_string_literal: true
2
+
3
+ # rubocop:disable Style/ClassAndModuleChildren
4
+ class RailwayIpc::Error < StandardError; end
5
+ class RailwayIpc::InvalidProtobuf < RailwayIpc::Error; end
6
+ class RailwayIpc::FailedToStoreOutgoingMessage < RailwayIpc::Error; end
7
+ class RailwayIpc::IncomingMessage::ParserError < RailwayIpc::Error; end
8
+ class RailwayIpc::IncomingMessage::InvalidMessage < RailwayIpc::Error; end
9
+ # rubocop:enable Style/ClassAndModuleChildren