event_store_client 1.4.9 → 2.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (116) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +30 -145
  3. data/docs/appending_events.md +155 -0
  4. data/docs/catch_up_subscriptions.md +253 -0
  5. data/docs/configuration.md +83 -0
  6. data/docs/deleting_streams.md +25 -0
  7. data/docs/encrypting_events.md +84 -0
  8. data/docs/linking_events.md +149 -0
  9. data/docs/reading_events.md +200 -0
  10. data/lib/event_store_client/adapters/grpc/client.rb +244 -105
  11. data/lib/event_store_client/adapters/grpc/cluster/gossip_discover.rb +131 -0
  12. data/lib/event_store_client/adapters/grpc/cluster/insecure_connection.rb +21 -0
  13. data/lib/event_store_client/adapters/grpc/cluster/member.rb +18 -0
  14. data/lib/event_store_client/adapters/grpc/cluster/queryless_discover.rb +25 -0
  15. data/lib/event_store_client/adapters/grpc/cluster/secure_connection.rb +71 -0
  16. data/lib/event_store_client/adapters/grpc/command_registrar.rb +7 -7
  17. data/lib/event_store_client/adapters/grpc/commands/command.rb +63 -25
  18. data/lib/event_store_client/adapters/grpc/commands/gossip/cluster_info.rb +24 -0
  19. data/lib/event_store_client/adapters/grpc/commands/streams/append.rb +43 -68
  20. data/lib/event_store_client/adapters/grpc/commands/streams/append_multiple.rb +44 -0
  21. data/lib/event_store_client/adapters/grpc/commands/streams/delete.rb +21 -17
  22. data/lib/event_store_client/adapters/grpc/commands/streams/hard_delete.rb +39 -0
  23. data/lib/event_store_client/adapters/grpc/commands/streams/link_to.rb +7 -52
  24. data/lib/event_store_client/adapters/grpc/commands/streams/link_to_multiple.rb +44 -0
  25. data/lib/event_store_client/adapters/grpc/commands/streams/read.rb +20 -85
  26. data/lib/event_store_client/adapters/grpc/commands/streams/read_paginated.rb +174 -0
  27. data/lib/event_store_client/adapters/grpc/commands/streams/subscribe.rb +31 -106
  28. data/lib/event_store_client/adapters/grpc/connection.rb +56 -36
  29. data/lib/event_store_client/adapters/grpc/discover.rb +75 -0
  30. data/lib/event_store_client/adapters/grpc/generated/cluster_pb.rb +106 -18
  31. data/lib/event_store_client/adapters/grpc/generated/cluster_services_pb.rb +12 -12
  32. data/lib/event_store_client/adapters/grpc/generated/code_pb.rb +34 -0
  33. data/lib/event_store_client/adapters/grpc/generated/gossip_pb.rb +3 -2
  34. data/lib/event_store_client/adapters/grpc/generated/gossip_services_pb.rb +3 -3
  35. data/lib/event_store_client/adapters/grpc/generated/monitoring_pb.rb +25 -0
  36. data/lib/event_store_client/adapters/grpc/generated/monitoring_services_pb.rb +26 -0
  37. data/lib/event_store_client/adapters/grpc/generated/operations_pb.rb +2 -1
  38. data/lib/event_store_client/adapters/grpc/generated/operations_services_pb.rb +8 -7
  39. data/lib/event_store_client/adapters/grpc/generated/persistent_pb.rb +199 -38
  40. data/lib/event_store_client/adapters/grpc/generated/persistent_services_pb.rb +7 -3
  41. data/lib/event_store_client/adapters/grpc/generated/projections_pb.rb +9 -26
  42. data/lib/event_store_client/adapters/grpc/generated/projections_services_pb.rb +4 -3
  43. data/lib/event_store_client/adapters/grpc/generated/serverfeatures_pb.rb +29 -0
  44. data/lib/event_store_client/adapters/grpc/generated/serverfeatures_services_pb.rb +26 -0
  45. data/lib/event_store_client/adapters/grpc/generated/shared_pb.rb +54 -12
  46. data/lib/event_store_client/adapters/grpc/generated/status_pb.rb +23 -0
  47. data/lib/event_store_client/adapters/grpc/generated/streams_pb.rb +104 -64
  48. data/lib/event_store_client/adapters/grpc/generated/streams_services_pb.rb +3 -2
  49. data/lib/event_store_client/adapters/grpc/generated/users_services_pb.rb +2 -2
  50. data/lib/event_store_client/adapters/grpc/options/streams/read_options.rb +78 -0
  51. data/lib/event_store_client/adapters/grpc/options/streams/write_options.rb +39 -0
  52. data/lib/event_store_client/adapters/grpc/shared/event_deserializer.rb +52 -0
  53. data/lib/event_store_client/adapters/grpc/shared/options/filter_options.rb +76 -0
  54. data/lib/event_store_client/adapters/grpc/shared/options/stream_options.rb +91 -0
  55. data/lib/event_store_client/adapters/grpc/shared/streams/process_response.rb +28 -0
  56. data/lib/event_store_client/adapters/grpc/shared/streams/process_responses.rb +33 -0
  57. data/lib/event_store_client/adapters/grpc.rb +28 -12
  58. data/lib/event_store_client/configuration.rb +39 -54
  59. data/lib/event_store_client/connection/url.rb +57 -0
  60. data/lib/event_store_client/connection/url_parser.rb +144 -0
  61. data/lib/event_store_client/data_decryptor.rb +2 -9
  62. data/lib/event_store_client/deserialized_event.rb +35 -10
  63. data/lib/event_store_client/encryption_metadata.rb +0 -1
  64. data/lib/event_store_client/event.rb +4 -2
  65. data/lib/event_store_client/extensions/options_extension.rb +87 -0
  66. data/lib/event_store_client/mapper/default.rb +12 -9
  67. data/lib/event_store_client/mapper/encrypted.rb +18 -17
  68. data/lib/event_store_client/types.rb +1 -1
  69. data/lib/event_store_client/utils.rb +30 -0
  70. data/lib/event_store_client/version.rb +1 -1
  71. data/lib/event_store_client.rb +8 -7
  72. metadata +75 -84
  73. data/lib/event_store_client/adapters/grpc/Protos/cluster.proto +0 -149
  74. data/lib/event_store_client/adapters/grpc/Protos/gossip.proto +0 -44
  75. data/lib/event_store_client/adapters/grpc/Protos/operations.proto +0 -45
  76. data/lib/event_store_client/adapters/grpc/Protos/persistent.proto +0 -180
  77. data/lib/event_store_client/adapters/grpc/Protos/projections.proto +0 -174
  78. data/lib/event_store_client/adapters/grpc/Protos/shared.proto +0 -22
  79. data/lib/event_store_client/adapters/grpc/Protos/streams.proto +0 -242
  80. data/lib/event_store_client/adapters/grpc/Protos/users.proto +0 -119
  81. data/lib/event_store_client/adapters/grpc/README.md +0 -16
  82. data/lib/event_store_client/adapters/grpc/commands/persistent_subscriptions/create.rb +0 -46
  83. data/lib/event_store_client/adapters/grpc/commands/persistent_subscriptions/delete.rb +0 -34
  84. data/lib/event_store_client/adapters/grpc/commands/persistent_subscriptions/read.rb +0 -77
  85. data/lib/event_store_client/adapters/grpc/commands/persistent_subscriptions/settings_schema.rb +0 -38
  86. data/lib/event_store_client/adapters/grpc/commands/persistent_subscriptions/update.rb +0 -48
  87. data/lib/event_store_client/adapters/grpc/commands/projections/create.rb +0 -48
  88. data/lib/event_store_client/adapters/grpc/commands/projections/delete.rb +0 -34
  89. data/lib/event_store_client/adapters/grpc/commands/projections/update.rb +0 -44
  90. data/lib/event_store_client/adapters/grpc/commands/streams/read_all.rb +0 -43
  91. data/lib/event_store_client/adapters/grpc/commands/streams/tombstone.rb +0 -35
  92. data/lib/event_store_client/adapters/http/README.md +0 -16
  93. data/lib/event_store_client/adapters/http/client.rb +0 -161
  94. data/lib/event_store_client/adapters/http/commands/command.rb +0 -27
  95. data/lib/event_store_client/adapters/http/commands/persistent_subscriptions/ack.rb +0 -15
  96. data/lib/event_store_client/adapters/http/commands/persistent_subscriptions/create.rb +0 -35
  97. data/lib/event_store_client/adapters/http/commands/persistent_subscriptions/read.rb +0 -60
  98. data/lib/event_store_client/adapters/http/commands/projections/create.rb +0 -33
  99. data/lib/event_store_client/adapters/http/commands/projections/update.rb +0 -31
  100. data/lib/event_store_client/adapters/http/commands/streams/append.rb +0 -49
  101. data/lib/event_store_client/adapters/http/commands/streams/delete.rb +0 -16
  102. data/lib/event_store_client/adapters/http/commands/streams/link_to.rb +0 -49
  103. data/lib/event_store_client/adapters/http/commands/streams/read.rb +0 -52
  104. data/lib/event_store_client/adapters/http/commands/streams/tombstone.rb +0 -17
  105. data/lib/event_store_client/adapters/http/connection.rb +0 -46
  106. data/lib/event_store_client/adapters/http/request_method.rb +0 -28
  107. data/lib/event_store_client/adapters/http.rb +0 -17
  108. data/lib/event_store_client/adapters/in_memory.rb +0 -144
  109. data/lib/event_store_client/broker.rb +0 -40
  110. data/lib/event_store_client/catch_up_subscription.rb +0 -42
  111. data/lib/event_store_client/catch_up_subscriptions.rb +0 -92
  112. data/lib/event_store_client/client.rb +0 -73
  113. data/lib/event_store_client/error_handler.rb +0 -10
  114. data/lib/event_store_client/subscription.rb +0 -23
  115. data/lib/event_store_client/subscriptions.rb +0 -38
  116. data/lib/event_store_client/value_objects/read_direction.rb +0 -43
