pg_eventstore 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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