pg_eventstore 0.2.6 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +8 -0
  3. data/README.md +4 -2
  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/connection.rb +2 -2
  14. data/lib/pg_eventstore/errors.rb +63 -0
  15. data/lib/pg_eventstore/{pg_result_deserializer.rb → event_deserializer.rb} +11 -14
  16. data/lib/pg_eventstore/extensions/callbacks_extension.rb +95 -0
  17. data/lib/pg_eventstore/extensions/options_extension.rb +25 -23
  18. data/lib/pg_eventstore/extensions/using_connection_extension.rb +35 -0
  19. data/lib/pg_eventstore/pg_connection.rb +29 -0
  20. data/lib/pg_eventstore/queries/event_queries.rb +5 -26
  21. data/lib/pg_eventstore/queries/event_type_queries.rb +13 -0
  22. data/lib/pg_eventstore/queries/subscription_command_queries.rb +81 -0
  23. data/lib/pg_eventstore/queries/subscription_queries.rb +160 -0
  24. data/lib/pg_eventstore/queries/subscriptions_set_command_queries.rb +76 -0
  25. data/lib/pg_eventstore/queries/subscriptions_set_queries.rb +89 -0
  26. data/lib/pg_eventstore/queries.rb +6 -0
  27. data/lib/pg_eventstore/query_builders/events_filtering_query.rb +14 -2
  28. data/lib/pg_eventstore/sql_builder.rb +54 -10
  29. data/lib/pg_eventstore/subscriptions/basic_runner.rb +220 -0
  30. data/lib/pg_eventstore/subscriptions/command_handlers/subscription_feeder_commands.rb +52 -0
  31. data/lib/pg_eventstore/subscriptions/command_handlers/subscription_runners_commands.rb +68 -0
  32. data/lib/pg_eventstore/subscriptions/commands_handler.rb +62 -0
  33. data/lib/pg_eventstore/subscriptions/events_processor.rb +72 -0
  34. data/lib/pg_eventstore/subscriptions/runner_state.rb +45 -0
  35. data/lib/pg_eventstore/subscriptions/subscription.rb +141 -0
  36. data/lib/pg_eventstore/subscriptions/subscription_feeder.rb +171 -0
  37. data/lib/pg_eventstore/subscriptions/subscription_handler_performance.rb +39 -0
  38. data/lib/pg_eventstore/subscriptions/subscription_runner.rb +125 -0
  39. data/lib/pg_eventstore/subscriptions/subscription_runners_feeder.rb +38 -0
  40. data/lib/pg_eventstore/subscriptions/subscriptions_manager.rb +105 -0
  41. data/lib/pg_eventstore/subscriptions/subscriptions_set.rb +97 -0
  42. data/lib/pg_eventstore/tasks/setup.rake +1 -1
  43. data/lib/pg_eventstore/utils.rb +66 -0
  44. data/lib/pg_eventstore/version.rb +1 -1
  45. data/lib/pg_eventstore.rb +19 -1
  46. metadata +31 -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.2.6"
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.2.6
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: 2023-12-20 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,20 +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
- - lib/pg_eventstore/pg_result_deserializer.rb
95
+ - lib/pg_eventstore/pg_connection.rb
87
96
  - lib/pg_eventstore/queries.rb
88
97
  - lib/pg_eventstore/queries/event_queries.rb
89
98
  - lib/pg_eventstore/queries/event_type_queries.rb
90
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
91
104
  - lib/pg_eventstore/queries/transaction_queries.rb
92
105
  - lib/pg_eventstore/query_builders/events_filtering_query.rb
93
106
  - lib/pg_eventstore/rspec/has_option_matcher.rb
94
107
  - lib/pg_eventstore/sql_builder.rb
95
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
96
122
  - lib/pg_eventstore/tasks/setup.rake
123
+ - lib/pg_eventstore/utils.rb
97
124
  - lib/pg_eventstore/version.rb
98
125
  - pg_eventstore.gemspec
99
126
  homepage: https://github.com/yousty/pg_eventstore
@@ -119,7 +146,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
119
146
  - !ruby/object:Gem::Version
120
147
  version: '0'
121
148
  requirements: []
122
- rubygems_version: 3.4.10
149
+ rubygems_version: 3.5.3
123
150
  signing_key:
124
151
  specification_version: 4
125
152
  summary: EventStore implementation using PostgreSQL