pub_sub_model_sync 0.1.1 → 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2c8298056c5729688a7abf53b29dbc2f9b97e2afec85d9db7a452819d60ab566
4
- data.tar.gz: 44f820404c84f8b05af73e1a2a2c43417573f5145f6ecd4baa90e0c6534f8951
3
+ metadata.gz: 6108e077451c0ffc3d3a4b277528ccf036a64edf85c84bb810bc7ce2769bdc35
4
+ data.tar.gz: 63924273967917c2b5f740f0f917fa772341bc7fbd7c77b52037e9c264e5a080
5
5
  SHA512:
6
- metadata.gz: 2edb2d736c84636863e6ab47141c5d8122be6ef105b3ca502e373bbf0b7f22aa41a0561731ee50de1ddc7c566c8bc51873a85c30112b7cbd170abdaf91c4e22a
7
- data.tar.gz: 8e2ccf24555c630299a1ca751ec060a89aabe4af2610b4ef4213873d67a7ae19060fb5c0e00921e60a91f5268bde68ccd5848eae35151c6fa44523ab202c50c1
6
+ metadata.gz: 480a5c3d69cf06837e443ed6abfef5d927ddfc3df1c48e9948487e470666cdbb11711bb73e17c85d594c29c176a3fc0108867563a14d630152f078f2034d54af
7
+ data.tar.gz: 57ec430feef5ed8a5b2318703620b2d5f0d6faba14d1227f108a5411ee6fde587219e00fd3cedb8735211c9ff24dac5127af4b88428749164ce3631eb5691e45
@@ -4,7 +4,7 @@ language: ruby
4
4
  cache: bundler
5
5
  rvm:
6
6
  - 2.3.1
7
- before_install: gem install bundler -v 2.0.2
7
+ before_install: gem install bundler
8
8
 
9
9
  script:
10
10
  - bundle install
@@ -1,5 +1,20 @@
1
1
  # Change Log
2
2
 
3
+ # 0.2.0
4
+ - Add apache kafka support
5
+ - Add Service interface for future references
6
+ - Improve Services to use a single/common message performer
7
+
8
+ # 0.1.4
9
+ - Add attribute aliases when publishing, ```ps_publish(['name:full_name', 'email'])```
10
+ - Ability to retrieve publisher/subscriber crud settings
11
+
12
+ # 0.1.3
13
+ - shorter publisher/subscriber methods: ps_msync_subscribe into ps_subscribe
14
+
15
+ # 0.1.2
16
+ - fix not found pub/sub library (buggy)
17
+
3
18
  # 0.1.1
4
19
  - Add rabbitmq pub/sub service support
5
20
  - Reformat to support multiple pub/sub services
data/Gemfile CHANGED
@@ -3,6 +3,7 @@ source "https://rubygems.org"
3
3
  gem 'rubocop'
4
4
  gem 'bunny' # rabbit-mq
5
5
  gem 'google-cloud-pubsub' # google pub/sub
6
+ gem 'ruby-kafka' # kafka pub/sub
6
7
 
7
8
  # Specify your gem's dependencies in pub_sub_model_sync.gemspec
8
9
  gemspec
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- pub_sub_model_sync (0.1.1)
4
+ pub_sub_model_sync (0.2.0)
5
5
  activesupport
6
6
  rails
7
7
 
@@ -60,6 +60,7 @@ GEM
60
60
  concurrent-ruby (1.1.6)
61
61
  crass (1.0.6)
62
62
  diff-lcs (1.3)
63
+ digest-crc (0.5.1)
63
64
  erubi (1.9.0)
64
65
  faraday (0.17.3)
65
66
  multipart-post (>= 1.2, < 3)
@@ -81,7 +82,7 @@ GEM
81
82
  googleauth (>= 0.6.2, < 0.10.0)
82
83
  grpc (>= 1.7.2, < 2.0)
83
84
  rly (~> 0.2.3)
84
- google-protobuf (3.11.4-universal-darwin)
85
+ google-protobuf (3.11.4)
85
86
  googleapis-common-protos (1.3.9)
86
87
  google-protobuf (~> 3.0)
87
88
  googleapis-common-protos-types (~> 1.0)
@@ -95,7 +96,7 @@ GEM
95
96
  multi_json (~> 1.11)