@@ -1,103 +1,38 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'grpc'
4
- require 'event_store_client/adapters/grpc/generated/streams_pb.rb'
5
- require 'event_store_client/adapters/grpc/generated/streams_services_pb.rb'
6
-
7
- require 'event_store_client/configuration'
8
- require 'event_store_client/adapters/grpc/commands/command'
3
+ require 'event_store_client/adapters/grpc/generated/streams_pb'
4
+ require 'event_store_client/adapters/grpc/generated/streams_services_pb'
9
5
 
10
6
  module EventStoreClient
11
7
  module GRPC
12
8
  module Commands
13
9
  module Streams
14
10
  class Read < Command
15
- include Configuration
16
-
17
11
  use_request EventStore::Client::Streams::ReadReq
18
12
  use_service EventStore::Client::Streams::Streams::Stub
19
13
 
20
- StreamNotFound = Class.new(StandardError)
21
-
22
- def call(name, options: {})
23
- direction =
24
- EventStoreClient::ReadDirection.new(options[:direction] || 'forwards').to_sym
25
- opts = {
26
- stream: {
27
- stream_identifier: {
28
- streamName: name
29
- }
30
- },
31
- read_direction: direction,
32
- resolve_links: options[:resolve_links] || true,
33
- count: options[:count] || config.per_page,
34
- uuid_option: {
35
- string: {}
36
- },
37
- no_filter: {}
38
- }
39
- options[:start] ||= 0
40
- if options[:start].zero?
41
- opts[:stream][:start] = {}
42
- else
43
- opts[:stream][:revision] = options[:start]
44
- end
45
-
46
- skip_decryption = options[:skip_decryption] || false
47
- events =
48
- if options[:skip_deserialization]
49
- read_stream_raw(opts)
50
- else
51
- read_stream(opts, skip_decryption)
52
- end
53
- Success(events)
54
- rescue StreamNotFound
55
- Failure(:not_found)
14
+ # @api private
15
+ # @see {EventStoreClient::GRPC::Client#read}
16
+ def call(stream_name, options:, skip_deserialization:, skip_decryption:)
17
+ options = normalize_options(stream_name, options)
18
+ yield options if block_given?
19
+ result =
20
+ retry_request { service.read(request.new(options: options), metadata: metadata).to_a }
21
+ EventStoreClient::GRPC::Shared::Streams::ProcessResponses.new.call(
22
+ result,
23
+ skip_deserialization,
24
+ skip_decryption
25
+ )
56
26
  end
