event_store_client 1.4.9 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|