cyclone_lariat 0.4.0 → 1.0.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.github/workflows/gem-push.yml +4 -4
- data/.rubocop.yml +9 -5
- data/Gemfile.lock +123 -21
- data/Guardfile +42 -0
- data/README.md +417 -220
- data/bin/cyclone_lariat +75 -43
- data/cyclone_lariat.gemspec +10 -3
- data/lib/cyclone_lariat/clients/abstract.rb +40 -0
- data/lib/cyclone_lariat/clients/sns.rb +163 -0
- data/lib/cyclone_lariat/clients/sqs.rb +114 -0
- data/lib/cyclone_lariat/core.rb +21 -0
- data/lib/cyclone_lariat/errors.rb +16 -0
- data/lib/cyclone_lariat/fake.rb +19 -0
- data/lib/cyclone_lariat/generators/command.rb +53 -0
- data/lib/cyclone_lariat/generators/event.rb +52 -0
- data/lib/cyclone_lariat/generators/queue.rb +30 -0
- data/lib/cyclone_lariat/generators/topic.rb +29 -0
- data/lib/cyclone_lariat/messages/v1/abstract.rb +139 -0
- data/lib/cyclone_lariat/messages/v1/command.rb +20 -0
- data/lib/cyclone_lariat/messages/v1/event.rb +20 -0
- data/lib/cyclone_lariat/messages/v1/validator.rb +31 -0
- data/lib/cyclone_lariat/messages/v2/abstract.rb +149 -0
- data/lib/cyclone_lariat/messages/v2/command.rb +20 -0
- data/lib/cyclone_lariat/messages/v2/event.rb +20 -0
- data/lib/cyclone_lariat/messages/v2/validator.rb +39 -0
- data/lib/cyclone_lariat/middleware.rb +9 -6
- data/lib/cyclone_lariat/migration.rb +54 -117
- data/lib/cyclone_lariat/options.rb +52 -0
- data/lib/cyclone_lariat/presenters/graph.rb +54 -0
- data/lib/cyclone_lariat/presenters/queues.rb +41 -0
- data/lib/cyclone_lariat/presenters/subscriptions.rb +34 -0
- data/lib/cyclone_lariat/presenters/topics.rb +40 -0
- data/lib/cyclone_lariat/publisher.rb +25 -0
- data/lib/cyclone_lariat/repo/active_record/messages.rb +92 -0
- data/lib/cyclone_lariat/repo/active_record/versions.rb +28 -0
- data/lib/cyclone_lariat/repo/messages.rb +43 -0
- data/lib/cyclone_lariat/repo/messages_mapper.rb +49 -0
- data/lib/cyclone_lariat/repo/sequel/messages.rb +73 -0
- data/lib/cyclone_lariat/repo/sequel/versions.rb +28 -0
- data/lib/cyclone_lariat/repo/versions.rb +42 -0
- data/lib/cyclone_lariat/resources/queue.rb +167 -0
- data/lib/cyclone_lariat/resources/topic.rb +132 -0
- data/lib/cyclone_lariat/services/migrate.rb +51 -0
- data/lib/cyclone_lariat/services/rollback.rb +51 -0
- data/lib/cyclone_lariat/version.rb +1 -1
- data/lib/cyclone_lariat.rb +4 -11
- data/lib/tasks/console.rake +1 -1
- data/lib/tasks/cyclone_lariat.rake +10 -12
- data/lib/tasks/db.rake +0 -15
- metadata +127 -27
- data/config/db.example.rb +0 -9
- data/config/initializers/sequel.rb +0 -7
- data/db/migrate/01_add_uuid_extensions.rb +0 -15
- data/db/migrate/02_add_events.rb +0 -19
- data/db/migrate/03_add_versions.rb +0 -9
- data/docs/_imgs/graphviz_01.png +0 -0
- data/docs/_imgs/graphviz_02.png +0 -0
- data/docs/_imgs/graphviz_03.png +0 -0
- data/docs/_imgs/lariat.jpg +0 -0
- data/docs/_imgs/logic.png +0 -0
- data/docs/_imgs/sqs_sns_diagram.png +0 -0
- data/lib/cyclone_lariat/abstract/client.rb +0 -112
- data/lib/cyclone_lariat/abstract/message.rb +0 -98
- data/lib/cyclone_lariat/command.rb +0 -13
- data/lib/cyclone_lariat/configure.rb +0 -15
- data/lib/cyclone_lariat/event.rb +0 -13
- data/lib/cyclone_lariat/messages_mapper.rb +0 -46
- data/lib/cyclone_lariat/messages_repo.rb +0 -60
- data/lib/cyclone_lariat/queue.rb +0 -147
- data/lib/cyclone_lariat/sns_client.rb +0 -149
- data/lib/cyclone_lariat/sqs_client.rb +0 -93
- data/lib/cyclone_lariat/topic.rb +0 -113
data/README.md
CHANGED
@@ -1,17 +1,183 @@
|
|
1
1
|
# Cyclone lariat
|
2
2
|
|
3
3
|
This gem work in few scenarios:
|
4
|
-
- As middleware for [shoryuken](https://github.com/ruby-shoryuken/shoryuken).
|
5
|
-
-
|
6
|
-
- As a
|
7
|
-
-
|
4
|
+
- As middleware for [shoryuken](https://github.com/ruby-shoryuken/shoryuken).
|
5
|
+
- It saves all events to the database and also catches and throws all exceptions.
|
6
|
+
- As a middleware, it can log all incoming messages.
|
7
|
+
- As a [client](#client--publisher) that can send messages to SNS topics and SQS queues.
|
8
|
+
- Also it can help you with CI\CD to manage topics, queues and subscriptions such as database [migration](#Migrations).
|
8
9
|
|
9
10
|
![Cyclone lariat](docs/_imgs/lariat.jpg)
|
10
11
|
|
12
|
+
## Install and configuration Cyclone Lariat
|
13
|
+
### Install
|
14
|
+
<details>
|
15
|
+
<summary>Sequel</summary>
|
16
|
+
|
17
|
+
#### Install with Sequel
|
18
|
+
Edit Gemfile:
|
19
|
+
```ruby
|
20
|
+
# Gemfile
|
21
|
+
gem 'sequel'
|
22
|
+
gem 'cyclone_lariat'
|
23
|
+
```
|
24
|
+
And run in console:
|
25
|
+
```bash
|
26
|
+
$ bundle install
|
27
|
+
$ cyclone_lariat install
|
28
|
+
```
|
29
|
+
</details>
|
30
|
+
<details>
|
31
|
+
<summary>ActiveRecord</summary>
|
32
|
+
|
33
|
+
#### Install with ActiveRecord
|
34
|
+
Edit Gemfile:
|
35
|
+
```ruby
|
36
|
+
# Gemfile
|
37
|
+
gem 'active_record'
|
38
|
+
gem 'cyclone_lariat'
|
39
|
+
```
|
40
|
+
And run in console:
|
41
|
+
```bash
|
42
|
+
$ bundle install
|
43
|
+
$ cyclone_lariat install --adapter=active_record
|
44
|
+
```
|
45
|
+
</details>
|
46
|
+
|
47
|
+
Last install command will create 2 files:
|
48
|
+
- ./lib/tasks/cyclone_lariat.rake - Rake tasks, for management migrations
|
49
|
+
- ./config/initializers/cyclone_lariat.rb - Configuration default values for cyclone lariat usage
|
50
|
+
|
51
|
+
|
52
|
+
### Configuration
|
53
|
+
<details>
|
54
|
+
<summary>Sequel</summary>
|
55
|
+
|
56
|
+
```ruby
|
57
|
+
# frozen_string_literal: true
|
58
|
+
|
59
|
+
CycloneLariat.configure do |c|
|
60
|
+
c.version = 1 # api version
|
61
|
+
|
62
|
+
c.aws_key = ENV['AWS_KEY'] # aws key
|
63
|
+
c.aws_secret_key = ENV['AWS_SECRET_KEY'] # aws secret
|
64
|
+
c.aws_account_id = ENV['AWS_ACCOUNT_ID'] # aws account id
|
65
|
+
c.aws_region = ENV['AWS_REGION'] # aws region
|
66
|
+
|
67
|
+
c.publisher = ENV['APP_NAME'] # name of your publishers, usually name of your application
|
68
|
+
c.instance = ENV['INSTANCE'] # stage, production, test
|
69
|
+
c.driver = :sequel # driver Sequel
|
70
|
+
c.messages_dataset = DB[:async_messages] # Sequel dataset for store income messages (on receiver)
|
71
|
+
c.versions_dataset = DB[:lariat_versions] # Sequel dataset for versions of publisher migrations
|
72
|
+
c.fake_publish = ENV['INSTANCE'] == 'test' # when true, prevents messages from being published
|
73
|
+
end
|
74
|
+
```
|
75
|
+
|
76
|
+
#### Example migrations
|
77
|
+
Before using the event store, add and apply these migrations:
|
78
|
+
|
79
|
+
```ruby
|
80
|
+
Sequel.migration do
|
81
|
+
up do
|
82
|
+
run <<-SQL
|
83
|
+
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
|
84
|
+
SQL
|
85
|
+
end
|
11
86
|
|
12
|
-
|
13
|
-
|
14
|
-
|
87
|
+
down do
|
88
|
+
run <<-SQL
|
89
|
+
DROP EXTENSION IF EXISTS "uuid-ossp";
|
90
|
+
SQL
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
Sequel.migration do
|
95
|
+
change do
|
96
|
+
create_table :async_messages do
|
97
|
+
column :uuid, :uuid, primary_key: true
|
98
|
+
String :type, null: false
|
99
|
+
Integer :version, null: false
|
100
|
+
String :publisher, null: false
|
101
|
+
column :data, :json, null: false
|
102
|
+
String :client_error_message, null: true, default: nil
|
103
|
+
column :client_error_details, :json, null: true, default: nil
|
104
|
+
DateTime :sent_at, null: true, default: nil
|
105
|
+
DateTime :received_at, null: false, default: Sequel::CURRENT_TIMESTAMP
|
106
|
+
DateTime :processed_at, null: true, default: nil
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
Sequel.migration do
|
112
|
+
change do
|
113
|
+
create_table :lariat_versions do
|
114
|
+
Integer :version, null: false, unique: true
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
```
|
119
|
+
</details>
|
120
|
+
<details>
|
121
|
+
<summary>ActiveRecord</summary>
|
122
|
+
|
123
|
+
```ruby
|
124
|
+
# frozen_string_literal: true
|
125
|
+
|
126
|
+
CycloneLariat.configure do |c|
|
127
|
+
c.version = 1 # api version
|
128
|
+
|
129
|
+
c.aws_key = ENV['AWS_KEY'] # aws key
|
130
|
+
c.aws_secret_key = ENV['AWS_SECRET_KEY'] # aws secret
|
131
|
+
c.aws_account_id = ENV['AWS_ACCOUNT_ID'] # aws account id
|
132
|
+
c.aws_region = ENV['AWS_REGION'] # aws region
|
133
|
+
|
134
|
+
c.publisher = ENV['APP_NAME'] # name of your publishers, usually name of your application
|
135
|
+
c.instance = ENV['INSTANCE'] # stage, production, test
|
136
|
+
c.driver = :active_record # driver ActiveRecord
|
137
|
+
c.messages_dataset = CycloneLariatMessage # ActiveRecord model for store income messages (on receiver)
|
138
|
+
c.versions_dataset = CycloneLariatVersion # ActiveRecord model for versions of publisher migrations
|
139
|
+
c.fake_publish = ENV['INSTANCE'] == 'test' # when true, prevents messages from being published
|
140
|
+
end
|
141
|
+
```
|
142
|
+
|
143
|
+
#### Example migrations
|
144
|
+
Before using the event store, add and apply these migrations:
|
145
|
+
```ruby
|
146
|
+
# migrations
|
147
|
+
execute('CREATE EXTENSION IF NOT EXISTS "uuid-ossp"')
|
148
|
+
|
149
|
+
create_table :cyclone_lariat_messages, id: :uuid, primary_key: :uuid, default: -> { 'public.uuid_generate_v4()' } do |t|
|
150
|
+
t.string :kind, null: false
|
151
|
+
t.string :type, null: false
|
152
|
+
t.integer :version, null: false
|
153
|
+
t.string :publisher, null: false
|
154
|
+
t.jsonb :data, null: false
|
155
|
+
t.string :client_error_message, null: true, default: nil
|
156
|
+
t.jsonb :client_error_details, null: true, default: nil
|
157
|
+
t.datetime :sent_at, null: true, default: nil
|
158
|
+
t.datetime :received_at, null: false, default: -> { 'CURRENT_TIMESTAMP' }
|
159
|
+
t.datetime :processed_at, null: true, default: nil
|
160
|
+
end
|
161
|
+
|
162
|
+
create_table :cyclone_lariat_versions do |t|
|
163
|
+
t.integer :version, null: false, index: { unique: true }
|
164
|
+
end
|
165
|
+
|
166
|
+
# models
|
167
|
+
class CycloneLariatMessage < ActiveRecord::Base
|
168
|
+
self.inheritance_column = :_type_disabled
|
169
|
+
self.primary_key = 'uuid'
|
170
|
+
end
|
171
|
+
class CycloneLariatVersion < ActiveRecord::Base
|
172
|
+
end
|
173
|
+
```
|
174
|
+
</details>
|
175
|
+
|
176
|
+
If you are only using your application as a publisher, you may not need to set the _messages_dataset_ parameter.
|
177
|
+
|
178
|
+
## Client / Publisher
|
179
|
+
At first lets understand what the difference between SQS and SNS:
|
180
|
+
- Amazon Simple Queue Service (SQS) lets you send, store, and receive messages between software components at any
|
15
181
|
volume, without losing messages or requiring other services to be available.
|
16
182
|
- Amazon Simple Notification Service (SNS) sends notifications two ways Application2Person (like send sms).
|
17
183
|
And the second way is Application2Application, that's way more important for us. In this way you case use
|
@@ -19,158 +185,183 @@ SNS service like fanout.
|
|
19
185
|
|
20
186
|
![SQS/SNS](docs/_imgs/sqs_sns_diagram.png)
|
21
187
|
|
22
|
-
For use **cyclone_lariat** as _Publisher_ lets make install CycloneLariat.
|
23
|
-
|
24
|
-
## Install cyclone_lariat
|
25
|
-
Edit Gemfile:
|
26
|
-
```ruby
|
27
|
-
# Gemfile
|
28
|
-
gem 'sequel'
|
29
|
-
gem 'cyclone_lariat'
|
30
|
-
```
|
31
|
-
And run in console:
|
32
|
-
```bash
|
33
|
-
$ bundle install
|
34
|
-
$ cyclone_lariat install
|
35
|
-
```
|
188
|
+
For use **cyclone_lariat** as _Publisher_ lets make install CycloneLariat.
|
36
189
|
|
37
|
-
|
38
|
-
- ./lib/tasks/cyclone_lariat.rake - Rake tasks, for management migrations
|
39
|
-
- ./config/initializers/cyclone_lariat.rb - Configuration default values for cyclone lariat usage
|
190
|
+
Before creating the first migration, let's explain what _CycloneLariat::Messages_ is.
|
40
191
|
|
41
|
-
|
42
|
-
|
43
|
-
#
|
192
|
+
### Messages
|
193
|
+
Message in Amazon SQS\SNS service it's a
|
194
|
+
[object](https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-message-metadata.html#sqs-message-attributes)
|
195
|
+
that has several attributes. The main attributes are the **body**, which consists of the published
|
196
|
+
data. The body is a _String_, but we can use it as a _JSON_ object. **Cyclone_lariat** use by default scheme - version 1:
|
44
197
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
198
|
+
```json
|
199
|
+
// Scheme: version 1
|
200
|
+
{
|
201
|
+
"uuid": "f2ce3813-0905-4d81-a60e-f289f2431f50", // Uniq message identificator
|
202
|
+
"publisher": "sample_app", // Publisher application name
|
203
|
+
"request_id": "51285005-8a06-4181-b5fd-bf29f3b1a45a", // Optional: X-Request-Id
|
204
|
+
"type": "event_note_created", // Type of Event or Command
|
205
|
+
"version": 1, // Version of data structure
|
206
|
+
"data": {
|
207
|
+
"id": 12,
|
208
|
+
"text": "Sample of published data",
|
209
|
+
"attributes": ["one", "two", "three"]
|
210
|
+
},
|
211
|
+
"sent_at": "2022-11-09T11:42:18.203+01:00" // Time when message was sended in ISO8601 Standard
|
212
|
+
}
|
56
213
|
```
|
57
|
-
If you are only using your application as a publisher, you may not need to set the `events_dataset`
|
58
|
-
parameter.
|
59
214
|
|
60
|
-
|
215
|
+
Idea about X-Request-Id you can see at
|
216
|
+
[StackOverflow](https://stackoverflow.com/questions/25433258/what-is-the-x-request-id-http-header).
|
61
217
|
|
62
|
-
|
63
|
-
|
64
|
-
[object](https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-message-metadata.html#sqs-message-attributes)
|
65
|
-
that has several attributes. The main attributes are the **body**, which consists of the published
|
66
|
-
data. The body is a _String_, but we can use it as a _JSON_ object. **Cyclone_lariat** use this scheme:
|
218
|
+
As you see, type has prefix 'event_' in cyclone lariat you has two kinds of messages - _Messages::V1::Event_ and
|
219
|
+
_Messages::V1::Command_.
|
67
220
|
|
221
|
+
If you want log all your messages you can use extended scheme - version 2:
|
68
222
|
```json
|
223
|
+
// Scheme: version 2
|
69
224
|
{
|
70
225
|
"uuid": "f2ce3813-0905-4d81-a60e-f289f2431f50", // Uniq message identificator
|
71
226
|
"publisher": "sample_app", // Publisher application name
|
72
|
-
"request_id": "51285005-8a06-4181-b5fd-bf29f3b1a45a", // Optional: X-Request-Id
|
227
|
+
"request_id": "51285005-8a06-4181-b5fd-bf29f3b1a45a", // Optional: X-Request-Id
|
73
228
|
"type": "event_note_created", // Type of Event or Command
|
74
|
-
"version":
|
229
|
+
"version": 2, // Version of data structure
|
230
|
+
"subject": {
|
231
|
+
"type": "user", // Subject type
|
232
|
+
"uuid": "a27c29e2-bbd3-490a-8f1b-caa4f8d902ef" // Subject uuid
|
233
|
+
},
|
234
|
+
"object": {
|
235
|
+
"type": "note", // Object type
|
236
|
+
"uuid": "f46e74db-3335-4c5e-b476-c2a87660a942" // Object uuid
|
237
|
+
},
|
75
238
|
"data": {
|
76
239
|
"id": 12,
|
77
240
|
"text": "Sample of published data",
|
78
|
-
"attributes": ["one", "two", "three"]
|
241
|
+
"attributes": ["one", "two", "three"]
|
79
242
|
},
|
80
|
-
"sent_at": "2022-11-09T11:42:18.203+01:00"
|
243
|
+
"sent_at": "2022-11-09T11:42:18.203+01:00" // Time when message was sended in ISO8601 Standard
|
81
244
|
}
|
82
245
|
```
|
246
|
+
#### Subject vs Object
|
83
247
|
|
84
|
-
|
85
|
-
|
248
|
+
The difference between scheme first and second version - is subject and object. This values need to help with actions log.
|
249
|
+
For example, user #42, write to support, "why he could not sign in". The messages log is:
|
86
250
|
|
87
|
-
|
251
|
+
| Subject | Action | Object |
|
252
|
+
|:---------|:------------|:----------|
|
253
|
+
| user #42 | sign_up | user #42 |
|
254
|
+
| user #42 | sign_in | user #42 |
|
255
|
+
| user #42 | create_note | note #769 |
|
256
|
+
| user #1 | ban | user #42 |
|
88
257
|
|
89
|
-
|
258
|
+
It is important to understand that user #42 can be both a subject and an object. And you should save both of these fields to keep track of the entire history of this user.
|
259
|
+
|
260
|
+
#### Command vs Event
|
90
261
|
Commands and events are both simple domain structures that contain solely data for reading. That means
|
91
262
|
they contain no behaviour or business logic.
|
92
263
|
|
93
264
|
A command is an object that is sent to the domain for a state change which is handled by a command
|
94
|
-
handler. They should be named with a verb in an imperative mood plus the aggregate name which it
|
265
|
+
handler. They should be named with a verb in an imperative mood plus the aggregate name which it
|
95
266
|
operates on. Such request can be rejected due to the data the command holds being invalid/inconsistent.
|
96
|
-
There should be exactly 1 handler for each command. Once the command has been executed, the consumer
|
267
|
+
There should be exactly 1 handler for each command. Once the command has been executed, the consumer
|
97
268
|
can then carry out whatever the task is depending on the output of the command.
|
98
269
|
|
99
|
-
An event is a statement of fact about what change has been made to the domain state. They are named
|
270
|
+
An event is a statement of fact about what change has been made to the domain state. They are named
|
100
271
|
with the aggregate name where the change took place plus the verb past-participle. An event happens off
|
101
|
-
the back of a command.
|
102
|
-
A command can emit any number of events. The sender of the event does not care who receives it or
|
272
|
+
the back of a command.
|
273
|
+
A command can emit any number of events. The sender of the event does not care who receives it or
|
103
274
|
whether it has been received at all.
|
104
275
|
|
105
276
|
### Publish
|
106
|
-
For publishing
|
277
|
+
For publishing _Messages::V1::Event_ or _Messages::V1::Commands_, you have two ways, send [_Message_](#Messages) directly:
|
107
278
|
|
108
279
|
```ruby
|
109
|
-
CycloneLariat.
|
110
|
-
#
|
280
|
+
CycloneLariat.configure do |config|
|
281
|
+
# Options app here
|
111
282
|
end
|
112
283
|
|
113
|
-
client = CycloneLariat::
|
284
|
+
client = CycloneLariat::Clients::Sns.new(publisher: 'auth', version: 1)
|
285
|
+
payload = {
|
286
|
+
first_name: 'John',
|
287
|
+
last_name: 'Doe',
|
288
|
+
mail: 'john.doe@example.com'
|
289
|
+
}
|
114
290
|
|
115
|
-
client.publish_command('register_user', data:
|
116
|
-
first_name: 'John',
|
117
|
-
last_name: 'Doe',
|
118
|
-
mail: 'john.doe@example.com'
|
119
|
-
}, fifo: false
|
120
|
-
)
|
291
|
+
client.publish_command('register_user', data: payload, fifo: false)
|
121
292
|
```
|
122
293
|
|
123
|
-
That's call, will generate a message body:
|
294
|
+
That's call, will generate a message body:
|
124
295
|
```json
|
125
296
|
{
|
126
297
|
"uuid": "f2ce3813-0905-4d81-a60e-f289f2431f50",
|
127
298
|
"publisher": "auth",
|
128
299
|
"type": "command_register_user",
|
129
|
-
"version":
|
300
|
+
"version": 1,
|
130
301
|
"data": {
|
131
|
-
"first_name": "John",
|
132
|
-
"last_name": "Doe",
|
133
|
-
"mail": "john.doe@example.com"
|
302
|
+
"first_name": "John",
|
303
|
+
"last_name": "Doe",
|
304
|
+
"mail": "john.doe@example.com"
|
134
305
|
},
|
135
|
-
"sent_at": "2022-11-09T11:42:18.203+01:00"
|
306
|
+
"sent_at": "2022-11-09T11:42:18.203+01:00" // The time the message was sent. ISO8601 standard.
|
136
307
|
}
|
137
308
|
```
|
138
309
|
|
139
|
-
Or
|
140
|
-
|
310
|
+
Or for second schema version code:
|
141
311
|
```ruby
|
142
|
-
|
312
|
+
CycloneLariat.configure do |config|
|
313
|
+
# Options app here
|
314
|
+
end
|
315
|
+
|
316
|
+
client = CycloneLariat::Clients::Sns.new(publisher: 'auth', version: 2)
|
317
|
+
|
318
|
+
client.publish_event(
|
319
|
+
'sign_up',
|
320
|
+
data: {
|
321
|
+
first_name: 'John',
|
322
|
+
last_name: 'Doe',
|
323
|
+
mail: 'john.doe@example.com'
|
324
|
+
},
|
325
|
+
subject: { type: 'user', uuid: '40250522-21c8-4fc7-9b0b-47d9666a4430'},
|
326
|
+
object: { type: 'user', uuid: '40250522-21c8-4fc7-9b0b-47d9666a4430'},
|
327
|
+
fifo: false
|
328
|
+
)
|
329
|
+
```
|
143
330
|
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
instance 'stage'
|
331
|
+
Or is it better to make your own client, like a [Repository](https://deviq.com/design-patterns/repository-pattern) pattern.
|
332
|
+
```ruby
|
333
|
+
require 'cyclone_lariat/publisher' # If require: false in Gemfile
|
148
334
|
|
335
|
+
class Publisher < CycloneLariat::Publisher
|
149
336
|
def email_is_created(mail)
|
150
|
-
publish event('email_is_created', data: { mail: mail }), fifo:
|
337
|
+
sns.publish event('email_is_created', data: { mail: mail }), fifo: false
|
151
338
|
end
|
152
339
|
|
153
340
|
def email_is_removed(mail)
|
154
|
-
publish event('email_is_removed', data: { mail: mail }), fifo:
|
341
|
+
sns.publish event('email_is_removed', data: { mail: mail }), fifo: false
|
155
342
|
end
|
156
343
|
|
157
|
-
|
158
344
|
def delete_user(mail)
|
159
|
-
publish command('delete_user', data: { mail: mail }), fifo: false
|
345
|
+
sns.publish command('delete_user', data: { mail: mail }), fifo: false
|
346
|
+
end
|
347
|
+
|
348
|
+
def welcome_message(mail, text)
|
349
|
+
sqs.publish command('welcome', data: {mail: mail, txt: text}), fifo: false
|
160
350
|
end
|
161
351
|
end
|
162
352
|
|
163
353
|
# Init repo
|
164
|
-
|
354
|
+
publisher = Publisher.new
|
165
355
|
|
166
356
|
# And send topics
|
167
|
-
|
168
|
-
|
169
|
-
|
357
|
+
publisher.email_is_created 'john.doe@example.com'
|
358
|
+
publisher.email_is_removed 'john.doe@example.com'
|
359
|
+
publisher.delete_user 'john.doe@example.com'
|
360
|
+
publisher.welcome_message 'john.doe@example.com', 'You are welcome'
|
170
361
|
```
|
171
362
|
|
172
|
-
|
173
|
-
An Amazon SNS topic and SQS queue is a logical access point that acts as a communication channel. Both
|
363
|
+
#### Topics and Queue
|
364
|
+
An Amazon SNS topic and SQS queue is a logical access point that acts as a communication channel. Both
|
174
365
|
of them has specific address ARN.
|
175
366
|
|
176
367
|
```
|
@@ -184,68 +375,77 @@ arn:aws:sqs:eu-west-1:247602342345:test-event-queue-cyclone_lariat-note_added-no
|
|
184
375
|
Split ARN:
|
185
376
|
- `arn:aws:sns` - Prefix for SNS Topics
|
186
377
|
- `arn:aws:sqs` - Prefix for SQS Queues
|
187
|
-
- `eu-west-1` -
|
188
|
-
[AWS Region](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-regions-availability-zones.html#concepts-regions)
|
378
|
+
- `eu-west-1` - [AWS Region](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-regions-availability-zones.html#concepts-regions)
|
189
379
|
- `247602342345` - [AWS account](https://docs.aws.amazon.com/IAM/latest/UserGuide/console_account-alias.html)
|
190
380
|
- `test-event-fanout-cyclone_lariat-note_added` - Topic \ Queue name
|
191
|
-
- `.fifo` - if Topic or queue is
|
192
|
-
|
193
|
-
has that suffix.
|
381
|
+
- `.fifo` - if Topic or queue is [FIFO](https://aws.amazon.com/blogs/aws/introducing-amazon-sns-fifo-first-in-first-out-pub-sub-messaging/), they must
|
382
|
+
has that suffix.
|
194
383
|
|
195
|
-
Region and
|
384
|
+
Region and account_id usually set using the **cyclone_lariat** [configuration](#Configuration).
|
196
385
|
|
197
|
-
|
386
|
+
#### Declaration for topic and queues names
|
198
387
|
In **cyclone_lariat** we have a declaration for defining topic and queue names.
|
199
388
|
This can help in organizing the order.
|
200
389
|
|
201
390
|
```ruby
|
202
|
-
CycloneLariat.
|
203
|
-
|
204
|
-
|
391
|
+
CycloneLariat.configure do |config|
|
392
|
+
config.instance = 'test'
|
393
|
+
config.publisher = 'cyclone_lariat'
|
205
394
|
# ...
|
206
395
|
end
|
207
396
|
|
208
|
-
CycloneLariat::
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
397
|
+
CycloneLariat::Clients::Sns.new.publish_command(
|
398
|
+
'register_user',
|
399
|
+
data: {
|
400
|
+
first_name: 'John',
|
401
|
+
last_name: 'Doe',
|
402
|
+
mail: 'john.doe@example.com'
|
403
|
+
},
|
404
|
+
fifo: false
|
213
405
|
)
|
214
406
|
|
215
|
-
# or in repository-like style:
|
216
|
-
|
217
|
-
class YourClient < CycloneLariat::SnsClient
|
407
|
+
# or in repository-like style:
|
408
|
+
class Publisher < CycloneLariat::Publisher
|
218
409
|
def register_user(first:, last:, mail:)
|
219
|
-
publish command(
|
410
|
+
sns.publish command(
|
411
|
+
'register_user',
|
412
|
+
data: {
|
413
|
+
mail: mail,
|
414
|
+
name: {
|
415
|
+
first: first,
|
416
|
+
last: last
|
417
|
+
}
|
418
|
+
}
|
419
|
+
), fifo: false
|
220
420
|
end
|
221
421
|
end
|
222
422
|
```
|
223
423
|
|
224
|
-
|
225
424
|
We will publish a message on this topic: `test-command-fanout-cyclone_lariat-register_user`.
|
226
425
|
|
227
426
|
Let's split the topic title:
|
228
427
|
- `test` - instance;
|
229
428
|
- `command` - kind - [event or command](#command-vs-event);
|
230
|
-
- `fanount` - resource type - fanout for SNS topics;
|
429
|
+
- `fanount` - resource type - fanout for SNS topics;
|
231
430
|
- `cyclone_lariat` - publisher name;
|
232
431
|
- `regiser_user` - message type.
|
233
432
|
|
234
433
|
For queues you also can define destination.
|
235
434
|
```ruby
|
236
|
-
CycloneLariat::
|
237
|
-
'register_user',
|
238
|
-
|
435
|
+
CycloneLariat::Clients::Sqs.new.publish_event(
|
436
|
+
'register_user',
|
437
|
+
data: { mail: 'john.doe@example.com' },
|
438
|
+
dest: :mailer,
|
439
|
+
fifo: false
|
239
440
|
)
|
240
441
|
|
442
|
+
# or in repository-like style:
|
241
443
|
|
242
|
-
|
243
|
-
|
244
|
-
class YourClient < CycloneLariat::SnsClient
|
444
|
+
class YourClient < CycloneLariat::Clients::Sns
|
245
445
|
# ...
|
246
|
-
|
446
|
+
|
247
447
|
def register_user(first:, last:, mail:)
|
248
|
-
publish event('register_user', data: { mail: mail }), fifo:
|
448
|
+
publish event('register_user', data: { mail: mail }), fifo: false
|
249
449
|
end
|
250
450
|
end
|
251
451
|
```
|
@@ -264,40 +464,81 @@ You also can sent message to queue with custom name. But this way does not recom
|
|
264
464
|
|
265
465
|
```ruby
|
266
466
|
# Directly
|
267
|
-
CycloneLariat::
|
268
|
-
'register_user', data: { mail: 'john.doe@example.com' },
|
269
|
-
dest: :mailer, topic: 'custom_topic_name.fifo', fifo:
|
467
|
+
CycloneLariat::Clients::Sqs.new.publish_event(
|
468
|
+
'register_user', data: { mail: 'john.doe@example.com' },
|
469
|
+
dest: :mailer, topic: 'custom_topic_name.fifo', fifo: false
|
270
470
|
)
|
271
471
|
|
272
472
|
# Repository
|
273
|
-
class
|
473
|
+
class Publisher < CycloneLariat::Publisher
|
274
474
|
# ...
|
275
475
|
|
276
476
|
def register_user(first:, last:, mail:)
|
277
|
-
publish event('register_user', data: { mail: mail }),
|
278
|
-
topic: 'custom_topic_name.fifo', fifo:
|
477
|
+
publish event('register_user', data: { mail: mail }),
|
478
|
+
topic: 'custom_topic_name.fifo', fifo: false
|
279
479
|
end
|
280
480
|
end
|
281
481
|
```
|
282
|
-
Will publish message on queue: `
|
482
|
+
Will publish message on queue: `custom_topic_name`
|
283
483
|
|
284
|
-
# Migrations
|
285
484
|
|
286
|
-
|
287
|
-
|
485
|
+
### FIFO and no FIFO
|
486
|
+
The main idea you can read on [AWS Docs](https://aws.amazon.com/blogs/aws/introducing-amazon-sns-fifo-first-in-first-out-pub-sub-messaging/).
|
487
|
+
|
488
|
+
FIFO message should consist two fields:
|
489
|
+
- `group_id` - In each topic, the FIFO sequence is defined only within one group.
|
490
|
+
[AWS Docs](https://docs.aws.amazon.com/sns/latest/dg/fifo-message-grouping.html)
|
491
|
+
- `deduplication_id` - Within the same group, a unique identifier must be defined for each message.
|
492
|
+
[AWS Docs](https://docs.aws.amazon.com/sns/latest/dg/fifo-message-dedup.html)
|
493
|
+
|
494
|
+
The unique identifier can definitely be the entire message. In this case, you
|
495
|
+
do not need to pass the deduplication_id parameter. But you must create a queue
|
496
|
+
with the `content_based_deduplication` parameter in migration.
|
497
|
+
|
288
498
|
|
289
499
|
```ruby
|
290
|
-
|
500
|
+
class Publisher < CycloneLariat::Publisher
|
501
|
+
def user_created(mail:, uuid:)
|
502
|
+
sns.publish event('user_created', data: {
|
503
|
+
user: {
|
504
|
+
uuid: uuid,
|
505
|
+
mail: mail
|
506
|
+
},
|
507
|
+
},
|
508
|
+
deduplication_id: uuid,
|
509
|
+
group_id: uuid),
|
510
|
+
fifo: true
|
511
|
+
end
|
291
512
|
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
513
|
+
def user_mail_changed(mail:, uuid:)
|
514
|
+
sns.publish event('user_mail_created', data: {
|
515
|
+
user: {
|
516
|
+
uuid: uuid,
|
517
|
+
mail: mail
|
518
|
+
},
|
519
|
+
},
|
520
|
+
deduplication_id: mail,
|
521
|
+
group_id: uuid),
|
522
|
+
fifo: true
|
297
523
|
end
|
298
524
|
end
|
299
525
|
```
|
300
|
-
|
526
|
+
|
527
|
+
### Tests for publishers
|
528
|
+
|
529
|
+
Instead of stub all requests to AWS services, you can set up cyclone lariat for make fake publishing.
|
530
|
+
|
531
|
+
```ruby
|
532
|
+
CycloneLariat.configure do |c|
|
533
|
+
# ...
|
534
|
+
c.fake_publish = ENV['INSTANCE'] == 'test' # when true, prevents messages from being published
|
535
|
+
end
|
536
|
+
```
|
537
|
+
|
538
|
+
## Migrations
|
539
|
+
|
540
|
+
With **cyclone_lariat** you can use migrations that can create, delete, and subscribe to your queues and topics, just like database migrations do.
|
541
|
+
Before using this function, you must complete the **cyclone_lariat** [configuration](#Configuration).
|
301
542
|
|
302
543
|
```bash
|
303
544
|
$ cyclone_lariat generate migration user_created
|
@@ -312,14 +553,16 @@ This command should create a migration file, let's edit it.
|
|
312
553
|
|
313
554
|
class UserCreatedQueue < CycloneLariat::Migration
|
314
555
|
def up
|
315
|
-
create queue(:user_created, dest: :mailer, fifo: true)
|
556
|
+
create queue(:user_created, dest: :mailer, content_based_deduplication: true, fifo: true)
|
316
557
|
end
|
317
558
|
|
318
559
|
def down
|
319
|
-
delete queue(:user_created, dest: :mailer, fifo: true)
|
560
|
+
delete queue(:user_created, dest: :mailer, content_based_deduplication: true, fifo: true)
|
320
561
|
end
|
321
562
|
end
|
322
563
|
```
|
564
|
+
The `content_based_dedupplication` parameter can only be specified for FIFO resources. When true, the whole message is
|
565
|
+
used as the unique message identifier instead of the `deduplication_id` key.
|
323
566
|
|
324
567
|
To apply migration use:
|
325
568
|
```bash
|
@@ -331,7 +574,7 @@ To decline migration use:
|
|
331
574
|
$ rake cyclone_lariat:rollback
|
332
575
|
```
|
333
576
|
|
334
|
-
Since the SNS\SQS management does not support an ACID transaction (in the sense of a database),
|
577
|
+
Since the SNS\SQS management does not support an ACID transaction (in the sense of a database),
|
335
578
|
I highly recommend using the atomic schema:
|
336
579
|
|
337
580
|
```ruby
|
@@ -388,10 +631,9 @@ class UserCreatedSubscription < CycloneLariat::Migration
|
|
388
631
|
end
|
389
632
|
```
|
390
633
|
|
634
|
+
### Example: one-to-many
|
391
635
|
|
392
|
-
|
393
|
-
|
394
|
-
The first example is when your _registration_ service creates new user. You also have two services:
|
636
|
+
The first example is when your _registration_ service creates new user. You also have two services:
|
395
637
|
_mailer_ - sending a welcome email, and _statistics_ service.
|
396
638
|
|
397
639
|
```ruby
|
@@ -399,7 +641,7 @@ create topic(:user_created, fifo: true)
|
|
399
641
|
create queue(:user_created, dest: :mailer, fifo: true)
|
400
642
|
create queue(:user_created, dest: :stat, fifo: true)
|
401
643
|
|
402
|
-
subscribe topic: topic(:user_created, fifo: true),
|
644
|
+
subscribe topic: topic(:user_created, fifo: true),
|
403
645
|
endpoint: queue(:user_created, dest: :mailer, fifo: true)
|
404
646
|
|
405
647
|
|
@@ -408,9 +650,9 @@ subscribe topic: topic(:user_created, fifo: true),
|
|
408
650
|
```
|
409
651
|
![one2many](docs/_imgs/graphviz_01.png)
|
410
652
|
|
411
|
-
|
653
|
+
### Example: many-to-one
|
412
654
|
|
413
|
-
The second example is when you have three services: _registration_ - creates new users, _order_
|
655
|
+
The second example is when you have three services: _registration_ - creates new users, _order_
|
414
656
|
service - allows you to create new orders, _statistics_ service collects all statistics.
|
415
657
|
|
416
658
|
```ruby
|
@@ -418,21 +660,21 @@ create topic(:user_created, fifo: false)
|
|
418
660
|
create topic(:order_created, fifo: false)
|
419
661
|
create queue(publisher: :any, dest: :statistic, fifo: false)
|
420
662
|
|
421
|
-
subscribe topic: topic(:user_created, fifo: false),
|
663
|
+
subscribe topic: topic(:user_created, fifo: false),
|
422
664
|
endpoint: queue(publisher: :any, dest: :statistic, fifo: false)
|
423
665
|
|
424
|
-
subscribe topic: topic(:order_created, fifo: false),
|
666
|
+
subscribe topic: topic(:order_created, fifo: false),
|
425
667
|
endpoint: queue(publisher: :any, dest: :statistic, fifo: false)
|
426
668
|
```
|
427
669
|
![one2many](docs/_imgs/graphviz_02.png)
|
428
670
|
|
429
|
-
If queue receives messages from multiple sources you must specify publisher as `:any`. If the
|
671
|
+
If queue receives messages from multiple sources you must specify publisher as `:any`. If the
|
430
672
|
subscriber receives messages with different types, `cyclone_lariat` uses a specific keyword - `all`.
|
431
673
|
|
432
|
-
|
674
|
+
### Example fanout-to-fanout
|
433
675
|
|
434
|
-
For better organisation you can subscribe topic on topic. For example, you have _management_panel_
|
435
|
-
and _client_panel_ services. Each of these services can register a user with predefined roles.
|
676
|
+
For better organisation you can subscribe topic on topic. For example, you have _management_panel_
|
677
|
+
and _client_panel_ services. Each of these services can register a user with predefined roles.
|
436
678
|
And you want to send this information to the _mailer_ and _statistics_ services.
|
437
679
|
|
438
680
|
```ruby
|
@@ -442,13 +684,13 @@ create topic(:user_created, publisher: :any, fifo: false)
|
|
442
684
|
create queue(:user_created, publisher: :any, dest: :mailer, fifo: false)
|
443
685
|
create queue(:user_created, publisher: :any, dest: :stat, fifo: false)
|
444
686
|
|
445
|
-
subscribe topic: topic(:
|
687
|
+
subscribe topic: topic(:client_created, fifo: false),
|
446
688
|
endpoint: topic(:user_created, publisher: :any, fifo: false)
|
447
689
|
|
448
|
-
subscribe topic: topic(:manager_created, fifo: false),
|
690
|
+
subscribe topic: topic(:manager_created, fifo: false),
|
449
691
|
endpoint: topic(:user_created, publisher: :any, fifo: false)
|
450
692
|
|
451
|
-
subscribe topic: topic(:user_created, publisher: :any, fifo: false),
|
693
|
+
subscribe topic: topic(:user_created, publisher: :any, fifo: false),
|
452
694
|
endpoint: queue(:user_created, publisher: :any, dest: :mailer, fifo: false)
|
453
695
|
|
454
696
|
subscribe topic: topic(:user_created, publisher: :any, fifo: false),
|
@@ -475,7 +717,7 @@ We recommend locate migration on:
|
|
475
717
|
- **queue** - on Subscriber side;
|
476
718
|
- **subscription** - on Subscriber side.
|
477
719
|
|
478
|
-
|
720
|
+
## Console tasks
|
479
721
|
|
480
722
|
```bash
|
481
723
|
$ cyclone_lariat install - install cyclone_lariat
|
@@ -489,15 +731,15 @@ $ rake cyclone_lariat:rollback[version] # Rollback topics for SQS/SNS
|
|
489
731
|
$ rake cyclone_lariat:graph # Make graph
|
490
732
|
```
|
491
733
|
|
492
|
-
Graph generated in [grpahviz](https://graphviz.org/) format for the entry scheme. You should install
|
734
|
+
Graph generated in [grpahviz](https://graphviz.org/) format for the entry scheme. You should install
|
493
735
|
it on your system. For convert it in png use:
|
494
736
|
```bash
|
495
737
|
$ rake cyclone_lariat:list:subscriptions | dot -Tpng -o foo.png
|
496
738
|
```
|
497
739
|
|
498
|
-
|
740
|
+
## Subscriber
|
499
741
|
|
500
|
-
This is gem work like middleware for [shoryuken](https://github.com/ruby-shoryuken/shoryuken). It save all events to
|
742
|
+
This is gem work like middleware for [shoryuken](https://github.com/ruby-shoryuken/shoryuken). It save all events to
|
501
743
|
database. And catch and produce all exceptions.
|
502
744
|
|
503
745
|
The logic of lariat as a subscriber. Imagine that you are working with an http server. And it gives you various response
|
@@ -510,15 +752,21 @@ codes. You have the following processing:
|
|
510
752
|
|
511
753
|
![diagram](docs/_imgs/logic.png)
|
512
754
|
|
513
|
-
|
755
|
+
## Middleware
|
514
756
|
If you use middleware:
|
515
757
|
- Store all events to dataset
|
516
758
|
- Notify every input sqs message
|
517
759
|
- Notify every error
|
518
760
|
|
519
761
|
```ruby
|
762
|
+
require 'sequel'
|
520
763
|
require 'cyclone_lariat/middleware' # If require: false in Gemfile
|
521
|
-
require '
|
764
|
+
require 'luna_park/notifiers/log'
|
765
|
+
|
766
|
+
require_relative './config/initializers/cyclone_lariat'
|
767
|
+
|
768
|
+
Shoryuken::Logging.logger = Logger.new STDOUT
|
769
|
+
Shoryuken::Logging.logger.level = Logger::INFO
|
522
770
|
|
523
771
|
class Receiver
|
524
772
|
include Shoryuken::Worker
|
@@ -529,9 +777,7 @@ class Receiver
|
|
529
777
|
body_parser: ->(sqs_msg) {
|
530
778
|
JSON.parse(sqs_msg.body, symbolize_names: true)
|
531
779
|
},
|
532
|
-
queue:
|
533
|
-
# or
|
534
|
-
# queue: CycloneLariat::SqsClient.new.queue('user_added', fifo: true).name
|
780
|
+
queue: CycloneLariat.queue(:user_created, dest: :stat, fifo: true).name
|
535
781
|
|
536
782
|
server_middleware do |chain|
|
537
783
|
# Options dataset, errors_notifier and message_notifier is optionals.
|
@@ -539,7 +785,7 @@ class Receiver
|
|
539
785
|
# If you dont define dataset - middleware does not store events in db
|
540
786
|
chain.add CycloneLariat::Middleware,
|
541
787
|
dataset: DB[:events],
|
542
|
-
errors_notifier:
|
788
|
+
errors_notifier: LunaPark::Notifiers::Sentry.new,
|
543
789
|
message_notifier: LunaPark::Notifiers::Log.new(min_lvl: :debug, format: :pretty_json)
|
544
790
|
end
|
545
791
|
|
@@ -548,76 +794,27 @@ class Receiver
|
|
548
794
|
|
549
795
|
def perform(sqs_message, sqs_message_body)
|
550
796
|
# Your logic here
|
551
|
-
|
797
|
+
|
552
798
|
# If you want to raise business error
|
553
799
|
raise UserIsNotRegistered.new(first_name: 'John', last_name: 'Doe')
|
554
800
|
end
|
555
801
|
end
|
556
802
|
```
|
557
803
|
|
558
|
-
##
|
559
|
-
Before using the event store, add and apply these two migrations:
|
560
|
-
|
561
|
-
```ruby
|
562
|
-
|
563
|
-
# First one
|
564
|
-
|
565
|
-
Sequel.migration do
|
566
|
-
up do
|
567
|
-
run <<-SQL
|
568
|
-
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
|
569
|
-
SQL
|
570
|
-
end
|
571
|
-
|
572
|
-
down do
|
573
|
-
run <<-SQL
|
574
|
-
DROP EXTENSION IF EXISTS "uuid-ossp";
|
575
|
-
SQL
|
576
|
-
end
|
577
|
-
end
|
578
|
-
|
579
|
-
# The second one:
|
580
|
-
Sequel.migration do
|
581
|
-
change do
|
582
|
-
create_table :async_messages do
|
583
|
-
column :uuid, :uuid, primary_key: true
|
584
|
-
String :type, null: false
|
585
|
-
Integer :version, null: false
|
586
|
-
String :publisher, null: false
|
587
|
-
column :data, :json, null: false
|
588
|
-
String :client_error_message, null: true, default: nil
|
589
|
-
column :client_error_details, :json, null: true, default: nil
|
590
|
-
DateTime :sent_at, null: true, default: nil
|
591
|
-
DateTime :received_at, null: false, default: Sequel::CURRENT_TIMESTAMP
|
592
|
-
DateTime :processed_at, null: true, default: nil
|
593
|
-
end
|
594
|
-
end
|
595
|
-
end
|
596
|
-
```
|
597
|
-
|
598
|
-
And don't forget to add it to the config file:
|
599
|
-
|
600
|
-
```ruby
|
601
|
-
# 'config/initializers/cyclone_lariat.rb'
|
602
|
-
CycloneLariat.tap do |cl|
|
603
|
-
cl.events_dataset = DB[:async_messages]
|
604
|
-
end
|
605
|
-
```
|
606
|
-
|
607
|
-
### Rake tasks
|
804
|
+
## Rake tasks
|
608
805
|
|
609
|
-
For simplify write some Rake tasks you can use `CycloneLariat::Repo`.
|
806
|
+
For simplify write some Rake tasks you can use `CycloneLariat::Repo::Messages`.
|
610
807
|
|
611
808
|
```ruby
|
612
809
|
# For retry all unprocessed
|
613
810
|
|
614
|
-
CycloneLariat.new
|
811
|
+
CycloneLariat::Repo::Messages.new.each_unprocessed do |event|
|
615
812
|
# Your logic here
|
616
813
|
end
|
617
814
|
|
618
815
|
# For retry all events with client errors
|
619
816
|
|
620
|
-
CycloneLariat.new
|
817
|
+
CycloneLariat::Repo::Messages.new.each_with_client_errors do |event|
|
621
818
|
# Your logic here
|
622
819
|
end
|
623
820
|
```
|