57
27
 
58
28
  private
59
29
 
60
- def read_stream(options, skip_decryption)
61
- retries ||= 0
62
- service.read(request.new(options: options), metadata: metadata).map do |res|
63
- raise StreamNotFound if res.stream_not_found
64
- deserialize_event(res.event.event, skip_decryption: skip_decryption)
65
- end
66
- rescue ::GRPC::Unavailable
67
- sleep config.grpc_unavailable_retry_sleep
68
- retry if (retries += 1) <= config.grpc_unavailable_retry_count
69
- raise GRPCUnavailableRetryFailed
70
- end
71
-
72
- def read_stream_raw(options)
73
- retries ||= 0
74
- service.read(request.new(options: options), metadata: metadata).map do |res|
75
- raise StreamNotFound if res.stream_not_found
76
- res.event.event
77
- end
78
- rescue ::GRPC::Unavailable
79
- sleep config.grpc_unavailable_retry_sleep
80
- retry if (retries += 1) <= config.grpc_unavailable_retry_count
81
- raise GRPCUnavailableRetryFailed
82
- end
83
-
84
- def deserialize_event(entry, skip_decryption: false)
85
- data = (entry.data.nil? || entry.data.empty?) ? '{}' : entry.data
86
-
87
- metadata =
88
- JSON.parse(entry.custom_metadata || '{}').merge(
89
- entry.metadata.to_h || {}
90
- ).to_json
91
-
92
- event = EventStoreClient::Event.new(
93
- id: entry.id.string,
94
- title: "#{entry.stream_revision}@#{entry.stream_identifier.streamName}",
95
- type: entry.metadata['type'],
96
- data: data,
97
- metadata: metadata
98
- )
99
-
100
- config.mapper.deserialize(event, skip_decryption: skip_decryption)
30
+ # @param stream_name [String]
31
+ # @param options [Hash]
32
+ # @return [EventStore::Client::Streams::ReadReq::Options]
33
+ def normalize_options(stream_name, options)
34
+ options = Options::Streams::ReadOptions.new(stream_name, options).request_options
35
+ EventStore::Client::Streams::ReadReq::Options.new(options)
101
36
  end
