railway-ipc 0.1.3 → 0.1.4

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 (39) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +8 -0
  3. data/.tool-versions +1 -0
  4. data/Gemfile.lock +125 -1
  5. data/README.md +5 -0
  6. data/lib/railway_ipc.rb +28 -21
  7. data/lib/railway_ipc/base_message.pb.rb +21 -0
  8. data/lib/railway_ipc/consumer/consumer.rb +112 -0
  9. data/lib/railway_ipc/consumer/consumer_response_handlers.rb +14 -0
  10. data/lib/railway_ipc/errors.rb +1 -0
  11. data/lib/railway_ipc/handler.rb +2 -0
  12. data/lib/railway_ipc/handler_manifest.rb +10 -0
  13. data/lib/railway_ipc/handler_store.rb +21 -0
  14. data/lib/railway_ipc/models/consumed_message.rb +48 -0
  15. data/lib/railway_ipc/models/published_message.rb +27 -0
  16. data/lib/railway_ipc/null_message.rb +1 -1
  17. data/lib/railway_ipc/publisher.rb +9 -4
  18. data/lib/railway_ipc/rabbitmq/adapter.rb +93 -0
  19. data/lib/railway_ipc/rabbitmq/payload.rb +7 -3
  20. data/lib/railway_ipc/rpc/client/client.rb +104 -0
  21. data/lib/railway_ipc/rpc/client/client_response_handlers.rb +25 -0
  22. data/lib/railway_ipc/rpc/client/errors/timeout_error.rb +5 -0
  23. data/lib/railway_ipc/rpc/concerns/error_adapter_configurable.rb +13 -0
  24. data/lib/railway_ipc/rpc/concerns/message_observation_configurable.rb +18 -0
  25. data/lib/railway_ipc/rpc/concerns/publish_location_configurable.rb +13 -0
  26. data/lib/railway_ipc/rpc/rpc.rb +2 -0
  27. data/lib/railway_ipc/rpc/server/server.rb +89 -0
  28. data/lib/railway_ipc/rpc/server/server_response_handlers.rb +17 -0
  29. data/lib/railway_ipc/tasks/generate_migrations.rake +26 -0
  30. data/lib/railway_ipc/version.rb +1 -1
  31. data/priv/migrations/add_railway_ipc_consumed_messages.rb +19 -0
  32. data/priv/migrations/add_railway_ipc_published_messages.rb +18 -0
  33. data/railway_ipc.gemspec +25 -16
  34. metadata +123 -8
  35. data/lib/railway_ipc/client.rb +0 -87
  36. data/lib/railway_ipc/concerns/message_handling.rb +0 -118
  37. data/lib/railway_ipc/consumer.rb +0 -26
  38. data/lib/railway_ipc/rabbitmq/connection.rb +0 -55
  39. data/lib/railway_ipc/server.rb +0 -50
