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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +14 -0
- data/CODE_OF_CONDUCT.md +1 -1
- data/README.md +2 -0
- data/db/migrations/10_create_subscription_commands.sql +15 -0
- data/db/migrations/11_create_subscriptions_set_commands.sql +15 -0
- data/db/migrations/12_improve_events_indexes.sql +1 -0
- data/db/migrations/13_remove_duplicated_index.sql +1 -0
- data/db/migrations/9_create_subscriptions.sql +46 -0
- data/docs/configuration.md +42 -21
- data/docs/linking_events.md +96 -0
- data/docs/reading_events.md +56 -0
- data/docs/subscriptions.md +170 -0
- data/lib/pg_eventstore/callbacks.rb +122 -0
- data/lib/pg_eventstore/client.rb +32 -2
- data/lib/pg_eventstore/commands/append.rb +3 -11
- data/lib/pg_eventstore/commands/event_modifiers/prepare_link_event.rb +22 -0
- data/lib/pg_eventstore/commands/event_modifiers/prepare_regular_event.rb +24 -0
- data/lib/pg_eventstore/commands/link_to.rb +33 -0
- data/lib/pg_eventstore/commands/regular_stream_read_paginated.rb +63 -0
- data/lib/pg_eventstore/commands/system_stream_read_paginated.rb +62 -0
- data/lib/pg_eventstore/commands.rb +5 -0
- data/lib/pg_eventstore/config.rb +35 -3
- data/lib/pg_eventstore/errors.rb +80 -0
- data/lib/pg_eventstore/{pg_result_deserializer.rb → event_deserializer.rb} +10 -22
- data/lib/pg_eventstore/extensions/callbacks_extension.rb +95 -0
- data/lib/pg_eventstore/extensions/options_extension.rb +69 -29
- data/lib/pg_eventstore/extensions/using_connection_extension.rb +35 -0
- data/lib/pg_eventstore/pg_connection.rb +20 -3
- data/lib/pg_eventstore/queries/event_queries.rb +18 -34
- data/lib/pg_eventstore/queries/event_type_queries.rb +24 -0
- data/lib/pg_eventstore/queries/preloader.rb +37 -0
- data/lib/pg_eventstore/queries/stream_queries.rb +14 -1
- data/lib/pg_eventstore/queries/subscription_command_queries.rb +81 -0
- data/lib/pg_eventstore/queries/subscription_queries.rb +166 -0
- data/lib/pg_eventstore/queries/subscriptions_set_command_queries.rb +76 -0
- data/lib/pg_eventstore/queries/subscriptions_set_queries.rb +89 -0
- data/lib/pg_eventstore/queries.rb +7 -0
- data/lib/pg_eventstore/query_builders/events_filtering_query.rb +17 -22
- data/lib/pg_eventstore/sql_builder.rb +54 -10
- data/lib/pg_eventstore/stream.rb +2 -1
- data/lib/pg_eventstore/subscriptions/basic_runner.rb +220 -0
- data/lib/pg_eventstore/subscriptions/command_handlers/subscription_feeder_commands.rb +52 -0
- data/lib/pg_eventstore/subscriptions/command_handlers/subscription_runners_commands.rb +68 -0
- data/lib/pg_eventstore/subscriptions/commands_handler.rb +62 -0
- data/lib/pg_eventstore/subscriptions/events_processor.rb +72 -0
- data/lib/pg_eventstore/subscriptions/runner_state.rb +45 -0
- data/lib/pg_eventstore/subscriptions/subscription.rb +141 -0
- data/lib/pg_eventstore/subscriptions/subscription_feeder.rb +171 -0
- data/lib/pg_eventstore/subscriptions/subscription_handler_performance.rb +39 -0
- data/lib/pg_eventstore/subscriptions/subscription_runner.rb +125 -0
- data/lib/pg_eventstore/subscriptions/subscription_runners_feeder.rb +38 -0
- data/lib/pg_eventstore/subscriptions/subscriptions_manager.rb +105 -0
- data/lib/pg_eventstore/subscriptions/subscriptions_set.rb +97 -0
- data/lib/pg_eventstore/tasks/setup.rake +5 -1
- data/lib/pg_eventstore/utils.rb +66 -0
- data/lib/pg_eventstore/version.rb +1 -1
- data/lib/pg_eventstore.rb +19 -1
- metadata +38 -4
@@ -0,0 +1,122 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PgEventstore
|
4
|
+
# Allows you to define before, around and after callbacks withing the certain action. It is especially useful during
|
5
|
+
# asynchronous programming when you need to be able to react on asynchronous actions from the outside.
|
6
|
+
# Example:
|
7
|
+
# class MyAwesomeClass
|
8
|
+
# attr_reader :callbacks
|
9
|
+
#
|
10
|
+
# def initialize
|
11
|
+
# @callbacks = PgEventstore::Callbacks.new
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# def do_something
|
15
|
+
# Thread.new do
|
16
|
+
# @callbacks.run_callbacks(:something_happened) do
|
17
|
+
# puts "I did something useful!"
|
18
|
+
# end
|
19
|
+
# end
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# def do_something_else
|
23
|
+
# @callbacks.run_callbacks(:something_else_happened, :foo, bar: :baz) do
|
24
|
+
# puts "Something else happened!"
|
25
|
+
# end
|
26
|
+
# end
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# obj = MyAwesomeClass.new
|
30
|
+
# obj.callbacks.define_callback(:something_happened, :before, proc { puts "In before callback" })
|
31
|
+
# obj.callbacks.define_callback(:something_happened, :after, proc { puts "In after callback" })
|
32
|
+
# obj.callbacks.define_callback(
|
33
|
+
# :something_happened, :around,
|
34
|
+
# proc { |action| puts "In around before action"; action.call; puts "In around after action" }
|
35
|
+
# )
|
36
|
+
# obj.do_something
|
37
|
+
# Outputs:
|
38
|
+
# In before callback
|
39
|
+
# In around callback before action
|
40
|
+
# I did something useful!
|
41
|
+
# In around callback after action
|
42
|
+
# In after callback
|
43
|
+
# Please note, that it is important to call *action.call* in around callback. Otherwise action simply won't be called.
|
44
|
+
#
|
45
|
+
# Optionally you can provide any set of arguments to {#run_callbacks} method. They will be passed to your callbacks
|
46
|
+
# functions then. Example:
|
47
|
+
#
|
48
|
+
# obj = MyAwesomeClass.new
|
49
|
+
# obj.callbacks.define_callback(
|
50
|
+
# :something_else_happened, :before,
|
51
|
+
# proc { |*args, **kwargs| puts "In before callback. Args: #{args}, kwargs: #{kwargs}." }
|
52
|
+
# )
|
53
|
+
# obj.callbacks.define_callback(
|
54
|
+
# :something_else_happened, :after,
|
55
|
+
# proc { |*args, **kwargs| puts "In after callback. Args: #{args}, kwargs: #{kwargs}." }
|
56
|
+
# )
|
57
|
+
# obj.callbacks.define_callback(
|
58
|
+
# :something_else_happened, :around,
|
59
|
+
# proc { |action, *args, **kwargs|
|
60
|
+
# puts "In around before action. Args: #{args}, kwargs: #{kwargs}."
|
61
|
+
# action.call
|
62
|
+
# puts "In around after action. Args: #{args}, kwargs: #{kwargs}."
|
63
|
+
# }
|
64
|
+
# )
|
65
|
+
# obj.do_something_else
|
66
|
+
# Outputs:
|
67
|
+
# In before callback. Args: [:foo], kwargs: {:bar=>:baz}.
|
68
|
+
# In around before action. Args: [:foo], kwargs: {:bar=>:baz}.
|
69
|
+
# I did something useful!
|
70
|
+
# In around after action. Args: [:foo], kwargs: {:bar=>:baz}.
|
71
|
+
# In after callback. Args: [:foo], kwargs: {:bar=>:baz}.
|
72
|
+
class Callbacks
|
73
|
+
def initialize
|
74
|
+
@callbacks = {}
|
75
|
+
end
|
76
|
+
|
77
|
+
# @param action [Object] an object, that represents your action. In most cases you want to use a symbol there
|
78
|
+
# @param filter [Symbol] callback filter. Supported values are :before, :after and :around
|
79
|
+
# @param callback [#call]
|
80
|
+
# @return [void]
|
81
|
+
def define_callback(action, filter, callback)
|
82
|
+
@callbacks[action] ||= {}
|
83
|
+
@callbacks[action][filter] ||= []
|
84
|
+
@callbacks[action][filter].push(callback)
|
85
|
+
end
|
86
|
+
|
87
|
+
# @param action [Object] an action to run
|
88
|
+
# @return [Object] the result of passed block
|
89
|
+
def run_callbacks(action, *args, **kwargs, &blk)
|
90
|
+
return (yield if block_given?) unless @callbacks[action]
|
91
|
+
|
92
|
+
run_before_callbacks(action, *args, **kwargs)
|
93
|
+
result = run_around_callbacks(action, *args, **kwargs, &blk)
|
94
|
+
run_after_callbacks(action, *args, **kwargs)
|
95
|
+
result
|
96
|
+
end
|
97
|
+
|
98
|
+
private
|
99
|
+
|
100
|
+
def run_before_callbacks(action, *args, **kwargs)
|
101
|
+
@callbacks[action][:before]&.each do |callback|
|
102
|
+
callback.call(*args, **kwargs)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def run_around_callbacks(action, *args, **kwargs, &blk)
|
107
|
+
result = nil
|
108
|
+
stack = [proc { result = yield if block_given? }]
|
109
|
+
@callbacks[action][:around]&.reverse_each&.with_index do |callback, index|
|
110
|
+
stack.push(proc { callback.call(stack[index], *args, **kwargs); result })
|
111
|
+
end
|
112
|
+
stack.last.call(*args, **kwargs)
|
113
|
+
result
|
114
|
+
end
|
115
|
+
|
116
|
+
def run_after_callbacks(action, *args, **kwargs)
|
117
|
+
@callbacks[action][:after]&.each do |callback|
|
118
|
+
callback.call(*args, **kwargs)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
data/lib/pg_eventstore/client.rb
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
require_relative 'commands'
|
4
4
|
require_relative 'event_serializer'
|
5
|
-
require_relative '
|
5
|
+
require_relative 'event_deserializer'
|
6
6
|
require_relative 'queries'
|
7
7
|
|
8
8
|
module PgEventstore
|
@@ -109,6 +109,36 @@ module PgEventstore
|
|
109
109
|
call(stream, options: { max_count: config.max_count }.merge(options))
|
110
110
|
end
|
111
111
|
|
112
|
+
# @see {#read} for available params
|
113
|
+
# @return [Enumerator] enumerator will yield PgEventstore::Event
|
114
|
+
def read_paginated(stream, options: {}, middlewares: nil)
|
115
|
+
cmd_class = stream.system? ? Commands::SystemStreamReadPaginated : Commands::RegularStreamReadPaginated
|
116
|
+
cmd_class.
|
117
|
+
new(Queries.new(streams: stream_queries, events: event_queries(middlewares(middlewares)))).
|
118
|
+
call(stream, options: { max_count: config.max_count }.merge(options))
|
119
|
+
end
|
120
|
+
|
121
|
+
# Links event from one stream into another stream. You can later access it by providing :resolve_link_tos option
|
122
|
+
# when reading from a stream. Only existing events can be linked.
|
123
|
+
# @param stream [PgEventstore::Stream]
|
124
|
+
# @param events_or_event [PgEventstore::Event, Array<PgEventstore::Event>]
|
125
|
+
# @param options [Hash]
|
126
|
+
# @option options [Integer] :expected_revision provide your own revision number
|
127
|
+
# @option options [Symbol] :expected_revision provide one of next values: :any, :no_stream or :stream_exists
|
128
|
+
# @param middlewares [Array] provide a list of middleware names to use. Defaults to empty array, meaning no
|
129
|
+
# middlewares will be applied to the "link" event
|
130
|
+
# @return [PgEventstore::Event, Array<PgEventstore::Event>] persisted event(s)
|
131
|
+
# @raise [PgEventstore::WrongExpectedRevisionError]
|
132
|
+
def link_to(stream, events_or_event, options: {}, middlewares: [])
|
133
|
+
result =
|
134
|
+
Commands::LinkTo.new(
|
135
|
+
Queries.new(
|
136
|
+
streams: stream_queries, events: event_queries(middlewares(middlewares)), transactions: transaction_queries
|
137
|
+
)
|
138
|
+
).call(stream, *events_or_event, options: options)
|
139
|
+
events_or_event.is_a?(Array) ? result : result.first
|
140
|
+
end
|
141
|
+
|
112
142
|
private
|
113
143
|
|
114
144
|
# @param middlewares [Array, nil]
|
@@ -140,7 +170,7 @@ module PgEventstore
|
|
140
170
|
EventQueries.new(
|
141
171
|
connection,
|
142
172
|
EventSerializer.new(middlewares),
|
143
|
-
|
173
|
+
EventDeserializer.new(middlewares, config.event_class_resolver)
|
144
174
|
)
|
145
175
|
end
|
146
176
|
end
|
@@ -9,9 +9,10 @@ module PgEventstore
|
|
9
9
|
# @param options [Hash]
|
10
10
|
# @option options [Integer] :expected_revision provide your own revision number
|
11
11
|
# @option options [Symbol] :expected_revision provide one of next values: :any, :no_stream or :stream_exists
|
12
|
+
# @param event_modifier [#call]
|
12
13
|
# @return [Array<PgEventstore::Event>] persisted events
|
13
14
|
# @raise [PgEventstore::WrongExpectedRevisionError]
|
14
|
-
def call(stream, *events, options: {})
|
15
|
+
def call(stream, *events, options: {}, event_modifier: EventModifiers::PrepareRegularEvent)
|
15
16
|
raise SystemStreamError, stream if stream.system?
|
16
17
|
|
17
18
|
queries.transactions.transaction do
|
@@ -19,7 +20,7 @@ module PgEventstore
|
|
19
20
|
revision = stream.stream_revision
|
20
21
|
assert_expected_revision!(revision, options[:expected_revision]) if options[:expected_revision]
|
21
22
|
events.map.with_index(1) do |event, index|
|
22
|
-
queries.events.insert(stream,
|
23
|
+
queries.events.insert(stream, event_modifier.call(event, revision + index))
|
23
24
|
end.tap do
|
24
25
|
queries.streams.update_stream_revision(stream, revision + events.size)
|
25
26
|
end
|
@@ -28,15 +29,6 @@ module PgEventstore
|
|
28
29
|
|
29
30
|
private
|
30
31
|
|
31
|
-
# @param event [PgEventstore::Event]
|
32
|
-
# @param revision [Integer]
|
33
|
-
# @return [PgEventstore::Event]
|
34
|
-
def prepared_event(event, revision)
|
35
|
-
event.class.new(
|
36
|
-
id: event.id, data: event.data, metadata: event.metadata, type: event.type, stream_revision: revision
|
37
|
-
)
|
38
|
-
end
|
39
|
-
|
40
32
|
# @param revision [Integer]
|
41
33
|
# @param expected_revision [Symbol, Integer]
|
42
34
|
# @raise [PgEventstore::WrongExpectedRevisionError] in case if revision does not satisfy expected revision
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PgEventstore
|
4
|
+
module Commands
|
5
|
+
module EventModifiers
|
6
|
+
# Defines how to transform regular event into a link event
|
7
|
+
# @!visibility private
|
8
|
+
class PrepareLinkEvent
|
9
|
+
class << self
|
10
|
+
# @param event [PgEventstore::Event]
|
11
|
+
# @param revision [Integer]
|
12
|
+
# @return [PgEventstore::Event]
|
13
|
+
def call(event, revision)
|
14
|
+
Event.new(link_id: event.id, type: Event::LINK_TYPE, stream_revision: revision).tap do |e|
|
15
|
+
%i[link_id type stream_revision].each { |attr| e.readonly!(attr) }
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PgEventstore
|
4
|
+
module Commands
|
5
|
+
module EventModifiers
|
6
|
+
# Defines how to transform regular event before appending it to the stream
|
7
|
+
# @!visibility private
|
8
|
+
class PrepareRegularEvent
|
9
|
+
class << self
|
10
|
+
# @param event [PgEventstore::Event]
|
11
|
+
# @param revision [Integer]
|
12
|
+
# @return [PgEventstore::Event]
|
13
|
+
def call(event, revision)
|
14
|
+
event.class.new(
|
15
|
+
id: event.id, data: event.data, metadata: event.metadata, type: event.type, stream_revision: revision
|
16
|
+
).tap do |e|
|
17
|
+
%i[link_id stream_revision].each { |attr| e.readonly!(attr) }
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PgEventstore
|
4
|
+
module Commands
|
5
|
+
# @!visibility private
|
6
|
+
class LinkTo < AbstractCommand
|
7
|
+
# @param stream [PgEventstore::Stream]
|
8
|
+
# @param events [Array<PgEventstore::Event>]
|
9
|
+
# @param options [Hash]
|
10
|
+
# @option options [Integer] :expected_revision provide your own revision number
|
11
|
+
# @option options [Symbol] :expected_revision provide one of next values: :any, :no_stream or :stream_exists
|
12
|
+
# @return [Array<PgEventstore::Event>] persisted events
|
13
|
+
# @raise [PgEventstore::WrongExpectedRevisionError]
|
14
|
+
# @raise [PgEventstore::NotPersistedEventError]
|
15
|
+
def call(stream, *events, options: {})
|
16
|
+
events.each(&method(:check_id_presence))
|
17
|
+
append_cmd = Append.new(queries)
|
18
|
+
append_cmd.call(stream, *events, options: options, event_modifier: EventModifiers::PrepareLinkEvent)
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
# Checks if Event#id is present. An event must have the #id value in order to be linked.
|
24
|
+
# @param event [PgEventstore::Event]
|
25
|
+
# @return [void]
|
26
|
+
def check_id_presence(event)
|
27
|
+
return unless event.id.nil?
|
28
|
+
|
29
|
+
raise NotPersistedEventError, event
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PgEventstore
|
4
|
+
module Commands
|
5
|
+
# @!visibility private
|
6
|
+
class RegularStreamReadPaginated < AbstractCommand
|
7
|
+
# @see PgEventstore::Commands::Read for docs
|
8
|
+
def call(stream, options: {})
|
9
|
+
revision = calc_initial_revision(stream, options)
|
10
|
+
Enumerator.new do |yielder|
|
11
|
+
loop do
|
12
|
+
events = read_cmd.call(stream, options: options.merge(from_revision: revision))
|
13
|
+
yielder << events if events.any?
|
14
|
+
raise StopIteration if end_reached?(events, options[:max_count])
|
15
|
+
|
16
|
+
revision = calc_next_revision(events, revision, options[:direction])
|
17
|
+
raise StopIteration if revision.negative?
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
# @param stream [PgEventstore::Stream]
|
25
|
+
# @param options [Hash]
|
26
|
+
# @return [Integer]
|
27
|
+
def calc_initial_revision(stream, options)
|
28
|
+
return options[:from_revision] if options[:from_revision]
|
29
|
+
return 0 if forwards?(options[:direction])
|
30
|
+
|
31
|
+
read_cmd.call(stream, options: options.merge(max_count: 1)).first.stream_revision
|
32
|
+
end
|
33
|
+
|
34
|
+
# @param events [Array<PgEventstore::Event>]
|
35
|
+
# @param max_count [Integer]
|
36
|
+
# @return [Boolean]
|
37
|
+
def end_reached?(events, max_count)
|
38
|
+
events.size < max_count
|
39
|
+
end
|
40
|
+
|
41
|
+
# @param events [Array<PgEventstore::Event>]
|
42
|
+
# @param revision [Integer]
|
43
|
+
# @param direction [String, Symbol, nil]
|
44
|
+
# @return [Integer]
|
45
|
+
def calc_next_revision(events, revision, direction)
|
46
|
+
return revision + events.size if forwards?(direction)
|
47
|
+
|
48
|
+
revision - events.size
|
49
|
+
end
|
50
|
+
|
51
|
+
# @param direction [String, Symbol, nil]
|
52
|
+
# @return [Boolean]
|
53
|
+
def forwards?(direction)
|
54
|
+
QueryBuilders::EventsFiltering::SQL_DIRECTIONS[direction] == QueryBuilders::EventsFiltering::SQL_DIRECTIONS[:asc]
|
55
|
+
end
|
56
|
+
|
57
|
+
# @return [PgEventstore::Commands::Read]
|
58
|
+
def read_cmd
|
59
|
+
@read_cmd ||= Read.new(queries)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PgEventstore
|
4
|
+
module Commands
|
5
|
+
# @!visibility private
|
6
|
+
class SystemStreamReadPaginated < AbstractCommand
|
7
|
+
# @see PgEventstore::Commands::Read for docs
|
8
|
+
def call(stream, options: {})
|
9
|
+
position = calc_initial_position(stream, options)
|
10
|
+
Enumerator.new do |yielder|
|
11
|
+
loop do
|
12
|
+
events = read_cmd.call(stream, options: options.merge(from_position: position))
|
13
|
+
yielder << events if events.any?
|
14
|
+
raise StopIteration if end_reached?(events, options[:max_count])
|
15
|
+
|
16
|
+
position = calc_next_position(events, options[:direction])
|
17
|
+
raise StopIteration if position <= 0
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
# @param stream [PgEventstore::Stream]
|
25
|
+
# @param options [Hash]
|
26
|
+
# @return [Integer]
|
27
|
+
def calc_initial_position(stream, options)
|
28
|
+
return options[:from_position] if options[:from_position]
|
29
|
+
return 1 if forwards?(options[:direction])
|
30
|
+
|
31
|
+
read_cmd.call(stream, options: options.merge(max_count: 1)).first.global_position
|
32
|
+
end
|
33
|
+
|
34
|
+
# @param events [Array<PgEventstore::Event>]
|
35
|
+
# @param max_count [Integer]
|
36
|
+
# @return [Boolean]
|
37
|
+
def end_reached?(events, max_count)
|
38
|
+
events.size < max_count
|
39
|
+
end
|
40
|
+
|
41
|
+
# @param events [Array<PgEventstore::Event>]
|
42
|
+
# @param direction [String, Symbol, nil]
|
43
|
+
# @return [Integer]
|
44
|
+
def calc_next_position(events, direction)
|
45
|
+
return events.last.global_position + 1 if forwards?(direction)
|
46
|
+
|
47
|
+
events.last.global_position - 1
|
48
|
+
end
|
49
|
+
|
50
|
+
# @param direction [String, Symbol, nil]
|
51
|
+
# @return [Boolean]
|
52
|
+
def forwards?(direction)
|
53
|
+
QueryBuilders::EventsFiltering::SQL_DIRECTIONS[direction] == QueryBuilders::EventsFiltering::SQL_DIRECTIONS[:asc]
|
54
|
+
end
|
55
|
+
|
56
|
+
# @return [PgEventstore::Commands::Read]
|
57
|
+
def read_cmd
|
58
|
+
@read_cmd ||= Read.new(queries)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -1,6 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative 'abstract_command'
|
4
|
+
require_relative 'commands/event_modifiers/prepare_link_event'
|
5
|
+
require_relative 'commands/event_modifiers/prepare_regular_event'
|
4
6
|
require_relative 'commands/append'
|
5
7
|
require_relative 'commands/read'
|
8
|
+
require_relative 'commands/regular_stream_read_paginated'
|
9
|
+
require_relative 'commands/system_stream_read_paginated'
|
6
10
|
require_relative 'commands/multiple'
|
11
|
+
require_relative 'commands/link_to'
|
data/lib/pg_eventstore/config.rb
CHANGED
@@ -6,14 +6,46 @@ module PgEventstore
|
|
6
6
|
|
7
7
|
attr_reader :name
|
8
8
|
|
9
|
-
#
|
9
|
+
# @!attribute pg_uri
|
10
|
+
# @return [String] PostgreSQL connection URI docs
|
11
|
+
# https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING-URIS
|
10
12
|
option(:pg_uri) { 'postgresql://postgres:postgres@localhost:5432/eventstore' }
|
13
|
+
# @!attribute max_count
|
14
|
+
# @return [Integer] Number of events to return in one response when reading from a stream
|
11
15
|
option(:max_count) { 1000 }
|
16
|
+
# @!attribute middlewares
|
17
|
+
# @return [Hash{Symbol => <#serialize, #deserialize>}] A set of identified(by key) objects that respond to
|
18
|
+
# #serialize and #deserialize
|
12
19
|
option(:middlewares) { {} }
|
13
|
-
#
|
20
|
+
# @!attribute event_class_resolver
|
21
|
+
# @return [#call] A callable object that must accept a string and return a class. It is used when resolving
|
22
|
+
# event's class during event's deserialization process
|
14
23
|
option(:event_class_resolver) { EventClassResolver.new }
|
24
|
+
# @!attribute connection_pool_size
|
25
|
+
# @return [Integer] Max number of connections per ruby process
|
15
26
|
option(:connection_pool_size) { 5 }
|
16
|
-
|
27
|
+
# @!attribute connection_pool_timeout
|
28
|
+
# @return [Integer] Time in seconds to wait for the connection in pool to be released
|
29
|
+
option(:connection_pool_timeout) { 5 }
|
30
|
+
# @!attribute subscription_pull_interval
|
31
|
+
# @return [Integer] How often Subscription should pull new events
|
32
|
+
option(:subscription_pull_interval) { 2 }
|
33
|
+
# @!attribute subscription_max_retries
|
34
|
+
# @return [Integer] max number of retries of failed Subscription
|
35
|
+
option(:subscription_max_retries) { 100 }
|
36
|
+
# @!attribute subscription_retries_interval
|
37
|
+
# @return [Integer] interval in seconds between retries of failed Subscription
|
38
|
+
option(:subscription_retries_interval) { 1 }
|
39
|
+
# @!attribute subscription_restart_terminator
|
40
|
+
# @return [#call, nil] provide callable object that accepts Subscription object to decide whether to prevent
|
41
|
+
# further Subscription restarts
|
42
|
+
option(:subscription_restart_terminator)
|
43
|
+
# @!attribute subscriptions_set_max_retries
|
44
|
+
# @return [Integer] max number of retries of failed SubscriptionsSet
|
45
|
+
option(:subscriptions_set_max_retries) { 10 }
|
46
|
+
# @!attribute subscriptions_set_retries_interval
|
47
|
+
# @return [Integer] interval in seconds between retries of failed SubscriptionsSet
|
48
|
+
option(:subscriptions_set_retries_interval) { 1 }
|
17
49
|
|
18
50
|
# @param name [Symbol] config's name. Its value matches the appropriate key in PgEventstore.config hash
|
19
51
|
def initialize(name:, **options)
|
data/lib/pg_eventstore/errors.rb
CHANGED
@@ -104,4 +104,84 @@ module PgEventstore
|
|
104
104
|
TEXT
|
105
105
|
end
|
106
106
|
end
|
107
|
+
|
108
|
+
class RecordNotFound < Error
|
109
|
+
attr_reader :table_name, :id
|
110
|
+
|
111
|
+
# @param table_name [String]
|
112
|
+
# @param id [Integer, String]
|
113
|
+
def initialize(table_name, id)
|
114
|
+
@table_name = table_name
|
115
|
+
@id = id
|
116
|
+
super(user_friendly_message)
|
117
|
+
end
|
118
|
+
|
119
|
+
# @return [String]
|
120
|
+
def user_friendly_message
|
121
|
+
"Could not find/update #{table_name.inspect} record with #{id.inspect} id."
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
class SubscriptionAlreadyLockedError < Error
|
126
|
+
attr_reader :set, :name, :lock_id
|
127
|
+
|
128
|
+
# @param set [String] subscriptions set name
|
129
|
+
# @param name [String] subscription's name
|
130
|
+
# @param lock_id [String] UUIDv4
|
131
|
+
def initialize(set, name, lock_id)
|
132
|
+
@set = set
|
133
|
+
@name = name
|
134
|
+
@lock_id = lock_id
|
135
|
+
super(user_friendly_message)
|
136
|
+
end
|
137
|
+
|
138
|
+
# @return [String]
|
139
|
+
def user_friendly_message
|
140
|
+
<<~TEXT.strip
|
141
|
+
Could not lock subscription from #{set.inspect} set with #{name.inspect} name. It is already locked by \
|
142
|
+
#{lock_id.inspect} set.
|
143
|
+
TEXT
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
class SubscriptionUnlockError < Error
|
148
|
+
attr_reader :set, :name, :expected_locked_by, :actual_locked_by
|
149
|
+
|
150
|
+
# @param set [String] subscription's set name
|
151
|
+
# @param name [String] subscription's name
|
152
|
+
# @param expected_locked_by [String] UUIDv4
|
153
|
+
# @param actual_locked_by [String, nil] UUIDv4
|
154
|
+
def initialize(set, name, expected_locked_by, actual_locked_by)
|
155
|
+
@set = set
|
156
|
+
@name = name
|
157
|
+
@expected_locked_by = expected_locked_by
|
158
|
+
@actual_locked_by = actual_locked_by
|
159
|
+
super(user_friendly_message)
|
160
|
+
end
|
161
|
+
|
162
|
+
# @return [String]
|
163
|
+
def user_friendly_message
|
164
|
+
<<~TEXT.strip
|
165
|
+
Failed to unlock subscription from #{set.inspect} set with #{name.inspect} name by \
|
166
|
+
#{expected_locked_by.inspect} lock id. It is currently locked by #{actual_locked_by.inspect} lock id.
|
167
|
+
TEXT
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
class NotPersistedEventError < Error
|
172
|
+
attr_reader :event
|
173
|
+
|
174
|
+
# @param event [PgEventstore::Event]
|
175
|
+
def initialize(event)
|
176
|
+
@event = event
|
177
|
+
super(user_friendly_message)
|
178
|
+
end
|
179
|
+
|
180
|
+
# @return [String]
|
181
|
+
def user_friendly_message
|
182
|
+
<<~TEXT.strip
|
183
|
+
Event#id must be present, got #{event.id.inspect} instead.
|
184
|
+
TEXT
|
185
|
+
end
|
186
|
+
end
|
107
187
|
end
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
module PgEventstore
|
4
4
|
# @!visibility private
|
5
|
-
class
|
5
|
+
class EventDeserializer
|
6
6
|
attr_reader :middlewares, :event_class_resolver
|
7
7
|
|
8
8
|
# @param middlewares [Array<Object<#deserialize, #serialize>>]
|
@@ -12,31 +12,14 @@ module PgEventstore
|
|
12
12
|
@event_class_resolver = event_class_resolver
|
13
13
|
end
|
14
14
|
|
15
|
-
# @param
|
16
|
-
|
17
|
-
|
18
|
-
pg_result.map(&method(:_deserialize))
|
15
|
+
# @param raw_events [Array<Hash>]
|
16
|
+
def deserialize_many(raw_events)
|
17
|
+
raw_events.map(&method(:deserialize))
|
19
18
|
end
|
20
|
-
alias deserialize_many deserialize
|
21
|
-
|
22
|
-
# @param pg_result [PG::Result]
|
23
|
-
# @return [PgEventstore::Event, nil]
|
24
|
-
def deserialize_one(pg_result)
|
25
|
-
return if pg_result.ntuples.zero?
|
26
|
-
|
27
|
-
_deserialize(pg_result.first)
|
28
|
-
end
|
29
|
-
|
30
|
-
# @return [PgEventstore::PgResultDeserializer]
|
31
|
-
def without_middlewares
|
32
|
-
self.class.new([], event_class_resolver)
|
33
|
-
end
|
34
|
-
|
35
|
-
private
|
36
19
|
|
37
20
|
# @param attrs [Hash]
|
38
21
|
# @return [PgEventstore::Event]
|
39
|
-
def
|
22
|
+
def deserialize(attrs)
|
40
23
|
event = event_class_resolver.call(attrs['type']).new(**attrs.transform_keys(&:to_sym))
|
41
24
|
middlewares.each do |middleware|
|
42
25
|
middleware.deserialize(event)
|
@@ -44,5 +27,10 @@ module PgEventstore
|
|
44
27
|
event.stream = PgEventstore::Stream.new(**attrs['stream'].transform_keys(&:to_sym)) if attrs.key?('stream')
|
45
28
|
event
|
46
29
|
end
|
30
|
+
|
31
|
+
# @return [PgEventstore::EventDeserializer]
|
32
|
+
def without_middlewares
|
33
|
+
self.class.new([], event_class_resolver)
|
34
|
+
end
|
47
35
|
end
|
48
36
|
end
|