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.
Files changed (73) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/gem-push.yml +4 -4
  3. data/.rubocop.yml +9 -5
  4. data/Gemfile.lock +123 -21
  5. data/Guardfile +42 -0
  6. data/README.md +417 -220
  7. data/bin/cyclone_lariat +75 -43
  8. data/cyclone_lariat.gemspec +10 -3
  9. data/lib/cyclone_lariat/clients/abstract.rb +40 -0
  10. data/lib/cyclone_lariat/clients/sns.rb +163 -0
  11. data/lib/cyclone_lariat/clients/sqs.rb +114 -0
  12. data/lib/cyclone_lariat/core.rb +21 -0
  13. data/lib/cyclone_lariat/errors.rb +16 -0
  14. data/lib/cyclone_lariat/fake.rb +19 -0
  15. data/lib/cyclone_lariat/generators/command.rb +53 -0
  16. data/lib/cyclone_lariat/generators/event.rb +52 -0
  17. data/lib/cyclone_lariat/generators/queue.rb +30 -0
  18. data/lib/cyclone_lariat/generators/topic.rb +29 -0
  19. data/lib/cyclone_lariat/messages/v1/abstract.rb +139 -0
  20. data/lib/cyclone_lariat/messages/v1/command.rb +20 -0
  21. data/lib/cyclone_lariat/messages/v1/event.rb +20 -0
  22. data/lib/cyclone_lariat/messages/v1/validator.rb +31 -0
  23. data/lib/cyclone_lariat/messages/v2/abstract.rb +149 -0
  24. data/lib/cyclone_lariat/messages/v2/command.rb +20 -0
  25. data/lib/cyclone_lariat/messages/v2/event.rb +20 -0
  26. data/lib/cyclone_lariat/messages/v2/validator.rb +39 -0
  27. data/lib/cyclone_lariat/middleware.rb +9 -6
  28. data/lib/cyclone_lariat/migration.rb +54 -117
  29. data/lib/cyclone_lariat/options.rb +52 -0
  30. data/lib/cyclone_lariat/presenters/graph.rb +54 -0
  31. data/lib/cyclone_lariat/presenters/queues.rb +41 -0
  32. data/lib/cyclone_lariat/presenters/subscriptions.rb +34 -0
  33. data/lib/cyclone_lariat/presenters/topics.rb +40 -0
  34. data/lib/cyclone_lariat/publisher.rb +25 -0
  35. data/lib/cyclone_lariat/repo/active_record/messages.rb +92 -0
  36. data/lib/cyclone_lariat/repo/active_record/versions.rb +28 -0
  37. data/lib/cyclone_lariat/repo/messages.rb +43 -0
  38. data/lib/cyclone_lariat/repo/messages_mapper.rb +49 -0
  39. data/lib/cyclone_lariat/repo/sequel/messages.rb +73 -0
  40. data/lib/cyclone_lariat/repo/sequel/versions.rb +28 -0
  41. data/lib/cyclone_lariat/repo/versions.rb +42 -0
  42. data/lib/cyclone_lariat/resources/queue.rb +167 -0
  43. data/lib/cyclone_lariat/resources/topic.rb +132 -0
  44. data/lib/cyclone_lariat/services/migrate.rb +51 -0
  45. data/lib/cyclone_lariat/services/rollback.rb +51 -0
  46. data/lib/cyclone_lariat/version.rb +1 -1
  47. data/lib/cyclone_lariat.rb +4 -11
  48. data/lib/tasks/console.rake +1 -1
  49. data/lib/tasks/cyclone_lariat.rake +10 -12
  50. data/lib/tasks/db.rake +0 -15
  51. metadata +127 -27
  52. data/config/db.example.rb +0 -9
  53. data/config/initializers/sequel.rb +0 -7
  54. data/db/migrate/01_add_uuid_extensions.rb +0 -15
  55. data/db/migrate/02_add_events.rb +0 -19
  56. data/db/migrate/03_add_versions.rb +0 -9
  57. data/docs/_imgs/graphviz_01.png +0 -0
  58. data/docs/_imgs/graphviz_02.png +0 -0
  59. data/docs/_imgs/graphviz_03.png +0 -0
  60. data/docs/_imgs/lariat.jpg +0 -0
  61. data/docs/_imgs/logic.png +0 -0
  62. data/docs/_imgs/sqs_sns_diagram.png +0 -0
  63. data/lib/cyclone_lariat/abstract/client.rb +0 -112
  64. data/lib/cyclone_lariat/abstract/message.rb +0 -98
  65. data/lib/cyclone_lariat/command.rb +0 -13
  66. data/lib/cyclone_lariat/configure.rb +0 -15
  67. data/lib/cyclone_lariat/event.rb +0 -13
  68. data/lib/cyclone_lariat/messages_mapper.rb +0 -46
  69. data/lib/cyclone_lariat/messages_repo.rb +0 -60
  70. data/lib/cyclone_lariat/queue.rb +0 -147
  71. data/lib/cyclone_lariat/sns_client.rb +0 -149
  72. data/lib/cyclone_lariat/sqs_client.rb +0 -93
  73. 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). It saves all events to the database and also catches and throws all exceptions.
