pub_sub_model_sync 0.1.4 → 0.1.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4eda619ab984f99ffe46cfef36fa380aaa6766c1564081dcc4e5e53e6f7976e0
4
- data.tar.gz: dd056d897c96bac38a20c115fee501cbb1383038483f74849bbb0e7217568163
3
+ metadata.gz: fd18a29b1e99da881b9c362dbc5541f6aeee0211988bcf301336691ac071e96d
4
+ data.tar.gz: cbc025ff0301e1bf4fd35d78fa2b70f06ff8a3c7c1dbfa12448f1cd3a53e592f
5
5
  SHA512:
6
- metadata.gz: 75796a135c2f820d89303c29c9bb6dc78eaa1e207391a07ba86a45f547a7007b4b00f767bb949d354c5a90c504b5d2d287619fd3106bca1dce71e75588ac6173
7
- data.tar.gz: fe4829f970142bfea0089beb4e86a27c9d1e08049213372ae77f94b9d02d16752218d58e88b25ac1f34848931da3651e4405caa3a8932401c1410d7f04e3d0da
6
+ metadata.gz: 3036baf17c9f78c6b4b445eb5a3a2f7be63c2a78cb36dcf9aad1abcf1eff8b691979cd1ff79e1bac916b44b1996e0f80bedf841be102ad1ec29684c1437910a2
7
+ data.tar.gz: aead45d302ed53d84457376dc9fa64f05d267b7441125722d4621d4d2fc9d7097fa24f3aa693c50862b616f5a25b32e77a202a0503143d347f80290728701680
@@ -1,5 +1,10 @@
1
1
  # Change Log
2
2
 
3
+ # 0.1.5
4
+ - Add apache kafka support
5
+ - Add Service interface for future references
6
+ - Improve Services to use a single/common message performer
7
+
3
8
  # 0.1.4
4
9
  - Add attribute aliases when publishing, ```ps_publish(['name:full_name', 'email'])```
5
10
  - Ability to retrieve publisher/subscriber crud settings
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.4)
4
+ pub_sub_model_sync (0.1.5)
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,6 +45,14 @@ 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", "kafka2:9092"], 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
58
  - Start subscribers to listen for publishers (Only in the app that has subscribers)
@@ -74,6 +84,7 @@ end
74
84
  # Samples
75
85
  User.create(name: 'test user', email: 'sample@gmail.com') # Review your App 2 to see the created user (only name will be saved)
76
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
@@ -109,15 +120,22 @@ end
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
  ```
@@ -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
@@ -13,6 +13,9 @@ module PubSubModelSync
13
13
  # rabbitmq service
14
14
  cattr_accessor :bunny_connection, :queue_name, :topic_name
15
15
 
16
+ # kafka service
17
+ cattr_accessor :kafka_connection, :topic_name
18
+
16
19
  def self.log(msg, kind = :info)
17
20
  msg = "PS_MSYNC ==> #{msg}"
18
21
  if logger == :raise_error
@@ -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
@@ -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
@@ -25,8 +25,7 @@ module PubSubModelSync
25
25
  {
26
26
  klass: klass.to_s,
27
27
  action: action.to_sym,
28
- id: id,
29
- service_model_sync: true
28
+ id: id
30
29
  }
31
30
  end
32
31
 
@@ -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,12 +49,9 @@ 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
56
  log("Error processing message: #{[received_message, e.message]}", :error)
58
57
  ensure
@@ -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
@@ -31,7 +30,7 @@ module PubSubModelSync
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
35
  info = [data, attributes, e.message, e.backtrace]
37
36
  log("Error publishing: #{info}", :error)
@@ -44,24 +43,19 @@ module PubSubModelSync
44
43
 
45
44
  private
46
45
 
46
+ def message_settings
47
+ { routing_key: queue.name, type: SERVICE_KEY }
48
+ end
49
+
47
50
  def process_message(_delivery_info, meta_info, payload)
48
51
  return unless meta_info[:type] == SERVICE_KEY
49
52
 
50
- data, attrs = parse_message_payload(payload)
51
- args = [data, attrs[:klass], attrs[:action], attrs]
52
- PubSubModelSync::MessageProcessor.new(*args).process
53
+ perform_message(payload)
53
54
  rescue => e
54
55
  error = [payload, e.message, e.backtrace]
55
56
  log("Error processing message: #{error}", :error)
56
57
  end
57
58
 
58
- def parse_message_payload(payload)
59
- message_payload = JSON.parse(payload).symbolize_keys
60
- data = message_payload[:data].symbolize_keys
61
- attrs = message_payload[:attributes].symbolize_keys
62
- [data, attrs]
63
- end
64
-
65
59
  def subscribe_to_queue
66
60
  service.start
67
61
  @channel = service.create_channel
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PubSubModelSync
4
- VERSION = '0.1.4'
4
+ VERSION = '0.1.5'
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.4
4
+ version: 0.1.5
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-25 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