event_hub 0.1.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +0 -3
- data/Gemfile +1 -0
- data/Gemfile.lock +10 -29
- data/README.md +130 -10
- data/event_hub.gemspec +1 -6
- data/lib/event_hub/adapters/test/message.rb +35 -19
- data/lib/event_hub/adapters/test.rb +20 -16
- data/lib/event_hub/adapters.rb +0 -5
- data/lib/event_hub/event.rb +37 -20
- data/lib/event_hub/handler.rb +16 -14
- data/lib/event_hub/message.rb +19 -17
- data/lib/event_hub/version.rb +1 -1
- data/lib/event_hub.rb +6 -2
- metadata +5 -50
- data/lib/event_hub/adapters/aws/message.rb +0 -55
- data/lib/event_hub/adapters/aws.rb +0 -78
- data/lib/event_hub/adapters/bunny/message.rb +0 -32
- data/lib/event_hub/adapters/bunny.rb +0 -55
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f88590e890094b75995c444528f90a10ba26df7573927c6cbfd3a1db609a3c65
|
4
|
+
data.tar.gz: 6b820bf00fe54147b2c4067f3bfb43a50d77b59f9c8e047939188fc761d6a646
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 906008f796563c57fe06c883ce24323aad1072110a11525ff35da682e4912f02ba336c737411c2999ccdd97b1ea11f58859f21fba478e342c1eab600c9c32b21
|
7
|
+
data.tar.gz: 391b241d8c1957f8a4f27d9f12daf060a50fccf34a5630be4b711b62d60fabcc8c1f3323720d30082d811401a7644d35259d227bc1abb00ec9221b073a3c9c84
|
data/.rubocop.yml
CHANGED
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,43 +1,20 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
event_hub (
|
5
|
-
aws-sdk-sns (~> 1.0)
|
6
|
-
aws-sdk-sqs (~> 1.0)
|
7
|
-
bunny (>= 2.13.0)
|
4
|
+
event_hub (1.0.0)
|
8
5
|
|
9
6
|
GEM
|
10
7
|
remote: https://rubygems.org/
|
11
8
|
specs:
|
12
|
-
amq-protocol (2.3.2)
|
13
9
|
ast (2.4.2)
|
14
|
-
aws-eventstream (1.2.0)
|
15
|
-
aws-partitions (1.770.0)
|
16
|
-
aws-sdk-core (3.173.1)
|
17
|
-
aws-eventstream (~> 1, >= 1.0.2)
|
18
|
-
aws-partitions (~> 1, >= 1.651.0)
|
19
|
-
aws-sigv4 (~> 1.5)
|
20
|
-
jmespath (~> 1, >= 1.6.1)
|
21
|
-
aws-sdk-sns (1.60.0)
|
22
|
-
aws-sdk-core (~> 3, >= 3.165.0)
|
23
|
-
aws-sigv4 (~> 1.1)
|
24
|
-
aws-sdk-sqs (1.55.0)
|
25
|
-
aws-sdk-core (~> 3, >= 3.165.0)
|
26
|
-
aws-sigv4 (~> 1.1)
|
27
|
-
aws-sigv4 (1.5.2)
|
28
|
-
aws-eventstream (~> 1, >= 1.0.2)
|
29
|
-
bunny (2.20.3)
|
30
|
-
amq-protocol (~> 2.3, >= 2.3.1)
|
31
|
-
sorted_set (~> 1, >= 1.0.2)
|
32
10
|
diff-lcs (1.5.0)
|
33
|
-
|
11
|
+
docile (1.4.0)
|
34
12
|
json (2.6.3)
|
35
13
|
parallel (1.23.0)
|
36
14
|
parser (3.2.2.1)
|
37
15
|
ast (~> 2.4.1)
|
38
16
|
rainbow (3.1.1)
|
39
17
|
rake (13.0.6)
|
40
|
-
rbtree (0.4.6)
|
41
18
|
regexp_parser (2.8.0)
|
42
19
|
rexml (3.2.5)
|
43
20
|
rspec (3.12.0)
|
@@ -76,14 +53,17 @@ GEM
|
|
76
53
|
rubocop-capybara (~> 2.17)
|
77
54
|
rubocop-factory_bot (~> 2.22)
|
78
55
|
ruby-progressbar (1.13.0)
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
56
|
+
simplecov (0.22.0)
|
57
|
+
docile (~> 1.1)
|
58
|
+
simplecov-html (~> 0.11)
|
59
|
+
simplecov_json_formatter (~> 0.1)
|
60
|
+
simplecov-html (0.12.3)
|
61
|
+
simplecov_json_formatter (0.1.4)
|
83
62
|
unicode-display_width (2.4.2)
|
84
63
|
|
85
64
|
PLATFORMS
|
86
65
|
x86_64-darwin-21
|
66
|
+
x86_64-linux
|
87
67
|
|
88
68
|
DEPENDENCIES
|
89
69
|
event_hub!
|
@@ -92,6 +72,7 @@ DEPENDENCIES
|
|
92
72
|
rubocop (~> 1.21)
|
93
73
|
rubocop-rake
|
94
74
|
rubocop-rspec
|
75
|
+
simplecov
|
95
76
|
|
96
77
|
BUNDLED WITH
|
97
78
|
2.4.13
|
data/README.md
CHANGED
@@ -1,24 +1,144 @@
|
|
1
1
|
# EventHub
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
3
|
+
This library structurizes the application code for inter microservice communication.
|
4
|
+
Each micro-service has their own queue. All those queues are bind to the single exchange. So all the
|
5
|
+
micro-services can publish events to that exchange and only the events that are needed will get into
|
6
|
+
the corresponding micro-service's queue.
|
6
7
|
|
7
8
|
## Installation
|
8
9
|
|
9
|
-
|
10
|
+
You don't need to specify this gem directly in your Gemfile. Instead you need to add one of (or many)
|
11
|
+
"adapter" gems like `event_hub_aws` for AWS or `event_hub_bunny` for RabbitMQ. This library will be added
|
12
|
+
as a dependency.
|
13
|
+
|
14
|
+
## Configuration
|
15
|
+
|
16
|
+
Create a config file where you should specify events you need in this specific app. In the subscribe
|
17
|
+
section specify events you wanna receive and the handlers.
|
18
|
+
|
19
|
+
```yaml
|
20
|
+
# config/event_hub.yml
|
21
|
+
|
22
|
+
development:
|
23
|
+
queue: my-micro-service-events
|
24
|
+
exchange: event-hub
|
25
|
+
adapter: Bunny
|
26
|
+
subscribe:
|
27
|
+
user_registered:
|
28
|
+
handler: Handlers::UserRegistered
|
29
|
+
```
|
30
|
+
|
31
|
+
Then apply this config:
|
32
|
+
|
33
|
+
```ruby
|
34
|
+
# config/initializers/event_hub.rb
|
35
|
+
|
36
|
+
config = Rails.application.config_for(:event_hub)
|
37
|
+
|
38
|
+
# This block will be called in case of problems during the event handling
|
39
|
+
config[:on_failure] = lambda do |e, _message|
|
40
|
+
raise(e) if Rails.env.test?
|
41
|
+
# notify developers about the problem
|
42
|
+
end
|
43
|
+
|
44
|
+
EventHub.configure(config)
|
45
|
+
```
|
46
|
+
|
47
|
+
You need to implement the `event` and `event handler`:
|
48
|
+
|
49
|
+
```ruby
|
50
|
+
# app/event_hub/events/user_registered.rb
|
51
|
+
class Events::TickerUpserted < EventHub::Event
|
52
|
+
event :user_registered
|
53
|
+
version '1.1'
|
54
|
+
|
55
|
+
attribute :id
|
56
|
+
attribute :email
|
57
|
+
attribute :name
|
58
|
+
end
|
59
|
+
|
60
|
+
# app/event_hub/handlers/user_registered.rb
|
61
|
+
class Handlers::UserRegistered < EventHub::Handler
|
62
|
+
version '1.1'
|
63
|
+
|
64
|
+
def call
|
65
|
+
User.create(event.as_json)
|
66
|
+
end
|
67
|
+
|
68
|
+
# this method will be called in case if the received event version isn't eql to the handler version
|
69
|
+
def on_incorrect_version
|
70
|
+
event_major, event_minor = message.version.split('.')
|
71
|
+
handler_major, handler_minor = self.class.version.split('.')
|
72
|
+
|
73
|
+
if event_major != handler_major || event_minor < handler_minor
|
74
|
+
# TODO: notify rollbar
|
75
|
+
raise IgnoreMessage
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
def event
|
82
|
+
@event ||= Events::UserRegistered.new(JSON.parse(message.body))
|
83
|
+
end
|
84
|
+
end
|
85
|
+
```
|
86
|
+
|
87
|
+
To bind the exchange to the queue run:
|
88
|
+
|
89
|
+
```ruby
|
90
|
+
EventHub.adapter.setup_bindings
|
91
|
+
```
|
92
|
+
You need to run this command each time you change the `subscription` part of the config file.
|
93
|
+
You can create a migration with `EventHub.adapter.setup_bindings` in your app each time you
|
94
|
+
need to update bindings.
|
95
|
+
|
96
|
+
### Listen to events
|
97
|
+
|
98
|
+
To receive events from other system you need to run a daemon process that will call blocking method:
|
99
|
+
```ruby
|
100
|
+
EventHub.subscribe
|
101
|
+
```
|
102
|
+
This method listens for events and call corresponding handlers.
|
103
|
+
|
104
|
+
### Event publishing
|
105
|
+
|
106
|
+
To publish an event you need to create its instance and call its `publish` method.
|
107
|
+
|
108
|
+
```ruby
|
109
|
+
Events::UserRegistered.new(id: user.id, email: user.email).publish
|
110
|
+
```
|
111
|
+
|
112
|
+
## Event versions
|
113
|
+
|
114
|
+
The event routing is done based on the event name. So if you want let's say to add an attribute to the event
|
115
|
+
you must upgrade its version. We recommend two-level version structure.
|
10
116
|
|
11
|
-
|
117
|
+
If the event change breaks the contract the published should publish both versions of the event for some time.
|
118
|
+
For the new event the major part of the version must be changed.
|
119
|
+
The subscriber should reject the unsuported version and notify the responsible developers. It can be done in
|
120
|
+
`on_incorrect_version` handler method. So the micro-service's development team will have time to react onto the
|
121
|
+
problem and update the handler.
|
122
|
+
|
123
|
+
If the event change just extends the contract then the minor version should be changed. The subscriber should
|
124
|
+
notify the development team but still handle the event.
|
12
125
|
|
13
|
-
|
126
|
+
## Race conditions
|
14
127
|
|
15
|
-
|
128
|
+
It can happen that the older event will be processed later and overrides the previous event changes. It especially
|
129
|
+
can happen if you use several listener daemons. But still if you use a single daemon you are not protected because
|
130
|
+
the event can get back into the queue and change the order.
|
16
131
|
|
17
|
-
|
132
|
+
As a solution you can add a timestamp to the event and on the subscriber side add a field to the model you are
|
133
|
+
going to update. Each time you receive a new event you should check that the event timestamp is newer that the
|
134
|
+
timestamp in the DB. In this case you can be sure that you've received the latest event.
|
18
135
|
|
19
|
-
|
136
|
+
There are still can be a problem with the above approach. It can happen that the publisher is run on several
|
137
|
+
servers and they have a small difference in their clock. If you have so frequently updated models then probably
|
138
|
+
you need to use Redis counter to generate the event "update version".
|
20
139
|
|
21
|
-
|
140
|
+
On the subscriber side you can use Redlock to prevent race conditions. But pay attention that Redis adds lag to
|
141
|
+
the communication so you don't need it for all the models.
|
22
142
|
|
23
143
|
## Development
|
24
144
|
|
data/event_hub.gemspec
CHANGED
@@ -9,7 +9,7 @@ Gem::Specification.new do |spec|
|
|
9
9
|
spec.email = ['biguban@gmail.com']
|
10
10
|
|
11
11
|
spec.summary = 'Event driven inter microservice communication'
|
12
|
-
spec.description = '
|
12
|
+
spec.description = 'This library structurizes the code for inter microservice communication using events'
|
13
13
|
spec.homepage = 'https://github.com/seekingalpha/event_hub'
|
14
14
|
spec.license = 'MIT'
|
15
15
|
spec.required_ruby_version = '>= 2.6.0'
|
@@ -31,11 +31,6 @@ Gem::Specification.new do |spec|
|
|
31
31
|
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
32
32
|
spec.require_paths = ['lib']
|
33
33
|
|
34
|
-
# Uncomment to register a new dependency of your gem
|
35
|
-
spec.add_dependency 'aws-sdk-sns', '~> 1.0'
|
36
|
-
spec.add_dependency 'aws-sdk-sqs', '~> 1.0'
|
37
|
-
spec.add_dependency 'bunny', '>= 2.13.0'
|
38
|
-
|
39
34
|
# For more information and examples about making a new gem, check out our
|
40
35
|
# guide at: https://bundler.io/guides/creating_gem.html
|
41
36
|
spec.metadata['rubygems_mfa_required'] = 'true'
|
@@ -1,28 +1,44 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
class EventHub
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
3
|
+
class EventHub
|
4
|
+
module Adapters
|
5
|
+
class Test
|
6
|
+
class Message < EventHub::Message
|
7
|
+
def initialize(event, queue, attributes = {})
|
8
|
+
@event = event
|
9
|
+
@body = event.body
|
10
|
+
@attributes = attributes
|
11
|
+
@queue = queue
|
12
|
+
end
|
9
13
|
|
10
|
-
|
14
|
+
attr_reader :attributes, :body
|
11
15
|
|
12
|
-
|
13
|
-
|
14
|
-
|
16
|
+
def event
|
17
|
+
@event.class.event
|
18
|
+
end
|
15
19
|
|
16
|
-
|
17
|
-
|
18
|
-
|
20
|
+
def version
|
21
|
+
@event.class.version
|
22
|
+
end
|
19
23
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
+
def ack
|
25
|
+
@ack = true
|
26
|
+
@queue.delete(self)
|
27
|
+
end
|
28
|
+
|
29
|
+
def reject
|
30
|
+
@rejected = true
|
31
|
+
@queue.delete(self)
|
32
|
+
end
|
33
|
+
|
34
|
+
def ack?
|
35
|
+
!!@ack
|
36
|
+
end
|
24
37
|
|
25
|
-
|
26
|
-
|
38
|
+
def rejected?
|
39
|
+
!!@rejected
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
27
43
|
end
|
28
44
|
end
|
@@ -1,24 +1,28 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
class EventHub
|
4
|
-
|
3
|
+
class EventHub
|
4
|
+
module Adapters
|
5
|
+
class Test
|
6
|
+
attr_accessor :queue
|
5
7
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
8
|
+
def initialize(config)
|
9
|
+
@config = config
|
10
|
+
@queue = []
|
11
|
+
end
|
10
12
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
def subscribe(&block)
|
14
|
+
@queue.each do |message|
|
15
|
+
block.call(message)
|
16
|
+
end
|
17
|
+
end
|
16
18
|
|
17
|
-
|
18
|
-
|
19
|
-
|
19
|
+
def publish(event)
|
20
|
+
@queue << Message.new(event, @queue)
|
21
|
+
end
|
20
22
|
|
21
|
-
|
22
|
-
|
23
|
+
def setup_bindings
|
24
|
+
true
|
25
|
+
end
|
26
|
+
end
|
23
27
|
end
|
24
28
|
end
|
data/lib/event_hub/adapters.rb
CHANGED
@@ -1,9 +1,4 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
module EventHub::Adapters; end
|
4
|
-
require_relative 'adapters/bunny'
|
5
|
-
require_relative 'adapters/bunny/message'
|
6
|
-
require_relative 'adapters/aws'
|
7
|
-
require_relative 'adapters/aws/message'
|
8
3
|
require_relative 'adapters/test'
|
9
4
|
require_relative 'adapters/test/message'
|
data/lib/event_hub/event.rb
CHANGED
@@ -1,27 +1,44 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
def publish
|
5
|
-
EventHub.publish(self)
|
6
|
-
end
|
3
|
+
require 'json'
|
7
4
|
|
8
|
-
|
9
|
-
|
10
|
-
|
5
|
+
class EventHub
|
6
|
+
class Event
|
7
|
+
def initialize(hash = {})
|
8
|
+
hash.transform_keys(&:to_s).slice(*self.class.attributes.keys).each do |attr, val|
|
9
|
+
public_send("#{attr}=", val)
|
10
|
+
end
|
11
|
+
end
|
11
12
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
end
|
13
|
+
def publish
|
14
|
+
EventHub.publish(self)
|
15
|
+
end
|
16
16
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
17
|
+
def body
|
18
|
+
self.class.attributes.keys.to_h do |attr|
|
19
|
+
[attr, public_send(attr)]
|
20
|
+
end.to_json
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.event(event = nil)
|
24
|
+
@event = event.to_s if event
|
25
|
+
@event
|
26
|
+
end
|
21
27
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
28
|
+
def self.version(version = nil)
|
29
|
+
@version = version if version
|
30
|
+
@version
|
31
|
+
end
|
32
|
+
|
33
|
+
class << self
|
34
|
+
attr_reader :attributes
|
35
|
+
|
36
|
+
def attribute(attribute, options = {})
|
37
|
+
@attributes ||= {}
|
38
|
+
@attributes[attribute.to_s] = options
|
39
|
+
|
40
|
+
attr_accessor(attribute)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
27
44
|
end
|
data/lib/event_hub/handler.rb
CHANGED
@@ -1,22 +1,24 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
class EventHub
|
4
|
-
|
3
|
+
class EventHub
|
4
|
+
class Handler
|
5
|
+
attr_reader :message
|
5
6
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
7
|
+
def self.version(version = nil)
|
8
|
+
@version = version if version
|
9
|
+
@version
|
10
|
+
end
|
10
11
|
|
11
|
-
|
12
|
-
|
13
|
-
|
12
|
+
def initialize(message)
|
13
|
+
@message = message
|
14
|
+
end
|
14
15
|
|
15
|
-
|
16
|
-
|
17
|
-
|
16
|
+
def on_incorrect_version
|
17
|
+
raise IncorrectVersion
|
18
|
+
end
|
18
19
|
|
19
|
-
|
20
|
-
|
20
|
+
def validate!
|
21
|
+
on_incorrect_version if @message.version != self.class.version
|
22
|
+
end
|
21
23
|
end
|
22
24
|
end
|
data/lib/event_hub/message.rb
CHANGED
@@ -1,25 +1,27 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
class EventHub
|
4
|
-
|
5
|
-
|
6
|
-
|
3
|
+
class EventHub
|
4
|
+
class Message
|
5
|
+
def body
|
6
|
+
raise NotImplementedError
|
7
|
+
end
|
7
8
|
|
8
|
-
|
9
|
-
|
10
|
-
|
9
|
+
def routing_key
|
10
|
+
raise NotImplementedError
|
11
|
+
end
|
11
12
|
|
12
|
-
|
13
|
-
|
14
|
-
|
13
|
+
def headers
|
14
|
+
raise NotImplementedError
|
15
|
+
end
|
15
16
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
17
|
+
# Message acknowledgment
|
18
|
+
def ack
|
19
|
+
raise NotImplementedError
|
20
|
+
end
|
20
21
|
|
21
|
-
|
22
|
-
|
23
|
-
|
22
|
+
# moves message to the dead queue
|
23
|
+
def reject
|
24
|
+
raise NotImplementedError
|
25
|
+
end
|
24
26
|
end
|
25
27
|
end
|
data/lib/event_hub/version.rb
CHANGED
data/lib/event_hub.rb
CHANGED
@@ -13,6 +13,10 @@ class EventHub
|
|
13
13
|
class RejectMessage < StandardError; end
|
14
14
|
|
15
15
|
def self.configure(config)
|
16
|
+
if !config[:on_failure] || !config[:on_failure].respond_to?(:call) || config[:on_failure].arity != 2
|
17
|
+
raise ArgumentError, 'EventHub configuration must have `on_failure` callable options with arity == 2'
|
18
|
+
end
|
19
|
+
|
16
20
|
@config = config
|
17
21
|
@instance = nil
|
18
22
|
end
|
@@ -52,10 +56,10 @@ class EventHub
|
|
52
56
|
def initialize(config)
|
53
57
|
@config = config
|
54
58
|
@config[:subscribe] ||= {}
|
55
|
-
@config[:subscribe].each_value { |subscription| subscription[:handler] = subscription[:handler]
|
59
|
+
@config[:subscribe].each_value { |subscription| subscription[:handler] = Object.const_get(subscription[:handler]) }
|
56
60
|
end
|
57
61
|
|
58
62
|
def adapter
|
59
|
-
@adapter ||= Adapters.const_get(@config[:adapter]
|
63
|
+
@adapter ||= Adapters.const_get(@config[:adapter]).new(@config)
|
60
64
|
end
|
61
65
|
end
|
metadata
CHANGED
@@ -1,58 +1,17 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: event_hub
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Bogdan Guban
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-06-
|
12
|
-
dependencies:
|
13
|
-
|
14
|
-
|
15
|
-
requirement: !ruby/object:Gem::Requirement
|
16
|
-
requirements:
|
17
|
-
- - "~>"
|
18
|
-
- !ruby/object:Gem::Version
|
19
|
-
version: '1.0'
|
20
|
-
type: :runtime
|
21
|
-
prerelease: false
|
22
|
-
version_requirements: !ruby/object:Gem::Requirement
|
23
|
-
requirements:
|
24
|
-
- - "~>"
|
25
|
-
- !ruby/object:Gem::Version
|
26
|
-
version: '1.0'
|
27
|
-
- !ruby/object:Gem::Dependency
|
28
|
-
name: aws-sdk-sqs
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
30
|
-
requirements:
|
31
|
-
- - "~>"
|
32
|
-
- !ruby/object:Gem::Version
|
33
|
-
version: '1.0'
|
34
|
-
type: :runtime
|
35
|
-
prerelease: false
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
37
|
-
requirements:
|
38
|
-
- - "~>"
|
39
|
-
- !ruby/object:Gem::Version
|
40
|
-
version: '1.0'
|
41
|
-
- !ruby/object:Gem::Dependency
|
42
|
-
name: bunny
|
43
|
-
requirement: !ruby/object:Gem::Requirement
|
44
|
-
requirements:
|
45
|
-
- - ">="
|
46
|
-
- !ruby/object:Gem::Version
|
47
|
-
version: 2.13.0
|
48
|
-
type: :runtime
|
49
|
-
prerelease: false
|
50
|
-
version_requirements: !ruby/object:Gem::Requirement
|
51
|
-
requirements:
|
52
|
-
- - ">="
|
53
|
-
- !ruby/object:Gem::Version
|
54
|
-
version: 2.13.0
|
55
|
-
description: Event driven inter microservice communication
|
11
|
+
date: 2023-06-22 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: This library structurizes the code for inter microservice communication
|
14
|
+
using events
|
56
15
|
email:
|
57
16
|
- biguban@gmail.com
|
58
17
|
executables: []
|
@@ -71,10 +30,6 @@ files:
|
|
71
30
|
- event_hub.gemspec
|
72
31
|
- lib/event_hub.rb
|
73
32
|
- lib/event_hub/adapters.rb
|
74
|
-
- lib/event_hub/adapters/aws.rb
|
75
|
-
- lib/event_hub/adapters/aws/message.rb
|
76
|
-
- lib/event_hub/adapters/bunny.rb
|
77
|
-
- lib/event_hub/adapters/bunny/message.rb
|
78
33
|
- lib/event_hub/adapters/test.rb
|
79
34
|
- lib/event_hub/adapters/test/message.rb
|
80
35
|
- lib/event_hub/event.rb
|
@@ -1,55 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
class EventHub::Adapters::Aws::Message < EventHub::Message
|
4
|
-
def initialize(adapter, message)
|
5
|
-
@adapter = adapter
|
6
|
-
@message = message
|
7
|
-
end
|
8
|
-
|
9
|
-
def attributes
|
10
|
-
@attributes ||= parsed_body['MessageAttributes'].transform_values { |v| v['Value'] }
|
11
|
-
end
|
12
|
-
|
13
|
-
def body
|
14
|
-
parsed_body['Message']
|
15
|
-
end
|
16
|
-
|
17
|
-
def event
|
18
|
-
attributes['event']
|
19
|
-
end
|
20
|
-
|
21
|
-
def version
|
22
|
-
attributes['version']
|
23
|
-
end
|
24
|
-
|
25
|
-
def ack
|
26
|
-
# Delete the message from the queue.
|
27
|
-
@adapter.delete_message(@message.receipt_handle)
|
28
|
-
end
|
29
|
-
|
30
|
-
def reject
|
31
|
-
if @adapter.config[:dead_queue_url]
|
32
|
-
# TODO: publish the message to the dead_queue
|
33
|
-
end
|
34
|
-
ack
|
35
|
-
end
|
36
|
-
|
37
|
-
private
|
38
|
-
|
39
|
-
# {
|
40
|
-
# "Type" : "Notification",
|
41
|
-
# "MessageId" : "025727dd-bb27-5c53-8a79-7b38f0cfe19a",
|
42
|
-
# "SequenceNumber" : "10000000000000020000",
|
43
|
-
# "TopicArn" : "arn:aws:sns:us-west-2:744522205193:event-hub.fifo",
|
44
|
-
# "Subject" : "subject",
|
45
|
-
# "Message" : "body",
|
46
|
-
# "Timestamp" : "2023-05-22T10:53:28.806Z",
|
47
|
-
# "UnsubscribeURL" : "https://sns.us-west-2.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:us-west-2:744522205193:event-hub.fifo:c8e1a595-4b0d-44b4-8087-094e7b0de09a",
|
48
|
-
# "MessageAttributes" : {
|
49
|
-
# "str_attr" : {"Type":"String","Value":"string_attr_val"}
|
50
|
-
# }
|
51
|
-
# }
|
52
|
-
def parsed_body
|
53
|
-
@parsed_body ||= JSON.parse(@message.body)
|
54
|
-
end
|
55
|
-
end
|
@@ -1,78 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'aws-sdk-sns'
|
4
|
-
require 'aws-sdk-sqs'
|
5
|
-
|
6
|
-
class EventHub::Adapters::Aws
|
7
|
-
attr_reader :config
|
8
|
-
|
9
|
-
def initialize(config)
|
10
|
-
@config = config
|
11
|
-
end
|
12
|
-
|
13
|
-
def subscribe(&block)
|
14
|
-
loop do
|
15
|
-
receive_message_result = sqs.receive_message({
|
16
|
-
queue_url: @config[:queue_url],
|
17
|
-
message_attribute_names: ['All'], # Receive all custom attributes.
|
18
|
-
max_number_of_messages: 10, # Receive at most one message.
|
19
|
-
wait_time_seconds: 15 # Do not wait to check for the message.
|
20
|
-
})
|
21
|
-
|
22
|
-
# Display information about the message.
|
23
|
-
# Display the message's body and each custom attribute value.
|
24
|
-
receive_message_result.messages.each do |aws_msg|
|
25
|
-
message = Message.new(self, aws_msg)
|
26
|
-
block.call(message)
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
def publish(event)
|
32
|
-
topic.publish({
|
33
|
-
message: event.to_json,
|
34
|
-
message_attributes: {
|
35
|
-
event: { data_type: 'String', string_value: event.class.event },
|
36
|
-
version: { data_type: 'String', string_value: event.class.version },
|
37
|
-
},
|
38
|
-
message_group_id: 'message_group_id',
|
39
|
-
message_deduplication_id: SecureRandom.uuid
|
40
|
-
})
|
41
|
-
end
|
42
|
-
|
43
|
-
def setup_bindings
|
44
|
-
policy = { event: @config[:subscribe].keys }.to_json
|
45
|
-
subscription = topic.subscriptions.find { |s| s.attributes['Endpoint'] == @config[:queue_arn] }
|
46
|
-
if subscription
|
47
|
-
subscription.set_attributes({
|
48
|
-
attribute_name: 'FilterPolicy',
|
49
|
-
attribute_value: policy
|
50
|
-
})
|
51
|
-
else
|
52
|
-
topic.subscribe({
|
53
|
-
protocol: 'sqs',
|
54
|
-
attributes: { 'FilterPolicy' => policy },
|
55
|
-
endpoint: @config[:queue_arn]
|
56
|
-
})
|
57
|
-
end
|
58
|
-
end
|
59
|
-
|
60
|
-
def delete_message(receipt_handle)
|
61
|
-
sqs.delete_message(
|
62
|
-
queue_url: @config[:queue_url],
|
63
|
-
receipt_handle: receipt_handle
|
64
|
-
)
|
65
|
-
end
|
66
|
-
|
67
|
-
def topic
|
68
|
-
@topic ||= sns.topic(@config[:exchange_arn])
|
69
|
-
end
|
70
|
-
|
71
|
-
def sns
|
72
|
-
@sns ||= Aws::SNS::Resource.new(@config[:credentials] || {})
|
73
|
-
end
|
74
|
-
|
75
|
-
def sqs
|
76
|
-
@sqs ||= Aws::SQS::Client.new(@config[:credentials] || {})
|
77
|
-
end
|
78
|
-
end
|
@@ -1,32 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
class EventHub::Adapters::Bunny::Message < EventHub::Message
|
4
|
-
def initialize(delivery_info, properties, body, channel)
|
5
|
-
@delivery_info = delivery_info
|
6
|
-
@properties = properties
|
7
|
-
@body = body
|
8
|
-
@channel = channel
|
9
|
-
end
|
10
|
-
|
11
|
-
def attributes
|
12
|
-
@properties
|
13
|
-
end
|
14
|
-
|
15
|
-
attr_reader :body
|
16
|
-
|
17
|
-
def event
|
18
|
-
@properties[:event]
|
19
|
-
end
|
20
|
-
|
21
|
-
def version
|
22
|
-
@properties[:version]
|
23
|
-
end
|
24
|
-
|
25
|
-
def ack
|
26
|
-
@channel.ack(@delivery_info.delivery_tag)
|
27
|
-
end
|
28
|
-
|
29
|
-
def reject
|
30
|
-
@channel.reject(@delivery_info.delivery_tag)
|
31
|
-
end
|
32
|
-
end
|
@@ -1,55 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'bunny'
|
4
|
-
|
5
|
-
class EventHub::Adapters::Bunny
|
6
|
-
def initialize(config)
|
7
|
-
@config = config
|
8
|
-
end
|
9
|
-
|
10
|
-
def subscribe(&block)
|
11
|
-
channel.prefetch(1)
|
12
|
-
queue.subscribe(block: true, manual_ack: true) do |delivery_info, properties, body|
|
13
|
-
message = Message.new(delivery_info, properties, body, channel)
|
14
|
-
block.call(message)
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
|
-
def publish(event)
|
19
|
-
exchange.publish(
|
20
|
-
event.body,
|
21
|
-
routing_key: event.class.event,
|
22
|
-
persistent: true,
|
23
|
-
event: event.class.event,
|
24
|
-
version: event.class.version
|
25
|
-
)
|
26
|
-
end
|
27
|
-
|
28
|
-
def setup_bindings
|
29
|
-
@config.subscribe.each_key do |routing_key|
|
30
|
-
queue.bind(exchange, routing_key: routing_key)
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
def channel
|
35
|
-
@channel ||= connection.create_channel
|
36
|
-
end
|
37
|
-
|
38
|
-
private
|
39
|
-
|
40
|
-
def exchange
|
41
|
-
@exchange ||= channel.direct(@config[:exchange])
|
42
|
-
end
|
43
|
-
|
44
|
-
def queue
|
45
|
-
@queue ||= channel.queue(@config[:queue], durable: true)
|
46
|
-
end
|
47
|
-
|
48
|
-
def connection
|
49
|
-
return @connection if defined?(@connection)
|
50
|
-
|
51
|
-
@connection = ::Bunny.new # TODO: config
|
52
|
-
@connection.start
|
53
|
-
@connection
|
54
|
-
end
|
55
|
-
end
|