pg_eventstore 0.3.0 → 0.4.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 (44) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +4 -0
  3. data/README.md +1 -0
  4. data/db/migrations/10_create_subscription_commands.sql +15 -0
  5. data/db/migrations/11_create_subscriptions_set_commands.sql +15 -0
  6. data/db/migrations/12_improve_events_indexes.sql +1 -0
  7. data/db/migrations/9_create_subscriptions.sql +46 -0
  8. data/docs/configuration.md +42 -21
  9. data/docs/subscriptions.md +170 -0
  10. data/lib/pg_eventstore/callbacks.rb +122 -0
  11. data/lib/pg_eventstore/client.rb +2 -2
  12. data/lib/pg_eventstore/config.rb +35 -3
  13. data/lib/pg_eventstore/errors.rb +63 -0
  14. data/lib/pg_eventstore/{pg_result_deserializer.rb → event_deserializer.rb} +11 -14
  15. data/lib/pg_eventstore/extensions/callbacks_extension.rb +95 -0
  16. data/lib/pg_eventstore/extensions/options_extension.rb +25 -23
  17. data/lib/pg_eventstore/extensions/using_connection_extension.rb +35 -0
  18. data/lib/pg_eventstore/queries/event_queries.rb +5 -26
  19. data/lib/pg_eventstore/queries/event_type_queries.rb +13 -0
  20. data/lib/pg_eventstore/queries/subscription_command_queries.rb +81 -0
  21. data/lib/pg_eventstore/queries/subscription_queries.rb +160 -0
  22. data/lib/pg_eventstore/queries/subscriptions_set_command_queries.rb +76 -0
  23. data/lib/pg_eventstore/queries/subscriptions_set_queries.rb +89 -0
  24. data/lib/pg_eventstore/queries.rb +6 -0
  25. data/lib/pg_eventstore/query_builders/events_filtering_query.rb +14 -2
  26. data/lib/pg_eventstore/sql_builder.rb +54 -10
  27. data/lib/pg_eventstore/subscriptions/basic_runner.rb +220 -0
  28. data/lib/pg_eventstore/subscriptions/command_handlers/subscription_feeder_commands.rb +52 -0
  29. data/lib/pg_eventstore/subscriptions/command_handlers/subscription_runners_commands.rb +68 -0
  30. data/lib/pg_eventstore/subscriptions/commands_handler.rb +62 -0
  31. data/lib/pg_eventstore/subscriptions/events_processor.rb +72 -0
  32. data/lib/pg_eventstore/subscriptions/runner_state.rb +45 -0
  33. data/lib/pg_eventstore/subscriptions/subscription.rb +141 -0
  34. data/lib/pg_eventstore/subscriptions/subscription_feeder.rb +171 -0
  35. data/lib/pg_eventstore/subscriptions/subscription_handler_performance.rb +39 -0
  36. data/lib/pg_eventstore/subscriptions/subscription_runner.rb +125 -0
  37. data/lib/pg_eventstore/subscriptions/subscription_runners_feeder.rb +38 -0
  38. data/lib/pg_eventstore/subscriptions/subscriptions_manager.rb +105 -0
  39. data/lib/pg_eventstore/subscriptions/subscriptions_set.rb +97 -0
  40. data/lib/pg_eventstore/tasks/setup.rake +1 -1
  41. data/lib/pg_eventstore/utils.rb +66 -0
  42. data/lib/pg_eventstore/version.rb +1 -1
  43. data/lib/pg_eventstore.rb +19 -1
  44. metadata +30 -4
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'forwardable'
4
+ require_relative 'basic_runner'
5
+ require_relative 'subscription'
6
+ require_relative 'events_processor'
7
+ require_relative 'subscription_handler_performance'
8
+ require_relative 'subscription_runner'
9
+ require_relative 'runner_state'
10
+ require_relative 'subscriptions_set'
11
+ require_relative 'subscription_runners_feeder'
12
+ require_relative 'subscription_feeder'
13
+ require_relative 'commands_handler'
14
+
15
+ module PgEventstore
16
+ # The public Subscriptions API, available to the user.
17
+ class SubscriptionsManager
18
+ extend Forwardable
19
+
20
+ attr_reader :config
21
+ private :config
22
+
23
+ def_delegators :@subscription_feeder, :start, :stop, :force_lock!
24
+
25
+ # @param config [PgEventstore::Config]
26
+ # @param set_name [String]
27
+ # @param max_retries [Integer, nil] max number of retries of failed SubscriptionsSet
28
+ # @param retries_interval [Integer, nil] a delay between retries of failed SubscriptionsSet
29
+ def initialize(config:, set_name:, max_retries: nil, retries_interval: nil)
30
+ @config = config
31
+ @set_name = set_name
32
+ @subscription_feeder = SubscriptionFeeder.new(
33
+ config_name: config.name,
34
+ set_name: set_name,
35
+ max_retries: max_retries || config.subscriptions_set_max_retries,
36
+ retries_interval: retries_interval || config.subscriptions_set_retries_interval
37
+ )
38
+ end
39
+
40
+ # @param subscription_name [String] subscription's name
41
+ # @param handler [#call] subscription's handler
42
+ # @param options [Hash] request options
43
+ # @option options [Integer, Symbol] :from_position a starting subscription position
44
+ # @option options [Boolean] :resolve_link_tos When using projections to create new events you
45
+ # can set whether the generated events are pointers to existing events. Setting this option to true tells
46
+ # PgEventstore to return the original event instead a link event.
47
+ # @option options [Hash] :filter provide it to filter events. It works the same way as a :filter option of
48
+ # {PgEventstore::Client#read} method. Filtering by both - event types and streams are available.
49
+ # @param middlewares [Array<Symbol>, nil] provide a list of middleware names to override a config's middlewares
50
+ # @param pull_interval [Integer] an interval in seconds to determine how often to query new events of the given
51
+ # subscription.
52
+ # @param max_retries [Integer] max number of retries of failed Subscription
53
+ # @param retries_interval [Integer] a delay between retries of failed Subscription
54
+ # @param restart_terminator [#call, nil] a callable object which, when called - accepts PgEventstore::Subscription
55
+ # object to determine whether restarts should be stopped(true - stops restarts, false - continues restarts)
56
+ # @return [void]
57
+ def subscribe(subscription_name, handler:, options: {}, middlewares: nil,
58
+ pull_interval: config.subscription_pull_interval,
59
+ max_retries: config.subscription_max_retries,
60
+ retries_interval: config.subscription_retries_interval,
61
+ restart_terminator: config.subscription_restart_terminator)
62
+ subscription = Subscription.using_connection(config.name).new(
63
+ set: @set_name, name: subscription_name, options: options, chunk_query_interval: pull_interval,
64
+ max_restarts_number: max_retries, time_between_restarts: retries_interval
65
+ )
66
+ runner = SubscriptionRunner.new(
67
+ stats: SubscriptionHandlerPerformance.new,
68
+ events_processor: EventsProcessor.new(create_event_handler(middlewares, handler)),
69
+ subscription: subscription,
70
+ restart_terminator: restart_terminator
71
+ )
72
+
73
+ @subscription_feeder.add(runner)
74
+ true
75
+ end
76
+
77
+ # @return [Array<PgEventstore::Subscription>]
78
+ def subscriptions
79
+ @subscription_feeder.read_only_subscriptions
80
+ end
81
+
82
+ # @return [PgEventstore::SubscriptionsSet, nil]
83
+ def subscriptions_set
84
+ @subscription_feeder.read_only_subscriptions_set
85
+ end
86
+
87
+ private
88
+
89
+ # @param middlewares [Array<Symbol>, nil]
90
+ # @param handler [#call]
91
+ # @return [Proc]
92
+ def create_event_handler(middlewares, handler)
93
+ deserializer = EventDeserializer.new(select_middlewares(middlewares), config.event_class_resolver)
94
+ ->(raw_event) { handler.call(deserializer.deserialize(raw_event)) }
95
+ end
96
+
97
+ # @param middlewares [Array, nil]
98
+ # @return [Array<Object<#serialize, #deserialize>>]
99
+ def select_middlewares(middlewares = nil)
100
+ return config.middlewares.values unless middlewares
101
+
102
+ config.middlewares.slice(*middlewares).values
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PgEventstore
4
+ # Defines ruby's representation of subscriptions_set record.
5
+ class SubscriptionsSet
6
+ include Extensions::UsingConnectionExtension
7
+ include Extensions::OptionsExtension
8
+
9
+ class << self
10
+ # @param attrs [Hash]
11
+ # @return [PgEventstore::SubscriptionsSet]
12
+ def create(attrs)
13
+ new(**subscriptions_set_queries.create(attrs))
14
+ end
15
+
16
+ private
17
+
18
+ # @return [PgEventstore::SubscriptionsSetQueries]
19
+ def subscriptions_set_queries
20
+ SubscriptionsSetQueries.new(connection)
21
+ end
22
+ end
23
+
24
+ # @!attribute id
25
+ # @return [String] UUIDv4. It is used to lock the Subscription by updating Subscription#locked_by attribute
26
+ attribute(:id)
27
+ # @!attribute name
28
+ # @return [String] name of the set
29
+ attribute(:name)
30
+ # @!attribute state
31
+ # @return [String]
32
+ attribute(:state)
33
+ # @!attribute restart_count
34
+ # @return [Integer] the number of SubscriptionsSet's restarts after its failure
35
+ attribute(:restart_count)
36
+ # @!attribute max_restarts_number
37
+ # @return [Integer] maximum number of times the SubscriptionsSet can be restarted
38
+ attribute(:max_restarts_number)
39
+ # @!attribute time_between_restarts
40
+ # @return [Integer] interval in seconds between retries of failed SubscriptionsSet
41
+ attribute(:time_between_restarts)
42
+ # @!attribute last_restarted_at
43
+ # @return [Time, nil] last time the SubscriptionsSet was restarted
44
+ attribute(:last_restarted_at)
45
+ # @!attribute last_error
46
+ # @return [Hash{'class' => String, 'message' => String, 'backtrace' => Array<String>}, nil] the information about
47
+ # last error caused when pulling Subscriptions events.
48
+ attribute(:last_error)
49
+ # @!attribute last_error_occurred_at
50
+ # @return [Time, nil] the time when the last error occurred
51
+ attribute(:last_error_occurred_at)
52
+ # @!attribute created_at
53
+ # @return [Time]
54
+ attribute(:created_at)
55
+ # @!attribute updated_at
56
+ # @return [Time]
57
+ attribute(:updated_at)
58
+
59
+ # @param attrs [Hash]
60
+ # @return [Hash]
61
+ def assign_attributes(attrs)
62
+ attrs.each do |attr, value|
63
+ public_send("#{attr}=", value)
64
+ end
65
+ end
66
+
67
+ # @param attrs [Hash]
68
+ # @return [Hash]
69
+ def update(attrs)
70
+ assign_attributes(subscriptions_set_queries.update(id, attrs))
71
+ end
72
+
73
+ # @return [void]
74
+ def delete
75
+ subscriptions_set_queries.delete(id)
76
+ end
77
+
78
+ # Dup the current object without assigned connection
79
+ # @return [PgEventstore::SubscriptionsSet]
80
+ def dup
81
+ SubscriptionsSet.new(**Utils.deep_dup(options_hash))
82
+ end
83
+
84
+ # @return [PgEventstore::SubscriptionsSet]
85
+ def reload
86
+ assign_attributes(subscriptions_set_queries.find!(id))
87
+ self
88
+ end
89
+
90
+ private
91
+
92
+ # @return [PgEventstore::SubscriptionsSetQueries]
93
+ def subscriptions_set_queries
94
+ SubscriptionsSetQueries.new(self.class.connection)
95
+ end
96
+ end
97
+ end
@@ -32,7 +32,7 @@ namespace :pg_eventstore do
32
32
  conn.exec('SELECT number FROM migrations ORDER BY number DESC LIMIT 1').to_a.dig(0, 'number') || -1
