pg_eventstore 0.3.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +14 -0
  3. data/CODE_OF_CONDUCT.md +1 -1
  4. data/README.md +2 -0
  5. data/db/migrations/10_create_subscription_commands.sql +15 -0
  6. data/db/migrations/11_create_subscriptions_set_commands.sql +15 -0
  7. data/db/migrations/12_improve_events_indexes.sql +1 -0
  8. data/db/migrations/13_remove_duplicated_index.sql +1 -0
  9. data/db/migrations/9_create_subscriptions.sql +46 -0
  10. data/docs/configuration.md +42 -21
  11. data/docs/linking_events.md +96 -0
  12. data/docs/reading_events.md +56 -0
  13. data/docs/subscriptions.md +170 -0
  14. data/lib/pg_eventstore/callbacks.rb +122 -0
  15. data/lib/pg_eventstore/client.rb +32 -2
  16. data/lib/pg_eventstore/commands/append.rb +3 -11
  17. data/lib/pg_eventstore/commands/event_modifiers/prepare_link_event.rb +22 -0
  18. data/lib/pg_eventstore/commands/event_modifiers/prepare_regular_event.rb +24 -0
  19. data/lib/pg_eventstore/commands/link_to.rb +33 -0
  20. data/lib/pg_eventstore/commands/regular_stream_read_paginated.rb +63 -0
  21. data/lib/pg_eventstore/commands/system_stream_read_paginated.rb +62 -0
  22. data/lib/pg_eventstore/commands.rb +5 -0
  23. data/lib/pg_eventstore/config.rb +35 -3
  24. data/lib/pg_eventstore/errors.rb +80 -0
  25. data/lib/pg_eventstore/{pg_result_deserializer.rb → event_deserializer.rb} +10 -22
  26. data/lib/pg_eventstore/extensions/callbacks_extension.rb +95 -0
  27. data/lib/pg_eventstore/extensions/options_extension.rb +69 -29
  28. data/lib/pg_eventstore/extensions/using_connection_extension.rb +35 -0
  29. data/lib/pg_eventstore/pg_connection.rb +20 -3
  30. data/lib/pg_eventstore/queries/event_queries.rb +18 -34
  31. data/lib/pg_eventstore/queries/event_type_queries.rb +24 -0
  32. data/lib/pg_eventstore/queries/preloader.rb +37 -0
  33. data/lib/pg_eventstore/queries/stream_queries.rb +14 -1
  34. data/lib/pg_eventstore/queries/subscription_command_queries.rb +81 -0
  35. data/lib/pg_eventstore/queries/subscription_queries.rb +166 -0
  36. data/lib/pg_eventstore/queries/subscriptions_set_command_queries.rb +76 -0
  37. data/lib/pg_eventstore/queries/subscriptions_set_queries.rb +89 -0
  38. data/lib/pg_eventstore/queries.rb +7 -0
  39. data/lib/pg_eventstore/query_builders/events_filtering_query.rb +17 -22
  40. data/lib/pg_eventstore/sql_builder.rb +54 -10
  41. data/lib/pg_eventstore/stream.rb +2 -1
  42. data/lib/pg_eventstore/subscriptions/basic_runner.rb +220 -0
  43. data/lib/pg_eventstore/subscriptions/command_handlers/subscription_feeder_commands.rb +52 -0
  44. data/lib/pg_eventstore/subscriptions/command_handlers/subscription_runners_commands.rb +68 -0
  45. data/lib/pg_eventstore/subscriptions/commands_handler.rb +62 -0
  46. data/lib/pg_eventstore/subscriptions/events_processor.rb +72 -0
  47. data/lib/pg_eventstore/subscriptions/runner_state.rb +45 -0
  48. data/lib/pg_eventstore/subscriptions/subscription.rb +141 -0
  49. data/lib/pg_eventstore/subscriptions/subscription_feeder.rb +171 -0
  50. data/lib/pg_eventstore/subscriptions/subscription_handler_performance.rb +39 -0
  51. data/lib/pg_eventstore/subscriptions/subscription_runner.rb +125 -0
  52. data/lib/pg_eventstore/subscriptions/subscription_runners_feeder.rb +38 -0
  53. data/lib/pg_eventstore/subscriptions/subscriptions_manager.rb +105 -0
  54. data/lib/pg_eventstore/subscriptions/subscriptions_set.rb +97 -0
  55. data/lib/pg_eventstore/tasks/setup.rake +5 -1
  56. data/lib/pg_eventstore/utils.rb +66 -0
  57. data/lib/pg_eventstore/version.rb +1 -1
  58. data/lib/pg_eventstore.rb +19 -1
  59. metadata +38 -4