5
- - As a middleware, it can log all incoming messages.
6
- - As a client that can send messages to SNS topics and SQS queues.
7
- - Also it can help you with CI\CD for theme, queue and subscription management like database migration.
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
- # Client / Publisher
13
- At first lets understand what the difference between SQS and SNS:
14
- - Amazon Simple Queue Service (SQS) lets you send, store, and receive messages between software components at any
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
- Last command will create 2 files:
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
- ## Configuration
42
- ```ruby
43
- # frozen_string_literal: true
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
- CycloneLariat.tap do |cl|
46
- cl.default_version = 1 # api version
47
- cl.aws_key = ENV['AWS_KEY'] # aws key
48
- cl.aws_account_id = ENV['AWS_ACCOUNT_ID'] # aws account id
49
- cl.aws_secret_key = ENV['AWS_SECRET_KEY'] # aws secret
50
- cl.aws_default_region = ENV['AWS_REGION'] # aws default region
51
- cl.publisher = ENV['APP_NAME'] # name of your publishers, usually name of your application
52
- cl.default_instance = ENV['INSTANCE'] # stage, production, test
53
- cl.events_dataset = DB[:events] # sequel dataset for store income messages, for receiver
54
- cl.versions_dataset = DB[:lariat_versions] # sequel dataset for migrations, for publisher
55
- end
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
- Before creating the first migration, let's explain what `CycloneLariat::Message` is.
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
- ## Messages
63
- Message in Amazon SQS\SNS service it's a
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": 1, // Version of data structure
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" // Time when message was sended in ISO8601 Standard
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
- Idea about X-Request-Id you can see at
85
- [StackOverflow](https://stackoverflow.com/questions/25433258/what-is-the-x-request-id-http-header).
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
- As you see, type has prefix 'event_' in cyclone lariat you has two kinds of messages - _Event_ and _Command_.
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
- ### Command vs Event
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 _Event_ or _Commands_, you have two ways, send _Message_ directly:
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.tap do |cl|
110
- # Config app here
280
+ CycloneLariat.configure do |config|
281
+ # Options app here
111
282
  end
112
283
 
113
- client = CycloneLariat::SnsClient.new(instance: 'auth', version: 2)
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": 2,
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" // Time when message was sended in ISO8601 Standard
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 is it better to make your own client, like a [Repository](https://deviq.com/design-patterns/repository-pattern) pattern.
140
-
310
+ Or for second schema version code:
141
311
  ```ruby
142
- require 'cyclone_lariat/sns_client' # If require: false in Gemfile
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
- class YourClient < CycloneLariat::SnsClient
145
- version 1
146
- publisher 'pilot'
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: true
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: true
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
- client = YourClient.new
354
+ publisher = Publisher.new
165
355
 
166
356
  # And send topics
167
- client.email_is_created 'john.doe@example.com'
168
- client.email_is_removed 'john.doe@example.com'
169
- client.delete_user 'john.doe@example.com'
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
- ### Topics and Queue
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
- [FIFO](https://aws.amazon.com/blogs/aws/introducing-amazon-sns-fifo-first-in-first-out-pub-sub-messaging/), they must
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 client_id usually set using the **cyclone_lariat** [configuration](#Configuration).
384
+ Region and account_id usually set using the **cyclone_lariat** [configuration](#Configuration).
196
385
 
197
- ## Declaration for topic and queues name
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.tap do |cfg|
203
- cfg.instance = 'test'
204
- cfg.publisher = 'cyclone_lariat'
391
+ CycloneLariat.configure do |config|
392
+ config.instance = 'test'
393
+ config.publisher = 'cyclone_lariat'
205
394
  # ...
206
395
  end
207
396
 
208
- CycloneLariat::SnsClient.new.publish_command('register_user', data: {
209
- first_name: 'John',
210
- last_name: 'Doe',
211
- mail: 'john.doe@example.com'
212
- }, fifo: true
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('register_user', data: { mail: mail }), fifo: true
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::SqsClient.new.publish_event(
237
- 'register_user', data: { mail: 'john.doe@example.com' },
238
- dest: :mailer, fifo: true
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
- # or in repository-like style:
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: true
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::SqsClient.new.publish_event(
268
- 'register_user', data: { mail: 'john.doe@example.com' },
269
- dest: :mailer, topic: 'custom_topic_name.fifo', fifo: true
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 YourClient < CycloneLariat::SnsClient
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: true
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: `custom_topic` with fifo suffix.
482
+ Will publish message on queue: `custom_topic_name`
283
483
 
284
- # Migrations
285
484
 
286
- With **cyclone_lariat** you can use migrations that can create, delete, and subscribe to your queues and topics, just like database migrations do.
287
- To store versions of **cyclone_lariat** migrations, you need to create a database table.
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
- # frozen_string_literal: true
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
- Sequel.migration do
293
- change do
294
- create_table :lariat_versions do
295
- Integer :version, null: false, unique: true
296
- end
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
- After migrate this database migration, create **cyclone_lariat** migration.
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
- #### Example: one-to-many
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
- #### Example: many-to-one
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
- #### Example fanout-to-fanout
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(:user_created, fifo: false),
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
- # Console tasks
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
- # Subscriber
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
- # Middleware
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 'cyclone_lariat/sqs_client' # If you want use queue name helper
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: 'your_sqs_queue_name.fifo'
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: LunaPark::Notifiers::Sentry.new,
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
- ## Migrations
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(DB[:events]).each_unprocessed do |event|
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(DB[:events]).each_with_client_errors do |event|
817
+ CycloneLariat::Repo::Messages.new.each_with_client_errors do |event|
621
818
  # Your logic here
622
819
  end
623
820
  ```