33
33
 
34
34
  Dir.chdir migration_files_root do
35
- Dir["*.{sql,rb}"].sort.each do |f_name|
35
+ Dir["*.{sql,rb}"].sort_by { |f_name| f_name.split('_').first.to_i }.each do |f_name|
36
36
  number = File.basename(f_name).split('_')[0].to_i
37
37
  next if latest_migration >= number
38
38
 
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PgEventstore
4
+ class Utils
5
+ class << self
6
+ # Deep transforms keys of a given Hash
7
+ # @param object [Object]
8
+ # @return [Object] a hash with transformed keys
9
+ def deep_transform_keys(object, &block)
10
+ case object
11
+ when Hash
12
+ object.each_with_object({}) do |(key, value), result|
13
+ result[yield(key)] = deep_transform_keys(value, &block)
14
+ end
15
+ when Array
16
+ object.map { |e| deep_transform_keys(e, &block) }
17
+ else
18
+ object
19
+ end
20
+ end
21
+
22
+ # Deep dup Array or Hash
23
+ # @param object [Object]
24
+ # @return [Object]
25
+ def deep_dup(object)
26
+ case object
27
+ when Hash
28
+ object.each_with_object({}) do |(key, value), result|
29
+ result[deep_dup(key)] = deep_dup(value)
30
+ end
31
+ when Array
32
+ object.map { |e| deep_dup(e) }
33
+ else
34
+ object.dup
35
+ end
36
+ end
37
+
38
+ # Converts array to the string containing SQL positional variables
39
+ # @param array [Array]
40
+ # @return [String] positional variables, based on array size. Example: "$1, $2, $3"
41
+ def positional_vars(array)
42
+ array.size.times.map { |t| "$#{t + 1}" }.join(', ')
43
+ end
44
+
45
+ # Transforms exception instance into a hash
46
+ # @param error [StandardError]
47
+ # @return [Hash]
48
+ def error_info(error)
49
+ {
50
+ class: error.class,
51
+ message: error.message,
52
+ backtrace: error.backtrace
53
+ }
54
+ end
55
+
56
+ # @param str [String]
57
+ # @return [String]
58
+ def underscore_str(str)
59
+ str = str.dup
60
+ str[0] = str[0].downcase
61
+ str.gsub!(/[A-Z]/) { |letter| '_' + letter.downcase }
62
+ str
63
+ end
64
+ end
65
+ end
66
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PgEventstore
4
- VERSION = "0.3.0"
4
+ VERSION = "0.4.0"
5
5
  end
