realm-sns 0.7.1 → 0.7.3

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: 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