pub_sub_model_sync 0.1.2 → 0.2.1

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: 2474d763e9b95cd2ddf83284ed095af1e1c8d53fe4ffc89fbe7249e8c198d38b
4
- data.tar.gz: 87ab9ca1c4167ebf314c1bdbd48bd7216e7e1036acbbb2a6c93f82ddd7bb80e6
3
+ metadata.gz: 6f3074098a97136efdb1698c4e0d3161e02f20d36d3ce4803608dcacdc826700
4
+ data.tar.gz: 0408d088ec5ee19373a6e49fa639830948d41387efc27a9fc904c04e2b5e11fe
5
5
  SHA512:
6
- metadata.gz: 7a9ecb33cb7e85a76d797de1e390cf2a6a1086c245512695e46610cc18c1fa9273c821002177d30bc78ffe4159239733f287a665b69ded4f0d4490486072ff97
7
- data.tar.gz: be9b8bf52ae416e9bf2667c3a6aa18cbbdc30aad265d71f00f1b90594a1f09d5a7498843e6f5361dd1562f151f0a7d73b7166bc4351fe02992a4dd1080ec587c
6
+ metadata.gz: e80c2eda985a22d547f2cce08c97a997e8d06da9ff2324e5760832d81070526a34d31e9f6c5e5d6156932ce839bd70eb4e033d1c17a86ac9207b7ae582f9f854
7
+ data.tar.gz: 13c9e536b71740620f5aaa2a4bce195f0a10dddc5fe5e2ba36e41e48191b7a4f7dce3077a7cf76f3f8882b29054a9b5b015d9150872f641e9875bf66f0748586
@@ -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,23 @@
1
1
  # Change Log
2
2
 
3
+ # 0.2.1
4
+ - Add on demand model sync method
5
+
6
+ # 0.2.0
7
+ - Add apache kafka support
8
+ - Add Service interface for future references
9
+ - Improve Services to use a single/common message performer
10
+
11
+ # 0.1.4
12
+ - Add attribute aliases when publishing, ```ps_publish(['name:full_name', 'email'])```
13
+ - Ability to retrieve publisher/subscriber crud settings
14
+
15
+ # 0.1.3
16
+ - shorter publisher/subscriber methods: ps_msync_subscribe into ps_subscribe
17
+
18
+ # 0.1.2
19
+ - fix not found pub/sub library (buggy)
20
+
3
21
  # 0.1.1
4
22
  - Add rabbitmq pub/sub service support
5
23
  - 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.2)
4
+ pub_sub_model_sync (0.2.1)
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,32 @@ 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.new(name: 'test user').ps_perform_sync(:create) # similar to above to perform sync on demand
87
+
88
+ User.ps_class_publish({ msg: 'Hello' }, action: :greeting) # User.greeting method (Class method) will be called in App2
89
+ PubSubModelSync::Publisher.new.publish_data(User, { msg: 'Hello' }, :greeting) # similar to above when not included publisher concern
77
90
  ```
78
91
 
79
92
  ## Advanced Example
80
93
  ```ruby
81
- # App 1
94
+ # App 1 (Publisher)
82
95
  class User < ActiveRecord::Base
83
96
  self.table_name = 'publisher_users'
84
97
  include PubSubModelSync::PublisherConcern
85
- ps_msync_publish(%i[name], actions: %i[update], as_klass: 'Client', id: :client_id)
98
+ ps_publish(%i[name:full_name email], actions: %i[update], as_klass: 'Client', id: :client_id)
86
99
 
87
- def ps_msync_skip_for?(_action)
100
+ def ps_skip_for?(_action)
88
101
  false # here logic with action to skip push message
89
102
  end
90
103
  end
91
104
 
92
- # App 2
105
+ # App 2 (Subscriber)
93
106
  class User < ActiveRecord::Base
94
107
  self.table_name = 'subscriber_users'
95
108
  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')