data/lib/pg_eventstore.rb CHANGED
@@ -1,16 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'pg_eventstore/version'
4
+ require_relative 'pg_eventstore/utils'
5
+ require_relative 'pg_eventstore/callbacks'
4
6
  require_relative 'pg_eventstore/extensions/options_extension'
7
+ require_relative 'pg_eventstore/extensions/callbacks_extension'
8
+ require_relative 'pg_eventstore/extensions/using_connection_extension'
5
9
  require_relative 'pg_eventstore/event_class_resolver'
6
10
  require_relative 'pg_eventstore/config'
7
11
  require_relative 'pg_eventstore/event'
8
- require_relative 'pg_eventstore/sql_builder'
9
12
  require_relative 'pg_eventstore/stream'
10
13
  require_relative 'pg_eventstore/client'
11
14
  require_relative 'pg_eventstore/connection'
12
15
  require_relative 'pg_eventstore/errors'
13
16
  require_relative 'pg_eventstore/middleware'
17
+ require_relative 'pg_eventstore/subscriptions/subscriptions_manager'
14
18
 
15
19
  module PgEventstore
16
20
  class << self
@@ -58,6 +62,20 @@ module PgEventstore
58
62
  end
59
63
  end
60
64
 
65
+ # @param config_name [Symbol]
66
+ # @param subscription_set [String]
67
+ # @param max_retries [Integer, nil] max number of retries of failed SubscriptionsSet
68
+ # @param retries_interval [Integer, nil] a delay between retries of failed SubscriptionsSet
69
+ # @return [PgEventstore::SubscriptionManager]
70
+ def subscriptions_manager(config_name = :default, subscription_set:, max_retries: nil, retries_interval: nil)
71
+ SubscriptionsManager.new(
72
+ config: config(config_name),
73
+ set_name: subscription_set,
74
+ max_retries: max_retries,
75
+ retries_interval: retries_interval
76
+ )
77
+ end
78
+
61
79
  # @param name [Symbol]