96
97
  os (>= 0.9, < 2.0)
97
98
  signet (~> 0.7)
98
- grpc (1.27.0-universal-darwin)
99
+ grpc (1.27.0)
99
100
  google-protobuf (~> 3.11)
100
101
  googleapis-common-protos-types (~> 1.0)
101
102
  grpc-google-iam-v1 (0.6.9)
@@ -180,6 +181,8 @@ GEM
180
181
  rexml
181
182
  ruby-progressbar (~> 1.7)
182
183
  unicode-display_width (>= 1.4.0, < 1.7)
184
+ ruby-kafka (1.0.0)
185
+ digest-crc
183
186
  ruby-progressbar (1.10.1)
184
187
  signet (0.11.0)
185
188
  addressable (~> 2.3)
@@ -214,6 +217,7 @@ DEPENDENCIES
214
217
  rake
215
218
  rspec
216
219
  rubocop
220
+ ruby-kafka
217
221
  sqlite3
218
222
 
219
223
  BUNDLED WITH
data/README.md CHANGED
@@ -1,5 +1,5 @@
1
1
  # PubSubModelSync
2
- Permit to sync models data and make calls between rails apps using google or rabbitmq pub/sub service.
2
+ Permit to sync models data and make calls between rails apps using google or rabbitmq or apache kafka pub/sub service.
3
3
 