@@ -0,0 +1,2 @@
1
+ require 'railway_ipc/rpc/client/client'
2
+ require 'railway_ipc/rpc/server/server'
@@ -0,0 +1,89 @@
1
+ require 'railway_ipc/rpc/server/server_response_handlers'
2
+ require 'railway_ipc/rpc/concerns/error_adapter_configurable'
3
+ require 'railway_ipc/rpc/concerns/message_observation_configurable'
4
+
5
+ module RailwayIpc
6
+ class Server
7
+ extend RailwayIpc::RPC::ErrorAdapterConfigurable
8
+ extend RailwayIpc::RPC::MessageObservationConfigurable
9
+ attr_reader :message, :responder
10
+
11
+ def self.respond_to(message_type, with:)
12
+ RailwayIpc::RPC::ServerResponseHandlers.instance.register(handler: with, message: message_type)
13
+ end
14
+
15
+ def initialize(opts = {automatic_recovery: true}, rabbit_adapter: RailwayIpc::Rabbitmq::Adapter)
16
+ @rabbit_connection = rabbit_adapter.new(
17
+ queue_name: self.class.queue_name,
18
+ exchange_name: self.class.exchange_name,
19
+ options: opts
20
+ )
21
+ end
22
+
23
+ def run
24
+ rabbit_connection
25
+ .connect
26
+ .create_exchange
27
+ .create_queue(durable: true)
28
+ .bind_queue_to_exchange
29
+ subscribe_to_queue
30
+ end
31
+
32
+ def work(payload)
33
+ decoded_payload = RailwayIpc::Rabbitmq::Payload.decode(payload)
34
+ case decoded_payload.type
35
+ when *registered_handlers
36
+ responder = get_responder(decoded_payload)
37
+ @message = get_message_class(decoded_payload).decode(decoded_payload.message)
38
+ responder.respond(message)
39
+ else
40
+ @message = LearnIpc::ErrorMessage.decode(decoded_payload.message)
41
+ raise RailwayIpc::UnhandledMessageError, "#{self.class} does not know how to handle #{decoded_payload.type}"
42
+ end
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
51
+ end
52
+
53
+ def handle_request(payload)
54
+ response = work(payload)
55
+ rescue StandardError => e
56
+ RailwayIpc.logger.error(message, "Error responding to message. Error: #{e.class}, #{e.message}")
57
+ response = self.class.rpc_error_adapter_class.error_message(e, message)
58
+ ensure
59
+ if response
60
+ rabbit_connection.reply(
61
+ RailwayIpc::Rabbitmq::Payload.encode(response), message.reply_to
62
+ )
63
+ end
64
+ end
65
+
66
+ private
67
+
68
+ attr_reader :rabbit_connection
69
+
70
+ def get_message_class(decoded_payload)
71
+ RailwayIpc::RPC::ServerResponseHandlers.instance.get(decoded_payload.type).message
72
+ end
73
+
74
+ def get_responder(decoded_payload)
75
+ RailwayIpc::RPC::ServerResponseHandlers.instance.get(decoded_payload.type).handler.new
76
+ end
77
+
78
+ def registered_handlers
79
+ RailwayIpc::RPC::ServerResponseHandlers.instance.registered
80
+ end
81
+
82
+ def subscribe_to_queue
83
+ rabbit_connection.subscribe do |_delivery_info, _metadata, payload|
84
+ handle_request(payload)
85
+ end
86
+ end
87
+
88
+ end
89
+ end
@@ -0,0 +1,17 @@
1
+ require 'railway_ipc/handler_store'
2
+ module RailwayIpc
3
+ module RPC
4
+ class ServerResponseHandlers
5
+ include Singleton
6
+ extend Forwardable
7
+
8
+ def_delegators :handler_store, :registered, :register, :get
9
+
10
+ private
11
+
12
+ def handler_store
13
+ @handler_store ||= RailwayIpc::HandlerStore.new
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,26 @@
1
+ require 'fileutils'
2
+
3
+ namespace :railway_ipc do
4
+ namespace :generate do
5
+ desc "Generates migrations to store Railway messages"
6
+ task :migrations do
7
+ if defined?(ActiveRecord::Base)
8
+ puts "generating Railway IPC table migrations"
9
+ seconds = 0
10
+ gem_path = Gem.loaded_specs['railway-ipc'].full_gem_path
11
+ folder_dest = "#{Rails.root.to_s}/db/migrate"
12
+ FileUtils.mkdir_p(folder_dest)
13
+
14
+ Dir.glob("#{gem_path}/priv/migrations/*.rb").each do |file_path|
15
+ file_name = File.basename(file_path)
16
+ migration_timestamp = (Time.now + seconds).utc.strftime("%Y%m%d%H%M%S") % "%.14d"
17
+ new_file_name = "#{migration_timestamp}_#{file_name}"
18
+ FileUtils.copy_file(file_path, "#{folder_dest}/#{new_file_name}")
19
+ seconds += 1
20
+ end
21
+ else
22
+ raise "Migration generation requires active record"
23
+ end
24
+ end
25
+ end
26
+ end
@@ -1,3 +1,3 @@
1
1
  module RailwayIpc
2
- VERSION = "0.1.3"
2
+ VERSION = "0.1.4"
3
3
  end