62
80
  # @return [PgEventstore::Client]
63
81
  def client(name = :default)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pg_eventstore
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ivan Dzyzenko
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-01-24 00:00:00.000000000 Z
11
+ date: 2024-01-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: pg
@@ -54,6 +54,9 @@ files:
54
54
  - db/initial/primary_and_foreign_keys.sql
55
55
  - db/initial/tables.sql
56
56
  - db/migrations/0_improve_all_stream_indexes.sql
57
+ - db/migrations/10_create_subscription_commands.sql
58
+ - db/migrations/11_create_subscriptions_set_commands.sql
59
+ - db/migrations/12_improve_events_indexes.sql
57
60
  - db/migrations/1_improve_specific_stream_indexes.sql
58
61
  - db/migrations/2_adjust_global_position_index.sql
59
62
  - db/migrations/3_extract_type_into_separate_table.sql
@@ -62,14 +65,17 @@ files:
62
65
  - db/migrations/6_change_events_event_type_id_null_constraint.sql
63
66
  - db/migrations/7_change_events_type_constraint.sql
64
67
  - db/migrations/8_drop_events_type.sql
68
+ - db/migrations/9_create_subscriptions.sql
65
69
  - docs/appending_events.md
66
70
  - docs/configuration.md
67
71
  - docs/events_and_streams.md
