artery 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.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +43 -0
- data/Rakefile +29 -0
- data/app/models/concerns/artery/message_model.rb +53 -0
- data/config/routes.rb +4 -0
- data/db/migrate/20170110090949_create_artery_messages.rb +14 -0
- data/db/migrate/20170116143013_create_artery_last_model_updates.rb +10 -0
- data/db/migrate/20170420155129_change_last_model_updates_to_subscription_infos.rb +17 -0
- data/db/migrate/20171020104646_add_subscriber_to_artery_subscription_infos.rb +5 -0
- data/db/migrate/20181211110018_add_latest_index_to_artery_subscription_infos.rb +5 -0
- data/db/migrate/20200109120304_add_index_on_model_to_artery_messages.rb +9 -0
- data/db/migrate/20200109120305_remove_last_message_at_from_artery_subscription_infos.rb +9 -0
- data/db/migrate/20240411120304_add_synchronization_heartbeat_to_artery_subscription_infos.rb +11 -0
- data/exe/artery-check +9 -0
- data/exe/artery-clean +9 -0
- data/exe/artery-sync +9 -0
- data/exe/artery-worker +9 -0
- data/lib/artery/active_record/message.rb +41 -0
- data/lib/artery/active_record/subscription_info.rb +50 -0
- data/lib/artery/active_record.rb +8 -0
- data/lib/artery/backend.rb +95 -0
- data/lib/artery/backends/base.rb +41 -0
- data/lib/artery/backends/fake.rb +24 -0
- data/lib/artery/backends/nats_pure.rb +86 -0
- data/lib/artery/check.rb +48 -0
- data/lib/artery/config.rb +72 -0
- data/lib/artery/engine.rb +16 -0
- data/lib/artery/errors.rb +76 -0
- data/lib/artery/healthz_subscription.rb +14 -0
- data/lib/artery/model/callbacks.rb +40 -0
- data/lib/artery/model/subscriptions.rb +147 -0
- data/lib/artery/model.rb +96 -0
- data/lib/artery/no_brainer/message.rb +67 -0
- data/lib/artery/no_brainer/subscription_info.rb +67 -0
- data/lib/artery/no_brainer.rb +8 -0
- data/lib/artery/routing.rb +63 -0
- data/lib/artery/subscription/incoming_message.rb +94 -0
- data/lib/artery/subscription/synchronization.rb +221 -0
- data/lib/artery/subscription.rb +136 -0
- data/lib/artery/subscriptions.rb +42 -0
- data/lib/artery/sync.rb +35 -0
- data/lib/artery/version.rb +5 -0
- data/lib/artery/worker.rb +75 -0
- data/lib/artery/worker_healthz_subscription.rb +23 -0
- data/lib/artery.rb +56 -0
- data/lib/multiblock_has_block.rb +11 -0
- data/lib/tasks/artery_tasks.rake +6 -0
- metadata +160 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 1e98c3102570a6fbac048ecad94e61d537475a13e74d64108d41a5538e209a65
|
|
4
|
+
data.tar.gz: b1d4dee0d394e6d3e358a8bcf53d46130ec4f6cb342bcbfddf281b8700d3d8d5
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 0afa95d312f8863dfcec43024bd910552021176eea27779ed34eca7f8d4ff36031e0b62572a8ca234d893eaa1e739eed3e4774e5e4a566d7564b2b1dede49ad8
|
|
7
|
+
data.tar.gz: 272299e4ad9c8df1fa915e53d1629ea102187c0e828e17d3d714d0d792ead53cf64539b1a9819a79337c890c5ee30521b03efaf026a371684c7f64edd1e5d753
|
data/MIT-LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Copyright 2017 Sergey Gnuskov
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
4
|
+
a copy of this software and associated documentation files (the
|
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
9
|
+
the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be
|
|
12
|
+
included in all copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Artery
|
|
2
|
+
Main messaging system between Rails [micro]services implementing message bus pattern on NATS (for now).
|
|
3
|
+
|
|
4
|
+
## Usage
|
|
5
|
+
How to use my plugin.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
Add this line to your application's Gemfile:
|
|
9
|
+
|
|
10
|
+
```ruby
|
|
11
|
+
gem 'artery'
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
And then execute:
|
|
15
|
+
```bash
|
|
16
|
+
$ bundle
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Or install it yourself as:
|
|
20
|
+
```bash
|
|
21
|
+
$ gem install artery
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Then install migrations and run (if using ActiveRecord):
|
|
25
|
+
```bash
|
|
26
|
+
$ rake artery:install:migrations
|
|
27
|
+
$ rake db:migrate
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Admin interface
|
|
31
|
+
|
|
32
|
+
In admin interface you can list your artery endpoints and check their statuses.
|
|
33
|
+
You can mount admin ui to your routes via:
|
|
34
|
+
```ruby
|
|
35
|
+
mount Artery::Engine => '/artery'
|
|
36
|
+
```
|
|
37
|
+
And then you can access it by url `http(s)://{ your_app_url }/artery/`.
|
|
38
|
+
|
|
39
|
+
## Contributing
|
|
40
|
+
Contribution directions go here.
|
|
41
|
+
|
|
42
|
+
## License
|
|
43
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
begin
|
|
4
|
+
require 'bundler/setup'
|
|
5
|
+
rescue LoadError
|
|
6
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
require 'rdoc/task'
|
|
10
|
+
|
|
11
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
|
12
|
+
rdoc.rdoc_dir = 'rdoc'
|
|
13
|
+
rdoc.title = 'Artery'
|
|
14
|
+
rdoc.options << '--line-numbers'
|
|
15
|
+
rdoc.rdoc_files.include('README.md')
|
|
16
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
APP_RAKEFILE = File.expand_path('spec/dummy/Rakefile', __dir__)
|
|
20
|
+
load 'rails/tasks/engine.rake'
|
|
21
|
+
|
|
22
|
+
load 'rails/tasks/statistics.rake'
|
|
23
|
+
|
|
24
|
+
require 'bundler/gem_tasks'
|
|
25
|
+
require 'rspec/core/rake_task'
|
|
26
|
+
|
|
27
|
+
RSpec::Core::RakeTask.new(:spec)
|
|
28
|
+
|
|
29
|
+
task default: :spec
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Artery
|
|
4
|
+
module MessageModel
|
|
5
|
+
extend ActiveSupport::Concern
|
|
6
|
+
|
|
7
|
+
included do
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
module ClassMethods
|
|
11
|
+
MAX_MESSAGE_AGE = ENV.fetch('ARTERY_MAX_MESSAGE_AGE', '90').to_i.days
|
|
12
|
+
|
|
13
|
+
def after_index(model, index)
|
|
14
|
+
raise NotImplementedError
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def latest_index(model)
|
|
18
|
+
raise NotImplementedError
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def delete_old
|
|
22
|
+
raise NotImplementedError
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def uri
|
|
27
|
+
Artery::Routing.uri(model: model, action: action)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def uri=(uri)
|
|
31
|
+
self.model = uri.model
|
|
32
|
+
self.action = uri.action
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def route
|
|
36
|
+
uri.to_route
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def to_artery
|
|
40
|
+
data.merge('_index' => index)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def previous_index
|
|
44
|
+
raise NotImplementedError
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
protected
|
|
48
|
+
|
|
49
|
+
def send_to_artery
|
|
50
|
+
Artery.publish route, to_artery.merge('_previous_index' => previous_index)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
data/config/routes.rb
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
class CreateArteryMessages < ActiveRecord::Migration[5.0]
|
|
2
|
+
def change
|
|
3
|
+
create_table :artery_messages do |t|
|
|
4
|
+
t.string :version
|
|
5
|
+
|
|
6
|
+
t.string :model, null: false
|
|
7
|
+
t.string :action, null: false
|
|
8
|
+
|
|
9
|
+
t.text :data, null: false
|
|
10
|
+
|
|
11
|
+
t.timestamp :created_at, limit: 6
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
class CreateArteryLastModelUpdates < ActiveRecord::Migration[5.0]
|
|
2
|
+
def change
|
|
3
|
+
create_table :artery_last_model_updates do |t|
|
|
4
|
+
t.string :service, null: false
|
|
5
|
+
t.string :model, null: false
|
|
6
|
+
|
|
7
|
+
t.timestamp :last_message_at, limit: 6, null: false
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
class ChangeLastModelUpdatesToSubscriptionInfos < ActiveRecord::Migration[5.0]
|
|
2
|
+
def up
|
|
3
|
+
rename_table :artery_last_model_updates, :artery_subscription_infos
|
|
4
|
+
|
|
5
|
+
change_column :artery_subscription_infos, :last_message_at, :timestamp, limit: 6, null: true
|
|
6
|
+
add_column :artery_subscription_infos, :synchronization_in_progress, :boolean, default: false
|
|
7
|
+
add_column :artery_subscription_infos, :synchronization_page, :integer
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def down
|
|
11
|
+
remove_column :artery_subscription_infos, :synchronization_in_progress
|
|
12
|
+
remove_column :artery_subscription_infos, :synchronization_page
|
|
13
|
+
change_column :artery_subscription_infos, :last_message_at, :timestamp, limit: 6, null: false
|
|
14
|
+
|
|
15
|
+
rename_table :artery_subscription_infos, :artery_last_model_updates
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
class RemoveLastMessageAtFromArterySubscriptionInfos < ActiveRecord::Migration[5.2]
|
|
2
|
+
def up
|
|
3
|
+
remove_column :artery_subscription_infos, :last_message_at if column_exists?(:artery_subscription_infos, :last_message_at)
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
def down
|
|
7
|
+
add_column :artery_subscription_infos, :last_message_at, :timestamp
|
|
8
|
+
end
|
|
9
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
class AddSynchronizationHeartbeatToArterySubscriptionInfos < ActiveRecord::Migration[5.2]
|
|
2
|
+
def up
|
|
3
|
+
return if column_exists?(:artery_subscription_infos, :synchronization_heartbeat)
|
|
4
|
+
|
|
5
|
+
add_column :artery_subscription_infos, :synchronization_heartbeat, :timestamp, after: :synchronization_in_progress
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def down
|
|
9
|
+
remove_column :artery_subscription_infos, :synchronization_heartbeat
|
|
10
|
+
end
|
|
11
|
+
end
|
data/exe/artery-check
ADDED
data/exe/artery-clean
ADDED
data/exe/artery-sync
ADDED
data/exe/artery-worker
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Artery
|
|
4
|
+
module ActiveRecord
|
|
5
|
+
class Message < ::ActiveRecord::Base
|
|
6
|
+
include MessageModel
|
|
7
|
+
|
|
8
|
+
self.table_name = 'artery_messages'
|
|
9
|
+
|
|
10
|
+
serialize :data, coder: JSON
|
|
11
|
+
|
|
12
|
+
after_commit :send_to_artery, on: :create
|
|
13
|
+
|
|
14
|
+
alias index id
|
|
15
|
+
|
|
16
|
+
class << self
|
|
17
|
+
def after_index(model, index)
|
|
18
|
+
where(model: model)
|
|
19
|
+
.where(arel_table[:id].gt(index)).order(:id)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def latest_index(model)
|
|
23
|
+
where(model: model).last&.id.to_i
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def delete_old
|
|
27
|
+
max_aged_id = where(arel_table[:created_at].lt(MAX_MESSAGE_AGE.ago)).maximum(:id)
|
|
28
|
+
where(arel_table[:id].lteq(max_aged_id)).delete_all if max_aged_id.to_i.positive?
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# It is used in after_commit, so we always know previous index based on our current index
|
|
33
|
+
def previous_index
|
|
34
|
+
scope = self.class.where(model: model).order(:id)
|
|
35
|
+
scope = scope.where(self.class.arel_table[:id].lt(index)) if index
|
|
36
|
+
|
|
37
|
+
scope.select(:id).last&.id.to_i
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Artery
|
|
4
|
+
module ActiveRecord
|
|
5
|
+
class SubscriptionInfo < ::ActiveRecord::Base
|
|
6
|
+
self.table_name = 'artery_subscription_infos'
|
|
7
|
+
|
|
8
|
+
class << self
|
|
9
|
+
def find_for_subscription(subscription)
|
|
10
|
+
info = find_or_initialize_by(subscriber: subscription.subscriber.to_s,
|
|
11
|
+
service: subscription.uri.service,
|
|
12
|
+
model: subscription.uri.model)
|
|
13
|
+
|
|
14
|
+
info.save! if info.new_record?
|
|
15
|
+
info
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def synchronization_transaction(&block)
|
|
20
|
+
with_lock(&block)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def with_lock
|
|
24
|
+
self.class.transaction do
|
|
25
|
+
unless (was_locked = @locked) # prevent double lock to reduce selects
|
|
26
|
+
Artery.logger.debug "WAITING FOR LOCK... [LATEST_INDEX: #{latest_index}]"
|
|
27
|
+
|
|
28
|
+
reload lock: true # explicitely reload record
|
|
29
|
+
|
|
30
|
+
Artery.logger.debug "GOT LOCK! [LATEST_INDEX: #{latest_index}]"
|
|
31
|
+
|
|
32
|
+
@locked = true
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
yield
|
|
36
|
+
ensure
|
|
37
|
+
@locked = false unless was_locked
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def lock_for_message(message, &blk)
|
|
42
|
+
if message.has_index? # only 'indexed' messages should lock
|
|
43
|
+
with_lock(&blk)
|
|
44
|
+
else
|
|
45
|
+
yield
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Artery
|
|
4
|
+
module Backend
|
|
5
|
+
extend ActiveSupport::Concern
|
|
6
|
+
|
|
7
|
+
included do
|
|
8
|
+
class << self
|
|
9
|
+
attr_reader :backend_in_use, :backends
|
|
10
|
+
|
|
11
|
+
def register_backend(type, class_name)
|
|
12
|
+
@backends ||= {}
|
|
13
|
+
@backends[type.to_sym] = class_name
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def use_backend(type)
|
|
17
|
+
type = type.to_sym
|
|
18
|
+
raise ArgumentError, "Artery has no registered backend '#{type}'" unless backends.key?(type)
|
|
19
|
+
|
|
20
|
+
@backend_in_use = type
|
|
21
|
+
|
|
22
|
+
@backend&.stop
|
|
23
|
+
@backend = nil
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def backend
|
|
27
|
+
@backend ||= begin
|
|
28
|
+
backend_class = Artery::Backends.const_get(backends[backend_in_use])
|
|
29
|
+
backend_class.new backend_config
|
|
30
|
+
rescue LoadError, NameError => e
|
|
31
|
+
raise "Unable to load backend #{type}: #{e.message}"
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
delegate :start, :stop, :connect, :unsubscribe, to: :backend
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
module ClassMethods
|
|
40
|
+
def subscribe(route, options = {})
|
|
41
|
+
backend.subscribe(route, options) do |json, reply, from|
|
|
42
|
+
json = '{}' if json.blank?
|
|
43
|
+
|
|
44
|
+
yield(JSON.parse(json).with_indifferent_access, reply, from)
|
|
45
|
+
rescue StandardError => e
|
|
46
|
+
Artery.handle_error FormatError.new(from, json, original_exception: e,
|
|
47
|
+
subscription: { route: from, data: json })
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# rubocop:disable Metrics/AbcSize
|
|
52
|
+
def request(route, data = nil, options = {})
|
|
53
|
+
raise ArgumentError, 'You must provide block to handle response' unless block_given?
|
|
54
|
+
|
|
55
|
+
handler = Multiblock.wrapper
|
|
56
|
+
uri = Routing.uri(route)
|
|
57
|
+
|
|
58
|
+
yield(handler)
|
|
59
|
+
|
|
60
|
+
data ||= {}
|
|
61
|
+
Artery.logger.debug "REQUESTED: <#{uri.to_route}> #{data.to_json}"
|
|
62
|
+
|
|
63
|
+
backend.request(uri.to_route, data.to_json, options) do |message|
|
|
64
|
+
if message.is_a?(Error) # timeout case
|
|
65
|
+
Artery.logger.debug "REQUEST ERROR: <#{uri.to_route}> #{message.message}"
|
|
66
|
+
handler.call :error, message
|
|
67
|
+
else
|
|
68
|
+
Artery.logger.debug "REQUEST RESPONSE: <#{uri.to_route}> #{message}"
|
|
69
|
+
begin
|
|
70
|
+
message ||= '{}'
|
|
71
|
+
response = JSON.parse(message).with_indifferent_access
|
|
72
|
+
|
|
73
|
+
if response.key?(:error)
|
|
74
|
+
handler.call :error, RequestError.new(uri, response, request: { route: uri.to_route, data: data.to_json },
|
|
75
|
+
response: message)
|
|
76
|
+
else
|
|
77
|
+
handler.call :success, response
|
|
78
|
+
end
|
|
79
|
+
rescue JSON::ParserError => e
|
|
80
|
+
Artery.handle_error FormatError.new(uri, message, original_exception: e,
|
|
81
|
+
request: { route: uri.to_route, data: data.to_json },
|
|
82
|
+
response: message)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
# rubocop:enable Metrics/AbcSize
|
|
88
|
+
|
|
89
|
+
def publish(route, data)
|
|
90
|
+
backend.publish(route, data.to_json)
|
|
91
|
+
Artery.logger.debug "PUBLISHED: <#{route}> #{data.to_json}"
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Artery
|
|
4
|
+
module Backends
|
|
5
|
+
class Base
|
|
6
|
+
attr_accessor :config
|
|
7
|
+
|
|
8
|
+
def initialize(config = {})
|
|
9
|
+
@config = config
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def start(*_args)
|
|
13
|
+
raise NotImplementedError
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def connect(*_args)
|
|
17
|
+
raise NotImplementedError
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def subscribe(*_args)
|
|
21
|
+
raise NotImplementedError
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def unsubscribe(*_args)
|
|
25
|
+
raise NotImplementedError
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def publish(*_args)
|
|
29
|
+
raise NotImplementedError
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def request(*_args)
|
|
33
|
+
raise NotImplementedError
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def stop(*_args)
|
|
37
|
+
raise NotImplementedError
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# For the test environment
|
|
4
|
+
module Artery
|
|
5
|
+
module Backends
|
|
6
|
+
class Fake < Base
|
|
7
|
+
def start(*_args); end
|
|
8
|
+
|
|
9
|
+
def connect(*_args); end
|
|
10
|
+
|
|
11
|
+
def subscribe(*_args); end
|
|
12
|
+
|
|
13
|
+
def unsubscribe(*_args); end
|
|
14
|
+
|
|
15
|
+
def publish(*_args); end
|
|
16
|
+
|
|
17
|
+
def request(_route, _data, _opts = {})
|
|
18
|
+
yield if block_given?
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def stop(*_args); end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'nats/client'
|
|
4
|
+
|
|
5
|
+
module Artery
|
|
6
|
+
module Backends
|
|
7
|
+
class NATSPure < Base
|
|
8
|
+
def client
|
|
9
|
+
@client || connect
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
delegate :connected?, :connecting?, :subscribe, to: :client
|
|
13
|
+
|
|
14
|
+
def connect
|
|
15
|
+
@client ||= begin
|
|
16
|
+
client = ::NATS.connect(options)
|
|
17
|
+
|
|
18
|
+
client.on_reconnect do
|
|
19
|
+
Artery.logger.debug "Reconnected to server at #{client.connected_server}"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
client.on_disconnect do
|
|
23
|
+
Artery.logger.debug 'Disconnected!'
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
client.on_close do
|
|
27
|
+
Artery.logger.debug 'Connection to NATS closed'
|
|
28
|
+
end
|
|
29
|
+
client
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
Artery.logger.debug "Connected to #{@client.connected_server}"
|
|
33
|
+
@client.connect unless @client.connected?
|
|
34
|
+
|
|
35
|
+
@client
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def stop
|
|
39
|
+
client.close
|
|
40
|
+
@stop = true
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def start
|
|
44
|
+
@stop = false
|
|
45
|
+
connect
|
|
46
|
+
|
|
47
|
+
yield
|
|
48
|
+
|
|
49
|
+
sleep 0.1 until @stop
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def request(route, data, opts = {})
|
|
53
|
+
opts[:timeout] ||= Artery.request_timeout
|
|
54
|
+
# Always synchronous for now
|
|
55
|
+
response = client.request route, data, **opts
|
|
56
|
+
yield response.data
|
|
57
|
+
rescue ::NATS::Timeout
|
|
58
|
+
yield(TimeoutError.new(request: { route: route, data: data }))
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def publish(route, data)
|
|
62
|
+
client.publish route, data
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
private
|
|
66
|
+
|
|
67
|
+
def options
|
|
68
|
+
options = {}
|
|
69
|
+
|
|
70
|
+
options[:servers] = config[:servers] unless config[:servers].blank?
|
|
71
|
+
options[:user] = config[:user] unless config[:user].blank?
|
|
72
|
+
options[:pass] = config[:password] unless config[:password].blank?
|
|
73
|
+
|
|
74
|
+
options[:reconnect_time_wait] = config[:reconnect_timeout] unless config[:reconnect_timeout].blank?
|
|
75
|
+
options[:max_reconnect_attempts] = config[:reconnect_attempts] unless config[:reconnect_attempts].blank?
|
|
76
|
+
|
|
77
|
+
if ENV.key?('NATS_URL')
|
|
78
|
+
options[:servers] ||= []
|
|
79
|
+
options[:servers] << ENV['NATS_URL']
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
options
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|