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 +4 -4
- data/CHANGELOG.md +5 -0
- data/Gemfile +1 -0
- data/Gemfile.lock +7 -3
- data/README.md +23 -5
- data/lib/pub_sub_model_sync.rb +3 -0
- data/lib/pub_sub_model_sync/config.rb +3 -0
- data/lib/pub_sub_model_sync/connector.rb +2 -0
- data/lib/pub_sub_model_sync/mock_kafka_service.rb +45 -0
- data/lib/pub_sub_model_sync/publisher.rb +1 -2
- data/lib/pub_sub_model_sync/service_base.rb +36 -0
- data/lib/pub_sub_model_sync/service_google.rb +6 -7
- data/lib/pub_sub_model_sync/service_kafka.rb +77 -0
- data/lib/pub_sub_model_sync/service_rabbit.rb +7 -13
- data/lib/pub_sub_model_sync/version.rb +1 -1
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fd18a29b1e99da881b9c362dbc5541f6aeee0211988bcf301336691ac071e96d
|
4
|
+
data.tar.gz: cbc025ff0301e1bf4fd35d78fa2b70f06ff8a3c7c1dbfa12448f1cd3a53e592f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3036baf17c9f78c6b4b445eb5a3a2f7be63c2a78cb36dcf9aad1abcf1eff8b691979cd1ff79e1bac916b44b1996e0f80bedf841be102ad1ec29684c1437910a2
|
7
|
+
data.tar.gz: aead45d302ed53d84457376dc9fa64f05d267b7441125722d4621d4d2fc9d7097fa24f3aa693c50862b616f5a25b32e77a202a0503143d347f80290728701680
|
data/CHANGELOG.md
CHANGED
@@ -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
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
pub_sub_model_sync (0.1.
|
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
|
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
|
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
|
-
|
113
|
-
allow(Google::Cloud::Pubsub).to receive(:new).and_return(
|
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
|
-
|
120
|
-
allow(Bunny).to receive(:new).and_return(
|
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
|
```
|
data/lib/pub_sub_model_sync.rb
CHANGED
@@ -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
|
@@ -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
|
@@ -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
|
-
|
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
|
-
|
51
|
-
return unless attrs[:service_model_sync]
|
52
|
+
return unless message.attributes[SERVICE_KEY]
|
52
53
|
|
53
|
-
|
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,
|
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
|
-
|
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
|
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
|
+
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-
|
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
|