109
+ ps_subscribe(%i[name], actions: %i[update], as_klass: 'Client', id: :custom_id)
110
+ ps_class_subscribe(:greeting, as_action: :custom_greeting, as_klass: 'CustomUser')
98
111
 
99
112
  def self.greeting(data)
100
113
  puts 'Class message called through custom_greeting'
@@ -102,22 +115,56 @@ class User < ActiveRecord::Base
102
115
  end
103
116
  ```
104
117
 
105
- ## Testing
106
- - Rspec: (spec/rails_helper.rb)
118
+ ## API
119
+ - Perform sync on demand (:create, :update, :destroy):
120
+ The target model will receive a notification to perform the indicated action
121
+ ```my_model.ps_perform_sync(action_name)```
122
+
123
+ - Class level notification:
124
+ ```User.ps_class_publish(data, action: action_name, as_klass: custom_klass_name)```
125
+ Target class ```User.action_name``` will be called when message is received
126
+ * data: (required, :hash) message value to deliver
127
+ * action_name: (required, :sim) same action name as defined in ps_class_subscribe(...)
128
+ * as_klass: (optional, :string) same class name as defined in ps_class_subscribe(...)
129
+
130
+ - Class level notification (Same as above: on demand call)
131
+ ```PubSubModelSync::Publisher.new.publish_data(Klass_name, data, action_name)```
132
+ * klass_name: (required, Class) same class name as defined in ps_class_subscribe(...)
133
+ * data: (required, :hash) message value to deliver
134
+ * action_name: (required, :sim) same action name as defined in ps_class_subscribe(...)
135
+
136
+ - Get crud subscription configured for the class
137
+ ```User.ps_subscriber(action_name)```
138
+ * action_name (default :create, :sym): can be :create, :update, :destroy
139
+ - Get crud publisher configured for the class
140
+ ```User.ps_publisher(action_name)```
141
+ * action_name (default :create, :sym): can be :create, :update, :destroy
142
+ - Inspect all listeners configured for a class
143
+ ```PubSubModelSync::Config.listeners```
144
+
145
+ ## Testing with RSpec
146
+ - Config: (spec/rails_helper.rb)
107
147
  ```ruby
108
148
 
109
149
  # when using google service
110
150
  require 'pub_sub_model_sync/mock_google_service'
111
151
  config.before(:each) do
112
- pub_sub_mock = PubSubModelSync::MockGoogleService.new
113
- allow(Google::Cloud::Pubsub).to receive(:new).and_return(pub_sub_mock)
152
+ google_mock = PubSubModelSync::MockGoogleService.new
153
+ allow(Google::Cloud::Pubsub).to receive(:new).and_return(google_mock)
114
154
  end
115
155
 
116
156
  # when using rabbitmq service
117
157
  require 'pub_sub_model_sync/mock_rabbit_service'
118
158
  config.before(:each) do
119
- bunny_mock = PubSubModelSync::MockRabbitService.new
120
- allow(Bunny).to receive(:new).and_return(bunny_mock)
159
+ rabbit_mock = PubSubModelSync::MockRabbitService.new
160
+ allow(Bunny).to receive(:new).and_return(rabbit_mock)
161
+ end
162
+
163
+ # when using apache kafka service
164
+ require 'pub_sub_model_sync/mock_kafka_service'
165
+ config.before(:each) do
166
+ kafka_mock = PubSubModelSync::MockKafkaService.new
167
+ allow(Kafka).to receive(:new).and_return(kafka_mock)
121
168
  end
122
169
 
123
170
  ```
@@ -147,7 +194,7 @@ end
147
194
  publisher = PubSubModelSync::Publisher
148
195
  data = { name: 'hello'}
149
196
  action = :create
150
- User.ps_msync_class_publish(data, action: action)
197
+ User.ps_class_publish(data, action: action)
151
198
  user = User.create(name: 'name', email: 'email')
152
199
  expect_any_instance_of(publisher).to receive(:publish_model).with(user, :create, anything)
153
200
  end
@@ -156,19 +203,10 @@ end
156
203
  publisher = PubSubModelSync::Publisher
157
204
  data = {msg: 'hello'}
158
205
  action = :greeting
159
- User.ps_msync_class_publish(data, action: action)
206
+ User.ps_class_publish(data, action: action)
160
207
  expect_any_instance_of(publisher).to receive(:publish_data).with('User', data, action)
161
208
  end
162
209
  ```