68
72
  - docs/multiple_commands.md
69
73
  - docs/reading_events.md
74
+ - docs/subscriptions.md
70
75
  - docs/writing_middleware.md
71
76
  - lib/pg_eventstore.rb
72
77
  - lib/pg_eventstore/abstract_command.rb
78
+ - lib/pg_eventstore/callbacks.rb
73
79
  - lib/pg_eventstore/client.rb
74
80
  - lib/pg_eventstore/commands.rb
75
81
  - lib/pg_eventstore/commands/append.rb
@@ -80,21 +86,41 @@ files:
80
86
  - lib/pg_eventstore/errors.rb
81
87
  - lib/pg_eventstore/event.rb
82
88
  - lib/pg_eventstore/event_class_resolver.rb
89
+ - lib/pg_eventstore/event_deserializer.rb
83
90
  - lib/pg_eventstore/event_serializer.rb
91
+ - lib/pg_eventstore/extensions/callbacks_extension.rb
84
92
  - lib/pg_eventstore/extensions/options_extension.rb
93
+ - lib/pg_eventstore/extensions/using_connection_extension.rb
85
94
  - lib/pg_eventstore/middleware.rb
86
95
  - lib/pg_eventstore/pg_connection.rb
87
- - lib/pg_eventstore/pg_result_deserializer.rb
88
96
  - lib/pg_eventstore/queries.rb
89
97
  - lib/pg_eventstore/queries/event_queries.rb
90
98
  - lib/pg_eventstore/queries/event_type_queries.rb
91
99
  - lib/pg_eventstore/queries/stream_queries.rb
100
+ - lib/pg_eventstore/queries/subscription_command_queries.rb
101
+ - lib/pg_eventstore/queries/subscription_queries.rb
102
+ - lib/pg_eventstore/queries/subscriptions_set_command_queries.rb
103
+ - lib/pg_eventstore/queries/subscriptions_set_queries.rb
92
104
  - lib/pg_eventstore/queries/transaction_queries.rb
93
105
  - lib/pg_eventstore/query_builders/events_filtering_query.rb
94
106
  - lib/pg_eventstore/rspec/has_option_matcher.rb
95
107
  - lib/pg_eventstore/sql_builder.rb
96
108
  - lib/pg_eventstore/stream.rb
109
+ - lib/pg_eventstore/subscriptions/basic_runner.rb
110
+ - lib/pg_eventstore/subscriptions/command_handlers/subscription_feeder_commands.rb
111
+ - lib/pg_eventstore/subscriptions/command_handlers/subscription_runners_commands.rb
112
+ - lib/pg_eventstore/subscriptions/commands_handler.rb
113
+ - lib/pg_eventstore/subscriptions/events_processor.rb
114
+ - lib/pg_eventstore/subscriptions/runner_state.rb
115
+ - lib/pg_eventstore/subscriptions/subscription.rb
116
+ - lib/pg_eventstore/subscriptions/subscription_feeder.rb
117
+ - lib/pg_eventstore/subscriptions/subscription_handler_performance.rb
118
+ - lib/pg_eventstore/subscriptions/subscription_runner.rb
119
+ - lib/pg_eventstore/subscriptions/subscription_runners_feeder.rb
120
+ - lib/pg_eventstore/subscriptions/subscriptions_manager.rb
121
+ - lib/pg_eventstore/subscriptions/subscriptions_set.rb
97
122
  - lib/pg_eventstore/tasks/setup.rake
123
+ - lib/pg_eventstore/utils.rb
98
124
  - lib/pg_eventstore/version.rb
99
125
  - pg_eventstore.gemspec
100
126
  homepage: https://github.com/yousty/pg_eventstore
@@ -120,7 +146,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
120
146
  - !ruby/object:Gem::Version
121
147
  version: '0'
122
148
  requirements: []
123
- rubygems_version: 3.4.10
149
+ rubygems_version: 3.5.3
124
150
  signing_key:
125
151
  specification_version: 4
126
152
  summary: EventStore implementation using PostgreSQL