102
37
  end
103
38
  end
@@ -0,0 +1,174 @@
1
+ # frozen_string_literal: true
2
+
3
+ # rubocop:disable all
4
+
5
+ module EventStoreClient
6
+ module GRPC
7
+ module Commands
8
+ module Streams
9
+ class ReadPaginated < Command
10
+ RecordsLimitError = Class.new(StandardError)
11
+ DEFAULT_READ_DIRECTION = :Forwards
12
+
13
+ # @api private
14
+ # @see {EventStoreClient::GRPC::Client#read_paginated}
15
+ def call(stream_name, options:, skip_deserialization:, skip_decryption:, &blk)
16
+ # TODO: Improve the implementation by extracting the pagination into a separate class to
17
+ # allow persisting the pagination options(position, direction, max_count) among the
18
+ # whole instance. This approach will allow us to get rid of passing paginate options
19
+ # into private methods explicitly.
20
+ position, direction, max_count = nil
21
+ first_call = true
22
+ Enumerator.new do |yielder|
23
+ loop do
24
+ response =
25
+ Read.new(**connection_options).call(
26
+ stream_name,
27
+ options: options,
28
+ skip_deserialization: true,
29
+ skip_decryption: true
30
+ ) do |opts|
31
+ if first_call
32
+ # Evaluate user-provided block only once
33
+ yield opts if blk
34
+ position = get_position(opts)
35
+ direction = get_direction(opts)
36
+ max_count = opts.count.to_i
37
+ validate_max_count(max_count)
38
+ first_call = false
39
+ end
40
+
41
+ paginate_options(opts, position)
42
+ end
43
+ unless response.success?
44
+ yielder << response
45
+ raise StopIteration
46
+ end
47
+ processed_response =
48
+ EventStoreClient::GRPC::Shared::Streams::ProcessResponses.new.call(
49
+ response.success,
50
+ skip_deserialization,
51
+ skip_decryption
52
+ )
53
+ yielder << processed_response if processed_response.success.any?
54
+ raise StopIteration if end_reached?(response.success, max_count)
55
+
56
+ position = calc_next_position(response.success, direction, stream_name)
57
+ raise StopIteration if position.negative?
58
+ end
59
+ end
60
+ end
61
+
62
+ private
63
+
64
+ # @param options [EventStore::Client::Streams::ReadReq::Options]
65
+ # @param position [Integer, nil]
66
+ # @return [EventStore::Client::Streams::ReadReq::Options, nil]
67
+ def paginate_options(options, position)
68
+ return unless position
69
+ return paginate_all_options(options, position) if options.stream.nil?
70
+
71
+ paginate_regular_options(options, position)
72
+ end
73
+
74
+ # @param options [EventStore::Client::Streams::ReadReq::Options]
75
+ # @param position [Integer]
76
+ # @return [EventStore::Client::Streams::ReadReq::Options]
77
+ def paginate_all_options(options, position)
78
+ options.all.position = EventStore::Client::Streams::ReadReq::Options::Position.new(
79
+ commit_position: position
80
+ )
81
+ options
82
+ end
83
+
84
+ # @param options [EventStore::Client::Streams::ReadReq::Options]
85
+ # @param position [Integer]
86
+ # @return [EventStore::Client::Streams::ReadReq::Options]
87
+ def paginate_regular_options(options, position)
88
+ options.stream.revision = position
89
+ options
90
+ end
91
+
92
+ # @param options [EventStore::Client::Streams::ReadReq::Options]
93
+ # @return [Symbol] :Backwards or :Forwards
94
+ def get_direction(options)
95
+ return options.read_direction if options.read_direction
96
+
97
+ DEFAULT_READ_DIRECTION
98
+ end
99
+
100
+ # @param options [EventStore::Client::Streams::ReadReq::Options]
101
+ # @return [Integer, nil]
102
+ def get_position(options)
103
+ # If start position is set to :end - then we need to wait for first response to get
104
+ # the value of the position
105
+ return if options.all&.end
106
+ return if options.stream&.end
107
+
108
+ # In case if user has provided a starting position value
109
+ return options.all&.position&.commit_position if options.all&.position&.commit_position
110
+ return options.stream&.revision if options.stream&.revision
111
+
112
+ 0
113
+ end
114
+
115
+ # @param raw_events [Array<EventStore::Client::Streams::ReadResp>]
116
+ # @param direction [Symbol] :Backwards or :Forwards
117
+ # @param stream_name [String]
118
+ # @return [Integer]
119
+ def calc_next_position(raw_events, direction, stream_name)
120
+ events = meaningful_events(raw_events).map { |e| e.event.event }
121
+
122
+ return next_position_for_all(events, direction) if stream_name == '$all'
123
+
124
+ next_position_for_regular(events, direction)
125
+ end
126
+
127
+ # @param events [Array<EventStore::Client::Streams::ReadResp::ReadEvent::RecordedEvent>]
128
+ # @param direction [Symbol] :Backwards or :Forwards
129
+ # @return [Integer]
130
+ def next_position_for_all(events, direction)
131
+ return events.last.commit_position if direction == DEFAULT_READ_DIRECTION
132
+
133
+ events.first.commit_position
134
+ end
135
+
136
+ # @param events [Array<EventStore::Client::Streams::ReadResp::ReadEvent::RecordedEvent>]
137
+ # @param direction [Symbol] :Backwards or :Forwards
138
+ # @return [Integer]
139
+ def next_position_for_regular(events, direction)
140
+ return events.last.stream_revision + 1 if direction == DEFAULT_READ_DIRECTION
141
+
142
+ events.last.stream_revision - 1
143
+ end
144
+
145
+ # @param raw_events [Array<EventStore::Client::Streams::ReadResp>]
146
+ # @return [Array<EventStore::Client::Streams::ReadResp::ReadEvent::RecordedEvent>]
147
+ def meaningful_events(raw_events)
148
+ raw_events.select { |read_resp| read_resp.event&.event }
149
+ end
150
+
151
+ # @param raw_events [Array<EventStore::Client::Streams::ReadResp>]
152
+ # @param max_count [Integer]
153
+ # @return [Boolean]
154
+ def end_reached?(raw_events, max_count)
155
+ meaningful_events(raw_events).size < max_count
156
+ end
157
+
158
+ # @return [void]
159
+ # @raise [RecordsLimitError] raises error in case if max_count is less than 2
160
+ def validate_max_count(max_count)
161
+ return if max_count >= 2
162
+
163
+ raise(
164
+ RecordsLimitError,
165
+ 'Pagination requires :max_count option to be greater than or equal to 2. ' \
166
+ "Current value is `#{max_count}'."
167
+ )
168
+ end
169
+ end
170
+ end
171
+ end
172
+ end
173
+ end
174
+ # rubocop:enable all
@@ -1,129 +1,54 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'grpc'
4
- require 'event_store_client/adapters/grpc/generated/streams_pb.rb'
5
- require 'event_store_client/adapters/grpc/generated/streams_services_pb.rb'
6
-
7
- require 'event_store_client/configuration'
8
- require 'event_store_client/adapters/grpc/commands/command'
3
+ require 'event_store_client/adapters/grpc/generated/streams_pb'
4
+ require 'event_store_client/adapters/grpc/generated/streams_services_pb'
9
5
 