163
-
164
- There are two special methods to extract crud configuration settings (attrs, id, ...):
165
-
166
- Subscribers: ```User.ps_msync_subscriber_settings```
167
-
168
- Publishers: ```User.ps_msync_publisher_settings```
169
-
170
- Note: Inspect all configured listeners with:
171
- ``` PubSubModelSync::Config.listeners ```
172
210
 
173
211
  ## Contributing
174
212
 
@@ -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,49 @@ 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
+ def ps_perform_sync(action = :create)
15
+ service = self.class.ps_publisher_service
16
+ service.publish_model(self, action, self.class.ps_publisher_info(action))
17
+ end
18
+
14
19
  module ClassMethods
15
20
  # Permit to publish crud actions (:create, :update, :destroy)
16
21
  # @param settings (Hash): { actions: nil, as_klass: nil, id: nil }
17
- def ps_msync_publish(attrs, settings = {})
22
+ def ps_publish(attrs, settings = {})
18
23
  actions = settings.delete(:actions) || %i[create update destroy]
19
- @ps_msync_publisher_settings = settings.merge(attrs: attrs)
20
- ps_msync_register_callbacks(actions)
24
+ actions.each do |action|
25
+ info = settings.merge(klass: name, action: action, attrs: attrs)
26
+ PubSubModelSync::Config.publishers << info
27
+ ps_register_callback(action.to_sym, info)
28
+ end
21
29
  end
22
30
 
23
- def ps_msync_publisher_settings
24
- @ps_msync_publisher_settings
31
+ def ps_publisher_info(action = :create)
32
+ PubSubModelSync::Config.publishers.select do |listener|
33
+ listener[:klass] == name && listener[:action] == action
34
+ end.last
25
35
  end
26
36
 
27
- def ps_msync_class_publish(data, action:, as_klass: nil)
37
+ def ps_class_publish(data, action:, as_klass: nil)
28
38
  as_klass = (as_klass || name).to_s
29
- ps_msync_publisher.publish_data(as_klass, data, action.to_sym)
39
+ ps_publisher_service.publish_data(as_klass, data, action.to_sym)
30
40
  end
31
41
 
32
- def ps_msync_publisher
42
+ def ps_publisher_service
33
43
  PubSubModelSync::Publisher.new
34
44
  end
35
45
 
36
46
  private
37
47
 
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
48
+ def ps_register_callback(action, info)
49
+ after_commit(on: action) do |model|
50
+ unless model.ps_skip_for?(action)
51
+ service = model.class.ps_publisher_service
52
+ service.publish_model(model, action.to_sym, info)
45
53
  end
46
54
  end
47
55
  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
@@ -6,7 +6,7 @@ rescue LoadError # rubocop:disable Lint/SuppressedException
6
6
  end
7
7
 
8
8
  module PubSubModelSync
9
- class ServiceGoogle
9
+ class ServiceGoogle < ServiceBase
10
10
  attr_accessor :service, :topic, :subscription, :config, :subscriber
11
11
 
12
12
  def initialize
@@ -30,7 +30,9 @@ module PubSubModelSync
30
30
 
31
31
  def publish(data, attributes)
32
32
  log("Publishing message: #{[data, attributes]}")
33
- topic.publish(data.to_json, attributes)
33
+
34
+ payload = { data: data, attributes: attributes }.to_json
35
+ topic.publish(payload, { SERVICE_KEY => true })
34
36
  end
35
37
 
36
38
  def stop
@@ -47,20 +49,17 @@ module PubSubModelSync
47
49
 
48
50
  def process_message(received_message)
49
51
  message = received_message.message
