event_store_client 1.4.9 → 2.0.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.
- checksums.yaml +4 -4
- data/README.md +30 -145
- data/docs/appending_events.md +155 -0
- data/docs/catch_up_subscriptions.md +253 -0
- data/docs/configuration.md +83 -0
- data/docs/deleting_streams.md +25 -0
- data/docs/encrypting_events.md +84 -0
- data/docs/linking_events.md +149 -0
- data/docs/reading_events.md +200 -0
- data/lib/event_store_client/adapters/grpc/client.rb +244 -105
- data/lib/event_store_client/adapters/grpc/cluster/gossip_discover.rb +131 -0
- data/lib/event_store_client/adapters/grpc/cluster/insecure_connection.rb +21 -0
- data/lib/event_store_client/adapters/grpc/cluster/member.rb +18 -0
- data/lib/event_store_client/adapters/grpc/cluster/queryless_discover.rb +25 -0
- data/lib/event_store_client/adapters/grpc/cluster/secure_connection.rb +71 -0
- data/lib/event_store_client/adapters/grpc/command_registrar.rb +7 -7
- data/lib/event_store_client/adapters/grpc/commands/command.rb +63 -25
- data/lib/event_store_client/adapters/grpc/commands/gossip/cluster_info.rb +24 -0
- data/lib/event_store_client/adapters/grpc/commands/streams/append.rb +43 -68
- data/lib/event_store_client/adapters/grpc/commands/streams/append_multiple.rb +44 -0
- data/lib/event_store_client/adapters/grpc/commands/streams/delete.rb +21 -17
- data/lib/event_store_client/adapters/grpc/commands/streams/hard_delete.rb +39 -0
- data/lib/event_store_client/adapters/grpc/commands/streams/link_to.rb +7 -52
- data/lib/event_store_client/adapters/grpc/commands/streams/link_to_multiple.rb +44 -0
- data/lib/event_store_client/adapters/grpc/commands/streams/read.rb +20 -85
- data/lib/event_store_client/adapters/grpc/commands/streams/read_paginated.rb +174 -0
- data/lib/event_store_client/adapters/grpc/commands/streams/subscribe.rb +31 -106
- data/lib/event_store_client/adapters/grpc/connection.rb +56 -36
- data/lib/event_store_client/adapters/grpc/discover.rb +75 -0
- data/lib/event_store_client/adapters/grpc/generated/cluster_pb.rb +106 -18
- data/lib/event_store_client/adapters/grpc/generated/cluster_services_pb.rb +12 -12
- data/lib/event_store_client/adapters/grpc/generated/code_pb.rb +34 -0
- data/lib/event_store_client/adapters/grpc/generated/gossip_pb.rb +3 -2
- data/lib/event_store_client/adapters/grpc/generated/gossip_services_pb.rb +3 -3
- data/lib/event_store_client/adapters/grpc/generated/monitoring_pb.rb +25 -0
- data/lib/event_store_client/adapters/grpc/generated/monitoring_services_pb.rb +26 -0
- data/lib/event_store_client/adapters/grpc/generated/operations_pb.rb +2 -1
- data/lib/event_store_client/adapters/grpc/generated/operations_services_pb.rb +8 -7
- data/lib/event_store_client/adapters/grpc/generated/persistent_pb.rb +199 -38
- data/lib/event_store_client/adapters/grpc/generated/persistent_services_pb.rb +7 -3
- data/lib/event_store_client/adapters/grpc/generated/projections_pb.rb +9 -26
- data/lib/event_store_client/adapters/grpc/generated/projections_services_pb.rb +4 -3
- data/lib/event_store_client/adapters/grpc/generated/serverfeatures_pb.rb +29 -0
- data/lib/event_store_client/adapters/grpc/generated/serverfeatures_services_pb.rb +26 -0
- data/lib/event_store_client/adapters/grpc/generated/shared_pb.rb +54 -12
- data/lib/event_store_client/adapters/grpc/generated/status_pb.rb +23 -0
- data/lib/event_store_client/adapters/grpc/generated/streams_pb.rb +104 -64
- data/lib/event_store_client/adapters/grpc/generated/streams_services_pb.rb +3 -2
- data/lib/event_store_client/adapters/grpc/generated/users_services_pb.rb +2 -2
- data/lib/event_store_client/adapters/grpc/options/streams/read_options.rb +78 -0
- data/lib/event_store_client/adapters/grpc/options/streams/write_options.rb +39 -0
- data/lib/event_store_client/adapters/grpc/shared/event_deserializer.rb +52 -0
- data/lib/event_store_client/adapters/grpc/shared/options/filter_options.rb +76 -0
- data/lib/event_store_client/adapters/grpc/shared/options/stream_options.rb +91 -0
- data/lib/event_store_client/adapters/grpc/shared/streams/process_response.rb +28 -0
- data/lib/event_store_client/adapters/grpc/shared/streams/process_responses.rb +33 -0
- data/lib/event_store_client/adapters/grpc.rb +28 -12
- data/lib/event_store_client/configuration.rb +39 -54
- data/lib/event_store_client/connection/url.rb +57 -0
- data/lib/event_store_client/connection/url_parser.rb +144 -0
- data/lib/event_store_client/data_decryptor.rb +2 -9
- data/lib/event_store_client/deserialized_event.rb +35 -10
- data/lib/event_store_client/encryption_metadata.rb +0 -1
- data/lib/event_store_client/event.rb +4 -2
- data/lib/event_store_client/extensions/options_extension.rb +87 -0
- data/lib/event_store_client/mapper/default.rb +12 -9
- data/lib/event_store_client/mapper/encrypted.rb +18 -17
- data/lib/event_store_client/types.rb +1 -1
- data/lib/event_store_client/utils.rb +30 -0
- data/lib/event_store_client/version.rb +1 -1
- data/lib/event_store_client.rb +8 -7
- metadata +74 -83
- data/lib/event_store_client/adapters/grpc/Protos/cluster.proto +0 -149
- data/lib/event_store_client/adapters/grpc/Protos/gossip.proto +0 -44
- data/lib/event_store_client/adapters/grpc/Protos/operations.proto +0 -45
- data/lib/event_store_client/adapters/grpc/Protos/persistent.proto +0 -180
- data/lib/event_store_client/adapters/grpc/Protos/projections.proto +0 -174
- data/lib/event_store_client/adapters/grpc/Protos/shared.proto +0 -22
- data/lib/event_store_client/adapters/grpc/Protos/streams.proto +0 -242
- data/lib/event_store_client/adapters/grpc/Protos/users.proto +0 -119
- data/lib/event_store_client/adapters/grpc/README.md +0 -16
- data/lib/event_store_client/adapters/grpc/commands/persistent_subscriptions/create.rb +0 -46
- data/lib/event_store_client/adapters/grpc/commands/persistent_subscriptions/delete.rb +0 -34
- data/lib/event_store_client/adapters/grpc/commands/persistent_subscriptions/read.rb +0 -77
- data/lib/event_store_client/adapters/grpc/commands/persistent_subscriptions/settings_schema.rb +0 -38
- data/lib/event_store_client/adapters/grpc/commands/persistent_subscriptions/update.rb +0 -48
- data/lib/event_store_client/adapters/grpc/commands/projections/create.rb +0 -48
- data/lib/event_store_client/adapters/grpc/commands/projections/delete.rb +0 -34
- data/lib/event_store_client/adapters/grpc/commands/projections/update.rb +0 -44
- data/lib/event_store_client/adapters/grpc/commands/streams/read_all.rb +0 -43
- data/lib/event_store_client/adapters/grpc/commands/streams/tombstone.rb +0 -35
- data/lib/event_store_client/adapters/http/README.md +0 -16
- data/lib/event_store_client/adapters/http/client.rb +0 -161
- data/lib/event_store_client/adapters/http/commands/command.rb +0 -27
- data/lib/event_store_client/adapters/http/commands/persistent_subscriptions/ack.rb +0 -15
- data/lib/event_store_client/adapters/http/commands/persistent_subscriptions/create.rb +0 -35
- data/lib/event_store_client/adapters/http/commands/persistent_subscriptions/read.rb +0 -60
- data/lib/event_store_client/adapters/http/commands/projections/create.rb +0 -33
- data/lib/event_store_client/adapters/http/commands/projections/update.rb +0 -31
- data/lib/event_store_client/adapters/http/commands/streams/append.rb +0 -49
- data/lib/event_store_client/adapters/http/commands/streams/delete.rb +0 -16
- data/lib/event_store_client/adapters/http/commands/streams/link_to.rb +0 -49
- data/lib/event_store_client/adapters/http/commands/streams/read.rb +0 -52
- data/lib/event_store_client/adapters/http/commands/streams/tombstone.rb +0 -17
- data/lib/event_store_client/adapters/http/connection.rb +0 -46
- data/lib/event_store_client/adapters/http/request_method.rb +0 -28
- data/lib/event_store_client/adapters/http.rb +0 -17
- data/lib/event_store_client/adapters/in_memory.rb +0 -144
- data/lib/event_store_client/broker.rb +0 -40
- data/lib/event_store_client/catch_up_subscription.rb +0 -42
- data/lib/event_store_client/catch_up_subscriptions.rb +0 -92
- data/lib/event_store_client/client.rb +0 -73
- data/lib/event_store_client/error_handler.rb +0 -10
- data/lib/event_store_client/subscription.rb +0 -23
- data/lib/event_store_client/subscriptions.rb +0 -38
- data/lib/event_store_client/value_objects/read_direction.rb +0 -43
@@ -1,136 +1,275 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# rubocop:disable Layout/LineLength, Metrics/ParameterLists
|
4
|
+
|
3
5
|
module EventStoreClient
|
4
6
|
module GRPC
|
5
7
|
class Client
|
6
8
|
include Configuration
|
7
|
-
# Appends given events to the stream
|
8
|
-
# @param [String] Stream name to append events to
|
9
|
-
# @param [Array](each: EventStoreClient::DeserializedEvent) list of events to publish
|
10
|
-
# @return Dry::Monads::Result::Success or Dry::Monads::Result::Failure
|
11
|
-
#
|
12
|
-
def append_to_stream(stream_name, events, options: {})
|
13
|
-
Commands::Streams::Append.new.call(
|
14
|
-
stream_name, events, options: options
|
15
|
-
)
|
16
|
-
end
|
17
9
|
|
18
|
-
#
|
19
|
-
# @param [
|
20
|
-
# @param options [Hash]
|
21
|
-
# @
|
22
|
-
#
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
10
|
+
# @param stream_name [String]
|
11
|
+
# @param events_or_event [EventStoreClient::DeserializedEvent, Array<EventStoreClient::DeserializedEvent>]
|
12
|
+
# @param options [Hash]
|
13
|
+
# @option options [Integer] :expected_revision provide your own revision number
|
14
|
+
# @option options [String] :expected_revision provide one of next values: 'any', 'no_stream'
|
15
|
+
# or 'stream_exists'
|
16
|
+
# @param credentials [Hash]
|
17
|
+
# @option credentials [String] :username override authentication username
|
18
|
+
# @option credentials [String] :password override authentication password
|
19
|
+
# @yield [EventStore::Client::Streams::AppendReq, EventStore::Client::Streams::AppendReq]
|
20
|
+
# yields options and proposed message option right before sending the request. You can
|
21
|
+
# extend it with your own options, not covered in the default implementation.
|
22
|
+
# Example:
|
23
|
+
# ```ruby
|
24
|
+
# append_to_stream('some-stream', event) do |req_opts, proposed_msg_opts|
|
25
|
+
# puts req_opts.options
|
26
|
+
# puts proposed_msg_opts.proposed_message
|
27
|
+
# end
|
28
|
+
# ```
|
29
|
+
# @return [Dry::Monads::Result::Success, Dry::Monads::Result::Failure, Array<Dry::Monads::Result::Success, Dry::Monads::Result::Failure>]
|
30
|
+
# Returns monads' Success/Failure in case whether request was performed.
|
31
|
+
def append_to_stream(stream_name, events_or_event, options: {}, credentials: {}, &blk)
|
32
|
+
if events_or_event.is_a?(Array)
|
33
|
+
Commands::Streams::AppendMultiple.new(**credentials).call(
|
34
|
+
stream_name, events_or_event, options: options
|
35
|
+
)
|
36
|
+
else
|
37
|
+
Commands::Streams::Append.new(**credentials).call(
|
38
|
+
stream_name, events_or_event, options: options, &blk
|
39
|
+
)
|
40
|
+
end
|
27
41
|
end
|
28
42
|
|
29
|
-
#
|
30
|
-
# @param [
|
31
|
-
# @param
|
32
|
-
# @
|
43
|
+
# @param stream_name [String]
|
44
|
+
# @param skip_deserialization [Boolean]
|
45
|
+
# @param skip_decryption [Boolean]
|
46
|
+
# @param options [Hash] request options
|
47
|
+
# @option options [String] :direction read direction - 'Forwards' or 'Backwards'
|
48
|
+
# @option options [Integer, Symbol] :from_revision. If number is provided - it is threaded
|
49
|
+
# as starting revision number. Alternatively you can provide :start or :end value to
|
50
|
+
# define a stream revision. **Use this option when stream name is a normal stream name**
|
51
|
+
# @option options [Hash, Symbol] :from_position. If hash is provided - you should supply
|
52
|
+
# it with :commit_position and/or :prepare_position keys. Alternatively you can provide
|
53
|
+
# :start or :end value to define a stream position. **Use this option when stream name
|
54
|
+
# is "$all"**
|
55
|
+
# @option options [Integer] :max_count max number of events to return in one response
|
56
|
+
# @option options [Boolean] :resolve_link_tos When using projections to create new events you
|
57
|
+
# can set whether the generated events are pointers to existing events. Setting this value
|
58
|
+
# to true tells EventStoreDB to return the event as well as the event linking to it.
|
59
|
+
# @option options [Hash] :filter provide it to filter events when reading from $all. You can
|
60
|
+
# either filter by stream name or filter by event type. Filtering can be done by using
|
61
|
+
# Regexp or by a string.
|
62
|
+
# Examples:
|
63
|
+
# ```ruby
|
64
|
+
# # Include events only from streams which names start from 'some-stream-1' and
|
65
|
+
# # 'some-stream-2'
|
66
|
+
# { filter: { stream_identifier: { prefix: ['some-stream-1', 'some-stream-2'] } } }
|
33
67
|
#
|
34
|
-
|
35
|
-
|
36
|
-
end
|
37
|
-
|
38
|
-
# Reads a page of events from the given stream
|
39
|
-
# @param [String] Stream name to read events from
|
40
|
-
# @param options [Hash] additional options to the request
|
41
|
-
# @return Dry::Monads::Result::Success with returned events or Dry::Monads::Result::Failure
|
68
|
+
# # Include events only from streams which names end with digit
|
69
|
+
# { filter: { stream_identifier: { regex: /\d$/.to_s } } }
|
42
70
|
#
|
43
|
-
|
44
|
-
|
45
|
-
end
|
46
|
-
|
47
|
-
# Reads all events from the given stream
|
48
|
-
# @param [String] Stream name to read events from
|
49
|
-
# @param options [Hash] additional options to the request
|
50
|
-
# @return Dry::Monads::Result::Success with returned events or Dry::Monads::Result::Failure
|
71
|
+
# # Include events which start from 'some-event-1' and 'some-event-2'
|
72
|
+
# { filter: { event_type: { prefix: ['some-event-1', 'some-event-2'] } } }
|
51
73
|
#
|
52
|
-
|
53
|
-
|
74
|
+
# # Include events which names end with digit
|
75
|
+
# { filter: { event_type: { regex: /\d$/.to_s } } }
|
76
|
+
# ```
|
77
|
+
# @param credentials [Hash]
|
78
|
+
# @option credentials [String] :username override authentication username
|
79
|
+
# @option credentials [String] :password override authentication password
|
80
|
+
# @yield [EventStore::Client::Streams::ReadReq::Options] yields request options right
|
81
|
+
# before sending the request. You can extend it with your own options, not covered in
|
82
|
+
# the default implementation.
|
83
|
+
# Example:
|
84
|
+
# ```ruby
|
85
|
+
# read('$all') do |opts|
|
86
|
+
# opts.filter = EventStore::Client::Streams::ReadReq::Options::FilterOptions.new(
|
87
|
+
# { stream_identifier: { prefix: ['as'] }, count: EventStore::Client::Empty.new }
|
88
|
+
# )
|
89
|
+
# end
|
90
|
+
# ```
|
91
|
+
# @return [Dry::Monads::Success, Dry::Monads::Failure]
|
92
|
+
def read(stream_name, options: {}, skip_deserialization: config.skip_deserialization,
|
93
|
+
skip_decryption: config.skip_decryption, credentials: {}, &blk)
|
94
|
+
Commands::Streams::Read.new(**credentials).call(
|
95
|
+
stream_name,
|
96
|
+
options: options,
|
97
|
+
skip_deserialization: skip_deserialization,
|
98
|
+
skip_decryption: skip_decryption,
|
99
|
+
&blk
|
100
|
+
)
|
54
101
|
end
|
55
102
|
|
56
|
-
#
|
57
|
-
# @
|
58
|
-
#
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
103
|
+
# @see {#read} for available params
|
104
|
+
# @return [Enumerator] enumerator will yield Dry::Monads::Success or Dry::Monads::Failure on
|
105
|
+
# each iteration
|
106
|
+
def read_paginated(stream_name, options: {}, credentials: {},
|
107
|
+
skip_deserialization: config.skip_deserialization,
|
108
|
+
skip_decryption: config.skip_decryption, &blk)
|
109
|
+
Commands::Streams::ReadPaginated.new(**credentials).call(
|
110
|
+
stream_name,
|
111
|
+
options: options,
|
112
|
+
skip_deserialization: skip_deserialization,
|
113
|
+
skip_decryption: skip_decryption,
|
114
|
+
&blk
|
67
115
|
)
|
68
116
|
end
|
69
117
|
|
70
|
-
#
|
71
|
-
# @param [String]
|
72
|
-
# @param [
|
73
|
-
# @
|
74
|
-
#
|
75
|
-
#
|
76
|
-
|
77
|
-
|
118
|
+
# Refs https://developers.eventstore.com/server/v5/streams.html#hard-delete
|
119
|
+
# @param stream_name [String]
|
120
|
+
# @param options [Hash]
|
121
|
+
# @option options [Integer, String] :expected_revision provide your own revision number.
|
122
|
+
# Alternatively you can provide one of next values: 'any', 'no_stream' or 'stream_exists'.
|
123
|
+
# @param credentials [Hash]
|
124
|
+
# @option credentials [String] :username override authentication username
|
125
|
+
# @option credentials [String] :password override authentication password
|
126
|
+
# @yield [EventStore::Client::Streams::TombstoneReq::Options] yields request options right
|
127
|
+
# before sending the request. You can override them in your own way.
|
128
|
+
# Example:
|
129
|
+
# ```ruby
|
130
|
+
# delete_stream('stream_name') do |opts|
|
131
|
+
# opts.stream_identifier.stream_name = 'overridden-stream-name'
|
132
|
+
# end
|
133
|
+
# ```
|
134
|
+
# @return [Dry::Monads::Success, Dry::Monads::Failure]
|
135
|
+
def hard_delete_stream(stream_name, options: {}, credentials: {}, &blk)
|
136
|
+
Commands::Streams::HardDelete.new(**credentials).call(stream_name, options: options, &blk)
|
78
137
|
end
|
79
138
|
|
80
|
-
#
|
81
|
-
# @param [
|
82
|
-
# @param options [Hash]
|
83
|
-
# @
|
84
|
-
#
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
139
|
+
# Refs https://developers.eventstore.com/server/v5/streams.html#soft-delete-and-truncatebefore
|
140
|
+
# @param stream_name [String]
|
141
|
+
# @param options [Hash]
|
142
|
+
# @option options [Integer, String] :expected_revision provide your own revision number.
|
143
|
+
# Alternatively you can provide one of next values: 'any', 'no_stream' or 'stream_exists'.
|
144
|
+
# @param credentials [Hash]
|
145
|
+
# @option credentials [String] :username override authentication username
|
146
|
+
# @option credentials [String] :password override authentication password
|
147
|
+
# @yield [EventStore::Client::Streams::DeleteReq::Options] yields request options right
|
148
|
+
# before sending the request. You can override them in your own way.
|
149
|
+
# Example:
|
150
|
+
# ```ruby
|
151
|
+
# delete_stream('stream_name') do |opts|
|
152
|
+
# opts.stream_identifier.stream_name = 'overridden-stream-name'
|
153
|
+
# end
|
154
|
+
# ```
|
155
|
+
# @return [Dry::Monads::Success, Dry::Monads::Failure]
|
156
|
+
def delete_stream(stream_name, options: {}, credentials: {}, &blk)
|
157
|
+
Commands::Streams::Delete.new(**credentials).call(stream_name, options: options, &blk)
|
93
158
|
end
|
94
159
|
|
95
|
-
# Subscribe to
|
96
|
-
#
|
97
|
-
#
|
160
|
+
# Subscribe to the given stream and listens for events. Note, that it will block execution of
|
161
|
+
# current stack. If you want to do it asynchronous - consider putting it out of current
|
162
|
+
# thread.
|
163
|
+
# @param stream_name [String]
|
164
|
+
# @param handler [#call] whenever new event arrives - #call method of your handler will be
|
165
|
+
# called with the response passed into it
|
166
|
+
# @param skip_deserialization [Boolean]
|
167
|
+
# @param skip_decryption [Boolean]
|
168
|
+
# @param options [Hash] request options
|
169
|
+
# @option options [String] :direction read direction - 'Forwards' or 'Backwards'
|
170
|
+
# @option options [Integer, Symbol] :from_revision. If number is provided - it is threaded
|
171
|
+
# as starting revision number. Alternatively you can provide :start or :end value to
|
172
|
+
# define a stream revision. **Use this option when stream name is a normal stream name**
|
173
|
+
# @option options [Hash, Symbol] :from_position. If hash is provided - you should supply
|
174
|
+
# it with :commit_position and/or :prepare_position keys. Alternatively you can provide
|
175
|
+
# :start or :end value to define a stream position. **Use this option when stream name
|
176
|
+
# is "$all"**
|
177
|
+
# @option options [Boolean] :resolve_link_tos When using projections to create new events you
|
178
|
+
# can set whether the generated events are pointers to existing events. Setting this value
|
179
|
+
# to true tells EventStoreDB to return the event as well as the event linking to it.
|
180
|
+
# @option options [Hash] :filter provide it to filter events when subscribing to $all. You can
|
181
|
+
# either filter by stream name or filter by event type. Filtering can be done by using
|
182
|
+
# Regexp or by a string.
|
183
|
+
# Examples:
|
184
|
+
# ```ruby
|
185
|
+
# # Include events only from streams which names start from 'some-stream-1' and
|
186
|
+
# # 'some-stream-2'
|
187
|
+
# { filter: { stream_identifier: { prefix: ['some-stream-1', 'some-stream-2'] } } }
|
98
188
|
#
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
config.error_handler&.call(e)
|
105
|
-
end
|
106
|
-
|
107
|
-
private
|
108
|
-
|
109
|
-
# Joins multiple streams into the new one under the given name
|
110
|
-
# @param [String] Name of the stream containing the ones to join
|
111
|
-
# @param [Array] (each: String) list of streams to join together
|
112
|
-
# @return Dry::Monads::Result::Success or Dry::Monads::Result::Failure
|
189
|
+
# # Include events only from streams which names end with digit
|
190
|
+
# { filter: { stream_identifier: { regex: /\d$/.to_s } } }
|
191
|
+
#
|
192
|
+
# # Include events which start from 'some-event-1' and 'some-event-2'
|
193
|
+
# { filter: { event_type: { prefix: ['some-event-1', 'some-event-2'] } } }
|
113
194
|
#
|
114
|
-
|
115
|
-
|
116
|
-
|
195
|
+
# # Include events which names end with digit
|
196
|
+
# { filter: { event_type: { regex: /\d$/.to_s } } }
|
197
|
+
# ```
|
198
|
+
# @param credentials [Hash]
|
199
|
+
# @option credentials [String] :username override authentication username
|
200
|
+
# @option credentials [String] :password override authentication password
|
201
|
+
# @yield [EventStore::Client::Streams::ReadReq::Options] yields request options right
|
202
|
+
# before sending the request. You can extend it with your own options, not covered in
|
203
|
+
# the default implementation.
|
204
|
+
# Example:
|
205
|
+
# ```ruby
|
206
|
+
# subscribe_to_stream('$all', handler: proc { |response| puts response }) do |opts|
|
207
|
+
# opts.filter = EventStore::Client::Streams::ReadReq::Options::FilterOptions.new(
|
208
|
+
# { stream_identifier: { prefix: ['as'] }, max: 100 }
|
209
|
+
# )
|
210
|
+
# end
|
211
|
+
# ```
|
212
|
+
# @return [Dry::Monads::Success, Dry::Monads::Failure]
|
213
|
+
def subscribe_to_stream(stream_name, handler:, options: {}, credentials: {},
|
214
|
+
skip_deserialization: config.skip_deserialization,
|
215
|
+
skip_decryption: config.skip_decryption, &blk)
|
216
|
+
Commands::Streams::Subscribe.new(**credentials).call(
|
217
|
+
stream_name,
|
218
|
+
handler: handler,
|
219
|
+
options: options,
|
220
|
+
skip_deserialization: skip_deserialization,
|
221
|
+
skip_decryption: skip_decryption,
|
222
|
+
&blk
|
223
|
+
)
|
224
|
+
end
|
117
225
|
|
118
|
-
|
226
|
+
# This method acts the same as #subscribe_to_stream with the only exception that it subscribes
|
227
|
+
# to $all stream
|
228
|
+
# @see #subscribe_to_stream
|
229
|
+
def subscribe_to_all(handler:, options: {}, credentials: {},
|
230
|
+
skip_deserialization: config.skip_deserialization,
|
231
|
+
skip_decryption: config.skip_decryption, &blk)
|
232
|
+
Commands::Streams::Subscribe.new(**credentials).call(
|
233
|
+
'$all',
|
234
|
+
handler: handler,
|
235
|
+
options: options,
|
236
|
+
skip_deserialization: skip_deserialization,
|
237
|
+
skip_decryption: skip_decryption,
|
238
|
+
&blk
|
239
|
+
)
|
119
240
|
end
|
120
241
|
|
121
|
-
#
|
122
|
-
#
|
123
|
-
#
|
124
|
-
#
|
125
|
-
# @
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
242
|
+
# Links event from one stream into another stream. You can later access it by providing
|
243
|
+
# :resolve_link_tos option when reading from a stream. If you provide an event that does not
|
244
|
+
# present in EventStore database yet - its data will not be appended properly to the stream,
|
245
|
+
# thus, making it look as a malformed event.
|
246
|
+
# @see #append_to_stream for available params and returned value
|
247
|
+
def link_to(stream_name, events_or_event, options: {}, credentials: {}, &blk)
|
248
|
+
if events_or_event.is_a?(Array)
|
249
|
+
Commands::Streams::LinkToMultiple.new(**credentials).call(
|
250
|
+
stream_name,
|
251
|
+
events_or_event,
|
252
|
+
options: options,
|
253
|
+
&blk
|
254
|
+
)
|
255
|
+
else
|
256
|
+
Commands::Streams::LinkTo.new(**credentials).call(
|
257
|
+
stream_name,
|
258
|
+
events_or_event,
|
259
|
+
options: options,
|
260
|
+
&blk
|
261
|
+
)
|
132
262
|
end
|
133
263
|
end
|
264
|
+
|
265
|
+
# @param credentials [Hash]
|
266
|
+
# @option credentials [String] :username
|
267
|
+
# @option credentials [String] :password
|
268
|
+
# @return [Dry::Monads::Success, Dry::Monads::Failure]
|
269
|
+
def cluster_info(credentials: {})
|
270
|
+
Commands::Gossip::ClusterInfo.new(**credentials).call
|
271
|
+
end
|
134
272
|
end
|
135
273
|
end
|
136
274
|
end
|
275
|
+
# rubocop:enable Layout/LineLength, Metrics/ParameterLists
|
@@ -0,0 +1,131 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
4
|
+
|
5
|
+
require 'event_store_client/adapters/grpc/generated/shared_pb'
|
6
|
+
require 'event_store_client/adapters/grpc/generated/gossip_pb'
|
7
|
+
require 'event_store_client/adapters/grpc/generated/gossip_services_pb'
|
8
|
+
|
9
|
+
module EventStoreClient
|
10
|
+
module GRPC
|
11
|
+
module Cluster
|
12
|
+
class GossipDiscover
|
13
|
+
include Configuration
|
14
|
+
|
15
|
+
DiscoverError = Class.new(StandardError)
|
16
|
+
|
17
|
+
# Order is important - it plays role of states priority as well
|
18
|
+
READ_ONLY_STATES = %i[ReadOnlyReplica PreReadOnlyReplica ReadOnlyLeaderless].freeze
|
19
|
+
ALLOWED_NODE_STATES =
|
20
|
+
(%i[Leader Follower] + READ_ONLY_STATES).freeze
|
21
|
+
|
22
|
+
# @param nodes [Array<EventStoreClient::Connection::Url::Node>]
|
23
|
+
# @param failed_member [EventStoreClient::GRPC::Cluster::Member, nil]
|
24
|
+
# @return [EventStoreClient::GRPC::Cluster::Member]
|
25
|
+
# @raise [EventStoreClient::GRPC::Cluster::GossipDiscover::DiscoverError]
|
26
|
+
def call(nodes, failed_member:)
|
27
|
+
nodes = sort_nodes(nodes, failed_member)
|
28
|
+
|
29
|
+
attempts = config.eventstore_url.max_discover_attempts
|
30
|
+
attempts.times do
|
31
|
+
nodes.each do |node|
|
32
|
+
suitable_member = suitable_member_of_node(node)
|
33
|
+
next unless suitable_member
|
34
|
+
|
35
|
+
return suitable_member
|
36
|
+
end
|
37
|
+
|
38
|
+
sleep(config.eventstore_url.discover_interval / 1000.0)
|
39
|
+
end
|
40
|
+
|
41
|
+
raise DiscoverError, "Failed to discover suitable host after #{attempts} attempts."
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
# @return [Array<Symbol>]
|
47
|
+
def ordered_states
|
48
|
+
@ordered_states ||=
|
49
|
+
# Move preferred state to the first place
|
50
|
+
case config.eventstore_url.node_preference
|
51
|
+
when :Leader, :Follower
|
52
|
+
[config.eventstore_url.node_preference] +
|
53
|
+
(ALLOWED_NODE_STATES - [config.eventstore_url.node_preference])
|
54
|
+
else
|
55
|
+
READ_ONLY_STATES + (ALLOWED_NODE_STATES - READ_ONLY_STATES)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# @param node [EventStoreClient::Connection::Url::Node]
|
60
|
+
# @return [Array<EventStoreClient::GRPC::Cluster::Member>, nil]
|
61
|
+
def node_members(node)
|
62
|
+
connection = Connection.new(
|
63
|
+
host: node.host,
|
64
|
+
port: node.port,
|
65
|
+
timeout: config.eventstore_url.gossip_timeout
|
66
|
+
)
|
67
|
+
members =
|
68
|
+
connection.
|
69
|
+
call(EventStore::Client::Gossip::Gossip::Stub).
|
70
|
+
read(EventStore::Client::Empty.new).
|
71
|
+
members
|
72
|
+
members.map do |member|
|
73
|
+
Member.new(
|
74
|
+
host: member.http_end_point.address,
|
75
|
+
port: member.http_end_point.port,
|
76
|
+
state: member.state,
|
77
|
+
active: member.is_alive,
|
78
|
+
instance_id: Utils.uuid_to_str(member.instance_id)
|
79
|
+
)
|
80
|
+
end
|
81
|
+
rescue ::GRPC::BadStatus, Errno::ECONNREFUSED
|
82
|
+
config.logger&.debug("Failed to get cluster list from #{node.host}:#{node.port} node.")
|
83
|
+
nil
|
84
|
+
end
|
85
|
+
|
86
|
+
# Pick a suitable member based on its status and node preference
|
87
|
+
# @return [Array<EventStoreClient::GRPC::Cluster::Member>]
|
88
|
+
# @return [EventStoreClient::GRPC::Cluster::Member]
|
89
|
+
def detect_suitable_member(members)
|
90
|
+
members = members.select(&:active)
|
91
|
+
members = members.select { |member| ALLOWED_NODE_STATES.include?(member.state) }
|
92
|
+
members = members.sort_by { |member| ordered_states.index(member.state) }
|
93
|
+
members.first
|
94
|
+
end
|
95
|
+
|
96
|
+
# Put failed node to the end of the list
|
97
|
+
# @param nodes [Array<EventStoreClient::Connection::Url::Node>]
|
98
|
+
# @param failed_member [EventStoreClient::GRPC::Cluster::Member, nil]
|
99
|
+
# @return [Array<EventStoreClient::Connection::Url::Node>]
|
100
|
+
def sort_nodes(nodes, failed_member)
|
101
|
+
return nodes unless failed_member
|
102
|
+
|
103
|
+
nodes.sort_by do |node|
|
104
|
+
failed_member.host == node.host && failed_member.port == node.port ? 1 : 0
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
# Searches a suitable member among members of the given node
|
109
|
+
# @param node [EventStoreClient::Connection::Url::Node]
|
110
|
+
# @return [EventStoreClient::GRPC::Cluster::Member, nil]
|
111
|
+
def suitable_member_of_node(node)
|
112
|
+
config.logger&.debug(
|
113
|
+
"Starting to discover #{node.host}:#{node.port} node for candidates."
|
114
|
+
)
|
115
|
+
members = node_members(node)
|
116
|
+
return unless members
|
117
|
+
|
118
|
+
suitable_member = detect_suitable_member(members)
|
119
|
+
return unless suitable_member
|
120
|
+
|
121
|
+
config.logger&.debug(
|
122
|
+
"Found suitable member: #{suitable_member.host}:#{suitable_member.port} with " \
|
123
|
+
"role \"#{suitable_member.state}\"."
|
124
|
+
)
|
125
|
+
suitable_member
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module EventStoreClient
|
4
|
+
module GRPC
|
5
|
+
module Cluster
|
6
|
+
class InsecureConnection < Connection
|
7
|
+
# @param stub_class GRPC request stub class. E.g. EventStore::Client::Gossip::Gossip::Stub
|
8
|
+
# @return instance of the given stub_class class
|
9
|
+
def call(stub_class)
|
10
|
+
config.logger&.debug('Using insecure connection.')
|
11
|
+
stub_class.new(
|
12
|
+
"#{host}:#{port}",
|
13
|
+
:this_channel_is_insecure,
|
14
|
+
channel_args: channel_args,
|
15
|
+
timeout: (timeout / 1000.0 if timeout)
|
16
|
+
)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module EventStoreClient
|
4
|
+
module GRPC
|
5
|
+
module Cluster
|
6
|
+
class Member
|
7
|
+
include Extensions::OptionsExtension
|
8
|
+
|
9
|
+
option(:host) # String
|
10
|
+
option(:port) # Integer
|
11
|
+
option(:active) # boolean
|
12
|
+
option(:instance_id) # string
|
13
|
+
option(:state) # symbol
|
14
|
+
option(:failed_endpoint) { false } # boolean
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module EventStoreClient
|
4
|
+
module GRPC
|
5
|
+
module Cluster
|
6
|
+
class QuerylessDiscover
|
7
|
+
include Configuration
|
8
|
+
|
9
|
+
NoHostError = Class.new(StandardError)
|
10
|
+
|
11
|
+
# @param nodes [EventStoreClient::Connection::Url::Node]
|
12
|
+
# @return [EventStoreClient::GRPC::Cluster::Member]
|
13
|
+
def call(nodes)
|
14
|
+
raise NoHostError, 'No host setup' if nodes.empty?
|
15
|
+
|
16
|
+
Member.new(host: nodes.first.host, port: nodes.first.port).tap do |member|
|
17
|
+
config.logger&.debug(
|
18
|
+
"Picking #{member.host}:#{member.port} member without cluster discovery."
|
19
|
+
)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module EventStoreClient
|
4
|
+
module GRPC
|
5
|
+
module Cluster
|
6
|
+
class SecureConnection < Connection
|
7
|
+
CertificateLookupError = Class.new(StandardError)
|
8
|
+
|
9
|
+
# @param stub_class GRPC request stub class. E.g. EventStore::Client::Gossip::Gossip::Stub
|
10
|
+
# @return instance of the given stub_class class
|
11
|
+
def call(stub_class)
|
12
|
+
config.logger&.debug("Using secure connection with credentials #{username}:#{password}.")
|
13
|
+
stub_class.new(
|
14
|
+
"#{host}:#{port}",
|
15
|
+
channel_credentials,
|
16
|
+
channel_args: channel_args,
|
17
|
+
timeout: (timeout / 1000.0 if timeout)
|
18
|
+
)
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
# @return [GRPC::Core::ChannelCredentials]
|
24
|
+
def channel_credentials
|
25
|
+
certificate =
|
26
|
+
if config.eventstore_url.tls_ca_file
|
27
|
+
config.logger&.debug('Picking certificate from tlsCAFile option.')
|
28
|
+
File.read(config.eventstore_url.tls_ca_file)
|
29
|
+
else
|
30
|
+
config.logger&.debug('Resolving certificate from current member.')
|
31
|
+
cert.to_s
|
32
|
+
end
|
33
|
+
|
34
|
+
::GRPC::Core::ChannelCredentials.new(certificate)
|
35
|
+
end
|
36
|
+
|
37
|
+
# rubocop:disable Metrics/AbcSize
|
38
|
+
|
39
|
+
# @return [String, nil] returns the X.509 certificates the server presented
|
40
|
+
# @raise [EventStoreClient::GRPC::Cluster::SecureConnection::CertificateLookupError]
|
41
|
+
def cert
|
42
|
+
retries = 0
|
43
|
+
|
44
|
+
begin
|
45
|
+
Net::HTTP.start(host, port, use_ssl: true, verify_mode: verify_mode, &:peer_cert)
|
46
|
+
rescue SocketError => e
|
47
|
+
attempts = config.eventstore_url.ca_lookup_attempts
|
48
|
+
sleep config.eventstore_url.ca_lookup_interval / 1000.0
|
49
|
+
retries += 1
|
50
|
+
if retries <= attempts
|
51
|
+
config.logger&.debug("Failed to lookup certificate. Reason: #{e.class}. Retying.")
|
52
|
+
retry
|
53
|
+
end
|
54
|
+
raise(
|
55
|
+
CertificateLookupError,
|
56
|
+
"Failed to get X.509 certificate after #{attempts} attempts."
|
57
|
+
)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
# rubocop:enable Metrics/AbcSize
|
61
|
+
|
62
|
+
# @return [Integer] SSL verify mode
|
63
|
+
def verify_mode
|
64
|
+
return OpenSSL::SSL::VERIFY_PEER if config.eventstore_url.tls_verify_cert
|
65
|
+
|
66
|
+
OpenSSL::SSL::VERIFY_NONE
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|