realm-sns 0.7.1 → 0.7.3
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 +4 -4
- data/README.md +40 -0
- data/Rakefile +19 -0
- metadata +25 -16
- data/lib/realm-sns.rb +0 -8
- data/lib/realm/sns/gateway.rb +0 -64
- data/lib/realm/sns/gateway/queue_adapter.rb +0 -57
- data/lib/realm/sns/gateway/queue_manager.rb +0 -64
- data/lib/realm/sns/gateway/topic_adapter.rb +0 -40
- data/lib/realm/sns/gateway/worker.rb +0 -114
- data/lib/realm/sns/plugin.rb +0 -14
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: dc34c9cede23ac937d62a7c1f496444a629427765d5bb3d7d4fc4f6eb8921507
|
4
|
+
data.tar.gz: 56571d1ffcff0d8c418de54f9bb3d3b99ef867e4c514d84af73694fc8c05f8d5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 972425f1c9c07b92209f2ad6eee95381ad8a346547cfe23981d8a936f5ca8d2dd726aa716ebc61db90d77cd0ce6fd6b7ee3f3350b2eb560edf516f975e94703a
|
7
|
+
data.tar.gz: b22a548fa14c648aba403c219b9b32ffcd0336761f363eb6d3874511ca269a92d13794ead1f9f5f676a56803eadc46e9e86f3693b066972df8d24494f7fb2ef6
|
data/README.md
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
# Realm
|
2
|
+
|
3
|
+
Domain layer framework following Domain-driven/CQRS design principles.
|
4
|
+
|
5
|
+
[](https://buildkite.com/reevoo/realm)
|
6
|
+
|
7
|
+
## Service layers
|
8
|
+
|
9
|
+
We follow the standard MVC design pattern of Rails but giving the model layer more structure and guidance regarding where
|
10
|
+
to put your code. The model is split into domain layer (using our [Realm](https://github.com/reevoo/smart-mono/tree/master/gems/realm) library)
|
11
|
+
and persistence layer (using [ROM](https://rom-rb.org/) library). The individual components are explained in the following section.
|
12
|
+
|
13
|
+

|
14
|
+
|
15
|
+
Advanced components are shown in lighter color, those will be needed only later on as the service domain logic grows.
|
16
|
+
|
17
|
+
## Model layer components
|
18
|
+
|
19
|
+

|
20
|
+
|
21
|
+
Each service has one **domain** module which consists of multiple [**aggregate**](https://martinfowler.com/bliki/DDD_Aggregate.html) modules.
|
22
|
+
Aggregate is a cluster of domain objects that can be treated as a single unit. The only way for outer world to communicate
|
23
|
+
with aggregate is by **queries** and **commands**. Query exposes aggregate's internal state and command changes it.
|
24
|
+
The state of an aggregate is represented by tree of **entities** with one being the aggregate root and zero or more dependent
|
25
|
+
entities with *belongs_to* relation to the root entity. The state of an aggregate (entity tree) is persisted
|
26
|
+
and retrieved by **repository**. There is generally one repository per aggregate unless we split the read/write
|
27
|
+
(query/command) persistence model for that particular domain. The repository uses **relations** to access the database
|
28
|
+
tables. Each relation class represents one table.
|
29
|
+
|
30
|
+
|
31
|
+
## Where to put my code as it grows?
|
32
|
+
|
33
|
+
TODO
|
34
|
+
|
35
|
+
|
36
|
+
## Roadmap
|
37
|
+
|
38
|
+
- [ ] Support Ruby 3
|
39
|
+
- [ ] Make it work outside of Rails engines
|
40
|
+
- [ ] Support multiple persistence gateways in one runtime
|
data/Rakefile
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
begin
|
4
|
+
require 'bundler/setup'
|
5
|
+
rescue LoadError
|
6
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
7
|
+
end
|
8
|
+
|
9
|
+
require 'rdoc/task'
|
10
|
+
|
11
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
12
|
+
rdoc.rdoc_dir = 'rdoc'
|
13
|
+
rdoc.title = 'Realm'
|
14
|
+
rdoc.options << '--line-numbers'
|
15
|
+
rdoc.rdoc_files.include('README.md')
|
16
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
17
|
+
end
|
18
|
+
|
19
|
+
require 'bundler/gem_tasks'
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: realm-sns
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.7.
|
4
|
+
version: 0.7.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- developers@reevoo.com
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-07-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: aws-sdk-sns
|
@@ -52,6 +52,20 @@ dependencies:
|
|
52
52
|
- - ">="
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: zeitwerk
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '2.4'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '2.4'
|
55
69
|
- !ruby/object:Gem::Dependency
|
56
70
|
name: pry-byebug
|
57
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -94,24 +108,19 @@ dependencies:
|
|
94
108
|
- - ">="
|
95
109
|
- !ruby/object:Gem::Version
|
96
110
|
version: '0'
|
97
|
-
description:
|
98
|
-
email:
|
111
|
+
description:
|
112
|
+
email:
|
99
113
|
executables: []
|
100
114
|
extensions: []
|
101
115
|
extra_rdoc_files: []
|
102
116
|
files:
|
103
|
-
-
|
104
|
-
-
|
105
|
-
|
106
|
-
- lib/realm/sns/gateway/queue_manager.rb
|
107
|
-
- lib/realm/sns/gateway/topic_adapter.rb
|
108
|
-
- lib/realm/sns/gateway/worker.rb
|
109
|
-
- lib/realm/sns/plugin.rb
|
110
|
-
homepage:
|
117
|
+
- README.md
|
118
|
+
- Rakefile
|
119
|
+
homepage:
|
111
120
|
licenses:
|
112
121
|
- MIT
|
113
122
|
metadata: {}
|
114
|
-
post_install_message:
|
123
|
+
post_install_message:
|
115
124
|
rdoc_options: []
|
116
125
|
require_paths:
|
117
126
|
- lib
|
@@ -126,8 +135,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
126
135
|
- !ruby/object:Gem::Version
|
127
136
|
version: '0'
|
128
137
|
requirements: []
|
129
|
-
rubygems_version: 3.1.
|
130
|
-
signing_key:
|
138
|
+
rubygems_version: 3.1.4
|
139
|
+
signing_key:
|
131
140
|
specification_version: 4
|
132
141
|
summary: SNS/SQS plugin for Realm
|
133
142
|
test_files: []
|
data/lib/realm-sns.rb
DELETED
data/lib/realm/sns/gateway.rb
DELETED
@@ -1,64 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'securerandom'
|
4
|
-
require 'active_support/core_ext/string'
|
5
|
-
|
6
|
-
require 'realm/event_handler'
|
7
|
-
require 'realm/event_router/gateway'
|
8
|
-
require_relative './gateway/queue_manager'
|
9
|
-
require_relative './gateway/topic_adapter'
|
10
|
-
require_relative './gateway/worker'
|
11
|
-
|
12
|
-
module Realm
|
13
|
-
module SNS
|
14
|
-
class Gateway < Realm::EventRouter::Gateway
|
15
|
-
def initialize(topic_arn:, queue_prefix: nil, event_processing_attempts: 3, **)
|
16
|
-
super
|
17
|
-
@topic = TopicAdapter.new(topic_arn)
|
18
|
-
@queue_prefix = queue_prefix
|
19
|
-
@event_processing_attempts = event_processing_attempts
|
20
|
-
@queue_map = {}
|
21
|
-
end
|
22
|
-
|
23
|
-
def add_listener(event_type, listener, queue_arn: nil)
|
24
|
-
queue = queue_arn ? queue_manager.get(arn: queue_arn) : provide_queue(event_type, listener)
|
25
|
-
@queue_map[queue] = listener
|
26
|
-
end
|
27
|
-
|
28
|
-
def trigger(event_type, attributes = {})
|
29
|
-
create_event(event_type, attributes).tap { |event| @topic.publish(event_type, event.to_json) }
|
30
|
-
end
|
31
|
-
|
32
|
-
def worker(**options)
|
33
|
-
@worker ||= Worker.new(
|
34
|
-
@queue_map,
|
35
|
-
event_factory: @event_factory,
|
36
|
-
logger: @runtime && @runtime.context[:logger],
|
37
|
-
event_processing_attempts: @event_processing_attempts,
|
38
|
-
**options,
|
39
|
-
)
|
40
|
-
end
|
41
|
-
|
42
|
-
def queues
|
43
|
-
@queue_map.keys
|
44
|
-
end
|
45
|
-
|
46
|
-
private
|
47
|
-
|
48
|
-
def provide_queue(event_type, listener)
|
49
|
-
queue_name = [event_type.to_s.gsub('.', '_'), queue_suffix(listener)].join('-')
|
50
|
-
queue = queue_manager.provide(queue_name)
|
51
|
-
@topic.subscribe(event_type, queue)
|
52
|
-
queue
|
53
|
-
end
|
54
|
-
|
55
|
-
def queue_manager
|
56
|
-
@queue_manager ||= QueueManager.new(prefix: @queue_prefix)
|
57
|
-
end
|
58
|
-
|
59
|
-
def queue_suffix(listener)
|
60
|
-
listener.try(:identifier) || SecureRandom.alphanumeric(16)
|
61
|
-
end
|
62
|
-
end
|
63
|
-
end
|
64
|
-
end
|
@@ -1,57 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'realm/event_router/gateway'
|
4
|
-
require 'realm/mixins/decorator'
|
5
|
-
|
6
|
-
module Realm
|
7
|
-
module SNS
|
8
|
-
class Gateway < Realm::EventRouter::Gateway
|
9
|
-
# Provides cleaner SDK over Aws::SQS::Queue
|
10
|
-
class QueueAdapter
|
11
|
-
include Mixins::Decorator[:@queue]
|
12
|
-
|
13
|
-
def arn
|
14
|
-
@queue.attributes['QueueArn']
|
15
|
-
end
|
16
|
-
|
17
|
-
def allow_send_messages(source_arn)
|
18
|
-
@queue.set_attributes(attributes: {
|
19
|
-
'Policy' => {
|
20
|
-
'Version' => '2012-10-17',
|
21
|
-
'Statement' => policy_statement(source_arn),
|
22
|
-
}.to_json,
|
23
|
-
})
|
24
|
-
end
|
25
|
-
|
26
|
-
def publish(event_type, message)
|
27
|
-
@queue.send_message(
|
28
|
-
message_body: message,
|
29
|
-
message_attributes: { 'event_type' => { data_type: 'String', string_value: event_type.to_s } },
|
30
|
-
)
|
31
|
-
end
|
32
|
-
|
33
|
-
def empty?
|
34
|
-
attributes.slice(
|
35
|
-
'ApproximateNumberOfMessages',
|
36
|
-
'ApproximateNumberOfMessagesDelayed',
|
37
|
-
'ApproximateNumberOfMessagesNotVisible',
|
38
|
-
).all? { |_, val| val.to_i.zero? }
|
39
|
-
end
|
40
|
-
|
41
|
-
private
|
42
|
-
|
43
|
-
def policy_statement(source_arn)
|
44
|
-
{
|
45
|
-
'Effect' => 'Allow',
|
46
|
-
'Principal' => { 'AWS' => '*' },
|
47
|
-
'Action' => 'sqs:SendMessage',
|
48
|
-
'Resource' => arn,
|
49
|
-
'Condition' => {
|
50
|
-
'ArnEquals' => { 'aws:SourceArn' => source_arn },
|
51
|
-
},
|
52
|
-
}
|
53
|
-
end
|
54
|
-
end
|
55
|
-
end
|
56
|
-
end
|
57
|
-
end
|
@@ -1,64 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'aws-sdk-sqs'
|
4
|
-
require 'realm/error'
|
5
|
-
require_relative './queue_adapter'
|
6
|
-
|
7
|
-
module Realm
|
8
|
-
module SNS
|
9
|
-
class Gateway < Realm::EventRouter::Gateway
|
10
|
-
class QueueManager
|
11
|
-
class QueueNameTooLong < Realm::Error
|
12
|
-
def initialize(name, msg: "Queue name '#{name}' cannot be longer than 80 characters, " \
|
13
|
-
"please provide custom EventHandler identifier if it's auto generated")
|
14
|
-
super(msg)
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
|
-
CleanupWithoutPrefix = Realm::Error[
|
19
|
-
'Cleaning up queues without prefix is not allowed, it can lead to deleting queues from other apps']
|
20
|
-
|
21
|
-
def initialize(prefix: nil, sqs: Aws::SQS::Resource.new)
|
22
|
-
@prefix = prefix
|
23
|
-
@sqs = sqs
|
24
|
-
end
|
25
|
-
|
26
|
-
def get(name: nil, arn: nil)
|
27
|
-
throw ArgumentError, 'You have to provide name or arn of the queue' unless name || arn
|
28
|
-
|
29
|
-
QueueAdapter.new(@sqs.get_queue_by_name(queue_name: name ? prefix_name(name) : arn.split(':')[-1]))
|
30
|
-
end
|
31
|
-
|
32
|
-
def create(queue_name)
|
33
|
-
name = prefix_name(queue_name)
|
34
|
-
raise QueueNameTooLong, name if name.size > 80
|
35
|
-
|
36
|
-
QueueAdapter.new(@sqs.create_queue(queue_name: name))
|
37
|
-
end
|
38
|
-
|
39
|
-
def provide(queue_name)
|
40
|
-
get(name: queue_name)
|
41
|
-
rescue Aws::SQS::Errors::NonExistentQueue
|
42
|
-
create(queue_name)
|
43
|
-
end
|
44
|
-
|
45
|
-
def cleanup(except: [])
|
46
|
-
raise CleanupWithoutPrefix unless @prefix
|
47
|
-
|
48
|
-
except_urls = Array(except).map(&:url)
|
49
|
-
@sqs.queues(queue_name_prefix: @prefix).each do |queue|
|
50
|
-
next if except_urls.include?(queue.url)
|
51
|
-
|
52
|
-
queue.delete if QueueAdapter.new(queue).empty?
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
private
|
57
|
-
|
58
|
-
def prefix_name(name)
|
59
|
-
[@prefix, name].compact.join('-')
|
60
|
-
end
|
61
|
-
end
|
62
|
-
end
|
63
|
-
end
|
64
|
-
end
|
@@ -1,40 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'aws-sdk-sns'
|
4
|
-
|
5
|
-
module Realm
|
6
|
-
module SNS
|
7
|
-
class Gateway < Realm::EventRouter::Gateway
|
8
|
-
# Provides cleaner SDK over Aws::SNS::Topic
|
9
|
-
class TopicAdapter
|
10
|
-
def initialize(topic_or_arn)
|
11
|
-
@topic = topic_or_arn.is_a?(Aws::SNS::Topic) ? topic_or_arn : Aws::SNS::Resource.new.topic(topic_or_arn)
|
12
|
-
end
|
13
|
-
|
14
|
-
def publish(event_type, message)
|
15
|
-
@topic.publish(
|
16
|
-
message: message,
|
17
|
-
message_attributes: { 'event_type' => { data_type: 'String', string_value: event_type.to_s } },
|
18
|
-
)
|
19
|
-
end
|
20
|
-
|
21
|
-
def subscribe(event_type, queue)
|
22
|
-
queue.allow_send_messages(@topic.arn)
|
23
|
-
@topic.subscribe(
|
24
|
-
protocol: 'sqs',
|
25
|
-
endpoint: queue.arn,
|
26
|
-
attributes: subscribe_attributes(event_type),
|
27
|
-
)
|
28
|
-
end
|
29
|
-
|
30
|
-
private
|
31
|
-
|
32
|
-
def subscribe_attributes(event_type)
|
33
|
-
attrs = { 'RawMessageDelivery' => true }
|
34
|
-
attrs['FilterPolicy'] = { 'event_type' => [event_type] } unless event_type == :any
|
35
|
-
attrs.transform_values(&:to_json)
|
36
|
-
end
|
37
|
-
end
|
38
|
-
end
|
39
|
-
end
|
40
|
-
end
|
@@ -1,114 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'active_support/core_ext/hash'
|
4
|
-
require 'aws-sdk-sqs'
|
5
|
-
|
6
|
-
module Realm
|
7
|
-
module SNS
|
8
|
-
class Gateway < Realm::EventRouter::Gateway
|
9
|
-
class Worker
|
10
|
-
def initialize(queue_map, event_factory:, event_processing_attempts: 3, logger: nil)
|
11
|
-
@queue_map = queue_map
|
12
|
-
@event_factory = event_factory
|
13
|
-
@event_processing_attempts = event_processing_attempts
|
14
|
-
@logger = logger || Logger.new($stdout)
|
15
|
-
@threads = []
|
16
|
-
end
|
17
|
-
|
18
|
-
def start(poller_options: {})
|
19
|
-
@signaler = { exiting: false }
|
20
|
-
@queue_map.each_pair do |queue, listener|
|
21
|
-
@threads << Thread.new { run_poller(queue, listener, @signaler, poller_options) }
|
22
|
-
end
|
23
|
-
self
|
24
|
-
end
|
25
|
-
|
26
|
-
def stop(timeout: 30)
|
27
|
-
Thread.new { @logger.info("Stopping worker (timeout: #{timeout}s)") }.join # Cannot log from trap context
|
28
|
-
@signaler[:exiting] = true
|
29
|
-
join(timeout)
|
30
|
-
@threads.clear
|
31
|
-
self
|
32
|
-
end
|
33
|
-
|
34
|
-
def join(timeout = nil)
|
35
|
-
@threads.each { |thread| thread.join(timeout) }
|
36
|
-
end
|
37
|
-
|
38
|
-
private
|
39
|
-
|
40
|
-
def run_poller(queue, listener, signaler, options)
|
41
|
-
@logger.info("Start polling #{queue.arn}")
|
42
|
-
init_poller(queue, signaler, options).poll do |messages, stats|
|
43
|
-
log_poller_stats(queue, stats)
|
44
|
-
messages.each { |msg| handle_message(listener, msg) }
|
45
|
-
end
|
46
|
-
@logger.info("Polling stopped #{queue.arn}")
|
47
|
-
end
|
48
|
-
|
49
|
-
def init_poller(queue, signaler, options = {})
|
50
|
-
Aws::SQS::QueuePoller.new(
|
51
|
-
queue.url,
|
52
|
-
max_number_of_messages: 10,
|
53
|
-
visibility_timeout: 60,
|
54
|
-
attribute_names: ['ApproximateReceiveCount'],
|
55
|
-
message_attribute_names: ['event_type'],
|
56
|
-
before_request: before_request_proc(queue, signaler),
|
57
|
-
**options,
|
58
|
-
)
|
59
|
-
end
|
60
|
-
|
61
|
-
def before_request_proc(queue, signaler)
|
62
|
-
proc {
|
63
|
-
if signaler[:exiting]
|
64
|
-
@logger.info("Stopping polling #{queue.arn}")
|
65
|
-
throw :stop_polling
|
66
|
-
end
|
67
|
-
}
|
68
|
-
end
|
69
|
-
|
70
|
-
def log_poller_stats(queue, stats)
|
71
|
-
@logger.info(
|
72
|
-
message: "Poller #{queue.arn} stats",
|
73
|
-
request_count: stats.request_count,
|
74
|
-
message_count: stats.received_message_count,
|
75
|
-
last_message_received_at: stats.last_message_received_at,
|
76
|
-
)
|
77
|
-
end
|
78
|
-
|
79
|
-
def handle_message(listener, msg)
|
80
|
-
event = message_to_event(msg)
|
81
|
-
listener.(event)
|
82
|
-
rescue StandardError => e
|
83
|
-
log_error(e, event, msg)
|
84
|
-
# Picks up the message again after visibility_timeout runs out:
|
85
|
-
throw :skip_delete if event && message_receive_count(msg) < @event_processing_attempts
|
86
|
-
end
|
87
|
-
|
88
|
-
def message_to_event(msg)
|
89
|
-
event_type = msg.message_attributes['event_type'].string_value
|
90
|
-
raise 'Message is missing event type' unless event_type
|
91
|
-
|
92
|
-
payload = JSON.parse(msg.body).deep_symbolize_keys
|
93
|
-
@event_factory.create_event(event_type, payload)
|
94
|
-
end
|
95
|
-
|
96
|
-
def message_receive_count(msg)
|
97
|
-
msg.attributes['ApproximateReceiveCount'].to_i
|
98
|
-
end
|
99
|
-
|
100
|
-
def log_error(error, event, msg)
|
101
|
-
return @logger.fatal("Unexpected message in queue: #{msg}; error: #{error.full_message}") unless event
|
102
|
-
|
103
|
-
attempt = message_receive_count(msg)
|
104
|
-
will_retry = attempt < @event_processing_attempts
|
105
|
-
log_line = [
|
106
|
-
"Processing of event failed type=#{event.type} id=#{event.head.id} attempt=#{attempt},",
|
107
|
-
"#{will_retry ? 'will retry' : 'final'}):\n#{error.full_message}",
|
108
|
-
].join(' ')
|
109
|
-
@logger.send(will_retry ? :warn : :error, log_line)
|
110
|
-
end
|
111
|
-
end
|
112
|
-
end
|
113
|
-
end
|
114
|
-
end
|
data/lib/realm/sns/plugin.rb
DELETED
@@ -1,14 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'realm/plugin'
|
4
|
-
require_relative 'gateway'
|
5
|
-
|
6
|
-
module Realm
|
7
|
-
module SNS
|
8
|
-
class Plugin < Realm::Plugin
|
9
|
-
def self.setup(_config, container)
|
10
|
-
container.register('event_router.gateway_classes.sns', SNS::Gateway)
|
11
|
-
end
|
12
|
-
end
|
13
|
-
end
|
14
|
-
end
|