@@ -0,0 +1,220 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PgEventstore
4
+ # Implements simple background jobs runner. The job execution is done via declaring a callback on the specific
5
+ # action. The implementation also allows you to hook into different places of life cycle of the runner by defining
6
+ # callbacks on various actions. Here is the list of available actions:
7
+ # - :before_runner_started. Happens before the runner's state switches from "initial"/"stopped" to "running" and
8
+ # runner's thread is started. It is also fired when the runner is restoring - right after :before_runner_restored
9
+ # action.
10
+ # - :after_runner_stopped. Happens after runner's state got switched from "running"/"dead" to "stopped" and runner's
11
+ # thread is terminated.
12
+ # - :before_runner_restored. Happens before runner's state gets switched from "dead" to "running" and runner's
13
+ # thread is started.
14
+ # - :process_async. Happens each @run_interval seconds within runner's thread.
15
+ # - :after_runner_died. Happens when runner's state switches to "dead" because of exception inside runner's thread.
16
+ # Callback function must be able to accept one argument - the exception which caused the runner to die will be
17
+ # passed.
18
+ # - :change_state. It happens each time the runner changes the state. Callback function must be able to accept one
19
+ # argument - current state will be passed.
20
+ #
21
+ # Example of BasicRunner usage:
22
+ # class MyAwesomeRunner
23
+ # extend Forwardable
24
+ #
25
+ # def_delegators :@basic_runner, :start, :stop, :wait_for_finish, :stop_async, :restore
26
+ #
27
+ # def initialize
28
+ # @basic_runner = PgEventstore::BasicRunner.new(1, 2)
29
+ # @jobs_performed = 0
30
+ # attach_runner_callbacks
31
+ # end
32
+ #
33
+ # private
34
+ #
35
+ # def attach_runner_callbacks
36
+ # @basic_runner.define_callback(:change_state, :after, method(:state_changed))
37
+ # @basic_runner.define_callback(:process_async, :before, method(:process_action))
38
+ # @basic_runner.define_callback(:process_async, :after, method(:count_jobs))
39
+ # @basic_runner.define_callback(:before_runner_started, :before, method(:before_runner_started))
40
+ # @basic_runner.define_callback(:after_runner_stopped, :before, method(:after_runner_stopped))
41
+ # @basic_runner.define_callback(:after_runner_died, :before, method(:after_runner_died))
42
+ # end
43
+ #
44
+ # def process_action
45
+ # raise "What's the point? I can not handle this any more!" if @jobs_performed >= 3
46
+ # puts "Doing some heavy lifting job"
47
+ # sleep 2 # Simulate long running job
48
+ # end
49
+ #
50
+ # def count_jobs
51
+ # @jobs_performed += 1
52
+ # end
53
+ #
54
+ # # @param state [String]
55
+ # def state_changed(state)
56
+ # puts "New state is #{state.inspect}"
57
+ # end
58
+ #
59
+ # def before_runner_started
60
+ # puts "Doing some preparations..."
61
+ # end
62
+ #
63
+ # def after_runner_stopped
64
+ # puts "You job is not processing any more. Total jobs performed: #{@jobs_performed}. Bye-bye!"
65
+ # end
66
+ #
67
+ # def after_runner_died(error)
68
+ # puts "Error occurred: #{error.inspect}"
69
+ # end
70
+ # end
71
+ #
72
+ # runner = MyAwesomeRunner.new
73
+ # runner.start # to start your background runner to process the job, defined by #process_action method
74
+ # runner.stop # to stop the runner
75
+ #
76
+ # See {PgEventstore::RunnerState} for the list of available states
77
+ # See {PgEventstore::CallbacksExtension} and {PgEventstore::Callbacks} for more info about how to use callbacks
78
+ class BasicRunner
79
+ extend Forwardable
80
+ include Extensions::CallbacksExtension
81
+
82
+ def_delegators :@state, :initial?, :running?, :halting?, :stopped?, :dead?
83
+
84
+ # @param run_interval [Integer, Float] seconds. Determines how often to run async task. Async task is determined by
85
+ # :after_runner_stopped callback
86
+ # @param async_shutdown_time [Integer, Float] seconds. Determines how long to wait for the async shutdown to wait
87
+ # for the runner to finish.
88
+ def initialize(run_interval, async_shutdown_time)
89
+ @run_interval = run_interval
90
+ @async_shutdown_time = async_shutdown_time
91
+ @state = RunnerState.new
92
+ @mutex = Thread::Mutex.new
93
+ delegate_change_state_cbx
94
+ end
95
+
96
+ # Start asynchronous runner. If the runner is dead - please use #restore to restart it.
97
+ # @return [self]
98
+ def start
99
+ synchronize do
100
+ return self unless @state.initial? || @state.stopped?
101
+
102
+ callbacks.run_callbacks(:before_runner_started)
103
+ _start
104
+ end
105
+ self
106
+ end
107
+
108
+ # Stop asynchronous runner. This operation is immediate and it won't be waiting for current job to finish - it will
109
+ # instantly halt it. If you care about the result of your async job - use #stop_async instead.
110
+ # @return [self]
111
+ def stop
112
+ synchronize do
113
+ return self unless @state.running? || @state.dead?
114
+
115
+ @runner&.exit
116
+ @runner = nil
117
+ @state.stopped!
118
+ callbacks.run_callbacks(:after_runner_stopped)
119
+ end
120
+ self
121
+ end
122
+
123
+ # Asynchronously stop asynchronous runner. This operation spawns another thread to gracefully stop the runner. It
124
+ # will wait up to @async_shutdown_time seconds before force-stopping the runner.
125
+ # @return [self]
126
+ def stop_async
127
+ synchronize do
128
+ return self unless @state.running? || @state.dead?
129
+
130
+ @state.halting!
131
+ Thread.new do
132
+ stopping_at = Time.now.utc
133
+ halt = false
134
+ loop do
135
+ synchronize do
136
+ # Give the runner up to @async_shutdown_time seconds for graceful shutdown
137
+ @runner&.exit if Time.now.utc - stopping_at > @async_shutdown_time
138
+
139
+ unless @runner&.alive?
140
+ @state.stopped!
141
+ @runner = nil
142
+ callbacks.run_callbacks(:after_runner_stopped)
143
+ halt = true
144
+ end
145
+ end
146
+ break if halt
147
+ sleep 0.1
148
+ end
149
+ end
150
+ self
151
+ end
152
+ end
153
+
154
+ # Restores the runner after its death.
155
+ # @return [self]
156
+ def restore
157
+ synchronize do
158
+ return self unless @state.dead?
159
+
160
+ callbacks.run_callbacks(:before_runner_restored)
161
+ _start
162
+ end
163
+ self
164
+ end
165
+
166
+ # Wait until the runner switches the state to either "stopped" or "dead". This operation is synchronous.
167
+ # @return [self]
168
+ def wait_for_finish
169
+ loop do
170
+ continue = synchronize do
171
+ @state.halting? || @state.running?
172
+ end
173
+ break unless continue
174
+
175
+ sleep 0.1
176
+ end
177
+ self
178
+ end
179
+
180
+ # @return [String]
181
+ def state
182
+ @state.to_s
183
+ end
184
+
185
+ private
186
+
187
+ def synchronize
188
+ @mutex.synchronize { yield }
189
+ end
190
+
191
+ # @return [void]
192
+ def _start
193
+ @state.running!
194
+ @runner = Thread.new do
195
+ loop do
196
+ Thread.current.exit unless @state.running?
197
+ sleep @run_interval
198
+
199
+ callbacks.run_callbacks(:process_async)
200
+ end
201
+ rescue => error
202
+ synchronize do
203
+ raise unless @state.halting? || @state.running?
204
+
205
+ @state.dead!
206
+ callbacks.run_callbacks(:after_runner_died, error)
207
+ end
208
+ end
209
+ end
210
+
211
+ # Delegates :change_state action to the runner
212
+ def delegate_change_state_cbx
213
+ @state.define_callback(:change_state, :before, method(:change_state))
214
+ end
215
+
216
+ def change_state(...)
217
+ callbacks.run_callbacks(:change_state, ...)
218
+ end
219
+ end
220
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PgEventstore
4
+ module CommandHandlers
5
+ class SubscriptionFeederCommands
6
+ AVAILABLE_COMMANDS = %w[StopAll StartAll].freeze
7
+
8
+ # @param config_name [Symbol]
9
+ # @param subscription_feeder [PgEventstore::SubscriptionFeeder]
10
+ def initialize(config_name, subscription_feeder)
11
+ @config_name = config_name
12
+ @subscription_feeder = subscription_feeder
13
+ end
14
+
15
+ # Look up commands for the given SubscriptionFeeder and execute them
16
+ # @return [void]
17
+ def process
18
+ queries.find_commands(@subscription_feeder.id).each do |command|
19
+ unless AVAILABLE_COMMANDS.include?(command[:name])
20
+ PgEventstore.logger&.warn(
21
+ "#{self.class.name}: Don't know how to handle #{command[:name].inspect}. Details: #{command.inspect}."
22
+ )
23
+ next
24
+ end
25
+ send(Utils.underscore_str(command[:name]))
26
+ ensure
27
+ queries.delete(command[:id])
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ # @return [PgEventstore::SubscriptionsSetCommandQueries]
34
+ def queries
35
+ SubscriptionsSetCommandQueries.new(connection)
36
+ end
37
+
38
+ # @return [PgEventstore::Connection]
39
+ def connection
40
+ PgEventstore.connection(@config_name)
41
+ end
42
+
43
+ def stop_all
44
+ @subscription_feeder.stop_all
45
+ end
46
+
47
+ def start_all
48
+ @subscription_feeder.start_all
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PgEventstore
4
+ module CommandHandlers
5
+ class SubscriptionRunnersCommands
6
+ AVAILABLE_COMMANDS = %w[StopRunner RestoreRunner StartRunner].freeze
7
+
8
+ # @param config_name [Symbol]
9
+ # @param runners [Array<PgEventstore::SubscriptionRunner>]
10
+ def initialize(config_name, runners)
11
+ @config_name = config_name
12
+ @runners = runners
13
+ end
14
+
15
+ # Look up commands for all given SubscriptionRunner-s and execute them
16
+ # @return [void]
17
+ def process
18
+ queries.find_commands(@runners.map(&:id)).each do |command|
19
+ unless AVAILABLE_COMMANDS.include?(command[:name])
20
+ PgEventstore.logger&.warn(
21
+ "#{self.class.name}: Don't know how to handle #{command[:name].inspect}. Details: #{command.inspect}."
22
+ )
23
+ next
24
+ end
25
+ send(Utils.underscore_str(command[:name]), command[:subscription_id])
26
+ ensure
27
+ queries.delete(command[:id])
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ # @return [PgEventstore::SubscriptionCommandQueries]
34
+ def queries
35
+ SubscriptionCommandQueries.new(connection)
36
+ end
37
+
38
+ # @return [PgEventstore::Connection]
39
+ def connection
40
+ PgEventstore.connection(@config_name)
41
+ end
42
+
43
+ # @param subscription_id [Integer]
44
+ # @return [PgEventstore::SubscriptionRunner, nil]
45
+ def find_subscription_runner(subscription_id)
46
+ @runners.find { |runner| runner.id == subscription_id }
47
+ end
48
+
49
+ # @param subscription_id [Integer]
50
+ # @return [void]
51
+ def start_runner(subscription_id)
52
+ find_subscription_runner(subscription_id)&.start
53
+ end
54
+
55
+ # @param subscription_id [Integer]
56
+ # @return [void]
57
+ def restore_runner(subscription_id)
58
+ find_subscription_runner(subscription_id)&.restore
59
+ end
60
+
61
+ # @param subscription_id [Integer]
62
+ # @return [void]
63
+ def stop_runner(subscription_id)
64
+ find_subscription_runner(subscription_id)&.stop_async
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'command_handlers/subscription_feeder_commands'
4
+ require_relative 'command_handlers/subscription_runners_commands'
5
+
6
+ module PgEventstore
7
+ # This class implements the runner which processes remote commands in the background. This allows you to remotely
8
+ # control such actions as stop, start and restart of your Subscriptions.
9
+ # @!visibility private
10
+ class CommandsHandler
11
+ extend Forwardable
12
+
13
+ RESTART_DELAY = 5 # seconds
14
+
15
+ def_delegators :@basic_runner, :start, :stop, :state, :stop_async, :wait_for_finish
16
+
17
+ # @param config_name [Symbol]
18
+ # @param subscription_feeder [PgEventstore::SUbscriptionFeeder]
19
+ # @param runners [Array<PgEventstore::SubscriptionRunner>]
20
+ def initialize(config_name, subscription_feeder, runners)
21
+ @config_name = config_name
22
+ @subscription_feeder = subscription_feeder
23
+ @runners = runners
24
+ @basic_runner = BasicRunner.new(1, 0)
25
+ attach_runner_callbacks
26
+ end
27
+
28
+ private
29
+
30
+ def attach_runner_callbacks
31
+ @basic_runner.define_callback(:process_async, :before, method(:process_async))
32
+ @basic_runner.define_callback(:after_runner_died, :before, method(:after_runner_died))
33
+ end
34
+
35
+ def process_async
36
+ subscription_feeder_commands.process
37
+ subscription_runners_commands.process
38
+ end
39
+
40
+ # @param error [StandardError]
41
+ # @return [void]
42
+ def after_runner_died(error)
43
+ PgEventstore.logger&.error "#{self.class.name}: Error occurred: #{error.message}"
44
+ PgEventstore.logger&.error "#{self.class.name}: Backtrace: #{error.backtrace&.join("\n")}"
45
+ PgEventstore.logger&.error "#{self.class.name}: Trying to auto-repair in #{RESTART_DELAY} seconds..."
46
+ Thread.new do
47
+ sleep RESTART_DELAY
48
+ @basic_runner.restore
49
+ end
50
+ end
51
+
52
+ # @return [PgEventstore::CommandHandlers::SubscriptionFeederCommands]
53
+ def subscription_feeder_commands
54
+ CommandHandlers::SubscriptionFeederCommands.new(@config_name, @subscription_feeder)
55
+ end
56
+
57
+ # @return [PgEventstore::CommandHandlers::SubscriptionRunnersCommands]
58
+ def subscription_runners_commands
59
+ CommandHandlers::SubscriptionRunnersCommands.new(@config_name, @runners)
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PgEventstore
4
+ # This class actually processes events.
5
+ # @!visibility private
6
+ class EventsProcessor
7
+ include Extensions::CallbacksExtension
8
+ extend Forwardable
9
+
10
+ def_delegators :@basic_runner, :state, :start, :stop, :wait_for_finish, :stop_async, :restore, :running?
11
+
12
+ # @param handler [#call]
13
+ def initialize(handler)
14
+ @handler = handler
15
+ @raw_events = []
16
+ @basic_runner = BasicRunner.new(0, 5)
17
+ attach_runner_callbacks
18
+ end
19
+
20
+ # @param raw_events [Array<Hash>]
21
+ # @return [void]
22
+ def feed(raw_events)
23
+ callbacks.run_callbacks(:feed, raw_events.last&.dig('global_position'))
24
+ @raw_events.push(*raw_events)
25
+ end
26
+
27
+ # Number of unprocessed events which are currently in a queue
28
+ # @return [Integer]
29
+ def events_left_in_chunk
30
+ @raw_events.size
31
+ end
32
+
33
+ private
34
+
35
+ # @param raw_event [Hash]
36
+ # @return [void]
37
+ def process_event(raw_event)
38
+ callbacks.run_callbacks(:process, raw_event['global_position']) do
39
+ @handler.call(raw_event)
40
+ end
41
+ end
42
+
43
+ def attach_runner_callbacks
44
+ @basic_runner.define_callback(:process_async, :before, method(:process_async))
45
+ @basic_runner.define_callback(:after_runner_died, :before, method(:after_runner_died))
46
+ @basic_runner.define_callback(:before_runner_restored, :before, method(:before_runner_restored))
47
+ @basic_runner.define_callback(:change_state, :before, method(:change_state))
48
+ end
49
+
50
+ def process_async
51
+ raw_event = @raw_events.shift
52
+ return sleep 0.5 if raw_event.nil?
53
+
54
+ process_event(raw_event)
55
+ rescue
56
+ @raw_events.unshift(raw_event)
57
+ raise
58
+ end
59
+
60
+ def after_runner_died(...)
61
+ callbacks.run_callbacks(:error, ...)
62
+ end
63
+
64
+ def before_runner_restored
65
+ callbacks.run_callbacks(:restart)
66
+ end
67
+
68
+ def change_state(...)
69
+ callbacks.run_callbacks(:change_state, ...)
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PgEventstore
4
+ # Implements different states of a runner.
5
+ # @!visibility private
6
+ class RunnerState
7
+ include Extensions::CallbacksExtension
8
+
9
+ STATES = %i(initial running halting stopped dead).to_h { [_1, _1.to_s.freeze] }.freeze
10
+
11
+ def initialize
12
+ initial!
13
+ end
14
+
15
+ STATES.each do |state, value|
16
+ # Checks whether a runner is in appropriate state
17
+ # @return [Boolean]
18
+ define_method "#{state}?" do
19
+ @state == value
20
+ end
21
+
22
+ # Sets the state.
23
+ # @return [String]
24
+ define_method "#{state}!" do
25
+ set_state(value)
26
+ end
27
+ end
28
+
29
+ # @return [String] string representation of the state
30
+ def to_s
31
+ @state
32
+ end
33
+
34
+ private
35
+
36
+ # @param state [String]
37
+ # @return [String]
38
+ def set_state(state)
39
+ old_state = @state
40
+ @state = state
41
+ callbacks.run_callbacks(:change_state, @state) unless old_state == @state
42
+ @state
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,141 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PgEventstore
4
+ # Defines ruby's representation of subscriptions record.
5
+ class Subscription
6
+ include Extensions::UsingConnectionExtension
7
+ include Extensions::OptionsExtension
8
+
9
+ # @!attribute id
10
+ # @return [Integer]
11
+ attribute(:id)
12
+ # @!attribute set
13
+ # @return [String] Subscription's set. Subscription should have unique pair of set and name.
14
+ attribute(:set)
15
+ # @!attribute name
16
+ # @return [String] Subscription's name. Subscription should have unique pair of set and name.
17
+ attribute(:name)
18
+ # @!attribute total_processed_events
19
+ # @return [Integer] total number of events, processed by this subscription
20
+ attribute(:total_processed_events)
21
+ # @!attribute options
22
+ # @return [Hash] subscription's options to be used to query events. See {SubscriptionManager#subscribe} for the
23
+ # list of available options
24
+ attribute(:options)
25
+ # @!attribute current_position
26
+ # @return [Integer, nil] current Subscription's position. It is updated automatically each time an event is processed
27
+ attribute(:current_position)
28
+ # @!attribute state
29
+ # @return [String, nil] current Subscription's state. It is updated automatically during Subscription's life cycle.
30
+ # See {RunnerState::STATES} for possible values.
31
+ attribute(:state)
32
+ # @!attribute average_event_processing_time
33
+ # @return [Float, nil] a speed of the subscription. Divide 1 by this value to determine how much events are
34
+ # processed by the Subscription per second.
35
+ attribute(:average_event_processing_time)
36
+ # @!attribute restart_count
37
+ # @return [Integer] the number of Subscription's restarts after its failure
38
+ attribute(:restart_count)
39
+ # @!attribute max_restarts_number
40
+ # @return [Integer] maximum number of times the Subscription can be restarted
41
+ attribute(:max_restarts_number)
42
+ # @!attribute time_between_restarts
43
+ # @return [Integer] interval in seconds between retries of failed Subscription
44
+ attribute(:time_between_restarts)
45
+ # @!attribute last_restarted_at
46
+ # @return [Time, nil] last time the Subscription was restarted
47
+ attribute(:last_restarted_at)
48
+ # @!attribute last_error
49
+ # @return [Hash{'class' => String, 'message' => String, 'backtrace' => Array<String>}, nil] the information about
50
+ # last error caused when processing events by the Subscription.
51
+ attribute(:last_error)
52
+ # @!attribute last_error_occurred_at
53
+ # @return [Time, nil] the time when the last error occurred
54
+ attribute(:last_error_occurred_at)
55
+ # @!attribute chunk_query_interval
56
+ # @return [Integer] determines how often to pull events for the given Subscription in seconds
57
+ attribute(:chunk_query_interval)
58
+ # @!attribute chunk_query_interval
59
+ # @return [Time] shows the time when last time events were fed to the event's processor
60
+ attribute(:last_chunk_fed_at)
61
+ # @!attribute last_chunk_greatest_position
62
+ # @return [Integer, nil] shows the greatest global_position of the last event in the last chunk fed to the event's
63
+ # processor
64
+ attribute(:last_chunk_greatest_position)
65
+ # @!attribute locked_by
66
+ # @return [String, nil] UUIDv4. The id of subscription manager which obtained the lock of the Subscription. _nil_
67
+ # value means that the Subscription isn't locked yet by any subscription manager.
68
+ attribute(:locked_by)
69
+ # @!attribute created_at
70
+ # @return [Time]
71
+ attribute(:created_at)
72
+ # @!attribute updated_at
73
+ # @return [Time]
74
+ attribute(:updated_at)
75
+
76
+ def options=(val)
77
+ @options = Utils.deep_transform_keys(val, &:to_sym)
78
+ end
79
+
80
+ # @param attrs [Hash]
81
+ # @return [Hash]
82
+ def update(attrs)
83
+ assign_attributes(subscription_queries.update(id, attrs))
84
+ end
85
+
86
+ # @param attrs [Hash]
87
+ # @return [Hash]
88
+ def assign_attributes(attrs)
89
+ attrs.each do |attr, value|
90
+ public_send("#{attr}=", value)
91
+ end
92
+ end
93
+
94
+ # Locks the Subscription by the given lock id
95
+ # @return [PgEventstore::Subscription]
96
+ def lock!(lock_id, force = false)
97
+ self.id = subscription_queries.find_or_create_by(set: set, name: name)[:id]
98
+ self.locked_by = subscription_queries.lock!(id, lock_id, force)
99
+ reset_runtime_attributes
100
+ self
101
+ end
102
+
103
+ # Unlocks the Subscription.
104
+ # @return [void]
105
+ def unlock!
106
+ subscription_queries.unlock!(id, locked_by)
107
+ self.locked_by = nil
108
+ end
109
+
110
+ # Dup the current object without assigned connection
111
+ # @return [PgEventstore::Subscription]
112
+ def dup
113
+ Subscription.new(**Utils.deep_dup(options_hash))
114
+ end
115
+
116
+ # @return [PgEventstore::Subscription]
117
+ def reload
118
+ assign_attributes(subscription_queries.find!(id))
119
+ self
120
+ end
121
+
122
+ private
123
+
124
+ def reset_runtime_attributes
125
+ update(
126
+ options: options,
127
+ restart_count: 0,
128
+ last_restarted_at: nil,
129
+ max_restarts_number: max_restarts_number,
130
+ chunk_query_interval: chunk_query_interval,
131
+ last_chunk_fed_at: Time.at(0).utc,
132
+ last_chunk_greatest_position: nil,
133
+ state: RunnerState::STATES[:initial]
134
+ )
135
+ end
136
+
137
+ def subscription_queries
138
+ SubscriptionQueries.new(self.class.connection)
139
+ end
140
+ end
141
+ end