50
- attrs = message.attributes.symbolize_keys
51
- return unless attrs[:service_model_sync]
52
+ return unless message.attributes[SERVICE_KEY]
52
53
 
53
- data = JSON.parse(message.data).symbolize_keys
54
- args = [data, attrs[:klass], attrs[:action], attrs]
55
- PubSubModelSync::MessageProcessor.new(*args).process
54
+ perform_message(message.data)
56
55
  rescue => e
57
- log("Error processing message: #{[received_message, e.message]}")
56
+ log("Error processing message: #{[received_message, e.message]}", :error)
58
57
  ensure
59
58
  received_message.acknowledge!
60
59
  end
61
60
 
62
- def log(msg)
63
- config.log("Google Service ==> #{msg}")
61
+ def log(msg, kind = :info)
62
+ config.log("Google Service ==> #{msg}", kind)
64
63
  end
65
64
  end
66
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
@@ -6,10 +6,9 @@ rescue LoadError # rubocop:disable Lint/SuppressedException
6
6
  end
7
7
 
8
8
  module PubSubModelSync
9
- class ServiceRabbit
9
+ class ServiceRabbit < ServiceBase
10
10
  attr_accessor :service, :channel, :queue, :topic
11
11
  attr_accessor :config
12
- SERVICE_KEY = 'service_model_sync'
13
12
 
14
13
  def initialize
15
14
  @config = PubSubModelSync::Config
@@ -24,16 +23,17 @@ module PubSubModelSync
24
23
  rescue PubSubModelSync::Runner::ShutDown
25
24
  raise
26
25
  rescue => e
27
- log("Error listening message: #{[e.message, e.backtrace]}")
26
+ log("Error listening message: #{[e.message, e.backtrace]}", :error)
28
27
  end
29
28
 
30
29
  def publish(data, attributes)
31
30
  log("Publishing: #{[data, attributes]}")
32
31
  subscribe_to_queue
33
32
  payload = { data: data, attributes: attributes }
34
- topic.publish(payload.to_json, routing_key: queue.name, type: SERVICE_KEY)
33
+ topic.publish(payload.to_json, message_settings)
35
34
  rescue => e
36
- log("Error publishing: #{[data, attributes, e.message, e.backtrace]}")
35
+ info = [data, attributes, e.message, e.backtrace]
36
+ log("Error publishing: #{info}", :error)
37
37
  end
38
38
 
39
39
  def stop
@@ -43,22 +43,17 @@ module PubSubModelSync
43
43
 
44
44
  private
45
45
 
46
+ def message_settings
47
+ { routing_key: queue.name, type: SERVICE_KEY }
48
+ end
49
+
46
50
  def process_message(_delivery_info, meta_info, payload)
47
51
  return unless meta_info[:type] == SERVICE_KEY
48
52
 
49
- data, attrs = parse_message_payload(payload)
50
- args = [data, attrs[:klass], attrs[:action], attrs]
51
- PubSubModelSync::MessageProcessor.new(*args).process
53
+ perform_message(payload)
52
54
  rescue => e
53
55
  error = [payload, e.message, e.backtrace]
54
- log("Error processing message: #{error}")
55
- end
56
-
57
- def parse_message_payload(payload)
58
- message_payload = JSON.parse(payload).symbolize_keys
59
- data = message_payload[:data].symbolize_keys
60
- attrs = message_payload[:attributes].symbolize_keys
61
- [data, attrs]
56
+ log("Error processing message: #{error}", :error)
62
57
  end
63
58
 
64
59
  def subscribe_to_queue
@@ -74,8 +69,8 @@ module PubSubModelSync
74
69
  queue.bind(topic, routing_key: queue.name)
75
70
  end
76
71
 
77
- def log(msg)
78
- config.log("Rabbit Service ==> #{msg}")
72
+ def log(msg, kind = :info)
73
+ config.log("Rabbit Service ==> #{msg}", kind)
79
74
  end
80
75
  end
81
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.2'
4
+ VERSION = '0.2.1'
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.2
4
+ version: 0.2.1
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-16 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