@@ -0,0 +1,19 @@
1
+ class AddRailwayIpcConsumedMessages < ActiveRecord::Migration
2
+ def change
3
+ create_table :railway_ipc_consumed_messages, id: false do | t |
4
+ t.uuid :uuid, null: false
5
+ t.string :message_type
6
+ t.uuid :user_uuid
7
+ t.uuid :correlation_id
8
+ t.text :encoded_message
9
+ t.string :status, null: false
10
+ t.string :queue
11
+ t.string :exchange
12
+
13
+ t.datetime :updated_at
14
+ t.datetime :inserted_at
15
+ end
16
+
17
+ add_index :railway_ipc_consumed_messages, :uuid, unique: true
18
+ end
19
+ end
@@ -0,0 +1,18 @@
1
+ class AddRailwayIpcPublishedMessages < ActiveRecord::Migration
2
+ def change
3
+ create_table :railway_ipc_published_messages, id: false do | t |
4
+ t.uuid :uuid, null: false
5
+ t.string :message_type
6
+ t.uuid :user_uuid
7
+ t.uuid :correlation_id
8
+ t.text :encoded_message
9
+ t.string :status, null: false
10
+ t.string :exchange
11
+
12
+ t.datetime :updated_at
13
+ t.datetime :inserted_at
14
+ end
15
+
16
+ add_index :railway_ipc_published_messages, :uuid, unique: true
17
+ end
18
+ end
data/railway_ipc.gemspec CHANGED
@@ -3,15 +3,15 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
3
  require "railway_ipc/version"
4
4
 
5
5
  Gem::Specification.new do |spec|
6
- spec.name = "railway-ipc"
7
- spec.version = RailwayIpc::VERSION
8
- spec.authors = ""
9
- spec.email = ""
6
+ spec.name = "railway-ipc"
7
+ spec.version = RailwayIpc::VERSION
8
+ spec.authors = ""
9
+ spec.email = ""
10
10
 
11
- spec.summary = %q{IPC components for Rails}
12
- spec.description = %q{IPC components for Rails}
13
- spec.homepage = "http://learn.co"
14
- spec.license = "MIT"
11
+ spec.summary = %q{IPC components for Rails}
12
+ spec.description = %q{IPC components for Rails}
13
+ spec.homepage = "http://learn.co"
14
+ spec.license = "MIT"
15
15
 
16
16
  # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
17
17
  # to allow pushing to a single host or delete this section to allow pushing to any host.
@@ -19,23 +19,32 @@ Gem::Specification.new do |spec|
19
19
  spec.metadata["allowed_push_host"] = "https://rubygems.org/"
20
20
  else
21
21
  raise "RubyGems 2.0 or newer is required to protect against " \
22
- "public gem pushes."
22
+ "public gem pushes."
23
23
  end
24
24
 
25
25
  # Specify which files should be added to the gem when it is released.