10
6
  module EventStoreClient
11
7
  module GRPC
12
8
  module Commands
13
9
  module Streams
14
10
  class Subscribe < Command
15
- include Configuration
16
-
17
11
  use_request EventStore::Client::Streams::ReadReq
18
12
  use_service EventStore::Client::Streams::Streams::Stub
19
13
 
20
- StreamNotFound = Class.new(StandardError)
14
+ def initialize(**conn_options)
15
+ # Subscriptions should never be timed out
16
+ super(**conn_options.merge(timeout: nil))
17
+ end
21
18
 
22
- def call(options = {})
23
- opts = options_with_defaults(options)
19
+ # @api private
20
+ # @see {EventStoreClient::GRPC::Client#read}
21
+ def call(stream_name, handler:, options:, skip_deserialization:, skip_decryption:)
22
+ options = normalize_options(stream_name, options)
23
+ yield options if block_given?
24
24
 
25
- service.read(request.new(options: opts), metadata: metadata).map do |res|
26
- raise StreamNotFound if res.stream_not_found
25
+ callback = proc do |response|
26
+ result = Shared::Streams::ProcessResponse.new.call(
27
+ response,
28
+ skip_deserialization,
29
+ skip_decryption
30
+ )
27
31
 
28
- yield prepared_response(res) if block_given?
32
+ handler.call(result) if result
29
33
  end
