railway-ipc 0.1.3 → 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
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