cyclone_lariat 1.0.0.rc5 → 1.0.0.rc6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +6 -0
- data/README.md +88 -17
- data/bin/cyclone_lariat +12 -12
- data/lib/cyclone_lariat/generators/event.rb +1 -0
- data/lib/cyclone_lariat/messages/abstract.rb +1 -1
- data/lib/cyclone_lariat/middleware.rb +2 -2
- data/lib/cyclone_lariat/options.rb +13 -13
- data/lib/cyclone_lariat/plugins/outbox/configurable.rb +18 -0
- data/lib/cyclone_lariat/plugins/outbox/extensions/active_record_outbox.rb +22 -0
- data/lib/cyclone_lariat/plugins/outbox/extensions/sequel_outbox.rb +22 -0
- data/lib/cyclone_lariat/plugins/outbox/loadable.rb +24 -0
- data/lib/cyclone_lariat/plugins/outbox/mappers/messages.rb +35 -0
- data/lib/cyclone_lariat/plugins/outbox/repo/active_record/messages.rb +65 -0
- data/lib/cyclone_lariat/plugins/outbox/repo/messages.rb +42 -0
- data/lib/cyclone_lariat/plugins/outbox/repo/sequel/messages.rb +61 -0
- data/lib/cyclone_lariat/plugins/outbox/services/resend.rb +36 -0
- data/lib/cyclone_lariat/plugins/outbox.rb +55 -0
- data/lib/cyclone_lariat/repo/active_record/{messages.rb → inbox_messages.rb} +4 -4
- data/lib/cyclone_lariat/repo/{messages.rb → inbox_messages.rb} +7 -7
- data/lib/cyclone_lariat/repo/mappers/base.rb +29 -0
- data/lib/cyclone_lariat/repo/mappers/inbox_messages.rb +35 -0
- data/lib/cyclone_lariat/repo/sequel/{messages.rb → inbox_messages.rb} +6 -6
- data/lib/cyclone_lariat/version.rb +1 -1
- metadata +17 -6
- data/lib/cyclone_lariat/repo/messages_mapper.rb +0 -49
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fab5b60b7bc9091b050be9f19f409dbb108b868c
|
4
|
+
data.tar.gz: 4c2458fe43a0353b5d2a8e1977858889220a9cb8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: '090c611cb5e0bd49c4654042fbd285472b369448887789ff2392fd279dc68f12ebbb82f734254da9ae11750f38423749461f8a794c09370d85b06e11b097c73c'
|
7
|
+
data.tar.gz: 1b66103e5350f18335b70789246be6379f21c92ea6c1e92d181e3439e1039f96558ef2fd9c97eb6d95b77bcb21bff61f8c8880cfbd8a8c342a02a3620032f50c
|
data/CHANGELOG.md
CHANGED
@@ -4,6 +4,12 @@ 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
|
+
## [1.0.0.rc6]
|
8
|
+
Changed
|
9
|
+
- Rename `messages_dataset` to `inbox_dataset`
|
10
|
+
Added
|
11
|
+
- `CycloneLariat::Outbox` - implementation of the transactional outbox pattern
|
12
|
+
|
7
13
|
## [1.0.0.rc5]
|
8
14
|
Changed
|
9
15
|
- Update Gemfile.lock
|
data/README.md
CHANGED
@@ -67,7 +67,7 @@ Last install command will create 2 files:
|
|
67
67
|
c.publisher = ENV['APP_NAME'] # name of your publishers, usually name of your application
|
68
68
|
c.instance = ENV['INSTANCE'] # stage, production, test
|
69
69
|
c.driver = :sequel # driver Sequel
|
70
|
-
c.
|
70
|
+
c.inbox_dataset = DB[:inbox_messages] # Sequel dataset for store incoming messages (on receiver)
|
71
71
|
c.versions_dataset = DB[:lariat_versions] # Sequel dataset for versions of publisher migrations
|
72
72
|
c.fake_publish = ENV['INSTANCE'] == 'test' # when true, prevents messages from being published
|
73
73
|
end
|
@@ -93,7 +93,7 @@ Last install command will create 2 files:
|
|
93
93
|
|
94
94
|
Sequel.migration do
|
95
95
|
change do
|
96
|
-
create_table :
|
96
|
+
create_table :inbox_messages do
|
97
97
|
column :uuid, :uuid, primary_key: true
|
98
98
|
String :type, null: false
|
99
99
|
Integer :version, null: false
|
@@ -124,19 +124,19 @@ Last install command will create 2 files:
|
|
124
124
|
# frozen_string_literal: true
|
125
125
|
|
126
126
|
CycloneLariat.configure do |c|
|
127
|
-
c.version = 1
|
127
|
+
c.version = 1 # api version
|
128
128
|
|
129
|
-
c.aws_key = ENV['AWS_KEY']
|
130
|
-
c.aws_secret_key = ENV['AWS_SECRET_KEY']
|
131
|
-
c.aws_account_id = ENV['AWS_ACCOUNT_ID']
|
132
|
-
c.aws_region = ENV['AWS_REGION']
|
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
133
|
|
134
|
-
c.publisher = ENV['APP_NAME']
|
135
|
-
c.instance = ENV['INSTANCE']
|
136
|
-
c.driver = :active_record
|
137
|
-
c.
|
138
|
-
c.versions_dataset = CycloneLariatVersion
|
139
|
-
c.fake_publish = ENV['INSTANCE'] == 'test'
|
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.inbox_dataset = CycloneLariatInboxMessage # 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
140
|
end
|
141
141
|
```
|
142
142
|
|
@@ -734,7 +734,7 @@ $ rake cyclone_lariat:graph # Make graph
|
|
734
734
|
Graph generated in [grpahviz](https://graphviz.org/) format for the entry scheme. You should install
|
735
735
|
it on your system. For convert it in png use:
|
736
736
|
```bash
|
737
|
-
$ rake cyclone_lariat:
|
737
|
+
$ rake cyclone_lariat:graph | dot -Tpng -o foo.png
|
738
738
|
```
|
739
739
|
|
740
740
|
## Subscriber
|
@@ -801,20 +801,91 @@ class Receiver
|
|
801
801
|
end
|
802
802
|
```
|
803
803
|
|
804
|
+
## Transactional outbox
|
805
|
+
|
806
|
+
This extension allows you to save messages to a database inside a transaction. It prevents messages from being lost when publishing fails. After the transaction is copmpleted, publishing will be perfromed and successfully published messages will be deleted from the database. For more information, see [Transactional outbox pattern](https://microservices.io/patterns/data/transactional-outbox.html)
|
807
|
+
|
808
|
+
|
809
|
+
### Configuration
|
810
|
+
|
811
|
+
```ruby
|
812
|
+
OutboxErrorLogger = LunaPark::Notifiers::Log.new
|
813
|
+
CycloneLariat::Outbox.configure do |config|
|
814
|
+
config.dataset = DB[:outbox_messages] # Outbox messages dataset. Sequel dataset or ActiveRecord model
|
815
|
+
config.on_sending_error = lambda do |event, error|
|
816
|
+
OutboxErrorLogger.error(error, details: event.to_h)
|
817
|
+
end
|
818
|
+
end
|
819
|
+
|
820
|
+
CycloneLariat::Outbox.load
|
821
|
+
```
|
822
|
+
|
823
|
+
Before using the outbox, add and apply this migration:
|
824
|
+
|
825
|
+
```ruby
|
826
|
+
# Sequel
|
827
|
+
DB.create_table :outbox_messages do
|
828
|
+
column :uuid, :uuid, primary_key: true
|
829
|
+
column :deduplication_id, String, null: true
|
830
|
+
column :group_id, String, null: true
|
831
|
+
column :serialized_message, :json, null: false
|
832
|
+
column :sending_error, String, null: true
|
833
|
+
DateTime :created_at, null: false, default: Sequel::CURRENT_TIMESTAMP
|
834
|
+
end
|
835
|
+
|
836
|
+
# ActiveRecord
|
837
|
+
create_table(:outbox_messages, id: :uuid, primary_key: :uuid, default: -> { 'public.uuid_generate_v4()' }) do |t|
|
838
|
+
t.string :deduplication_id, null: true
|
839
|
+
t.string :group_id, null: true
|
840
|
+
t.string :sending_error, null: true
|
841
|
+
t.jsonb :serialized_message, null: false
|
842
|
+
t.datetime :created_at, null: false, default: -> { 'CURRENT_TIMESTAMP' }
|
843
|
+
end
|
844
|
+
```
|
845
|
+
|
846
|
+
### Usage example
|
847
|
+
|
848
|
+
```ruby
|
849
|
+
# Sequel
|
850
|
+
DB.transaction(with_outbox: true) do |outbox|
|
851
|
+
some_action
|
852
|
+
outbox << CycloneLariat::Messages::V1::Event.new(...)
|
853
|
+
...
|
854
|
+
end
|
855
|
+
|
856
|
+
# ActiveRecord
|
857
|
+
ActiveRecord::Base.transaction(with_outbox: true) do |outbox|
|
858
|
+
some_action
|
859
|
+
outbox << CycloneLariat::Messages::V1::Event.new(...)
|
860
|
+
...
|
861
|
+
end
|
862
|
+
```
|
863
|
+
|
864
|
+
### Resending
|
865
|
+
|
866
|
+
To resend messages you can use the following service:
|
867
|
+
|
868
|
+
```ruby
|
869
|
+
CycloneLariat::Outbox::Services::Resend.call
|
870
|
+
```
|
871
|
+
|
872
|
+
This service tries to publish messages from the outbox table with `sending_error != nil`.
|
873
|
+
Successfully published messages will be removed.
|
874
|
+
|
804
875
|
## Rake tasks
|
805
876
|
|
806
|
-
For simplify write some Rake tasks you can use `CycloneLariat::Repo::
|
877
|
+
For simplify write some Rake tasks you can use `CycloneLariat::Repo::InboxMessages`.
|
807
878
|
|
808
879
|
```ruby
|
809
880
|
# For retry all unprocessed
|
810
881
|
|
811
|
-
CycloneLariat::Repo::
|
882
|
+
CycloneLariat::Repo::InboxMessages.new.each_unprocessed do |event|
|
812
883
|
# Your logic here
|
813
884
|
end
|
814
885
|
|
815
886
|
# For retry all events with client errors
|
816
887
|
|
817
|
-
CycloneLariat::Repo::
|
888
|
+
CycloneLariat::Repo::InboxMessages.new.each_with_client_errors do |event|
|
818
889
|
# Your logic here
|
819
890
|
end
|
820
891
|
```
|
data/bin/cyclone_lariat
CHANGED
@@ -70,7 +70,7 @@ module CycloneLariat
|
|
70
70
|
c.publisher = ENV['APP_NAME'] # name of your publishers, usually name of your application
|
71
71
|
c.instance = ENV['INSTANCE'] # stage, production, test
|
72
72
|
c.driver = :sequel # :sequel or :active_record
|
73
|
-
c.
|
73
|
+
c.inbox_dataset = DB[:inbox_messages] # Sequel dataset / ActiveRecord model for store income messages (on receiver)
|
74
74
|
c.versions_dataset = DB[:lariat_versions] # Sequel dataset / ActiveRecord model for publisher migrations
|
75
75
|
c.fake_publish = ENV['INSTANCE'] == 'test' # when true, prevents messages from being published
|
76
76
|
end
|
@@ -82,17 +82,17 @@ module CycloneLariat
|
|
82
82
|
# frozen_string_literal: true
|
83
83
|
|
84
84
|
CycloneLariat.configure do |c|
|
85
|
-
c.version = 1
|
86
|
-
c.aws_key = ENV['AWS_KEY']
|
87
|
-
c.aws_account_id = ENV['AWS_ACCOUNT_ID']
|
88
|
-
c.aws_secret_key = ENV['AWS_SECRET_KEY']
|
89
|
-
c.aws_region = ENV['AWS_REGION']
|
90
|
-
c.publisher = ENV['APP_NAME']
|
91
|
-
c.instance = ENV['INSTANCE']
|
92
|
-
c.driver = :active_record
|
93
|
-
c.
|
94
|
-
c.versions_dataset = CycloneLariatVersion
|
95
|
-
c.fake_publish = ENV['INSTANCE'] == 'test'
|
85
|
+
c.version = 1 # messages version
|
86
|
+
c.aws_key = ENV['AWS_KEY'] # aws key
|
87
|
+
c.aws_account_id = ENV['AWS_ACCOUNT_ID'] # aws account id
|
88
|
+
c.aws_secret_key = ENV['AWS_SECRET_KEY'] # aws secret
|
89
|
+
c.aws_region = ENV['AWS_REGION'] # aws default region
|
90
|
+
c.publisher = ENV['APP_NAME'] # name of your publishers, usually name of your application
|
91
|
+
c.instance = ENV['INSTANCE'] # stage, production, test
|
92
|
+
c.driver = :active_record # :sequel or :active_record
|
93
|
+
c.inbox_dataset = CycloneLariatInboxMessage # Sequel dataset / ActiveRecord model for store incoming messages (on receiver)
|
94
|
+
c.versions_dataset = CycloneLariatVersion # Sequel dataset / ActiveRecord model for publisher migrations
|
95
|
+
c.fake_publish = ENV['INSTANCE'] == 'test' # when true, prevents messages from being published
|
96
96
|
end
|
97
97
|
CONFIG
|
98
98
|
end
|
@@ -15,7 +15,7 @@ module CycloneLariat
|
|
15
15
|
attr :publisher, String, :new
|
16
16
|
attr :type, String, :new
|
17
17
|
|
18
|
-
attrs :client_error, :version, :data, :request_id, :sent_at,
|
18
|
+
attrs :client_error, :sending_error, :version, :data, :request_id, :sent_at,
|
19
19
|
:deduplication_id, :group_id, :processed_at, :received_at
|
20
20
|
|
21
21
|
# Make validation public
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'cyclone_lariat/repo/
|
3
|
+
require 'cyclone_lariat/repo/inbox_messages'
|
4
4
|
require 'cyclone_lariat/core'
|
5
5
|
require 'luna_park/errors'
|
6
6
|
require 'cyclone_lariat/messages/builder'
|
@@ -10,7 +10,7 @@ module CycloneLariat
|
|
10
10
|
class Middleware
|
11
11
|
attr_reader :config
|
12
12
|
|
13
|
-
def initialize(errors_notifier: nil, message_notifier: nil, repo: Repo::
|
13
|
+
def initialize(errors_notifier: nil, message_notifier: nil, repo: Repo::InboxMessages, **options)
|
14
14
|
@config = CycloneLariat::Options.wrap(options).merge!(CycloneLariat.config)
|
15
15
|
@events_repo = repo.new(**@config.to_h)
|
16
16
|
@message_notifier = message_notifier
|
@@ -6,7 +6,7 @@ module CycloneLariat
|
|
6
6
|
class Options < LunaPark::Values::Compound
|
7
7
|
attr_accessor :aws_key, :aws_secret_key, :publisher,
|
8
8
|
:aws_region, :instance, :aws_account_id,
|
9
|
-
:
|
9
|
+
:inbox_dataset, :version, :versions_dataset,
|
10
10
|
:driver, :fake_publish
|
11
11
|
|
12
12
|
# @param [CycloneLariat::Options, Hash] other
|
@@ -14,17 +14,17 @@ module CycloneLariat
|
|
14
14
|
def merge!(other)
|
15
15
|
other = self.class.wrap(other)
|
16
16
|
|
17
|
-
self.aws_key
|
18
|
-
self.aws_secret_key
|
19
|
-
self.publisher
|
20
|
-
self.aws_region
|
21
|
-
self.instance
|
22
|
-
self.aws_account_id
|
23
|
-
self.
|
24
|
-
self.version
|
25
|
-
self.versions_dataset
|
26
|
-
self.driver
|
27
|
-
self.fake_publish
|
17
|
+
self.aws_key ||= other.aws_key
|
18
|
+
self.aws_secret_key ||= other.aws_secret_key
|
19
|
+
self.publisher ||= other.publisher
|
20
|
+
self.aws_region ||= other.aws_region
|
21
|
+
self.instance ||= other.instance
|
22
|
+
self.aws_account_id ||= other.aws_account_id
|
23
|
+
self.inbox_dataset ||= other.inbox_dataset
|
24
|
+
self.version ||= other.version
|
25
|
+
self.versions_dataset ||= other.versions_dataset
|
26
|
+
self.driver ||= other.driver
|
27
|
+
self.fake_publish ||= other.fake_publish
|
28
28
|
|
29
29
|
self
|
30
30
|
end
|
@@ -41,7 +41,7 @@ module CycloneLariat
|
|
41
41
|
aws_region: aws_region,
|
42
42
|
instance: instance,
|
43
43
|
aws_account_id: aws_account_id,
|
44
|
-
|
44
|
+
inbox_dataset: inbox_dataset,
|
45
45
|
version: version,
|
46
46
|
versions_dataset: versions_dataset,
|
47
47
|
driver: driver,
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CycloneLariat
|
4
|
+
class Outbox
|
5
|
+
module Configurable
|
6
|
+
CONFIG_ATTRS = %i[dataset on_sending_error].freeze
|
7
|
+
|
8
|
+
def config
|
9
|
+
@config ||= Struct.new(*CONFIG_ATTRS).new
|
10
|
+
end
|
11
|
+
|
12
|
+
def configure
|
13
|
+
yield(config) if block_given?
|
14
|
+
config
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CycloneLariat
|
4
|
+
class Outbox
|
5
|
+
module Extensions
|
6
|
+
module ActiveRecordOutbox
|
7
|
+
def transaction(opts = {}, &block)
|
8
|
+
opts = opts.dup
|
9
|
+
return super unless opts.delete(:with_outbox)
|
10
|
+
|
11
|
+
outbox = CycloneLariat::Outbox.new
|
12
|
+
result = super(opts) do
|
13
|
+
block.call(outbox)
|
14
|
+
end
|
15
|
+
|
16
|
+
outbox.publish
|
17
|
+
result
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CycloneLariat
|
4
|
+
class Outbox
|
5
|
+
module Extensions
|
6
|
+
module SequelOutbox
|
7
|
+
def transaction(opts = {}, &block)
|
8
|
+
opts = Sequel::OPTS.dup.merge(opts)
|
9
|
+
return super unless opts.delete(:with_outbox)
|
10
|
+
|
11
|
+
outbox = CycloneLariat::Outbox.new
|
12
|
+
result = super(opts) do |conn|
|
13
|
+
block.call(outbox, conn)
|
14
|
+
end
|
15
|
+
|
16
|
+
outbox.publish
|
17
|
+
result
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CycloneLariat
|
4
|
+
class Outbox
|
5
|
+
module Loadable
|
6
|
+
def load
|
7
|
+
extend_driver_transaction
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def extend_driver_transaction
|
13
|
+
case CycloneLariat.config.driver
|
14
|
+
when :sequel
|
15
|
+
Sequel::Database.prepend(Outbox::Extensions::SequelOutbox)
|
16
|
+
when :active_record
|
17
|
+
ActiveRecord::ConnectionAdapters::AbstractAdapter.prepend(Outbox::Extensions::ActiveRecordOutbox)
|
18
|
+
else
|
19
|
+
raise ArgumentError, "Undefined driver `#{driver}`"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cyclone_lariat/repo/mappers/base'
|
4
|
+
|
5
|
+
module CycloneLariat
|
6
|
+
class Outbox
|
7
|
+
module Mappers
|
8
|
+
class Messages < CycloneLariat::Repo::Mappers::Base
|
9
|
+
class << self
|
10
|
+
def from_row(row)
|
11
|
+
return if row.nil?
|
12
|
+
|
13
|
+
attrs = hash_from_json_column(row[:serialized_message]).symbolize_keys
|
14
|
+
attrs[:uuid] = row[:uuid]
|
15
|
+
attrs[:deduplication_id] = row[:deduplication_id]
|
16
|
+
attrs[:group_id] = row[:group_id]
|
17
|
+
attrs[:sending_error] = row[:sending_error]
|
18
|
+
|
19
|
+
attrs
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_row(input)
|
23
|
+
{}.tap do |row|
|
24
|
+
row[:uuid] = input.uuid if input.uuid
|
25
|
+
row[:deduplication_id] = input.deduplication_id
|
26
|
+
row[:group_id] = input.group_id
|
27
|
+
row[:serialized_message] = input.to_json
|
28
|
+
row[:sending_error] = input.sending_error
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cyclone_lariat/messages/v1/event'
|
4
|
+
require 'cyclone_lariat/messages/v1/command'
|
5
|
+
require 'cyclone_lariat/messages/builder'
|
6
|
+
require 'cyclone_lariat/plugins/outbox/mappers/messages'
|
7
|
+
|
8
|
+
module CycloneLariat
|
9
|
+
class Outbox
|
10
|
+
module Repo
|
11
|
+
module ActiveRecord
|
12
|
+
class Messages
|
13
|
+
LIMIT = 1000
|
14
|
+
|
15
|
+
attr_reader :dataset
|
16
|
+
|
17
|
+
def initialize(dataset)
|
18
|
+
@dataset = dataset
|
19
|
+
end
|
20
|
+
|
21
|
+
def create(msg)
|
22
|
+
dataset.create(Outbox::Mappers::Messages.to_row(msg)).uuid
|
23
|
+
end
|
24
|
+
|
25
|
+
def delete(uuid)
|
26
|
+
dataset.where(uuid: uuid).delete_all
|
27
|
+
end
|
28
|
+
|
29
|
+
def update_error(uuid, error_message)
|
30
|
+
dataset.where(uuid: uuid).update(sending_error: error_message)
|
31
|
+
end
|
32
|
+
|
33
|
+
def each_with_error
|
34
|
+
dataset
|
35
|
+
.where('sending_error IS NOT NULL')
|
36
|
+
.order(created_at: :asc)
|
37
|
+
.limit(LIMIT)
|
38
|
+
.each do |row|
|
39
|
+
msg = build_message_from_ar_row(row)
|
40
|
+
yield(msg)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def transaction(&block)
|
45
|
+
dataset.transaction(&block)
|
46
|
+
end
|
47
|
+
|
48
|
+
def lock(uuid)
|
49
|
+
dataset.lock('FOR UPDATE NOWAIT').where(uuid: uuid)
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def build_message_from_ar_row(row)
|
55
|
+
build Outbox::Mappers::Messages.from_row(row.attributes.symbolize_keys)
|
56
|
+
end
|
57
|
+
|
58
|
+
def build(raw)
|
59
|
+
CycloneLariat::Messages::Builder.new(raw_message: raw).call
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'forwardable'
|
4
|
+
require 'luna_park/extensions/injector'
|
5
|
+
require 'cyclone_lariat/plugins/outbox/repo/active_record/messages'
|
6
|
+
require 'cyclone_lariat/plugins/outbox/repo/sequel/messages'
|
7
|
+
|
8
|
+
module CycloneLariat
|
9
|
+
class Outbox
|
10
|
+
module Repo
|
11
|
+
class Messages
|
12
|
+
include LunaPark::Extensions::Injector
|
13
|
+
|
14
|
+
dependency(:sequel_messages_class) { Repo::Sequel::Messages }
|
15
|
+
dependency(:active_record_messages_class) { Repo::ActiveRecord::Messages }
|
16
|
+
dependency(:general_config) { CycloneLariat.config }
|
17
|
+
|
18
|
+
extend Forwardable
|
19
|
+
|
20
|
+
def_delegators :driver, :transaction, :lock, :update_error, :create, :delete, :each_with_error
|
21
|
+
|
22
|
+
def driver
|
23
|
+
@driver ||= select_driver
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def select_driver
|
29
|
+
case general_config.driver
|
30
|
+
when :sequel then sequel_messages_class.new(config.dataset)
|
31
|
+
when :active_record then active_record_messages_class.new(config.dataset)
|
32
|
+
else raise ArgumentError, "Undefined driver `#{general_config.driver}`"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def config
|
37
|
+
@config ||= CycloneLariat::Outbox.config
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cyclone_lariat/messages/v1/event'
|
4
|
+
require 'cyclone_lariat/messages/v1/command'
|
5
|
+
require 'cyclone_lariat/messages/builder'
|
6
|
+
require 'cyclone_lariat/plugins/outbox/mappers/messages'
|
7
|
+
|
8
|
+
module CycloneLariat
|
9
|
+
class Outbox
|
10
|
+
module Repo
|
11
|
+
module Sequel
|
12
|
+
class Messages
|
13
|
+
LIMIT = 1000
|
14
|
+
|
15
|
+
attr_reader :dataset
|
16
|
+
|
17
|
+
def initialize(dataset)
|
18
|
+
@dataset = dataset
|
19
|
+
end
|
20
|
+
|
21
|
+
def create(msg)
|
22
|
+
dataset.returning.insert(Outbox::Mappers::Messages.to_row(msg)).first[:uuid]
|
23
|
+
end
|
24
|
+
|
25
|
+
def delete(uuid)
|
26
|
+
dataset.where(uuid: uuid).delete
|
27
|
+
end
|
28
|
+
|
29
|
+
def update_error(uuid, error_message)
|
30
|
+
dataset.where(uuid: uuid).update(sending_error: error_message)
|
31
|
+
end
|
32
|
+
|
33
|
+
def each_with_error
|
34
|
+
dataset
|
35
|
+
.where { sending_error !~ nil }
|
36
|
+
.order(::Sequel.asc(:created_at))
|
37
|
+
.limit(LIMIT)
|
38
|
+
.each do |row|
|
39
|
+
msg = build Outbox::Mappers::Messages.from_row(row)
|
40
|
+
yield(msg)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def transaction(&block)
|
45
|
+
dataset.db.transaction(&block)
|
46
|
+
end
|
47
|
+
|
48
|
+
def lock(uuid)
|
49
|
+
dataset.where(uuid: uuid).for_update.nowait
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def build(raw)
|
55
|
+
CycloneLariat::Messages::Builder.new(raw_message: raw).call
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'luna_park/extensions/callable'
|
4
|
+
require 'luna_park/extensions/injector'
|
5
|
+
require 'cyclone_lariat/clients/sns'
|
6
|
+
require 'cyclone_lariat/plugins/outbox/repo/messages'
|
7
|
+
|
8
|
+
module CycloneLariat
|
9
|
+
class Outbox
|
10
|
+
module Services
|
11
|
+
class Resend
|
12
|
+
extend LunaPark::Extensions::Callable
|
13
|
+
include LunaPark::Extensions::Injector
|
14
|
+
|
15
|
+
dependency(:messages_repo) { CycloneLariat::Outbox::Repo::Messages.new }
|
16
|
+
dependency(:sns_client) { CycloneLariat::Clients::Sns.new }
|
17
|
+
dependency(:on_sending_error) { CycloneLariat::Outbox.config.on_sending_error }
|
18
|
+
|
19
|
+
def call
|
20
|
+
messages_repo.each_with_error do |message|
|
21
|
+
messages_repo.transaction do
|
22
|
+
begin
|
23
|
+
messages_repo.lock(message.uuid)
|
24
|
+
sns_client.publish message, fifo: message.fifo?
|
25
|
+
messages_repo.delete(message.uuid)
|
26
|
+
rescue StandardError => e
|
27
|
+
messages_repo.update_error(message.uuid, e.message)
|
28
|
+
on_sending_error&.call(message, e)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cyclone_lariat/core'
|
4
|
+
require 'cyclone_lariat/clients/sns'
|
5
|
+
require 'cyclone_lariat/plugins/outbox/configurable'
|
6
|
+
require 'cyclone_lariat/plugins/outbox/loadable'
|
7
|
+
require 'cyclone_lariat/plugins/outbox/extensions/active_record_outbox'
|
8
|
+
require 'cyclone_lariat/plugins/outbox/extensions/sequel_outbox'
|
9
|
+
require 'cyclone_lariat/plugins/outbox/repo/messages'
|
10
|
+
|
11
|
+
module CycloneLariat
|
12
|
+
class Outbox
|
13
|
+
extend CycloneLariat::Outbox::Configurable
|
14
|
+
extend CycloneLariat::Outbox::Loadable
|
15
|
+
include LunaPark::Extensions::Injector
|
16
|
+
|
17
|
+
dependency(:sns_client) { CycloneLariat::Clients::Sns.new }
|
18
|
+
dependency(:repo) { CycloneLariat::Outbox::Repo::Messages.new }
|
19
|
+
|
20
|
+
attr_reader :messages
|
21
|
+
|
22
|
+
def initialize
|
23
|
+
@messages = []
|
24
|
+
end
|
25
|
+
|
26
|
+
def publish
|
27
|
+
sent_message_uids = messages.each_with_object([]) do |message, sent_message_uuids|
|
28
|
+
begin
|
29
|
+
sns_client.publish message, fifo: message.fifo?
|
30
|
+
sent_message_uuids << message.uuid
|
31
|
+
rescue StandardError => e
|
32
|
+
repo.update_error(message.uuid, e.message)
|
33
|
+
config.on_sending_error&.call(message, e)
|
34
|
+
next
|
35
|
+
end
|
36
|
+
end
|
37
|
+
repo.delete(sent_message_uids) unless sent_message_uids.empty?
|
38
|
+
end
|
39
|
+
|
40
|
+
def <<(message)
|
41
|
+
message.uuid = repo.create(message)
|
42
|
+
messages << message
|
43
|
+
end
|
44
|
+
|
45
|
+
def push(message)
|
46
|
+
self << message
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def config
|
52
|
+
self.class.config
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -2,13 +2,13 @@
|
|
2
2
|
|
3
3
|
require 'cyclone_lariat/messages/v1/event'
|
4
4
|
require 'cyclone_lariat/messages/v1/command'
|
5
|
-
require 'cyclone_lariat/repo/
|
5
|
+
require 'cyclone_lariat/repo/mappers/inbox_messages'
|
6
6
|
require 'cyclone_lariat/messages/builder'
|
7
7
|
|
8
8
|
module CycloneLariat
|
9
9
|
module Repo
|
10
10
|
module ActiveRecord
|
11
|
-
class
|
11
|
+
class InboxMessages
|
12
12
|
attr_reader :dataset
|
13
13
|
|
14
14
|
def initialize(dataset)
|
@@ -24,7 +24,7 @@ module CycloneLariat
|
|
24
24
|
end
|
25
25
|
|
26
26
|
def create(msg)
|
27
|
-
dataset.create(
|
27
|
+
dataset.create(Mappers::InboxMessages.to_row(msg)).uuid
|
28
28
|
end
|
29
29
|
|
30
30
|
def exists?(uuid:)
|
@@ -68,7 +68,7 @@ module CycloneLariat
|
|
68
68
|
private
|
69
69
|
|
70
70
|
def build_message_from_ar_row(row)
|
71
|
-
build
|
71
|
+
build Mappers::InboxMessages.from_row(row.attributes.symbolize_keys)
|
72
72
|
end
|
73
73
|
|
74
74
|
def current_timestamp_from_db
|
@@ -3,18 +3,18 @@
|
|
3
3
|
require 'forwardable'
|
4
4
|
require 'luna_park/extensions/injector'
|
5
5
|
require 'cyclone_lariat/core'
|
6
|
-
require 'cyclone_lariat/repo/sequel/
|
7
|
-
require 'cyclone_lariat/repo/active_record/
|
6
|
+
require 'cyclone_lariat/repo/sequel/inbox_messages'
|
7
|
+
require 'cyclone_lariat/repo/active_record/inbox_messages'
|
8
8
|
|
9
9
|
module CycloneLariat
|
10
10
|
module Repo
|
11
|
-
class
|
11
|
+
class InboxMessages
|
12
12
|
include LunaPark::Extensions::Injector
|
13
13
|
|
14
14
|
attr_reader :config
|
15
15
|
|
16
|
-
dependency(:sequel_messages_class) { Repo::Sequel::
|
17
|
-
dependency(:active_record_messages_class) { Repo::ActiveRecord::
|
16
|
+
dependency(:sequel_messages_class) { Repo::Sequel::InboxMessages }
|
17
|
+
dependency(:active_record_messages_class) { Repo::ActiveRecord::InboxMessages }
|
18
18
|
|
19
19
|
extend Forwardable
|
20
20
|
|
@@ -33,8 +33,8 @@ module CycloneLariat
|
|
33
33
|
|
34
34
|
def select(driver:)
|
35
35
|
case driver
|
36
|
-
when :sequel then sequel_messages_class.new(config.
|
37
|
-
when :active_record then active_record_messages_class.new(config.
|
36
|
+
when :sequel then sequel_messages_class.new(config.inbox_dataset)
|
37
|
+
when :active_record then active_record_messages_class.new(config.inbox_dataset)
|
38
38
|
else raise ArgumentError, "Undefined driver `#{driver}`"
|
39
39
|
end
|
40
40
|
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CycloneLariat
|
4
|
+
module Repo
|
5
|
+
module Mappers
|
6
|
+
class Base
|
7
|
+
class << self
|
8
|
+
private
|
9
|
+
|
10
|
+
def hash_from_json_column(data)
|
11
|
+
return data if data.is_a?(Hash)
|
12
|
+
return JSON.parse(data) if data.is_a?(String)
|
13
|
+
|
14
|
+
if pg_json_extension_enabled?
|
15
|
+
return data.to_h if data.is_a?(::Sequel::Postgres::JSONHash)
|
16
|
+
return JSON.parse(data.to_s) if data.is_a?(::Sequel::Postgres::JSONString)
|
17
|
+
end
|
18
|
+
|
19
|
+
raise ArgumentError, "Unknown type of `#{data}`"
|
20
|
+
end
|
21
|
+
|
22
|
+
def pg_json_extension_enabled?
|
23
|
+
Object.const_defined?('Sequel::Postgres::JSONHash')
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cyclone_lariat/repo/mappers/base'
|
4
|
+
|
5
|
+
module CycloneLariat
|
6
|
+
module Repo
|
7
|
+
module Mappers
|
8
|
+
class InboxMessages < Base
|
9
|
+
class << self
|
10
|
+
def from_row(row)
|
11
|
+
return if row.nil?
|
12
|
+
|
13
|
+
row[:data] = hash_from_json_column(row[:data])
|
14
|
+
row[:client_error_details] = hash_from_json_column(row[:client_error_details]) if row[:client_error_details]
|
15
|
+
row
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_row(input)
|
19
|
+
{
|
20
|
+
uuid: input.uuid,
|
21
|
+
kind: input.kind,
|
22
|
+
type: input.type,
|
23
|
+
publisher: input.publisher,
|
24
|
+
data: JSON.generate(input.data),
|
25
|
+
client_error_message: input.client_error&.message,
|
26
|
+
client_error_details: JSON.generate(input.client_error&.details),
|
27
|
+
version: input.version,
|
28
|
+
sent_at: input.sent_at
|
29
|
+
}
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -2,13 +2,13 @@
|
|
2
2
|
|
3
3
|
require 'cyclone_lariat/messages/v1/event'
|
4
4
|
require 'cyclone_lariat/messages/v1/command'
|
5
|
-
require 'cyclone_lariat/repo/
|
5
|
+
require 'cyclone_lariat/repo/mappers/inbox_messages'
|
6
6
|
require 'cyclone_lariat/messages/builder'
|
7
7
|
|
8
8
|
module CycloneLariat
|
9
9
|
module Repo
|
10
10
|
module Sequel
|
11
|
-
class
|
11
|
+
class InboxMessages
|
12
12
|
attr_reader :dataset
|
13
13
|
|
14
14
|
def initialize(dataset)
|
@@ -24,7 +24,7 @@ module CycloneLariat
|
|
24
24
|
end
|
25
25
|
|
26
26
|
def create(msg)
|
27
|
-
dataset.insert
|
27
|
+
dataset.insert Mappers::InboxMessages.to_row(msg)
|
28
28
|
end
|
29
29
|
|
30
30
|
def exists?(uuid:)
|
@@ -42,19 +42,19 @@ module CycloneLariat
|
|
42
42
|
row = dataset.where(uuid: uuid).first
|
43
43
|
return if row.nil?
|
44
44
|
|
45
|
-
build
|
45
|
+
build Mappers::InboxMessages.from_row(row)
|
46
46
|
end
|
47
47
|
|
48
48
|
def each_unprocessed
|
49
49
|
dataset.where(processed_at: nil).each do |row|
|
50
|
-
msg = build
|
50
|
+
msg = build Mappers::InboxMessages.from_row(row)
|
51
51
|
yield(msg)
|
52
52
|
end
|
53
53
|
end
|
54
54
|
|
55
55
|
def each_with_client_errors
|
56
56
|
dataset.where { (processed_at !~ nil) & (client_error_message !~ nil) }.each do |row|
|
57
|
-
msg = build
|
57
|
+
msg = build Mappers::InboxMessages.from_row(row)
|
58
58
|
yield(msg)
|
59
59
|
end
|
60
60
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cyclone_lariat
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.0.
|
4
|
+
version: 1.0.0.rc6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Alexander Kudrin
|
@@ -11,7 +11,7 @@ authors:
|
|
11
11
|
autorequire:
|
12
12
|
bindir: bin
|
13
13
|
cert_chain: []
|
14
|
-
date: 2023-
|
14
|
+
date: 2023-04-19 00:00:00.000000000 Z
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
17
17
|
name: aws-sdk-sns
|
@@ -400,16 +400,27 @@ files:
|
|
400
400
|
- lib/cyclone_lariat/middleware.rb
|
401
401
|
- lib/cyclone_lariat/migration.rb
|
402
402
|
- lib/cyclone_lariat/options.rb
|
403
|
+
- lib/cyclone_lariat/plugins/outbox.rb
|
404
|
+
- lib/cyclone_lariat/plugins/outbox/configurable.rb
|
405
|
+
- lib/cyclone_lariat/plugins/outbox/extensions/active_record_outbox.rb
|
406
|
+
- lib/cyclone_lariat/plugins/outbox/extensions/sequel_outbox.rb
|
407
|
+
- lib/cyclone_lariat/plugins/outbox/loadable.rb
|
408
|
+
- lib/cyclone_lariat/plugins/outbox/mappers/messages.rb
|
409
|
+
- lib/cyclone_lariat/plugins/outbox/repo/active_record/messages.rb
|
410
|
+
- lib/cyclone_lariat/plugins/outbox/repo/messages.rb
|
411
|
+
- lib/cyclone_lariat/plugins/outbox/repo/sequel/messages.rb
|
412
|
+
- lib/cyclone_lariat/plugins/outbox/services/resend.rb
|
403
413
|
- lib/cyclone_lariat/presenters/graph.rb
|
404
414
|
- lib/cyclone_lariat/presenters/queues.rb
|
405
415
|
- lib/cyclone_lariat/presenters/subscriptions.rb
|
406
416
|
- lib/cyclone_lariat/presenters/topics.rb
|
407
417
|
- lib/cyclone_lariat/publisher.rb
|
408
|
-
- lib/cyclone_lariat/repo/active_record/
|
418
|
+
- lib/cyclone_lariat/repo/active_record/inbox_messages.rb
|
409
419
|
- lib/cyclone_lariat/repo/active_record/versions.rb
|
410
|
-
- lib/cyclone_lariat/repo/
|
411
|
-
- lib/cyclone_lariat/repo/
|
412
|
-
- lib/cyclone_lariat/repo/
|
420
|
+
- lib/cyclone_lariat/repo/inbox_messages.rb
|
421
|
+
- lib/cyclone_lariat/repo/mappers/base.rb
|
422
|
+
- lib/cyclone_lariat/repo/mappers/inbox_messages.rb
|
423
|
+
- lib/cyclone_lariat/repo/sequel/inbox_messages.rb
|
413
424
|
- lib/cyclone_lariat/repo/sequel/versions.rb
|
414
425
|
- lib/cyclone_lariat/repo/versions.rb
|
415
426
|
- lib/cyclone_lariat/resources/queue.rb
|
@@ -1,49 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module CycloneLariat
|
4
|
-
module Repo
|
5
|
-
class MessagesMapper
|
6
|
-
class << self
|
7
|
-
def from_row(row)
|
8
|
-
return if row.nil?
|
9
|
-
|
10
|
-
row[:data] = hash_from_json_column(row[:data])
|
11
|
-
row[:client_error_details] = hash_from_json_column(row[:client_error_details]) if row[:client_error_details]
|
12
|
-
row
|
13
|
-
end
|
14
|
-
|
15
|
-
def to_row(input)
|
16
|
-
{
|
17
|
-
uuid: input.uuid,
|
18
|
-
kind: input.kind,
|
19
|
-
type: input.type,
|
20
|
-
publisher: input.publisher,
|
21
|
-
data: JSON.generate(input.data),
|
22
|
-
client_error_message: input.client_error&.message,
|
23
|
-
client_error_details: JSON.generate(input.client_error&.details),
|
24
|
-
version: input.version,
|
25
|
-
sent_at: input.sent_at
|
26
|
-
}
|
27
|
-
end
|
28
|
-
|
29
|
-
private
|
30
|
-
|
31
|
-
def hash_from_json_column(data)
|
32
|
-
return data if data.is_a?(Hash)
|
33
|
-
return JSON.parse(data) if data.is_a?(String)
|
34
|
-
|
35
|
-
if pg_json_extension_enabled?
|
36
|
-
return data.to_h if data.is_a?(::Sequel::Postgres::JSONHash)
|
37
|
-
return JSON.parse(data.to_s) if data.is_a?(::Sequel::Postgres::JSONString)
|
38
|
-
end
|
39
|
-
|
40
|
-
raise ArgumentError, "Unknown type of `#{data}`"
|
41
|
-
end
|
42
|
-
|
43
|
-
def pg_json_extension_enabled?
|
44
|
-
Object.const_defined?('Sequel::Postgres::JSONHash')
|
45
|
-
end
|
46
|
-
end
|
47
|
-
end
|
48
|
-
end
|
49
|
-
end
|