30
- rescue StreamNotFound
31
- Failure(:not_found)
34
+ Success(
35
+ retry_request do
36
+ service.read(request.new(options: options), metadata: metadata, &callback)
37
+ end
38
+ )
32
39
  end
33
40
 
34
41
  private
35
42
 
36
- def prepared_response(res)
37
- if res.event
38
- event = res.event.event
39
- [position(event), deserialize_event(event)] rescue event
40
- elsif res.checkpoint
41
- [position(res.checkpoint), nil]
42
- elsif res.confirmation
43
- res.confirmation
44
- end
45
- end
46
-
47
- def position(event_or_checkpoint)
48
- {
49
- prepare_position: event_or_checkpoint.prepare_position,
50
- commit_position: event_or_checkpoint.commit_position
51
- }
52
- end
53
-
54
- def read_direction(direction)
55
- EventStoreClient::ReadDirection.new(direction || 'forwards').to_sym
56
- end
57
-
58
- def options_with_defaults(options)
59
- options[:without_system_events] = true unless options[:without_system_events] == false
60
- opts = {
61
- subscription: {},
62
- read_direction: read_direction(options[:direction]),
63
- resolve_links: options[:resolve_links] || true,
64
- uuid_option: {
65
- string: {}
66
- }
67
- }
68
- if options[:stream]
69
- opts[:stream] = {
70
- stream_identifier: {
71
- streamName: stream
72
- }
73
- }
74
- else
75
- opts[:all] = options[:all] || default_all_options
76
- end
77
- if options[:filter]
78
- opts[:filter] = options[:filter]
79
- elsif options[:without_system_events]
80
- opts[:filter] = {
81
- event_type: { regex: '^[^$].*' },
82
- max: 32,
83
- checkpointIntervalMultiplier: 1000
84
- }
85
- else
86
- opts[:no_filter] = {}
87
- end
88
-
89
- options[:start] ||= 0
90
-
91
- return opts unless options[:stream]
92
-
93
- if options[:start].zero?
94
- opts[:stream][:start] = {}
95
- else
96
- opts[:stream][:revision] = options[:start]
43
+ # @param stream_name [String]
44
+ # @param options [Hash]
45
+ # @return [EventStore::Client::Streams::ReadReq::Options]
46
+ def normalize_options(stream_name, options)
47
+ options = Options::Streams::ReadOptions.new(stream_name, options).request_options
48
+ EventStore::Client::Streams::ReadReq::Options.new(options).tap do |opts|
49
+ opts.subscription =
50
+ EventStore::Client::Streams::ReadReq::Options::SubscriptionOptions.new
97
51
  end