26
26
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
27
- spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
27
+ spec.files = Dir.chdir(File.expand_path("..", __FILE__)) do
28
28
  `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
29
29
  end
30
- spec.bindir = "exe"
31
- spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
30
+ spec.bindir = "exe"
31
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
32
32
  spec.require_paths = ["lib"]
33
33
 
34
34
  spec.add_development_dependency "rake", ">= 10.0.0"
35
35
  spec.add_development_dependency "bundler", "2.0.1"
36
36
  spec.add_development_dependency "rspec", "~> 3.0"
37
- spec.add_development_dependency "pry-byebug", '3.4.2'
38
- spec.add_development_dependency 'google-protobuf', '~> 3.9'
39
- spec.add_dependency 'sneakers', '~> 2.3.5'
40
- spec.add_dependency 'bunny', '~> 2.2.0'
37
+ spec.add_development_dependency "factory_bot", "~> 5.1"
38
+ spec.add_development_dependency "pry-byebug", "3.4.2"
39
+ spec.add_development_dependency "google-protobuf", "~> 3.9"
40
+ spec.add_dependency "sneakers", "~> 2.3.5"
41
+ spec.add_dependency "bunny", "~> 2.2.0"
42
+
43
+ # Setup for testing Rails type code within mock Rails app
44
+ spec.add_development_dependency "rails", "~> 5.0.7"
45
+ spec.add_development_dependency "rspec-rails"
46
+ spec.add_development_dependency "pg", "~> 0.18"
47
+ spec.add_development_dependency "shoulda-matchers", "~> 4.2"
48
+ spec.add_development_dependency "database_cleaner", "~> 1.7"
49
+ spec.add_development_dependency "listen", "~> 3.0.5"
41
50
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: railway-ipc
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - ''
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-10-07 00:00:00.000000000 Z
11
+ date: 2020-01-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -52,6 +52,20 @@ dependencies:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
54
  version: '3.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: factory_bot
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '5.1'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '5.1'
55
69
  - !ruby/object:Gem::Dependency
56
70
  name: pry-byebug
57
71
  requirement: !ruby/object:Gem::Requirement
@@ -108,6 +122,90 @@ dependencies:
108
122
  - - "~>"
109
123
  - !ruby/object:Gem::Version
110
124
  version: 2.2.0
125
+ - !ruby/object:Gem::Dependency
126
+ name: rails
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: 5.0.7
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: 5.0.7
139
+ - !ruby/object:Gem::Dependency
140
+ name: rspec-rails
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
153
+ - !ruby/object:Gem::Dependency
154
+ name: pg
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - "~>"
158
+ - !ruby/object:Gem::Version
159
+ version: '0.18'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - "~>"
165
+ - !ruby/object:Gem::Version
166
+ version: '0.18'
167
+ - !ruby/object:Gem::Dependency
168
+ name: shoulda-matchers
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - "~>"
172
+ - !ruby/object:Gem::Version
173
+ version: '4.2'
174
+ type: :development
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - "~>"
179
+ - !ruby/object:Gem::Version
180
+ version: '4.2'
181
+ - !ruby/object:Gem::Dependency
182
+ name: database_cleaner
183
+ requirement: !ruby/object:Gem::Requirement
184
+ requirements:
185
+ - - "~>"
186
+ - !ruby/object:Gem::Version
187
+ version: '1.7'
188
+ type: :development
189
+ prerelease: false
190
+ version_requirements: !ruby/object:Gem::Requirement
191
+ requirements:
192
+ - - "~>"
193
+ - !ruby/object:Gem::Version
194
+ version: '1.7'
195
+ - !ruby/object:Gem::Dependency
196
+ name: listen
197
+ requirement: !ruby/object:Gem::Requirement
198
+ requirements:
199
+ - - "~>"
200
+ - !ruby/object:Gem::Version
201
+ version: 3.0.5
202
+ type: :development
203
+ prerelease: false
204
+ version_requirements: !ruby/object:Gem::Requirement
205
+ requirements:
206
+ - - "~>"
207
+ - !ruby/object:Gem::Version
208
+ version: 3.0.5
111
209
  description: IPC components for Rails
112
210
  email: ''
113
211
  executables: []
@@ -116,6 +214,7 @@ extra_rdoc_files: []
116
214
  files:
117
215
  - ".gitignore"
118
216
  - ".rspec"
217
+ - ".tool-versions"
119
218
  - ".travis.yml"
120
219
  - CODE_OF_CONDUCT.md
121
220
  - Gemfile
@@ -127,24 +226,40 @@ files:
127
226
  - bin/setup
128
227
  - lib/railway_ipc.rb
129
228
  - lib/railway_ipc/Rakefile
130
- - lib/railway_ipc/client.rb
131
- - lib/railway_ipc/concerns/message_handling.rb
132
- - lib/railway_ipc/consumer.rb
229
+ - lib/railway_ipc/base_message.pb.rb
230
+ - lib/railway_ipc/consumer/consumer.rb
231
+ - lib/railway_ipc/consumer/consumer_response_handlers.rb
232
+ - lib/railway_ipc/errors.rb
133
233
  - lib/railway_ipc/handler.rb
234
+ - lib/railway_ipc/handler_manifest.rb
235
+ - lib/railway_ipc/handler_store.rb
134
236
  - lib/railway_ipc/logger.rb
237
+ - lib/railway_ipc/models/consumed_message.rb
238
+ - lib/railway_ipc/models/published_message.rb
135
239
  - lib/railway_ipc/null_handler.rb
136
240
  - lib/railway_ipc/null_message.rb
137
241
  - lib/railway_ipc/publisher.rb
138
- - lib/railway_ipc/rabbitmq/connection.rb
242
+ - lib/railway_ipc/rabbitmq/adapter.rb
139
243
  - lib/railway_ipc/rabbitmq/payload.rb
140
244
  - lib/railway_ipc/railtie.rb
141
245
  - lib/railway_ipc/responder.rb
142
246
  - lib/railway_ipc/response.rb
143
- - lib/railway_ipc/server.rb
247
+ - lib/railway_ipc/rpc/client/client.rb
248
+ - lib/railway_ipc/rpc/client/client_response_handlers.rb
249
+ - lib/railway_ipc/rpc/client/errors/timeout_error.rb
250
+ - lib/railway_ipc/rpc/concerns/error_adapter_configurable.rb
251
+ - lib/railway_ipc/rpc/concerns/message_observation_configurable.rb
252
+ - lib/railway_ipc/rpc/concerns/publish_location_configurable.rb
253
+ - lib/railway_ipc/rpc/rpc.rb
254
+ - lib/railway_ipc/rpc/server/server.rb
255
+ - lib/railway_ipc/rpc/server/server_response_handlers.rb
256
+ - lib/railway_ipc/tasks/generate_migrations.rake
144
257
  - lib/railway_ipc/tasks/start_consumers.rake
145
258
  - lib/railway_ipc/tasks/start_servers.rake
146
259
  - lib/railway_ipc/unhandled_message_error.rb
147
260
  - lib/railway_ipc/version.rb
261
+ - priv/migrations/add_railway_ipc_consumed_messages.rb
262
+ - priv/migrations/add_railway_ipc_published_messages.rb
148
263
  - railway_ipc.gemspec
149
264
  homepage: http://learn.co
150
265
  licenses:
@@ -167,7 +282,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
167
282
  version: '0'
168
283
  requirements: []
169
284
  rubyforge_project:
170
- rubygems_version: 2.7.7
285
+ rubygems_version: 2.7.6.2
171
286
  signing_key:
172
287
  specification_version: 4
173
288
  summary: IPC components for Rails
@@ -1,87 +0,0 @@
1
- require "railway_ipc/rabbitmq/connection"
2
- require "railway_ipc/concerns/message_handling"
3
-
4
- module RailwayIpc
5
- class Client
6
- include RailwayIpc::Rabbitmq::Connection
7
- include RailwayIpc::Concerns::MessageHandling
8
-
9
- class TimeoutError < StandardError; end
10
-
11
- attr_accessor :call_id, :response, :lock, :condition, :reply_queue, :request_message
12
-
13
- def self.request(message)
14
- new.request(message)
15
- end
16
-
17
- def self.publish_to(queue:, exchange:)
18
- queue(queue)
19
- exchange(exchange)
20
- end
21
-
22
- def initialize(queue=nil, pool=nil, opts={automatic_recovery: false})
23
- super
24
- setup_exchange
25
- end
26
-
27
- def request(request_message, timeout=10)
28
- @request_message = request_message
29
- setup_reply_queue
30
- publish_message
31
- poll_for_message(timeout)
32
- build_timeout_response unless response
33
- response
34
- end
35
-
36
- private
37
-
38
- def setup_exchange
39
- @exchange = Bunny::Exchange.new(channel, :fanout, self.class.exchange_name, durable: true)
40
- channel.queue(self.class.queue_name, durable: true).bind(exchange)
41
- end
42
-
43
- def setup_reply_queue
44
- @reply_queue = channel.queue('', auto_delete: true, exclusive: true)
45
- @call_id = request_message.correlation_id
46
- request_message.reply_to = reply_queue.name
47
- end
48
-
49
- def publish_message
50
- RailwayIpc.logger.info(request_message, "Sending request")
51
- exchange.publish(RailwayIpc::Rabbitmq::Payload.encode(request_message), routing_key: '')
52
- end
53
-
54
- def poll_for_message(timeout)
55
- count = 0
56
- until response || count >= timeout do
57
- delivery_info, properties, payload = reply_queue.pop
58
- handle_response(payload) if payload
59
- count+= 1
60
- sleep(1)
61
- end
62
- end
63
-
64
- def build_timeout_response
65
- error = TimeoutError.new("Client timed out")
66
- response_message = rpc_error_adapter.error_message(error, request_message)
67
- self.response = RailwayIpc::Response.new(response_message, success: false)
68
- self.stop
69
- end
70
-
71
- def handle_response(payload)
72
- begin
73
- response_message = work(payload)
74
- if response_message.correlation_id == self.call_id
75
- RailwayIpc.logger.info(response_message, "Handling response")
76
- self.response = RailwayIpc::Response.new(response_message, success: true)
77
- self.stop
78
- end
79
- rescue StandardError => e
80
- RailwayIpc.logger.error(message, "Error handling response. Error #{e.class}, message: #{e.message}")
81
- response_message = rpc_error_adapter.error_message(e, message)
82
- self.response = RailwayIpc::Response.new(response_message, success: false)
83
- self.stop
84
- end
85
- end
86
- end
87
- end