cyclone_lariat 0.2.1 → 0.3.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/gem-push.yml +43 -0
- data/.gitignore +1 -1
- data/CHANGELOG.md +28 -2
- data/Gemfile.lock +5 -1
- data/README.md +114 -38
- data/cyclone_lariat.gemspec +2 -1
- data/db/migrate/02_add_events.rb +11 -10
- data/docs/_imgs/diagram.png +0 -0
- data/lib/cyclone_lariat.rb +2 -2
- data/lib/cyclone_lariat/abstract/client.rb +94 -0
- data/lib/cyclone_lariat/abstract/message.rb +84 -0
- data/lib/cyclone_lariat/command.rb +13 -0
- data/lib/cyclone_lariat/errors.rb +5 -1
- data/lib/cyclone_lariat/event.rb +2 -70
- data/lib/cyclone_lariat/messages_repo.rb +81 -0
- data/lib/cyclone_lariat/middleware.rb +21 -16
- data/lib/cyclone_lariat/sns_client.rb +41 -0
- data/lib/cyclone_lariat/sqs_client.rb +41 -0
- data/lib/cyclone_lariat/version.rb +1 -1
- metadata +29 -11
- data/config/db.rb +0 -9
- data/lib/cyclone_lariat/client.rb +0 -98
- data/lib/cyclone_lariat/events_repo.rb +0 -50
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0cebf79d1deb7a107446827a619897bf59364b89e8745fd767ed001bea3d6272
|
4
|
+
data.tar.gz: 6ff840519d31a46ab2f2ae6615176489041154213093052d5f924495dd537a20
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 45eab91fdef2c3c97d6638bf2ec0bf5d83dbaa54b18037a8245d25f2142b4582d60cd771c758f6d9300b2ed51977ce430864606f9155b2a235592470e92db539
|
7
|
+
data.tar.gz: 2f58eef796c7e0c52b6f3b8e4341466f7c8baf77ca3a6ff482e4435bd4aad73c04e1a667f39a11d820e774421c58b0519ce8401bdd1984ad872178a85d92a034
|
@@ -0,0 +1,43 @@
|
|
1
|
+
name: Ruby Gem
|
2
|
+
|
3
|
+
on:
|
4
|
+
push:
|
5
|
+
branches: [ master ]
|
6
|
+
|
7
|
+
jobs:
|
8
|
+
build:
|
9
|
+
name: Build + Publish
|
10
|
+
runs-on: ubuntu-latest
|
11
|
+
permissions:
|
12
|
+
contents: read
|
13
|
+
packages: write
|
14
|
+
|
15
|
+
steps:
|
16
|
+
- uses: actions/checkout@v2
|
17
|
+
- name: Set up Ruby 2.6
|
18
|
+
uses: actions/setup-ruby@v1
|
19
|
+
with:
|
20
|
+
ruby-version: 2.6.x
|
21
|
+
|
22
|
+
- name: Publish to GPR
|
23
|
+
run: |
|
24
|
+
mkdir -p $HOME/.gem
|
25
|
+
touch $HOME/.gem/credentials
|
26
|
+
chmod 0600 $HOME/.gem/credentials
|
27
|
+
printf -- "---\n:github: ${GEM_HOST_API_KEY}\n" > $HOME/.gem/credentials
|
28
|
+
gem build *.gemspec
|
29
|
+
gem push --KEY github --host https://rubygems.pkg.github.com/${OWNER} *.gem
|
30
|
+
env:
|
31
|
+
GEM_HOST_API_KEY: "Bearer ${{secrets.GITHUB_TOKEN}}"
|
32
|
+
OWNER: ${{ github.repository_owner }}
|
33
|
+
|
34
|
+
- name: Publish to RubyGems
|
35
|
+
run: |
|
36
|
+
mkdir -p $HOME/.gem
|
37
|
+
touch $HOME/.gem/credentials
|
38
|
+
chmod 0600 $HOME/.gem/credentials
|
39
|
+
printf -- "---\n:rubygems_api_key: ${GEM_HOST_API_KEY}\n" > $HOME/.gem/credentials
|
40
|
+
gem build *.gemspec
|
41
|
+
gem push *.gem
|
42
|
+
env:
|
43
|
+
GEM_HOST_API_KEY: "${{secrets.RUBYGEMS_AUTH_TOKEN}}"
|
data/.gitignore
CHANGED
data/CHANGELOG.md
CHANGED
@@ -4,11 +4,37 @@ All notable changes to this project will be documented in this file.
|
|
4
4
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
5
5
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
6
6
|
|
7
|
-
## [0.2
|
7
|
+
## [0.3.2] - 2021-06-11
|
8
8
|
Changed
|
9
|
+
- Bugfix
|
10
|
+
|
11
|
+
## [0.3.1] - 2021-06-11
|
12
|
+
Changed
|
13
|
+
- Command
|
14
|
+
- SqsClient
|
15
|
+
|
16
|
+
## [0.3.0] - 2021-06-09
|
17
|
+
Added
|
18
|
+
- Command
|
19
|
+
- SqsClient
|
20
|
+
|
21
|
+
Changed:
|
22
|
+
- Client renamed to SnsClient
|
23
|
+
- `to:` renamed to `topic:`
|
24
|
+
|
25
|
+
## [0.2.3] - 2021-06-09
|
26
|
+
Added
|
27
|
+
- Skip on empty message with error notify
|
28
|
+
|
29
|
+
## [0.2.2] - 2021-06-08
|
30
|
+
Changed
|
31
|
+
- Fix save to database
|
32
|
+
- Rename error to client error
|
33
|
+
|
34
|
+
## [0.2.1] - 2021-06-02
|
9
35
|
- Fix can load from database if error_details is nil
|
10
36
|
|
11
|
-
## [0.2.0] - 2021-06-
|
37
|
+
## [0.2.0] - 2021-06-02
|
12
38
|
Added
|
13
39
|
- Complete tests
|
14
40
|
- Production ready
|
data/Gemfile.lock
CHANGED
@@ -1,8 +1,9 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
cyclone_lariat (0.2
|
4
|
+
cyclone_lariat (0.3.2)
|
5
5
|
aws-sdk-sns
|
6
|
+
aws-sdk-sqs
|
6
7
|
luna_park (~> 0.11)
|
7
8
|
|
8
9
|
GEM
|
@@ -21,6 +22,9 @@ GEM
|
|
21
22
|
aws-sdk-sns (1.40.0)
|
22
23
|
aws-sdk-core (~> 3, >= 3.112.0)
|
23
24
|
aws-sigv4 (~> 1.1)
|
25
|
+
aws-sdk-sqs (1.39.0)
|
26
|
+
aws-sdk-core (~> 3, >= 3.112.0)
|
27
|
+
aws-sigv4 (~> 1.1)
|
24
28
|
aws-sigv4 (1.2.3)
|
25
29
|
aws-eventstream (~> 1, >= 1.0.2)
|
26
30
|
byebug (11.1.3)
|
data/README.md
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
This is gem work like middleware for [shoryuken](https://github.com/ruby-shoryuken/shoryuken). It save all events to database. And catch and produce all exceptions.
|
4
4
|
|
5
|
-
![
|
5
|
+
![Cyclone lariat](docs/_imgs/lariat.jpg)
|
6
6
|
|
7
7
|
|
8
8
|
```ruby
|
@@ -15,59 +15,86 @@ gem 'cyclone_lariat', require: false
|
|
15
15
|
gem 'cyclone_lariat'
|
16
16
|
```
|
17
17
|
|
18
|
+
## Logic
|
18
19
|
|
19
|
-
|
20
|
+
![diagram](docs/_imgs/diagram.png)
|
20
21
|
|
22
|
+
## Command vs Event
|
23
|
+
Commands and events are both simple domain structures that contain solely data for reading. That means they contain no
|
24
|
+
behaviour or business logic.
|
25
|
+
|
26
|
+
A command is an object that is sent to the domain for a state change which is handled by a command handler. They should
|
27
|
+
be named with a verb in an imperative mood plus the aggregate name which it operates on. Such request can be rejected
|
28
|
+
due to the data the command holds being invalid/inconsistent. There should be exactly 1 handler for each command.
|
29
|
+
Once the command has been executed, the consumer can then carry out whatever the task is depending on the output of the
|
30
|
+
command.
|
31
|
+
|
32
|
+
An event is a statement of fact about what change has been made to the domain state. They are named with the aggregate
|
33
|
+
name where the change took place plus the verb past-participle. An event happens off the back of a command.
|
34
|
+
A command can emit any number of events. The sender of the event does not care who receives it or whether it has been
|
35
|
+
received at all.
|
36
|
+
|
37
|
+
## SnsClient
|
21
38
|
You can use client directly
|
22
39
|
|
23
40
|
```ruby
|
24
|
-
require 'cyclone_lariat/
|
41
|
+
require 'cyclone_lariat/sns_client' # If require: false in Gemfile
|
25
42
|
|
26
|
-
client = CycloneLariat::
|
27
|
-
key:
|
43
|
+
client = CycloneLariat::SnsClient.new(
|
44
|
+
key: APP_CONF.aws.key,
|
28
45
|
secret_key: APP_CONF.aws.secret_key,
|
29
|
-
region:
|
30
|
-
version:
|
31
|
-
publisher:
|
32
|
-
instance:
|
46
|
+
region: APP_CONF.aws.region,
|
47
|
+
version: 1, # at default 1
|
48
|
+
publisher: 'pilot',
|
49
|
+
instance: INSTANCE # at default :prod
|
33
50
|
)
|
34
51
|
```
|
35
52
|
|
36
53
|
You can don't define topic, and it's name will be defined automatically
|
37
54
|
```ruby
|
38
55
|
# event_type data topic
|
39
|
-
client.publish_event
|
40
|
-
client.publish_event
|
56
|
+
client.publish_event 'email_is_created', data: { mail: 'john.doe@example.com' } # prod-event-fanout-pilot-email_is_created
|
57
|
+
client.publish_event 'email_is_removed', data: { mail: 'john.doe@example.com' } # prod-event-fanout-pilot-email_is_removed
|
58
|
+
client.publish_command 'delete_user', data: { mail: 'john.doe@example.com' } # prod-command-fanout-pilot-delete_user
|
41
59
|
```
|
42
60
|
Or you can define it by handle. For example, if you want to send different events to same channel.
|
43
61
|
```ruby
|
44
62
|
# event_type data topic
|
45
|
-
client.publish_event
|
46
|
-
client.publish_event
|
63
|
+
client.publish_event 'email_is_created', data: { mail: 'john.doe@example.com' }, topic: 'prod-event-fanout-pilot-emails'
|
64
|
+
client.publish_event 'email_is_removed', data: { mail: 'john.doe@example.com' }, topic: 'prod-event-fanout-pilot-emails'
|
65
|
+
client.publish_command 'delete_user', data: { mail: 'john.doe@example.com' }, topic: 'prod-command-fanout-pilot-emails'
|
47
66
|
```
|
48
67
|
|
49
68
|
Or you can use client as Repo.
|
50
69
|
|
51
70
|
```ruby
|
52
|
-
require 'cyclone_lariat/
|
71
|
+
require 'cyclone_lariat/sns_client' # If require: false in Gemfile
|
53
72
|
|
54
|
-
class YourClient < CycloneLariat::
|
55
|
-
version
|
73
|
+
class YourClient < CycloneLariat::SnsClient
|
74
|
+
version 1
|
56
75
|
publisher 'pilot'
|
57
|
-
instance
|
58
|
-
|
76
|
+
instance 'stage'
|
77
|
+
|
59
78
|
def email_is_created(mail)
|
60
|
-
publish event(
|
61
|
-
|
62
|
-
|
63
|
-
|
79
|
+
publish event('email_is_created',
|
80
|
+
data: { mail: mail }
|
81
|
+
),
|
82
|
+
to: APP_CONF.aws.fanout.emails
|
64
83
|
end
|
65
|
-
|
84
|
+
|
66
85
|
def email_is_removed(mail)
|
67
|
-
publish event(
|
68
|
-
|
69
|
-
|
70
|
-
|
86
|
+
publish event('email_is_removed',
|
87
|
+
data: { mail: mail }
|
88
|
+
),
|
89
|
+
to: APP_CONF.aws.fanout.email
|
90
|
+
end
|
91
|
+
|
92
|
+
|
93
|
+
def delete_user(mail)
|
94
|
+
publish command('delete_user',
|
95
|
+
data: { mail: mail }
|
96
|
+
),
|
97
|
+
to: APP_CONF.aws.fanout.email
|
71
98
|
end
|
72
99
|
end
|
73
100
|
|
@@ -77,8 +104,40 @@ client = YourClient.new(key: APP_CONF.aws.key, secret_key: APP_CONF.aws.secret_k
|
|
77
104
|
# And send topics
|
78
105
|
client.email_is_created 'john.doe@example.com'
|
79
106
|
client.email_is_removed 'john.doe@example.com'
|
107
|
+
client.delete_user 'john.doe@example.com'
|
108
|
+
```
|
109
|
+
|
110
|
+
|
111
|
+
# SqsClient
|
112
|
+
SqsClient is really similar to SnsClient. It can be initialized in same way:
|
113
|
+
|
114
|
+
```ruby
|
115
|
+
require 'cyclone_lariat/sns_client' # If require: false in Gemfile
|
116
|
+
|
117
|
+
client = CycloneLariat::SqsClient.new(
|
118
|
+
key: APP_CONF.aws.key,
|
119
|
+
secret_key: APP_CONF.aws.secret_key,
|
120
|
+
region: APP_CONF.aws.region,
|
121
|
+
version: 1, # at default 1
|
122
|
+
publisher: 'pilot',
|
123
|
+
instance: INSTANCE # at default :prod
|
124
|
+
)
|
80
125
|
```
|
81
126
|
|
127
|
+
As you see all params identity. And you can easily change your sqs-queue to sns-topic when you start work with more
|
128
|
+
subscribes. But you should define destination.
|
129
|
+
|
130
|
+
```ruby
|
131
|
+
client.publish_event 'email_is_created', data: { mail: 'john.doe@example.com' }, dest: 'notify_service'
|
132
|
+
# prod-event-queue-pilot-email_is_created-notify_service
|
133
|
+
```
|
134
|
+
|
135
|
+
Or you can define topic directly:
|
136
|
+
```ruby
|
137
|
+
client.publish_event 'email_is_created', data: { mail: 'john.doe@example.com' }, topic: 'prod-event-fanout-pilot-emails'
|
138
|
+
```
|
139
|
+
|
140
|
+
|
82
141
|
# Middleware
|
83
142
|
If you use middleware:
|
84
143
|
- Store all events to dataset
|
@@ -100,7 +159,6 @@ class Receiver
|
|
100
159
|
queue: 'your_sqs_queue_name'
|
101
160
|
|
102
161
|
server_middleware do |chain|
|
103
|
-
|
104
162
|
# Options dataset, errors_notifier and message_notifier is optionals.
|
105
163
|
# If you dont define notifiers - middleware does not notify
|
106
164
|
# If you dont define dataset - middleware does store events in db
|
@@ -140,18 +198,36 @@ end
|
|
140
198
|
# The second one:
|
141
199
|
Sequel.migration do
|
142
200
|
change do
|
143
|
-
create_table :
|
201
|
+
create_table :async_messages do
|
144
202
|
column :uuid, :uuid, primary_key: true
|
145
|
-
String :type,
|
146
|
-
Integer :version,
|
147
|
-
String :publisher,
|
148
|
-
column :data, :json,
|
149
|
-
String :
|
150
|
-
column :
|
151
|
-
DateTime :sent_at,
|
152
|
-
DateTime :received_at,
|
153
|
-
DateTime :processed_at,
|
203
|
+
String :type, null: false
|
204
|
+
Integer :version, null: false
|
205
|
+
String :publisher, null: false
|
206
|
+
column :data, :json, null: false
|
207
|
+
String :client_error_message, null: true, default: nil
|
208
|
+
column :client_error_details, :json, null: true, default: nil
|
209
|
+
DateTime :sent_at, null: true, default: nil
|
210
|
+
DateTime :received_at, null: false, default: Sequel::CURRENT_TIMESTAMP
|
211
|
+
DateTime :processed_at, null: true, default: nil
|
154
212
|
end
|
155
213
|
end
|
156
214
|
end
|
157
215
|
```
|
216
|
+
|
217
|
+
### Rake tasks
|
218
|
+
|
219
|
+
For simplify write some Rake tasks you can use CycloneLariat::Repo.
|
220
|
+
|
221
|
+
```ruby
|
222
|
+
# For retry all unprocessed
|
223
|
+
|
224
|
+
CycloneLariat.new(DB[:events]).each_unprocessed do |event|
|
225
|
+
# Your logic here
|
226
|
+
end
|
227
|
+
|
228
|
+
# For retry all events with client errors
|
229
|
+
|
230
|
+
CycloneLariat.new(DB[:events]).each_with_client_errors do |event|
|
231
|
+
# Your logic here
|
232
|
+
end
|
233
|
+
```
|
data/cyclone_lariat.gemspec
CHANGED
@@ -18,7 +18,7 @@ Gem::Specification.new do |spec|
|
|
18
18
|
# Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
|
19
19
|
# to allow pushing to a single host or delete this section to allow pushing to any host.
|
20
20
|
if spec.respond_to?(:metadata)
|
21
|
-
spec.metadata['allowed_push_host'] = 'https://rubygems.org'
|
21
|
+
# spec.metadata['allowed_push_host'] = 'https://rubygems.org'
|
22
22
|
else
|
23
23
|
raise 'RubyGems 2.0 or newer is required to protect against ' \
|
24
24
|
'public gem pushes.'
|
@@ -33,6 +33,7 @@ Gem::Specification.new do |spec|
|
|
33
33
|
spec.require_paths = ['lib']
|
34
34
|
|
35
35
|
spec.add_dependency 'aws-sdk-sns'
|
36
|
+
spec.add_dependency 'aws-sdk-sqs'
|
36
37
|
spec.add_dependency 'luna_park', '~> 0.11'
|
37
38
|
|
38
39
|
spec.add_development_dependency 'bundler', '~> 2.1'
|
data/db/migrate/02_add_events.rb
CHANGED
@@ -2,17 +2,18 @@
|
|
2
2
|
|
3
3
|
Sequel.migration do
|
4
4
|
change do
|
5
|
-
create_table :
|
5
|
+
create_table :async_messages do
|
6
6
|
column :uuid, :uuid, primary_key: true
|
7
|
-
String :
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
DateTime :
|
15
|
-
DateTime :
|
7
|
+
String :kind, null: false
|
8
|
+
String :type, null: false
|
9
|
+
Integer :version, null: false
|
10
|
+
String :publisher, null: false
|
11
|
+
column :data, :json, null: false
|
12
|
+
String :client_error_message, null: true, default: nil
|
13
|
+
column :client_error_details, :json, null: true, default: nil
|
14
|
+
DateTime :sent_at, null: true, default: nil
|
15
|
+
DateTime :received_at, null: false, default: Sequel::CURRENT_TIMESTAMP
|
16
|
+
DateTime :processed_at, null: true, default: nil
|
16
17
|
end
|
17
18
|
end
|
18
19
|
end
|
Binary file
|
data/lib/cyclone_lariat.rb
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative 'cyclone_lariat/
|
3
|
+
require_relative 'cyclone_lariat/sns_client'
|
4
4
|
require_relative 'cyclone_lariat/errors'
|
5
5
|
require_relative 'cyclone_lariat/event'
|
6
|
-
require_relative 'cyclone_lariat/
|
6
|
+
require_relative 'cyclone_lariat/messages_repo'
|
7
7
|
require_relative 'cyclone_lariat/middleware'
|
8
8
|
require_relative 'cyclone_lariat/version'
|
9
9
|
|
@@ -0,0 +1,94 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# require 'aws-sdk-sns'
|
4
|
+
require 'luna_park/extensions/injector'
|
5
|
+
require_relative '../event'
|
6
|
+
require_relative '../command'
|
7
|
+
require_relative '../errors'
|
8
|
+
|
9
|
+
module CycloneLariat
|
10
|
+
module Abstract
|
11
|
+
class Client
|
12
|
+
include LunaPark::Extensions::Injector
|
13
|
+
|
14
|
+
dependency(:aws_client_class) { raise ArgumentError, 'Client class should be defined' }
|
15
|
+
dependency(:aws_credentials_class) { Aws::Credentials }
|
16
|
+
|
17
|
+
DEFAULT_VERSION = 1
|
18
|
+
DEFAULT_INSTANCE = :prod
|
19
|
+
|
20
|
+
def initialize(key:, secret_key:, region:, version: nil, publisher: nil, instance: nil)
|
21
|
+
@key = key
|
22
|
+
@secret_key = secret_key
|
23
|
+
@region = region
|
24
|
+
@version = version
|
25
|
+
@publisher = publisher
|
26
|
+
@instance = instance
|
27
|
+
end
|
28
|
+
|
29
|
+
def event(type, data: {}, version: self.version, uuid: SecureRandom.uuid)
|
30
|
+
Event.wrap(
|
31
|
+
uuid: uuid,
|
32
|
+
type: type,
|
33
|
+
sent_at: Time.now.iso8601,
|
34
|
+
version: version,
|
35
|
+
publisher: publisher,
|
36
|
+
data: data
|
37
|
+
)
|
38
|
+
end
|
39
|
+
|
40
|
+
def command(type, data: {}, version: self.version, uuid: SecureRandom.uuid)
|
41
|
+
Command.wrap(
|
42
|
+
uuid: uuid,
|
43
|
+
type: type,
|
44
|
+
sent_at: Time.now.iso8601,
|
45
|
+
version: version,
|
46
|
+
publisher: publisher,
|
47
|
+
data: data
|
48
|
+
)
|
49
|
+
end
|
50
|
+
|
51
|
+
def publish
|
52
|
+
raise LunaPark::Errors::AbstractMethod, 'Publish method should be defined'
|
53
|
+
end
|
54
|
+
|
55
|
+
class << self
|
56
|
+
def version(version = nil)
|
57
|
+
version.nil? ? @version || DEFAULT_VERSION : @version = version
|
58
|
+
end
|
59
|
+
|
60
|
+
def instance(instance = nil)
|
61
|
+
instance.nil? ? @instance || DEFAULT_INSTANCE : @instance = instance
|
62
|
+
end
|
63
|
+
|
64
|
+
def publisher(publisher = nil)
|
65
|
+
publisher.nil? ? @publisher || (raise 'You should define publisher') : @publisher = publisher
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def version
|
70
|
+
@version ||= self.class.version
|
71
|
+
end
|
72
|
+
|
73
|
+
def publisher
|
74
|
+
@publisher ||= self.class.publisher
|
75
|
+
end
|
76
|
+
|
77
|
+
def instance
|
78
|
+
@instance ||= self.class.instance
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
82
|
+
|
83
|
+
attr_reader :key, :secret_key, :region
|
84
|
+
|
85
|
+
def aws_client
|
86
|
+
@aws_client ||= aws_client_class.new(credentials: aws_credentials, region: region)
|
87
|
+
end
|
88
|
+
|
89
|
+
def aws_credentials
|
90
|
+
@aws_credentials ||= aws_credentials_class.new(key, secret_key)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'luna_park/entities/attributable'
|
4
|
+
require_relative '../errors'
|
5
|
+
|
6
|
+
module CycloneLariat
|
7
|
+
module Abstract
|
8
|
+
class Message < LunaPark::Entities::Attributable
|
9
|
+
attr :uuid, String, :new
|
10
|
+
attr :publisher, String, :new
|
11
|
+
attr :type, String, :new
|
12
|
+
attrs :client_error, :version, :data,
|
13
|
+
:sent_at, :processed_at, :received_at
|
14
|
+
|
15
|
+
def kind
|
16
|
+
raise LunaPark::Errors::AbstractMethod
|
17
|
+
end
|
18
|
+
|
19
|
+
def version=(value)
|
20
|
+
@version = Integer(value)
|
21
|
+
end
|
22
|
+
|
23
|
+
def sent_at=(value)
|
24
|
+
@sent_at = wrap_time(value)
|
25
|
+
end
|
26
|
+
|
27
|
+
def received_at=(value)
|
28
|
+
@received_at = wrap_time(value)
|
29
|
+
end
|
30
|
+
|
31
|
+
def processed_at=(value)
|
32
|
+
@processed_at = wrap_time(value)
|
33
|
+
end
|
34
|
+
|
35
|
+
def processed?
|
36
|
+
!@processed_at.nil?
|
37
|
+
end
|
38
|
+
|
39
|
+
def client_error_message=(txt)
|
40
|
+
return unless txt
|
41
|
+
|
42
|
+
@client_error ||= Errors::ClientError.new
|
43
|
+
@client_error.message = txt
|
44
|
+
end
|
45
|
+
|
46
|
+
def client_error_details=(details)
|
47
|
+
return unless details
|
48
|
+
|
49
|
+
@client_error ||= Errors::ClientError.new
|
50
|
+
@client_error.details = details
|
51
|
+
end
|
52
|
+
|
53
|
+
def ==(other)
|
54
|
+
kind == other.kind &&
|
55
|
+
uuid == other.uuid &&
|
56
|
+
publisher == other.publisher &&
|
57
|
+
type == other.type &&
|
58
|
+
client_error&.message == other.client_error&.message &&
|
59
|
+
client_error&.details == other.client_error&.details &&
|
60
|
+
version == other.version &&
|
61
|
+
sent_at.to_i == other.sent_at.to_i &&
|
62
|
+
received_at.to_i == other.received_at.to_i
|
63
|
+
processed_at.to_i == other.processed_at.to_i
|
64
|
+
end
|
65
|
+
|
66
|
+
def to_json(*args)
|
67
|
+
hash = serialize
|
68
|
+
hash[:type] = [kind, hash[:type]].join '_'
|
69
|
+
hash.to_json(*args)
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
def wrap_time(value)
|
75
|
+
case value
|
76
|
+
when String then Time.parse(value)
|
77
|
+
when Time then value
|
78
|
+
when NilClass then nil
|
79
|
+
else raise ArgumentError, "Unknown type `#{value.class}`"
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -5,11 +5,15 @@ require 'luna_park/errors/business'
|
|
5
5
|
|
6
6
|
module CycloneLariat
|
7
7
|
module Errors
|
8
|
+
class EmptyMessage < LunaPark::Errors::System
|
9
|
+
message 'Received message is empty'
|
10
|
+
end
|
11
|
+
|
8
12
|
class TopicNotFound < LunaPark::Errors::System
|
9
13
|
message { |d| "Could not found topic: `#{d[:expected_topic]}`" }
|
10
14
|
end
|
11
15
|
|
12
|
-
class
|
16
|
+
class ClientError < LunaPark::Errors::Business
|
13
17
|
attr_writer :message, :details
|
14
18
|
|
15
19
|
def ==(other)
|
data/lib/cyclone_lariat/event.rb
CHANGED
@@ -1,81 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
require_relative 'errors'
|
3
|
+
require_relative 'abstract/message'
|
5
4
|
|
6
5
|
module CycloneLariat
|
7
|
-
class Event <
|
6
|
+
class Event < Abstract::Message
|
8
7
|
KIND = 'event'
|
9
8
|
|
10
|
-
attr :uuid, String, :new
|
11
|
-
attr :publisher, String, :new
|
12
|
-
attr :type, String, :new
|
13
|
-
attr :error
|
14
|
-
attr :version
|
15
|
-
attr :data
|
16
|
-
|
17
|
-
attr_reader :sent_at,
|
18
|
-
:processed_at,
|
19
|
-
:received_at
|
20
|
-
|
21
9
|
def kind
|
22
10
|
KIND
|
23
11
|
end
|
24
|
-
|
25
|
-
def version=(value)
|
26
|
-
@version = Integer(value)
|
27
|
-
end
|
28
|
-
|
29
|
-
def sent_at=(value)
|
30
|
-
@sent_at = wrap_time(value)
|
31
|
-
end
|
32
|
-
|
33
|
-
def received_at=(value)
|
34
|
-
@received_at = wrap_time(value)
|
35
|
-
end
|
36
|
-
|
37
|
-
def processed_at=(value)
|
38
|
-
@processed_at = wrap_time(value)
|
39
|
-
end
|
40
|
-
|
41
|
-
def error_message=(txt)
|
42
|
-
@error ||= Errors::ProcessingEventLogic.new
|
43
|
-
@error.message = txt
|
44
|
-
end
|
45
|
-
|
46
|
-
def error_details=(details)
|
47
|
-
@error ||= Errors::ProcessingEventLogic.new
|
48
|
-
@error.details = details
|
49
|
-
end
|
50
|
-
|
51
|
-
def ==(other)
|
52
|
-
kind == other.kind &&
|
53
|
-
uuid == other.uuid &&
|
54
|
-
publisher == other.publisher &&
|
55
|
-
type == other.type &&
|
56
|
-
error&.message == other.error&.message &&
|
57
|
-
error&.details == other.error&.details &&
|
58
|
-
version == other.version &&
|
59
|
-
sent_at.to_i == other.sent_at.to_i &&
|
60
|
-
received_at.to_i == other.received_at.to_i
|
61
|
-
processed_at.to_i == other.processed_at.to_i
|
62
|
-
end
|
63
|
-
|
64
|
-
def to_json(*args)
|
65
|
-
hash = serialize
|
66
|
-
hash[:type] = [kind, hash[:type]].join '_'
|
67
|
-
hash.to_json(*args)
|
68
|
-
end
|
69
|
-
|
70
|
-
private
|
71
|
-
|
72
|
-
def wrap_time(value)
|
73
|
-
case value
|
74
|
-
when String then Time.parse(value)
|
75
|
-
when Time then value
|
76
|
-
when NilClass then nil
|
77
|
-
else raise ArgumentError, "Unknown type `#{value.class}`"
|
78
|
-
end
|
79
|
-
end
|
80
12
|
end
|
81
13
|
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'event'
|
4
|
+
|
5
|
+
module CycloneLariat
|
6
|
+
class MessagesRepo
|
7
|
+
attr_reader :dataset
|
8
|
+
|
9
|
+
def initialize(dataset)
|
10
|
+
@dataset = dataset
|
11
|
+
end
|
12
|
+
|
13
|
+
def create(msg)
|
14
|
+
dataset.insert(
|
15
|
+
uuid: msg.uuid,
|
16
|
+
kind: msg.kind,
|
17
|
+
type: msg.type,
|
18
|
+
publisher: msg.publisher,
|
19
|
+
data: JSON.generate(msg.data),
|
20
|
+
client_error_message: msg.client_error&.message,
|
21
|
+
client_error_details: JSON.generate(msg.client_error&.details),
|
22
|
+
version: msg.version,
|
23
|
+
sent_at: msg.sent_at
|
24
|
+
)
|
25
|
+
end
|
26
|
+
|
27
|
+
def exists?(uuid:)
|
28
|
+
dataset.where(uuid: uuid).limit(1).any?
|
29
|
+
end
|
30
|
+
|
31
|
+
def processed!(uuid:, error: nil)
|
32
|
+
data = { processed_at: Sequel.function(:NOW) }
|
33
|
+
data.merge!(client_error_message: error.message, client_error_details: JSON.generate(error.details)) if error
|
34
|
+
|
35
|
+
!dataset.where(uuid: uuid).update(data).zero?
|
36
|
+
end
|
37
|
+
|
38
|
+
def find(uuid:)
|
39
|
+
raw = dataset.where(uuid: uuid).first
|
40
|
+
return nil unless raw
|
41
|
+
|
42
|
+
raw[:data] = JSON.parse(raw[:data], symbolize_names: true)
|
43
|
+
if raw[:client_error_details]
|
44
|
+
raw[:client_error_details] = JSON.parse(raw[:client_error_details], symbolize_names: true)
|
45
|
+
end
|
46
|
+
build raw
|
47
|
+
end
|
48
|
+
|
49
|
+
def each_unprocessed
|
50
|
+
dataset.where(processed_at: nil).each do |raw|
|
51
|
+
raw[:data] = JSON.parse(raw[:data], symbolize_names: true)
|
52
|
+
if raw[:client_error_details]
|
53
|
+
raw[:client_error_details] = JSON.parse(raw[:client_error_details], symbolize_names: true)
|
54
|
+
end
|
55
|
+
msg = build raw
|
56
|
+
yield(msg)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def each_with_client_errors
|
61
|
+
dataset.where { (processed_at !~ nil) & (client_error_message !~ nil) }.each do |raw|
|
62
|
+
raw[:data] = JSON.parse(raw[:data], symbolize_names: true)
|
63
|
+
if raw[:client_error_details]
|
64
|
+
raw[:client_error_details] = JSON.parse(raw[:client_error_details], symbolize_names: true)
|
65
|
+
end
|
66
|
+
msg = build raw
|
67
|
+
yield(msg)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
def build(raw)
|
74
|
+
case kind = raw.delete(:kind)
|
75
|
+
when 'event' then Event.wrap raw
|
76
|
+
when 'command' then Command.wrap raw
|
77
|
+
else raise ArgumentError, "Unknown kind `#{kind}` of message"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -1,25 +1,27 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative '
|
3
|
+
require_relative 'messages_repo'
|
4
4
|
require 'luna_park/errors'
|
5
5
|
require 'json'
|
6
6
|
|
7
7
|
module CycloneLariat
|
8
8
|
class Middleware
|
9
|
-
def initialize(dataset: nil, errors_notifier: nil, message_notifier: nil, repo:
|
9
|
+
def initialize(dataset: nil, errors_notifier: nil, message_notifier: nil, repo: MessagesRepo)
|
10
10
|
@events_repo = repo.new(dataset) if dataset
|
11
11
|
@message_notifier = message_notifier
|
12
12
|
@errors_notifier = errors_notifier
|
13
13
|
end
|
14
14
|
|
15
15
|
def call(_worker_instance, queue, _sqs_msg, body, &block)
|
16
|
-
|
16
|
+
msg = receive_message(body)
|
17
17
|
|
18
|
-
|
19
|
-
event = Event.wrap(JSON.parse(body[:Message]))
|
18
|
+
message_notifier&.info 'Receive message', message: msg, queue: queue
|
20
19
|
|
21
|
-
|
22
|
-
|
20
|
+
catch_standard_error(queue, msg) do
|
21
|
+
event = Event.wrap(msg)
|
22
|
+
|
23
|
+
store_in_dataset(event) do
|
24
|
+
catch_business_error(event, &block)
|
23
25
|
end
|
24
26
|
end
|
25
27
|
end
|
@@ -28,29 +30,32 @@ module CycloneLariat
|
|
28
30
|
|
29
31
|
attr_reader :errors_notifier, :message_notifier, :events_repo
|
30
32
|
|
31
|
-
def
|
32
|
-
|
33
|
+
def receive_message(body)
|
34
|
+
body[:Message] ? JSON.parse(body[:Message], symbolize_names: true ) : body
|
33
35
|
end
|
34
36
|
|
35
37
|
def store_in_dataset(event)
|
36
38
|
return yield if events_repo.nil?
|
37
|
-
return true if events_repo.exists?(uuid: event.uuid)
|
38
39
|
|
39
|
-
events_repo.
|
40
|
+
existed = events_repo.find(uuid: event.uuid)
|
41
|
+
return true if existed&.processed?
|
42
|
+
|
43
|
+
events_repo.create(event) unless existed
|
40
44
|
yield
|
41
|
-
events_repo.processed! uuid: event.uuid
|
45
|
+
events_repo.processed! uuid: event.uuid, error: event.client_error
|
42
46
|
end
|
43
47
|
|
44
48
|
def catch_business_error(event)
|
45
49
|
yield
|
46
50
|
rescue LunaPark::Errors::Business => e
|
47
|
-
errors_notifier&.
|
51
|
+
errors_notifier&.error(e, event: event)
|
52
|
+
event.client_error = e
|
48
53
|
end
|
49
54
|
|
50
|
-
def catch_standard_error(queue,
|
55
|
+
def catch_standard_error(queue, msg)
|
51
56
|
yield
|
52
|
-
rescue
|
53
|
-
errors_notifier&.error(e, queue: queue,
|
57
|
+
rescue Exception => e
|
58
|
+
errors_notifier&.error(e, queue: queue, message: msg)
|
54
59
|
raise e
|
55
60
|
end
|
56
61
|
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'aws-sdk-sns'
|
4
|
+
require_relative 'abstract/client'
|
5
|
+
|
6
|
+
module CycloneLariat
|
7
|
+
class SnsClient < Abstract::Client
|
8
|
+
include LunaPark::Extensions::Injector
|
9
|
+
|
10
|
+
dependency(:aws_client_class) { Aws::SNS::Client }
|
11
|
+
|
12
|
+
SNS_SUFFIX = :fanout
|
13
|
+
|
14
|
+
def publish(msg, topic: nil)
|
15
|
+
topic ||= [instance, msg.kind, SNS_SUFFIX, publisher, msg.type].join('-')
|
16
|
+
|
17
|
+
aws_client.publish(
|
18
|
+
topic_arn: topic_arn(topic),
|
19
|
+
message: msg.to_json
|
20
|
+
)
|
21
|
+
end
|
22
|
+
|
23
|
+
def publish_event(type, data: {}, version: self.version, uuid: SecureRandom.uuid, topic: nil)
|
24
|
+
publish event(type, data: data, version: version, uuid: uuid), topic: topic
|
25
|
+
end
|
26
|
+
|
27
|
+
def publish_command(type, data: {}, version: self.version, uuid: SecureRandom.uuid, topic: nil)
|
28
|
+
publish command(type, data: data, version: version, uuid: uuid), topic: topic
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def topic_arn(topic_name)
|
34
|
+
list = aws_client.list_topics.topics
|
35
|
+
topic = list.find { |t| t.topic_arn.match?(topic_name) }
|
36
|
+
raise Errors::TopicNotFound.new(expected_topic: topic_name, existed_topics: list.map(&:topic_arn)) if topic.nil?
|
37
|
+
|
38
|
+
topic.topic_arn
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'aws-sdk-sqs'
|
4
|
+
require_relative 'abstract/client'
|
5
|
+
|
6
|
+
module CycloneLariat
|
7
|
+
class SqsClient < Abstract::Client
|
8
|
+
include LunaPark::Extensions::Injector
|
9
|
+
|
10
|
+
dependency(:aws_client_class) { Aws::SQS::Client }
|
11
|
+
|
12
|
+
SQS_SUFFIX = :queue
|
13
|
+
|
14
|
+
def publish(msg, dest: nil, topic: nil)
|
15
|
+
raise ArgumentError, 'You should define dest or topic' if dest.nil? && topic.nil?
|
16
|
+
|
17
|
+
topic ||= [instance, msg.kind, SQS_SUFFIX, publisher, msg.type, dest].join('-')
|
18
|
+
|
19
|
+
aws_client.send_message(
|
20
|
+
queue_url: url(topic),
|
21
|
+
message_body: msg.to_json
|
22
|
+
)
|
23
|
+
end
|
24
|
+
|
25
|
+
def publish_event(type, dest: nil, data: {}, version: self.version, uuid: SecureRandom.uuid, topic: nil)
|
26
|
+
publish event(type, data: data, version: version, uuid: uuid), dest: dest, topic: topic
|
27
|
+
end
|
28
|
+
|
29
|
+
def publish_command(type, dest: nil, data: {}, version: self.version, uuid: SecureRandom.uuid, topic: nil)
|
30
|
+
publish command(type, data: data, version: version, uuid: uuid), dest: dest, topic: topic
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def url(topic_name)
|
36
|
+
aws_client.get_queue_url(queue_name: topic_name).queue_url
|
37
|
+
rescue Aws::SQS::Errors::NonExistentQueue => _e
|
38
|
+
raise Errors::TopicNotFound.new(expected_topic: topic_name, existed_topics: aws_client.list_queues)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
metadata
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cyclone_lariat
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2
|
4
|
+
version: 0.3.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Alexander Kudrin
|
8
8
|
- Philip Sorokin
|
9
|
-
autorequire:
|
9
|
+
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2021-06-
|
12
|
+
date: 2021-06-11 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: aws-sdk-sns
|
@@ -25,6 +25,20 @@ dependencies:
|
|
25
25
|
- - ">="
|
26
26
|
- !ruby/object:Gem::Version
|
27
27
|
version: '0'
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: aws-sdk-sqs
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - ">="
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: '0'
|
35
|
+
type: :runtime
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - ">="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '0'
|
28
42
|
- !ruby/object:Gem::Dependency
|
29
43
|
name: luna_park
|
30
44
|
requirement: !ruby/object:Gem::Requirement
|
@@ -235,13 +249,14 @@ dependencies:
|
|
235
249
|
- - "~>"
|
236
250
|
- !ruby/object:Gem::Version
|
237
251
|
version: '0.9'
|
238
|
-
description:
|
252
|
+
description:
|
239
253
|
email:
|
240
254
|
- kudrin.alexander@gmail.com
|
241
255
|
executables: []
|
242
256
|
extensions: []
|
243
257
|
extra_rdoc_files: []
|
244
258
|
files:
|
259
|
+
- ".github/workflows/gem-push.yml"
|
245
260
|
- ".gitignore"
|
246
261
|
- ".rspec"
|
247
262
|
- ".rubocop.yml"
|
@@ -253,26 +268,29 @@ files:
|
|
253
268
|
- README.md
|
254
269
|
- Rakefile
|
255
270
|
- config/db.example.rb
|
256
|
-
- config/db.rb
|
257
271
|
- cyclone_lariat.gemspec
|
258
272
|
- db/migrate/01_add_uuid_extensions.rb
|
259
273
|
- db/migrate/02_add_events.rb
|
274
|
+
- docs/_imgs/diagram.png
|
260
275
|
- docs/_imgs/lariat.jpg
|
261
276
|
- lib/cyclone_lariat.rb
|
262
|
-
- lib/cyclone_lariat/client.rb
|
277
|
+
- lib/cyclone_lariat/abstract/client.rb
|
278
|
+
- lib/cyclone_lariat/abstract/message.rb
|
279
|
+
- lib/cyclone_lariat/command.rb
|
263
280
|
- lib/cyclone_lariat/errors.rb
|
264
281
|
- lib/cyclone_lariat/event.rb
|
265
|
-
- lib/cyclone_lariat/
|
282
|
+
- lib/cyclone_lariat/messages_repo.rb
|
266
283
|
- lib/cyclone_lariat/middleware.rb
|
284
|
+
- lib/cyclone_lariat/sns_client.rb
|
285
|
+
- lib/cyclone_lariat/sqs_client.rb
|
267
286
|
- lib/cyclone_lariat/version.rb
|
268
287
|
- lib/tasks/db.rake
|
269
288
|
homepage: https://am-team.github.io/cyclone_lariat/#/
|
270
289
|
licenses:
|
271
290
|
- MIT
|
272
291
|
metadata:
|
273
|
-
allowed_push_host: https://rubygems.org
|
274
292
|
yard.run: yri
|
275
|
-
post_install_message:
|
293
|
+
post_install_message:
|
276
294
|
rdoc_options: []
|
277
295
|
require_paths:
|
278
296
|
- lib
|
@@ -287,8 +305,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
287
305
|
- !ruby/object:Gem::Version
|
288
306
|
version: '0'
|
289
307
|
requirements: []
|
290
|
-
rubygems_version: 3.0.
|
291
|
-
signing_key:
|
308
|
+
rubygems_version: 3.0.3.1
|
309
|
+
signing_key:
|
292
310
|
specification_version: 4
|
293
311
|
summary: Shoryuken middleware for LunaPark based application.
|
294
312
|
test_files: []
|
data/config/db.rb
DELETED
@@ -1,98 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'aws-sdk-sns'
|
4
|
-
require 'luna_park/extensions/injector'
|
5
|
-
require_relative 'event'
|
6
|
-
require_relative 'errors'
|
7
|
-
|
8
|
-
module CycloneLariat
|
9
|
-
class Client
|
10
|
-
include LunaPark::Extensions::Injector
|
11
|
-
|
12
|
-
dependency(:aws_sns_client_class) { Aws::SNS::Client }
|
13
|
-
dependency(:aws_credentials_class) { Aws::Credentials }
|
14
|
-
|
15
|
-
DEFAULT_VERSION = 1
|
16
|
-
DEFAULT_INSTANCE = :prod
|
17
|
-
SNS_SUFFIX = :fanout
|
18
|
-
|
19
|
-
def initialize(key:, secret_key:, region:, version: nil, publisher: nil, instance: nil)
|
20
|
-
@key = key
|
21
|
-
@secret_key = secret_key
|
22
|
-
@region = region
|
23
|
-
@version = version
|
24
|
-
@publisher = publisher
|
25
|
-
@instance = instance
|
26
|
-
end
|
27
|
-
|
28
|
-
def event(type, data: {}, version: self.version, uuid: SecureRandom.uuid)
|
29
|
-
Event.wrap(
|
30
|
-
uuid: uuid,
|
31
|
-
type: type,
|
32
|
-
sent_at: Time.now.iso8601,
|
33
|
-
version: version,
|
34
|
-
publisher: publisher,
|
35
|
-
data: data
|
36
|
-
)
|
37
|
-
end
|
38
|
-
|
39
|
-
def publish(msg, to: nil)
|
40
|
-
topic = to || [instance, msg.kind, SNS_SUFFIX, publisher, msg.type].join('-')
|
41
|
-
|
42
|
-
aws_client.publish(
|
43
|
-
topic_arn: topic_arn(topic),
|
44
|
-
message: msg.to_json
|
45
|
-
)
|
46
|
-
end
|
47
|
-
|
48
|
-
def publish_event(type, data: {}, version: self.version, uuid: SecureRandom.uuid, to: nil)
|
49
|
-
publish event(type, data: data, version: version, uuid: uuid), to: to
|
50
|
-
end
|
51
|
-
|
52
|
-
class << self
|
53
|
-
def version(version = nil)
|
54
|
-
version.nil? ? @version || DEFAULT_VERSION : @version = version
|
55
|
-
end
|
56
|
-
|
57
|
-
def instance(instance = nil)
|
58
|
-
instance.nil? ? @instance || DEFAULT_INSTANCE : @instance = instance
|
59
|
-
end
|
60
|
-
|
61
|
-
def publisher(publisher = nil)
|
62
|
-
publisher.nil? ? @publisher || (raise 'You should define publisher') : @publisher = publisher
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
def version
|
67
|
-
@version ||= self.class.version
|
68
|
-
end
|
69
|
-
|
70
|
-
def publisher
|
71
|
-
@publisher ||= self.class.publisher
|
72
|
-
end
|
73
|
-
|
74
|
-
def instance
|
75
|
-
@instance ||= self.class.instance
|
76
|
-
end
|
77
|
-
|
78
|
-
private
|
79
|
-
|
80
|
-
attr_reader :key, :secret_key, :region
|
81
|
-
|
82
|
-
def topic_arn(topic_name)
|
83
|
-
list = aws_client.list_topics.topics
|
84
|
-
topic = list.find { |t| t.topic_arn.match?(topic_name) }
|
85
|
-
raise Errors::TopicNotFound.new(expected_topic: topic_name, existed_topics: list.map(&:topic_arn)) if topic.nil?
|
86
|
-
|
87
|
-
topic.topic_arn
|
88
|
-
end
|
89
|
-
|
90
|
-
def aws_client
|
91
|
-
@aws_client ||= aws_sns_client_class.new(credentials: aws_credentials, region: region)
|
92
|
-
end
|
93
|
-
|
94
|
-
def aws_credentials
|
95
|
-
@aws_credentials ||= aws_credentials_class.new(key, secret_key)
|
96
|
-
end
|
97
|
-
end
|
98
|
-
end
|
@@ -1,50 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative 'event'
|
4
|
-
|
5
|
-
module CycloneLariat
|
6
|
-
class EventsRepo
|
7
|
-
attr_reader :dataset
|
8
|
-
|
9
|
-
def initialize(dataset)
|
10
|
-
@dataset = dataset
|
11
|
-
end
|
12
|
-
|
13
|
-
def create(event)
|
14
|
-
dataset.insert(
|
15
|
-
uuid: event.uuid,
|
16
|
-
type: event.type,
|
17
|
-
publisher: event.publisher,
|
18
|
-
data: JSON.generate(event.data),
|
19
|
-
error_message: event.error&.message,
|
20
|
-
error_details: JSON.generate(event.error&.details),
|
21
|
-
version: event.version,
|
22
|
-
sent_at: event.sent_at
|
23
|
-
)
|
24
|
-
end
|
25
|
-
|
26
|
-
def exists?(uuid:)
|
27
|
-
dataset.where(uuid: uuid).limit(1).any?
|
28
|
-
end
|
29
|
-
|
30
|
-
def processed!(uuid:)
|
31
|
-
!dataset.where(uuid: uuid).update(processed_at: Sequel.function(:NOW)).zero?
|
32
|
-
end
|
33
|
-
|
34
|
-
def find(uuid:)
|
35
|
-
raw = dataset.where(uuid: uuid).first
|
36
|
-
raw[:data] = JSON.parse(raw[:data], symbolize_names: true)
|
37
|
-
raw[:error_details] = JSON.parse(raw[:error_details], symbolize_names: true) if raw[:error_details]
|
38
|
-
Event.wrap raw
|
39
|
-
end
|
40
|
-
|
41
|
-
def each_unprocessed
|
42
|
-
dataset.where(processed_at: nil).each do |raw|
|
43
|
-
raw[:data] = JSON.parse(raw[:data], symbolize_names: true)
|
44
|
-
raw[:error_details] = JSON.parse(raw[:error_details], symbolize_names: true) if raw[:error_details]
|
45
|
-
event = Event.wrap(raw)
|
46
|
-
yield(event)
|
47
|
-
end
|
48
|
-
end
|
49
|
-
end
|
50
|
-
end
|