98
- opts
99
- end
100
-
101
- def default_all_options
102
- {
103
- position: {
104
- commit_position: 0,
105
- prepare_position: 0
106
- }
107
- }
108
- end
109
-
110
- def deserialize_event(entry)
111
- data = (entry.data.nil? || entry.data.empty?) ? '{}' : entry.data
112
-
113
- metadata =
114
- JSON.parse(entry.custom_metadata || '{}').merge(
115
- entry.metadata.to_h || {}
116
- ).to_json
117
-
118
- event = EventStoreClient::Event.new(
119
- id: entry.id.string,
120
- title: "#{entry.stream_revision}@#{entry.stream_identifier.streamName}",
121
- type: entry.metadata['type'],
122
- data: data,
123
- metadata: metadata
124
- )
125
-
126
- config.mapper.deserialize(event)
127
52
  end
128
53
  end
129
54
  end
@@ -8,49 +8,69 @@ module EventStoreClient
8
8
  module GRPC
9
9
  class Connection
10
10
  include Configuration
11
+ include Extensions::OptionsExtension
11
12
 
12
- class SocketErrorRetryFailed < StandardError; end
13
-
14
- # Initializes the proper stub with the necessary credentials
15
- # to create working gRPC connection - Refer to generated grpc files
16
- # @return [Stub] Instance of a given `Stub` klass
17
- #
18
- def call(stub_klass, options: {})
19
- credentials =
20
- options[:credentials] ||
21
- Base64.encode64("#{config.eventstore_user}:#{config.eventstore_password}")
22
- stub_klass.new(
23
- "#{config.eventstore_url.host}:#{config.eventstore_url.port}",
24
- channel_credentials,
25
- channel_args: { 'authorization' => "Basic #{credentials.delete("\n")}" }
26
- )
27
- end
13
+ option(:host) { Discover.current_member.host }
14
+ option(:port) { Discover.current_member.port }
15
+ option(:username) { config.eventstore_url.username }
16
+ option(:password) { config.eventstore_url.password }
17
+ option(:timeout) { config.eventstore_url.timeout }
28
18
 
29
- private
19
+ class << self
20
+ include Configuration
21
+
22
+ # Resolve which connection class we instantiate, based on config.eventstore_url.tls config
23
+ # option. If :new method is called from SecureConnection or InsecureConnection class - then
24
+ # that particular class will be instantiated despite on config.eventstore_url.tls config
25
+ # option. Example:
26
+ # ```ruby
27
+ # config.eventstore_url.tls = true
28
+ # Connection.new # => #<EventStoreClient::GRPC::Cluster::SecureConnection>
29
+ #
30
+ # config.eventstore_url.tls = false
31
+ # Connection.new # => #<EventStoreClient::GRPC::Cluster::InsecureConnection>
32
+ #
33
+ # Cluster::SecureConnection.new
34
+ # # => #<EventStoreClient::GRPC::Cluster::SecureConnection>
35
+ # Cluster::InsecureConnection.new
36
+ # # => #<EventStoreClient::GRPC::Cluster::InsecureConnection>
37
+ # ```
38
+ def new(*args, **kwargs, &blk)
39
+ return super unless self == Connection
30
40
 
