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.
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 +74 -83
  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