4
4
  Note: This gem is based on [MultipleMan](https://github.com/influitive/multiple_man) which for now looks unmaintained.
5
5
 
@@ -14,8 +14,10 @@ Note: This gem is based on [MultipleMan](https://github.com/influitive/multiple_
14
14
  Add this line to your application's Gemfile:
15
15
  ```ruby
16
16
  gem 'pub_sub_model_sync'
17
+
17
18
  gem 'google-cloud-pubsub' # to use google pub/sub service
18
19
  gem 'bunny' # to use rabbit-mq pub/sub service
20
+ gem 'ruby-kafka' # to use apache kafka pub/sub service
19
21
  ```
20
22
  And then execute: $ bundle install
21
23
 
@@ -43,9 +45,17 @@ And then execute: $ bundle install
43
45
  ```
44
46
  See details here: https://github.com/ruby-amqp/bunny
45
47
 
48
+ - configuration for Apache Kafka (You need kafka installed)
49
+ ```ruby
50
+ PubSubModelSync::Config.service_name = :kafka
51
+ PubSubModelSync::Config.kafka_connection = [["kafka1:9092", "localhost:2121"], logger: Rails.logger]
52
+ PubSubModelSync::Config.topic_name = 'sample-topic'
53
+ ```
54
+ See details here: https://github.com/zendesk/ruby-kafka
55
+
46
56
  - Add publishers/subscribers to your models (See examples below)
47
57
 
48
- - Start listening for publishers (Only in the app that has subscribers)
58
+ - Start subscribers to listen for publishers (Only in the app that has subscribers)
49
59
  ```ruby
50
60
  rake pub_sub_model_sync:start
51
61
  ```
@@ -53,18 +63,18 @@ And then execute: $ bundle install
53
63
 
54
64
  ## Examples
55
65
  ```ruby
56
- # App 1
66
+ # App 1 (Publisher)
57
67
  # attributes: name email age
58
68
  class User < ActiveRecord::Base
59
69
  include PubSubModelSync::PublisherConcern
60
- ps_msync_publish(%i[name email])
70
+ ps_publish(%i[name email])
61
71
  end
62
72
 
63
- # App 2
73
+ # App 2 (Subscriber)
64
74
  class User < ActiveRecord::Base
65
75
  include PubSubModelSync::SubscriberConcern
66
- ps_msync_subscribe(%i[name])
67
- ps_msync_class_subscribe(:greeting)
76
+ ps_subscribe(%i[name])
77
+ ps_class_subscribe(:greeting)
68
78
 
69
79
  def self.greeting(data)
70
80
  puts 'Class message called'
@@ -72,29 +82,30 @@ class User < ActiveRecord::Base
72
82
  end
73
83
 
74
84
  # Samples
75
- User.create(name: 'test user') # Review your App 2 to see the created user (only name will be saved)
76
- User.ps_msync_class_publish({ msg: 'Hello' }, action: :greeting) # User.greeting method (Class method) will be called in App2
85
+ User.create(name: 'test user', email: 'sample@gmail.com') # Review your App 2 to see the created user (only name will be saved)
86
+ User.ps_class_publish({ msg: 'Hello' }, action: :greeting) # User.greeting method (Class method) will be called in App2
87
+ PubSubModelSync::Publisher.new.publish_data(User, { msg: 'Hello' }, :greeting) # similar to above when not included publisher concern
77
88
  ```
78
89
 
79
90
  ## Advanced Example
80
91
  ```ruby
81
- # App 1
92
+ # App 1 (Publisher)
82
93
  class User < ActiveRecord::Base
83
94
  self.table_name = 'publisher_users'
84
95
  include PubSubModelSync::PublisherConcern
85
- ps_msync_publish(%i[name], actions: %i[update], as_klass: 'Client', id: :client_id)
96
+ ps_publish(%i[name:full_name email], actions: %i[update], as_klass: 'Client', id: :client_id)
86
97
 
87
- def ps_msync_skip_for?(_action)
98
+ def ps_skip_for?(_action)
88
99
  false # here logic with action to skip push message
89
100
  end
90
101
  end
91
102
 
92
- # App 2
103
+ # App 2 (Subscriber)
93
104
  class User < ActiveRecord::Base
94
105
  self.table_name = 'subscriber_users'
95
106
  include PubSubModelSync::SubscriberConcern
96
- ps_msync_subscribe(%i[name], actions: %i[update], as_klass: 'Client', id: :custom_id)
97
- ps_msync_class_subscribe(:greeting, as_action: :custom_greeting, as_klass: 'CustomUser')
107
+ ps_subscribe(%i[name], actions: %i[update], as_klass: 'Client', id: :custom_id)
108
+ ps_class_subscribe(:greeting, as_action: :custom_greeting, as_klass: 'CustomUser')
98
109
 
99
110
  def self.greeting(data)
100
111
  puts 'Class message called through custom_greeting'
@@ -102,22 +113,29 @@ class User < ActiveRecord::Base
102
113
  end
103
114
  ```
104
115
 
105
- ## Testing
106
- - Rspec: (spec/rails_helper.rb)
116
+ ## Testing with RSpec
117
+ - Config: (spec/rails_helper.rb)
107
118
  ```ruby
108
119
 
109
120
  # when using google service
110
121
  require 'pub_sub_model_sync/mock_google_service'
111
122
  config.before(:each) do
112
- pub_sub_mock = PubSubModelSync::MockGoogleService.new
113
- allow(Google::Cloud::Pubsub).to receive(:new).and_return(pub_sub_mock)
123
+ google_mock = PubSubModelSync::MockGoogleService.new
124
+ allow(Google::Cloud::Pubsub).to receive(:new).and_return(google_mock)
114
125
  end
115
126
 
116
127
  # when using rabbitmq service
117
128
  require 'pub_sub_model_sync/mock_rabbit_service'
118
129
  config.before(:each) do
119
- bunny_mock = PubSubModelSync::MockRabbitService.new
120
- allow(Bunny).to receive(:new).and_return(bunny_mock)
130
+ rabbit_mock = PubSubModelSync::MockRabbitService.new
131
+ allow(Bunny).to receive(:new).and_return(rabbit_mock)
132
+ end
133
+
134
+ # when using apache kafka service
135
+ require 'pub_sub_model_sync/mock_kafka_service'
136
+ config.before(:each) do
137
+ kafka_mock = PubSubModelSync::MockKafkaService.new
138
+ allow(Kafka).to receive(:new).and_return(kafka_mock)
121
139
  end
122
140
 
123
141
  ```
@@ -147,7 +165,7 @@ end
147
165
  publisher = PubSubModelSync::Publisher
148
166
  data = { name: 'hello'}
149
167
  action = :create
150
- User.ps_msync_class_publish(data, action: action)
168
+ User.ps_class_publish(data, action: action)
151
169
  user = User.create(name: 'name', email: 'email')
152
170
  expect_any_instance_of(publisher).to receive(:publish_model).with(user, :create, anything)
153
171
  end
@@ -156,16 +174,16 @@ end
156
174
  publisher = PubSubModelSync::Publisher
157
175
  data = {msg: 'hello'}
158
176
  action = :greeting
159
- User.ps_msync_class_publish(data, action: action)
177
+ User.ps_class_publish(data, action: action)
160
178
  expect_any_instance_of(publisher).to receive(:publish_data).with('User', data, action)
161
179
  end
162
180
  ```
163
181
 
164
182
  There are two special methods to extract crud configuration settings (attrs, id, ...):
165
183
 
166
- Subscribers: ```User.ps_msync_subscriber_settings```
184
+ Subscribers: ```User.ps_subscriber```
167
185
 
168
- Publishers: ```User.ps_msync_publisher_settings```
186
+ Publishers: ```User.ps_publisher```
169
187
 
170
188
  Note: Inspect all configured listeners with:
171
189
  ``` PubSubModelSync::Config.listeners ```
@@ -11,8 +11,11 @@ require 'pub_sub_model_sync/publisher_concern'
11
11
  require 'pub_sub_model_sync/runner'
12
12
  require 'pub_sub_model_sync/connector'
13
13
  require 'pub_sub_model_sync/message_processor'
14
+
15
+ require 'pub_sub_model_sync/service_base'
14
16
  require 'pub_sub_model_sync/service_google'
15
17
  require 'pub_sub_model_sync/service_rabbit'
18
+ require 'pub_sub_model_sync/service_kafka'
16
19
 
17
20
  module PubSubModelSync
18
21
  class Error < StandardError; end
@@ -3,6 +3,7 @@
3
3
  module PubSubModelSync
4
4
  class Config
5
5
  cattr_accessor :listeners, default: []
6
+ cattr_accessor :publishers, default: []
6
7
  cattr_accessor :service_name, default: :google
7
8
  cattr_accessor :logger
8
9
 
@@ -12,9 +13,16 @@ module PubSubModelSync
12
13
  # rabbitmq service
13
14
  cattr_accessor :bunny_connection, :queue_name, :topic_name
14
15
 
16
+ # kafka service
17
+ cattr_accessor :kafka_connection, :topic_name
18
+
15
19
  def self.log(msg, kind = :info)
16
20
  msg = "PS_MSYNC ==> #{msg}"
17
- logger ? logger.send(kind, msg) : puts(msg)
21
+ if logger == :raise_error
22
+ kind == :error ? raise(msg) : puts(msg)
23
+ else
24
+ logger ? logger.send(kind, msg) : puts(msg)
25
+ end
18
26
  end
19
27
  end
20
28
  end
@@ -16,6 +16,8 @@ module PubSubModelSync
16
16
  case Config.service_name
17
17
  when :google
18
18
  PubSubModelSync::ServiceGoogle.new
19
+ when :kafka
20
+ PubSubModelSync::ServiceKafka.new
19
21
  else # :rabbit_mq
20
22
  PubSubModelSync::ServiceRabbit.new
21
23
  end
@@ -40,7 +40,6 @@ module PubSubModelSync
40
40
 
41
41
  # support for: create, update, destroy
42
42
  def call_listener(listener)
43
- listener_add_crud_settings(listener)
44
43
  model = find_model(listener)
45
44
  if attrs[:action].to_sym == :destroy
46
45
  model.destroy!
@@ -74,11 +73,6 @@ module PubSubModelSync
74
73
  end
75
74
  end
76
75
 
77
- def listener_add_crud_settings(listener)
78
- model_class = listener[:klass].constantize
79
- listener[:settings] = model_class.ps_msync_subscriber_settings
80
- end
81
-
82
76
  def log(message, kind = :info)
83
77
  PubSubModelSync::Config.log "#{message} ==> #{[data, attrs]}", kind
84
78
  end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PubSubModelSync
4
+ class MockKafkaService
5
+ class MockProducer
6
+ def produce(*_args)
7
+ true
8
+ end
9
+
10
+ def deliver_messages(*_args)
11
+ true
12
+ end
13
+
14
+ def shutdown
15
+ true
16
+ end
17
+ end
18
+
19
+ class MockConsumer
20
+ def each_message(*_args)
21
+ true
22
+ end
23
+
24
+ def stop(*_args)
25
+ true
26
+ end
27
+
28
+ def subscribe(*_args)
29
+ true
30
+ end
31
+ end
32
+
33
+ def producer(*_args)
34
+ MockProducer.new
35
+ end
36
+
37
+ def consumer(*_args)
38
+ MockConsumer.new
39
+ end
40
+
41
+ def close
42
+ true
43
+ end
44
+ end
45
+ end
@@ -14,12 +14,10 @@ module PubSubModelSync
14
14
 
15
15
  # @param settings (Hash): { attrs: [], as_klass: nil, id: nil }
16
16
  def publish_model(model, action, settings = nil)
17
- settings ||= model.class.ps_msync_publisher_settings
17
+ settings ||= model.class.ps_publisher_info(action)
18
18
  attributes = build_model_attrs(model, action, settings)
19
19
  data = {}
20
- if action != 'destroy'
21
- data = model.as_json(only: settings[:attrs], methods: settings[:attrs])
22
- end
20
+ data = build_model_data(model, settings[:attrs]) if action != :destroy
23
21
  connector.publish(data.symbolize_keys, attributes)
24
22
  end
25
23
 
@@ -27,13 +25,23 @@ module PubSubModelSync
27
25
  {
28
26
  klass: klass.to_s,
29
27
  action: action.to_sym,
30
- id: id,
31
- service_model_sync: true
28
+ id: id
32
29
  }
33
30
  end
34
31
 
35
32
  private
36
33
 
34
+ def build_model_data(model, model_props)
35
+ source_props = model_props.map { |prop| prop.to_s.split(':').first }
36
+ data = model.as_json(only: source_props, methods: source_props)
37
+ aliased_props = model_props.select { |prop| prop.to_s.include?(':') }
38
+ aliased_props.each do |prop|
39
+ source, target = prop.to_s.split(':')
40
+ data[target] = data.delete(source)
41
+ end
42
+ data.symbolize_keys
43
+ end
44
+
37
45
  def build_model_attrs(model, action, settings)
38
46
  as_klass = (settings[:as_klass] || model.class.name).to_s
39
47
  id_val = model.send(settings[:id] || :id)
@@ -7,41 +7,44 @@ module PubSubModelSync
7
7
  end
8
8
 
9
9
  # Permit to skip a publish callback
10
- def ps_msync_skip_for?(_action)
10
+ def ps_skip_for?(_action)
11
11
  false
12
12
  end
13
13
 
14
14
  module ClassMethods
15
15
  # Permit to publish crud actions (:create, :update, :destroy)
16
16
  # @param settings (Hash): { actions: nil, as_klass: nil, id: nil }
17
- def ps_msync_publish(attrs, settings = {})
17
+ def ps_publish(attrs, settings = {})
18
18
  actions = settings.delete(:actions) || %i[create update destroy]
19
- @ps_msync_publisher_settings = settings.merge(attrs: attrs)
20
- ps_msync_register_callbacks(actions)
19
+ actions.each do |action|
20
+ info = settings.merge(klass: name, action: action, attrs: attrs)
21
+ PubSubModelSync::Config.publishers << info
22
+ ps_register_callback(action.to_sym, info)
23
+ end
21
24
  end
22
25
 
23
- def ps_msync_publisher_settings
24
- @ps_msync_publisher_settings
26
+ def ps_publisher_info(action = :create)
27
+ PubSubModelSync::Config.publishers.select do |listener|
28
+ listener[:klass] == name && listener[:action] == action
29
+ end.last
25
30
  end
26
31
 
27
- def ps_msync_class_publish(data, action:, as_klass: nil)
32
+ def ps_class_publish(data, action:, as_klass: nil)
28
33
  as_klass = (as_klass || name).to_s
29
- ps_msync_publisher.publish_data(as_klass, data, action.to_sym)
34
+ ps_publisher_service.publish_data(as_klass, data, action.to_sym)
30
35
  end
31
36
 
32
- def ps_msync_publisher
37
+ def ps_publisher_service
33
38
  PubSubModelSync::Publisher.new
34
39
  end
35
40
 
36
41
  private
37
42
 
38
- def ps_msync_register_callbacks(actions)
39
- actions.each do |action|
40
- after_commit(on: action) do |model|
41
- unless model.ps_msync_skip_for?(action)
42
- publisher = model.class.ps_msync_publisher
43
- publisher.publish_model(model, action.to_sym)
44
- end
43
+ def ps_register_callback(action, info)
44
+ after_commit(on: action) do |model|
45
+ unless model.ps_skip_for?(action)
46
+ service = model.class.ps_publisher_service
47
+ service.publish_model(model, action.to_sym, info)
45
48
  end
46
49
  end
47
50
  end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PubSubModelSync
4
+ class ServiceBase
5
+ SERVICE_KEY = 'service_model_sync'
6
+
7
+ def listen_messages
8
+ raise 'method :listen_messages must be defined in service'
9
+ end
10
+
11
+ def publish(_data, _attributes)
12
+ raise 'method :publish must be defined in service'
13
+ end
14
+
15
+ def stop
16
+ raise 'method :stop must be defined in service'
17
+ end
18
+
19
+ private
20
+
21
+ # @param payload (String JSON): '{"data":{},"attributes":{..}}'
22
+ # refer: PubSubModelSync::Publisher (.publish_model | .publish_data)
23
+ def perform_message(payload)
24
+ data, attrs = parse_message_payload(payload)
25
+ args = [data, attrs[:klass], attrs[:action], attrs]
26
+ PubSubModelSync::MessageProcessor.new(*args).process
27
+ end
28
+
29
+ def parse_message_payload(payload)
30
+ message_payload = JSON.parse(payload).symbolize_keys
31
+ data = message_payload[:data].symbolize_keys
32
+ attrs = message_payload[:attributes].symbolize_keys
33
+ [data, attrs]
34
+ end
35
+ end
36
+ end
@@ -1,8 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'google/cloud/pubsub'
3
+ begin
4
+ require 'google/cloud/pubsub'
5
+ rescue LoadError # rubocop:disable Lint/SuppressedException
6
+ end
7
+
4
8
  module PubSubModelSync
5
- class ServiceGoogle
9
+ class ServiceGoogle < ServiceBase
6
10
  attr_accessor :service, :topic, :subscription, :config, :subscriber
7
11
 
8
12
  def initialize
@@ -26,7 +30,9 @@ module PubSubModelSync
26
30
 
27
31
  def publish(data, attributes)
28
32
  log("Publishing message: #{[data, attributes]}")
29
- topic.publish(data.to_json, attributes)
33
+
34
+ payload = { data: data, attributes: attributes }.to_json
35
+ topic.publish(payload, { SERVICE_KEY => true })
30
36
  end
31
37
 
32
38
  def stop
@@ -43,20 +49,17 @@ module PubSubModelSync
43
49
 
44
50
  def process_message(received_message)
45
51
  message = received_message.message
46
- attrs = message.attributes.symbolize_keys
47
- return unless attrs[:service_model_sync]
52
+ return unless message.attributes[SERVICE_KEY]
48
53
 
49
- data = JSON.parse(message.data).symbolize_keys
50
- args = [data, attrs[:klass], attrs[:action], attrs]
51
- PubSubModelSync::MessageProcessor.new(*args).process
54
+ perform_message(message.data)
52
55
  rescue => e
53
- log("Error processing message: #{[received_message, e.message]}")
56
+ log("Error processing message: #{[received_message, e.message]}", :error)
54
57
  ensure
55
58
  received_message.acknowledge!
56
59
  end
57
60
 
58
- def log(msg)
59
- config.log("Google Service ==> #{msg}")
61
+ def log(msg, kind = :info)
62
+ config.log("Google Service ==> #{msg}", kind)
60
63
  end
61
64
  end
62
65
  end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ begin
4
+ require 'kafka'
5
+ rescue LoadError # rubocop:disable Lint/SuppressedException
6
+ end
7
+
8
+ module PubSubModelSync
9
+ class ServiceKafka < ServiceBase
10
+ cattr_accessor :producer
11
+
12
+ attr_accessor :service, :consumer
13
+ attr_accessor :config
14
+ CONSUMER_GROUP = 'service_model_sync'
15
+
16
+ def initialize
17
+ @config = PubSubModelSync::Config
18
+ @service = Kafka.new(*config.kafka_connection)
19
+ end
20
+
21
+ def listen_messages
22
+ log('Listener starting...')
23
+ start_consumer
24
+ consumer.each_message(&method(:process_message))
25
+ rescue PubSubModelSync::Runner::ShutDown
26
+ raise
27
+ rescue => e
28
+ log("Error listening message: #{[e.message, e.backtrace]}", :error)
29
+ end
30
+
31
+ def publish(data, attributes)
32
+ log("Publishing: #{[data, attributes]}")
33
+ payload = { data: data, attributes: attributes }
34
+ producer.produce(payload.to_json, message_settings)
35
+ producer.deliver_messages
36
+ rescue => e
37
+ info = [data, attributes, e.message, e.backtrace]
38
+ log("Error publishing: #{info}", :error)
39
+ end
40
+
41
+ def stop
42
+ log('Listener stopping...')
43
+ consumer.stop
44
+ end
45
+
46
+ private
47
+
48
+ def message_settings
49
+ { topic: config.topic_name, headers: { SERVICE_KEY => true } }
50
+ end
51
+
52
+ def start_consumer
53
+ @consumer = service.consumer(group_id: CONSUMER_GROUP)
54
+ consumer.subscribe(config.topic_name)
55
+ end
56
+
57
+ def producer
58
+ return self.class.producer if self.class.producer
59
+
60
+ at_exit { self.class.producer.shutdown }
61
+ self.class.producer = service.producer
62
+ end
63
+
64
+ def process_message(message)
65
+ return unless message.headers[SERVICE_KEY]
66
+
67
+ perform_message(message.value)
68
+ rescue => e
69
+ error = [message, e.message, e.backtrace]
70
+ log("Error processing message: #{error}", :error)
71
+ end
72
+
73
+ def log(msg, kind = :info)
74
+ config.log("Kafka Service ==> #{msg}", kind)
75
+ end
76
+ end
77
+ end
@@ -1,11 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'bunny'
3
+ begin
4
+ require 'bunny'
5
+ rescue LoadError # rubocop:disable Lint/SuppressedException
6
+ end
7
+
4
8
  module PubSubModelSync
5
- class ServiceRabbit
9
+ class ServiceRabbit < ServiceBase
6
10
  attr_accessor :service, :channel, :queue, :topic
7
11
  attr_accessor :config
8
- SERVICE_KEY = 'service_model_sync'
9
12
 
10
13
  def initialize
11
14
  @config = PubSubModelSync::Config
@@ -20,16 +23,17 @@ module PubSubModelSync
20
23
  rescue PubSubModelSync::Runner::ShutDown
21
24
  raise
22
25
  rescue => e
23
- log("Error listening message: #{[e.message, e.backtrace]}")
26
+ log("Error listening message: #{[e.message, e.backtrace]}", :error)
24
27
  end
25
28
 
26
29
  def publish(data, attributes)
27
30
  log("Publishing: #{[data, attributes]}")
28
31
  subscribe_to_queue
29
32
  payload = { data: data, attributes: attributes }
30
- topic.publish(payload.to_json, routing_key: queue.name, type: SERVICE_KEY)
33
+ topic.publish(payload.to_json, message_settings)
31
34
  rescue => e
32
- log("Error publishing: #{[data, attributes, e.message, e.backtrace]}")
35
+ info = [data, attributes, e.message, e.backtrace]
36
+ log("Error publishing: #{info}", :error)
33
37
  end
34
38
 
35
39
  def stop
@@ -39,22 +43,17 @@ module PubSubModelSync
39
43
 
40
44
  private
41
45
 
46
+ def message_settings
47
+ { routing_key: queue.name, type: SERVICE_KEY }
48
+ end
49
+
42
50
  def process_message(_delivery_info, meta_info, payload)
43
51
  return unless meta_info[:type] == SERVICE_KEY
44
52
 
45
- data, attrs = parse_message_payload(payload)
46
- args = [data, attrs[:klass], attrs[:action], attrs]
47
- PubSubModelSync::MessageProcessor.new(*args).process
53
+ perform_message(payload)
48
54
  rescue => e
49
55
  error = [payload, e.message, e.backtrace]
50
- log("Error processing message: #{error}")
51
- end
52
-
53
- def parse_message_payload(payload)
54
- message_payload = JSON.parse(payload).symbolize_keys
55
- data = message_payload[:data].symbolize_keys
56
- attrs = message_payload[:attributes].symbolize_keys
57
- [data, attrs]
56
+ log("Error processing message: #{error}", :error)
58
57
  end
59
58
 
60
59
  def subscribe_to_queue
@@ -70,8 +69,8 @@ module PubSubModelSync
70
69
  queue.bind(topic, routing_key: queue.name)
71
70
  end
72
71
 
73
- def log(msg)
74
- config.log("Rabbit Service ==> #{msg}")
72
+ def log(msg, kind = :info)
73
+ config.log("Rabbit Service ==> #{msg}", kind)
75
74
  end
76
75
  end
77
76
  end
@@ -8,32 +8,35 @@ module PubSubModelSync
8
8
 
9
9
  module ClassMethods
10
10
  # @param settings (Hash): { as_klass: nil, actions: nil, id: nil }
11
- def ps_msync_subscribe(attrs, settings = {})
12
- settings[:as_klass] = (settings[:as_klass] || name).to_s
11
+ def ps_subscribe(attrs, settings = {})
12
+ as_klass = (settings[:as_klass] || name).to_s
13
13
  actions = settings.delete(:actions) || %i[create update destroy]
14
- @ps_msync_subscriber_settings = { attrs: attrs }.merge(settings)
14
+ settings = settings.merge(attrs: attrs)
15
15
  actions.each do |action|
16
- add_ps_msync_subscriber(settings[:as_klass], action, action, false)
16
+ add_ps_subscriber(as_klass, action, action, false, settings)
17
17
  end
18
18
  end
19
19
 
20
- def ps_msync_class_subscribe(action, as_action: nil, as_klass: nil)
21
- add_ps_msync_subscriber(as_klass, action, as_action, true)
20
+ def ps_class_subscribe(action, as_action: nil, as_klass: nil)
21
+ add_ps_subscriber(as_klass, action, as_action, true, {})
22
22
  end
23
23
 
24
- def ps_msync_subscriber_settings
25
- @ps_msync_subscriber_settings || {}
24
+ def ps_subscriber(action = :create)
25
+ PubSubModelSync::Config.listeners.select do |listener|
26
+ listener[:klass] == name && listener[:action] == action
27
+ end.last
26
28
  end
27
29
 
28
30
  private
29
31
 
30
- def add_ps_msync_subscriber(as_klass, action, as_action, direct_mode)
32
+ def add_ps_subscriber(as_klass, action, as_action, direct_mode, settings)
31
33
  listener = {
32
34
  klass: name,
33
35
  as_klass: (as_klass || name).to_s,
34
36
  action: action.to_sym,
35
37
  as_action: (as_action || action).to_sym,
36
- direct_mode: direct_mode
38
+ direct_mode: direct_mode,
39
+ settings: settings
37
40
  }
38
41
  PubSubModelSync::Config.listeners << listener
39
42
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PubSubModelSync
4
- VERSION = '0.1.1'
4
+ VERSION = '0.2.0'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pub_sub_model_sync
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Owen
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-03-13 00:00:00.000000000 Z
11
+ date: 2020-03-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -119,12 +119,15 @@ files:
119
119
  - lib/pub_sub_model_sync/connector.rb
120
120
  - lib/pub_sub_model_sync/message_processor.rb
121
121
  - lib/pub_sub_model_sync/mock_google_service.rb
122
+ - lib/pub_sub_model_sync/mock_kafka_service.rb
122
123
  - lib/pub_sub_model_sync/mock_rabbit_service.rb
123
124
  - lib/pub_sub_model_sync/publisher.rb
124
125
  - lib/pub_sub_model_sync/publisher_concern.rb
125
126
  - lib/pub_sub_model_sync/railtie.rb
126
127
  - lib/pub_sub_model_sync/runner.rb
128
+ - lib/pub_sub_model_sync/service_base.rb
127
129
  - lib/pub_sub_model_sync/service_google.rb
130
+ - lib/pub_sub_model_sync/service_kafka.rb
128
131
  - lib/pub_sub_model_sync/service_rabbit.rb
129
132
  - lib/pub_sub_model_sync/subscriber_concern.rb
130
133
  - lib/pub_sub_model_sync/tasks/worker.rake