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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b1e7467d1e73e5d76157783241088571198c8932dbe51819bc82f4edb102e307
4
- data.tar.gz: fc5b0d42b8c40784688f0e583d81cf62631d0b191b0c0ae62515f7e726457f99
3
+ metadata.gz: dc34c9cede23ac937d62a7c1f496444a629427765d5bb3d7d4fc4f6eb8921507
4
+ data.tar.gz: 56571d1ffcff0d8c418de54f9bb3d3b99ef867e4c514d84af73694fc8c05f8d5
5
5
  SHA512:
6
- metadata.gz: e6815fe29645cd8ba4b8e1b997f71749c128a24247cfec45272c7a8542fd14a6bfd714db1e84b605585d636beee3c5c6bdf1787b402466186c43f348204767d5
7
- data.tar.gz: da3ac55bae5f9d469ca1c6443c65f195dd1415494bfd7ff1412557e3fd396ed11cb3eed1d8ff5855c9ddeb646df6ce8bef96c47d85e1498e3eec553c7979b8fe
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
+ [![Build status](https://badge.buildkite.com/346cce75f6c31e0a41bb98b198e85eb6b722243624459fad9c.svg)](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
+ ![Service layers](https://confluence-connect.gliffy.net/embed/image/d02d04b1-5e40-415f-b7ba-3a631efa9bf3.png?utm_medium=live&utm_source=custom)
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
+ ![Service external components](https://confluence-connect.gliffy.net/embed/image/c593fcc2-304e-47c3-8e3c-b0cc09e0ed54.png?utm_medium=live&utm_source=custom)
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.1
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-06-29 00:00:00.000000000 Z
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
- - lib/realm-sns.rb
104
- - lib/realm/sns/gateway.rb
105
- - lib/realm/sns/gateway/queue_adapter.rb
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.6
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
@@ -1,8 +0,0 @@
1
- # rubocop:disable Naming/FileName
2
- # frozen_string_literal: true
3
-
4
- Dir[File.join(File.dirname(__FILE__), 'realm', '**', '*.rb')].sort.each do |f|
5
- require f
6
- end
7
-
8
- # rubocop:enable Naming/FileName
@@ -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
@@ -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