31
- attr_reader :cert
32
-
33
- def initialize
34
- retries ||= 0
35
- @cert =
36
- Net::HTTP.start(
37
- config.eventstore_url.host, config.eventstore_url.port,
38
- use_ssl: true,
39
- verify_mode: verify_ssl,
40
- &:peer_cert
41
- )
42
- rescue SocketError
43
- sleep config.socket_error_retry_sleep
44
- retry if (retries += 1) <= config.socket_error_retry_count
45
- raise SocketErrorRetryFailed
41
+ if config.eventstore_url.tls
42
+ Cluster::SecureConnection.new(*args, **kwargs, &blk)
43
+ else
44
+ Cluster::InsecureConnection.new(*args, **kwargs, &blk)
45
+ end
46
+ end
47
+
48
+ # Checks if connection class is secure
49
+ # @return [Boolean]
50
+ def secure?
51
+ self == Cluster::SecureConnection
52
+ end
46
53
  end
47
54
 
48
- def channel_credentials
49
- ::GRPC::Core::ChannelCredentials.new(cert.to_s)
55
+ def call(stub_class)
56
+ raise NotImplementedError
50
57
  end
51
58
 
52
- def verify_ssl
53
- config.verify_ssl || OpenSSL::SSL::VERIFY_NONE
59
+ private
60
+
61
+ # Common channel arguments for all GRPC requests.
62
+ # Available channel arguments are described here
63
+ # https://github.com/grpc/grpc/blob/master/include/grpc/impl/codegen/grpc_types.h
64
+ # @return [Hash]
65
+ def channel_args
66
+ {
67
+ # disable build-in GRPC retries functional
68
+ 'grpc.enable_retries' => 0,
69
+ # These three options reduce delays between failed requests.
70
+ 'grpc.min_reconnect_backoff_ms' => 100, # milliseconds
71
+ 'grpc.max_reconnect_backoff_ms' => 100, # milliseconds
72
+ 'grpc.initial_reconnect_backoff_ms' => 100 # milliseconds
73
+ }
54
74
  end
55
75
  end
56
76
  end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ # rubocop:disable Metrics/CyclomaticComplexity
4
+
5
+ module EventStoreClient
6
+ module GRPC
7
+ class Discover
8
+ include Configuration
9
+
10
+ class << self
11
+ # @return [EventStoreClient::GRPC::Cluster::Member]
12
+ def current_member
13
+ @exception = nil
14
+ return @current_member if member_alive?
15
+
16
+ semaphore.synchronize do
17
+ raise @exception if @exception
18
+ return @current_member if member_alive?
19
+
20
+ failed_member = @current_member if @current_member&.failed_endpoint
21
+ @current_member =
22
+ begin
23
+ new.call(failed_member: failed_member)
24
+ rescue StandardError => e
25
+ @exception = e
26
+ nil
27
+ end
28
+ end
29
+ raise @exception if @exception
30
+
31
+ @current_member
32
+ end
33
+
34
+ # @return [Boolean]
35
+ def member_alive?
36
+ return false if @current_member&.failed_endpoint
37
+
38
+ !@current_member.nil?
39
+ end
40
+
41
+ private
42
+
43
+ # @return [Thread::Mutex]
44
+ def semaphore
45
+ @semaphore ||= Thread::Mutex.new
46
+ end
47
+ end
48
+
49
+ # @param failed_member [EventStoreClient::GRPC::Cluster::Member, nil]
50
+ # @return [EventStoreClient::GRPC::Cluster::Member]
51
+ def call(failed_member: nil)
52
+ if needs_discover?
53
+ return Cluster::GossipDiscover.new.call(nodes, failed_member: failed_member)
54
+ end
55
+
56
+ Cluster::QuerylessDiscover.new.call(config.eventstore_url.nodes.to_a)
57
+ end
58
+
59
+ private
60
+
61
+ # @return [Array<EventStoreClient::Connection::Url::Node>]
62
+ def nodes
63
+ return [config.eventstore_url.nodes.first] if config.eventstore_url.dns_discover
64
+
65
+ config.eventstore_url.nodes.to_a
66
+ end
67
+
68
+ # @return [Boolean]
69
+ def needs_discover?
70
+ config.eventstore_url.dns_discover || config.eventstore_url.nodes.size > 1
71
+ end
72
+ end
73
+ end
74
+ end
75
+ # rubocop:enable Metrics/CyclomaticComplexity