event_store_client 2.3.0 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/docs/appending_events.md +15 -19
- data/docs/catch_up_subscriptions.md +34 -67
- data/docs/configuration.md +0 -2
- data/docs/deleting_streams.md +13 -1
- data/docs/encrypting_events.md +1 -3
- data/docs/linking_events.md +19 -23
- data/docs/reading_events.md +32 -59
- data/lib/event_store_client/adapters/grpc/client.rb +13 -9
- data/lib/event_store_client/adapters/grpc/commands/command.rb +0 -2
- data/lib/event_store_client/adapters/grpc/commands/gossip/cluster_info.rb +1 -1
- data/lib/event_store_client/adapters/grpc/commands/streams/append.rb +11 -5
- data/lib/event_store_client/adapters/grpc/commands/streams/append_multiple.rb +2 -6
- data/lib/event_store_client/adapters/grpc/commands/streams/delete.rb +7 -5
- data/lib/event_store_client/adapters/grpc/commands/streams/hard_delete.rb +7 -5
- data/lib/event_store_client/adapters/grpc/commands/streams/link_to_multiple.rb +5 -10
- data/lib/event_store_client/adapters/grpc/commands/streams/read_paginated.rb +5 -8
- data/lib/event_store_client/adapters/grpc/commands/streams/subscribe.rb +3 -5
- data/lib/event_store_client/adapters/grpc/shared/streams/process_response.rb +6 -8
- data/lib/event_store_client/adapters/grpc/shared/streams/process_responses.rb +11 -13
- data/lib/event_store_client/adapters/grpc.rb +0 -1
- data/lib/event_store_client/deserialized_event.rb +21 -3
- data/lib/event_store_client/errors.rb +95 -0
- data/lib/event_store_client/mapper/default.rb +1 -1
- data/lib/event_store_client/mapper/encrypted.rb +2 -2
- data/lib/event_store_client/serializer/event_deserializer.rb +4 -2
- data/lib/event_store_client/serializer/event_serializer.rb +41 -17
- data/lib/event_store_client/version.rb +1 -1
- data/lib/event_store_client.rb +3 -1
- metadata +19 -18
@@ -11,7 +11,7 @@ module EventStoreClient
|
|
11
11
|
# @api private
|
12
12
|
# @see {EventStoreClient::GRPC::Client#cluster_info}
|
13
13
|
def call
|
14
|
-
|
14
|
+
retry_request { service.read(request.new, metadata: metadata) }
|
15
15
|
end
|
16
16
|
end
|
17
17
|
end
|
@@ -20,7 +20,7 @@ module EventStoreClient
|
|
20
20
|
response = retry_request(skip_retry: config.eventstore_url.throw_on_append_failure) do
|
21
21
|
service.append(payload, metadata: metadata)
|
22
22
|
end
|
23
|
-
validate_response(response)
|
23
|
+
validate_response(response, caused_by: event)
|
24
24
|
end
|
25
25
|
|
26
26
|
private
|
@@ -41,11 +41,17 @@ module EventStoreClient
|
|
41
41
|
end
|
42
42
|
|
43
43
|
# @param resp [EventStore::Client::Streams::AppendResp]
|
44
|
-
# @
|
45
|
-
|
46
|
-
|
44
|
+
# @param caused_by [EventStoreClient::DeserializedEvent]
|
45
|
+
# @return [EventStore::Client::Streams::AppendResp]
|
46
|
+
# @raise [EventStoreClient::WrongExpectedVersionError]
|
47
|
+
def validate_response(resp, caused_by:)
|
48
|
+
return resp if resp.success
|
47
49
|
|
48
|
-
|
50
|
+
error = WrongExpectedVersionError.new(
|
51
|
+
resp.wrong_expected_version,
|
52
|
+
caused_by: caused_by
|
53
|
+
)
|
54
|
+
raise error
|
49
55
|
end
|
50
56
|
end
|
51
57
|
end
|
@@ -10,9 +10,8 @@ module EventStoreClient
|
|
10
10
|
# @api private
|
11
11
|
# @see {EventStoreClient::GRPC::Client#append_to_stream}
|
12
12
|
def call(stream_name, events, options:, &blk)
|
13
|
-
|
14
|
-
|
15
|
-
response = Commands::Streams::Append.new(
|
13
|
+
events.map.with_index do |event, index|
|
14
|
+
Commands::Streams::Append.new(
|
16
15
|
config: config, **connection_options
|
17
16
|
).call(
|
18
17
|
stream_name, event, options: options
|
@@ -21,10 +20,7 @@ module EventStoreClient
|
|
21
20
|
|
22
21
|
yield(req_opts, proposed_msg_opts) if blk
|
23
22
|
end
|
24
|
-
result.push(response)
|
25
|
-
break if response.failure?
|
26
23
|
end
|
27
|
-
result
|
28
24
|
end
|
29
25
|
|
30
26
|
private
|
@@ -13,11 +13,13 @@ module EventStoreClient
|
|
13
13
|
def call(stream_name, options:, &blk)
|
14
14
|
options = normalize_options(stream_name, options)
|
15
15
|
yield options if blk
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
16
|
+
retry_request { service.delete(request.new(options: options), metadata: metadata) }
|
17
|
+
rescue ::GRPC::FailedPrecondition => e
|
18
|
+
# GRPC::FailedPrecondition may happen for several reasons. For example, stream may not
|
19
|
+
# be existing, or :expected_revision option value does not match the current state of
|
20
|
+
# the stream. So, re-raise our own error, and pass there the original message - just in
|
21
|
+
# case.
|
22
|
+
raise StreamDeletionError.new(stream_name, details: e.message)
|
21
23
|
end
|
22
24
|
|
23
25
|
private
|
@@ -13,11 +13,13 @@ module EventStoreClient
|
|
13
13
|
def call(stream_name, options:, &blk)
|
14
14
|
options = normalize_options(stream_name, options)
|
15
15
|
yield options if blk
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
16
|
+
retry_request { service.delete(request.new(options: options), metadata: metadata) }
|
17
|
+
rescue ::GRPC::FailedPrecondition => e
|
18
|
+
# GRPC::FailedPrecondition may happen for several reasons. For example, stream may not
|
19
|
+
# be existing, or :expected_revision option value does not match the current state of
|
20
|
+
# the stream. So, re-raise our own error, and pass there the original message - just in
|
21
|
+
# case.
|
22
|
+
raise StreamDeletionError.new(stream_name, details: e.message)
|
21
23
|
end
|
22
24
|
|
23
25
|
private
|
@@ -10,18 +10,13 @@ module EventStoreClient
|
|
10
10
|
# @api private
|
11
11
|
# @see {EventStoreClient::GRPC::Client#link_to}
|
12
12
|
def call(stream_name, events, options:, &blk)
|
13
|
-
result = []
|
14
13
|
link_cmd = Commands::Streams::LinkTo.new(config: config, **connection_options)
|
15
|
-
events.
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
end
|
21
|
-
result.push(response)
|
22
|
-
break if response.failure?
|
14
|
+
events.map.with_index do |event, index|
|
15
|
+
link_cmd.call(stream_name, event, options: options) do |req_opts, proposed_msg_opts|
|
16
|
+
req_opts.options.revision += index if has_revision_option?(req_opts.options)
|
17
|
+
yield(req_opts, proposed_msg_opts) if blk
|
18
|
+
end
|
23
19
|
end
|
24
|
-
result
|
25
20
|
end
|
26
21
|
|
27
22
|
private
|
@@ -40,22 +40,19 @@ module EventStoreClient
|
|
40
40
|
|
41
41
|
paginate_options(opts, position)
|
42
42
|
end
|
43
|
-
unless response.success?
|
44
|
-
yielder << response
|
45
|
-
raise StopIteration
|
46
|
-
end
|
47
43
|
processed_response =
|
48
44
|
EventStoreClient::GRPC::Shared::Streams::ProcessResponses.
|
49
45
|
new(config: config).
|
50
46
|
call(
|
51
|
-
response
|
47
|
+
response,
|
52
48
|
skip_deserialization,
|
53
49
|
skip_decryption
|
54
50
|
)
|
55
|
-
yielder << processed_response if processed_response.success.any?
|
56
|
-
raise StopIteration if end_reached?(response.success, max_count)
|
57
51
|
|
58
|
-
|
52
|
+
yielder << processed_response if processed_response.any?
|
53
|
+
raise StopIteration if end_reached?(response, max_count)
|
54
|
+
|
55
|
+
position = calc_next_position(response, direction, stream_name)
|
59
56
|
raise StopIteration if position.negative?
|
60
57
|
end
|
61
58
|
end
|
@@ -28,11 +28,9 @@ module EventStoreClient
|
|
28
28
|
|
29
29
|
handler.call(result) if result
|
30
30
|
end
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
end
|
35
|
-
)
|
31
|
+
retry_request do
|
32
|
+
service.read(request.new(options: options), metadata: metadata, &callback)
|
33
|
+
end
|
36
34
|
end
|
37
35
|
|
38
36
|
private
|
@@ -5,8 +5,6 @@ module EventStoreClient
|
|
5
5
|
module Shared
|
6
6
|
module Streams
|
7
7
|
class ProcessResponse
|
8
|
-
include Dry::Monads[:result]
|
9
|
-
|
10
8
|
attr_reader :config
|
11
9
|
private :config
|
12
10
|
|
@@ -19,15 +17,15 @@ module EventStoreClient
|
|
19
17
|
# @param response [EventStore::Client::Streams::ReadResp]
|
20
18
|
# @param skip_deserialization [Boolean]
|
21
19
|
# @param skip_decryption [Boolean]
|
22
|
-
# @return [
|
20
|
+
# @return [EventStoreClient::DeserializedEvent, EventStore::Client::Streams::ReadResp, nil]
|
21
|
+
# @raise [EventStoreClient::StreamNotFoundError]
|
23
22
|
def call(response, skip_deserialization, skip_decryption)
|
24
|
-
|
25
|
-
|
23
|
+
non_existing_stream = response.stream_not_found&.stream_identifier&.stream_name
|
24
|
+
raise StreamNotFoundError, non_existing_stream if non_existing_stream
|
25
|
+
return response if skip_deserialization
|
26
26
|
return unless response.event&.event
|
27
27
|
|
28
|
-
|
29
|
-
config.mapper.deserialize(response.event.event, skip_decryption: skip_decryption)
|
30
|
-
)
|
28
|
+
config.mapper.deserialize(response.event.event, skip_decryption: skip_decryption)
|
31
29
|
end
|
32
30
|
end
|
33
31
|
end
|
@@ -5,8 +5,6 @@ module EventStoreClient
|
|
5
5
|
module Shared
|
6
6
|
module Streams
|
7
7
|
class ProcessResponses
|
8
|
-
include Dry::Monads[:result]
|
9
|
-
|
10
8
|
attr_reader :config
|
11
9
|
private :config
|
12
10
|
|
@@ -19,20 +17,20 @@ module EventStoreClient
|
|
19
17
|
# @param responses [Array<EventStore::Client::Streams::ReadResp>]
|
20
18
|
# @param skip_deserialization [Boolean]
|
21
19
|
# @param skip_decryption [Boolean]
|
22
|
-
# @return [
|
20
|
+
# @return [Array<EventStoreClient::DeserializedEvent>, Array<EventStore::Client::Streams::ReadResp>]
|
21
|
+
# @raise [EventStoreClient::StreamNotFoundError]
|
23
22
|
def call(responses, skip_deserialization, skip_decryption)
|
24
|
-
|
25
|
-
|
23
|
+
non_existing_stream = responses.first&.stream_not_found&.stream_identifier&.stream_name
|
24
|
+
raise StreamNotFoundError, non_existing_stream if non_existing_stream
|
25
|
+
return responses if skip_deserialization
|
26
26
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
next unless response.event&.event
|
27
|
+
responses.map do |response|
|
28
|
+
# It could be <EventStore::Client::Streams::ReadResp: last_stream_position: 39> for
|
29
|
+
# example. Such responses should be skipped. See generated files for more info.
|
30
|
+
next unless response.event&.event
|
32
31
|
|
33
|
-
|
34
|
-
|
35
|
-
Success(events.compact)
|
32
|
+
config.mapper.deserialize(response.event.event, skip_decryption: skip_decryption)
|
33
|
+
end.compact
|
36
34
|
end
|
37
35
|
end
|
38
36
|
end
|
@@ -7,8 +7,8 @@ module EventStoreClient
|
|
7
7
|
InvalidDataError = Class.new(StandardError)
|
8
8
|
private_constant :InvalidDataError
|
9
9
|
|
10
|
-
attr_reader :id, :type, :title, :data, :metadata, :
|
11
|
-
:prepare_position, :commit_position
|
10
|
+
attr_reader :id, :type, :title, :data, :metadata, :custom_metadata, :stream_name,
|
11
|
+
:stream_revision, :prepare_position, :commit_position
|
12
12
|
|
13
13
|
# @args [Hash] opts
|
14
14
|
# @option opts [Boolean] :skip_validation
|
@@ -34,6 +34,7 @@ module EventStoreClient
|
|
34
34
|
'type' => @type,
|
35
35
|
'content-type' => payload_content_type
|
36
36
|
)
|
37
|
+
@custom_metadata = args[:custom_metadata] || {}
|
37
38
|
@stream_name = args[:stream_name]
|
38
39
|
@stream_revision = args[:stream_revision]
|
39
40
|
@prepare_position = args[:prepare_position]
|
@@ -57,7 +58,7 @@ module EventStoreClient
|
|
57
58
|
def ==(other)
|
58
59
|
return false unless other.is_a?(EventStoreClient::DeserializedEvent)
|
59
60
|
|
60
|
-
to_h == other.to_h
|
61
|
+
meaningful_attrs(to_h) == meaningful_attrs(other.to_h)
|
61
62
|
end
|
62
63
|
|
63
64
|
# @return [Hash]
|
@@ -75,8 +76,25 @@ module EventStoreClient
|
|
75
76
|
type == LINK_TYPE
|
76
77
|
end
|
77
78
|
|
79
|
+
# Detect whether an event is a system event
|
80
|
+
# @return [Boolean]
|
81
|
+
def system?
|
82
|
+
return false unless type
|
83
|
+
|
84
|
+
type.start_with?('$')
|
85
|
+
end
|
86
|
+
|
78
87
|
private
|
79
88
|
|
89
|
+
# When comparing two events - we drop commit_position and prepare_position from attributes list
|
90
|
+
# to compare. This is because their value are different when retrieving the same event from
|
91
|
+
# '$all' stream and from specific stream.
|
92
|
+
# @param hash [Hash]
|
93
|
+
# @return [Hash]
|
94
|
+
def meaningful_attrs(hash)
|
95
|
+
hash.delete_if { |key, _val| %i(commit_position prepare_position).include?(key) }
|
96
|
+
end
|
97
|
+
|
80
98
|
def validate(data)
|
81
99
|
return unless schema
|
82
100
|
|
@@ -0,0 +1,95 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module EventStoreClient
|
4
|
+
class Error < StandardError
|
5
|
+
# @return [Hash]
|
6
|
+
def as_json(*)
|
7
|
+
to_h.transform_keys(&:to_s)
|
8
|
+
end
|
9
|
+
|
10
|
+
# @return [Hash]
|
11
|
+
def to_h
|
12
|
+
hash =
|
13
|
+
instance_variables.each_with_object({}) do |var, result|
|
14
|
+
key = var.to_s
|
15
|
+
key[0] = '' # remove @ sign
|
16
|
+
result[key.to_sym] = instance_variable_get(var)
|
17
|
+
end
|
18
|
+
hash[:message] = message
|
19
|
+
hash[:backtrace] = backtrace
|
20
|
+
hash
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class StreamNotFoundError < Error
|
25
|
+
attr_reader :stream_name
|
26
|
+
|
27
|
+
# @param stream_name [String]
|
28
|
+
def initialize(stream_name)
|
29
|
+
@stream_name = stream_name
|
30
|
+
super("Stream #{stream_name.inspect} does not exist.")
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class WrongExpectedVersionError < Error
|
35
|
+
attr_reader :wrong_expected_version, :caused_by
|
36
|
+
|
37
|
+
# @param wrong_expected_version [EventStore::Client::Streams::AppendResp::WrongExpectedVersion]
|
38
|
+
# @param caused_by [EventStoreClient::DeserializedEvent] an event on which
|
39
|
+
# WrongExpectedVersionError error happened. It can be useful when appending array of events -
|
40
|
+
# based on it you will know which events were appended and which weren't.
|
41
|
+
def initialize(wrong_expected_version, caused_by:)
|
42
|
+
@wrong_expected_version = wrong_expected_version
|
43
|
+
@caused_by = caused_by
|
44
|
+
super(user_friendly_message)
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
# @return [String]
|
50
|
+
def user_friendly_message
|
51
|
+
if wrong_expected_version.expected_stream_exists
|
52
|
+
return "Expected stream to exist, but it doesn't."
|
53
|
+
end
|
54
|
+
if wrong_expected_version.expected_no_stream
|
55
|
+
return "Expected stream to be absent, but it actually exists."
|
56
|
+
end
|
57
|
+
if wrong_expected_version.current_no_stream
|
58
|
+
return <<~TEXT.strip
|
59
|
+
Stream revision #{wrong_expected_version.expected_revision.inspect} is expected, but \
|
60
|
+
stream does not exist.
|
61
|
+
TEXT
|
62
|
+
end
|
63
|
+
unless wrong_expected_version.expected_revision == wrong_expected_version.current_revision
|
64
|
+
return <<~TEXT.strip
|
65
|
+
Stream revision #{wrong_expected_version.expected_revision.inspect} is expected, but \
|
66
|
+
actual stream revision is #{wrong_expected_version.current_revision.inspect}.
|
67
|
+
TEXT
|
68
|
+
end
|
69
|
+
# Unhandled case. Could happen if something else would be added to proto and I don't add it
|
70
|
+
# here.
|
71
|
+
self.class.to_s
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
class StreamDeletionError < Error
|
76
|
+
attr_reader :stream_name, :details
|
77
|
+
|
78
|
+
# @param stream_name [String]
|
79
|
+
# @param details [String]
|
80
|
+
def initialize(stream_name, details:)
|
81
|
+
@stream_name = stream_name
|
82
|
+
@details = details
|
83
|
+
super(user_friendly_message)
|
84
|
+
end
|
85
|
+
|
86
|
+
# @return [String]
|
87
|
+
def user_friendly_message
|
88
|
+
<<~TEXT.strip
|
89
|
+
Could not delete #{stream_name.inspect} stream. It seems that a stream with that \
|
90
|
+
name does not exist, has already been deleted or its state does not match the \
|
91
|
+
provided :expected_revision option. Please check #details for more info.
|
92
|
+
TEXT
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -18,7 +18,7 @@ module EventStoreClient
|
|
18
18
|
# @param event [EventStoreClient::DeserializedEvent]
|
19
19
|
# @return [EventStoreClient::SerializedEvent]
|
20
20
|
def serialize(event)
|
21
|
-
Serializer::EventSerializer.call(event, serializer: serializer)
|
21
|
+
Serializer::EventSerializer.call(event, serializer: serializer, config: config)
|
22
22
|
end
|
23
23
|
|
24
24
|
# @param event_or_raw_event [EventStoreClient::DeserializedEvent, EventStore::Client::Streams::ReadResp::ReadEvent::RecordedEvent, EventStore::Client::PersistentSubscriptions::ReadResp::ReadEvent::RecordedEvent]
|
@@ -32,7 +32,7 @@ module EventStoreClient
|
|
32
32
|
# Links don't need to be encrypted
|
33
33
|
return Default.new(serializer: serializer, config: config).serialize(event) if event.link?
|
34
34
|
|
35
|
-
serialized = Serializer::EventSerializer.call(event, serializer: serializer)
|
35
|
+
serialized = Serializer::EventSerializer.call(event, serializer: serializer, config: config)
|
36
36
|
encryption_schema =
|
37
37
|
if event.class.respond_to?(:encryption_schema)
|
38
38
|
event.class.encryption_schema
|
@@ -70,7 +70,7 @@ module EventStoreClient
|
|
70
70
|
decrypted_data =
|
71
71
|
EventStoreClient::DataDecryptor.new(
|
72
72
|
data: event.data,
|
73
|
-
schema: event.
|
73
|
+
schema: event.custom_metadata['encryption'],
|
74
74
|
repository: key_repository
|
75
75
|
).call
|
76
76
|
event.class.new(**event.to_h.merge(data: decrypted_data, skip_validation: true))
|
@@ -7,8 +7,8 @@ module EventStoreClient
|
|
7
7
|
class EventDeserializer
|
8
8
|
class << self
|
9
9
|
# @param raw_event [EventStore::Client::Streams::ReadResp::ReadEvent::RecordedEvent, EventStore::Client::PersistentSubscriptions::ReadResp::ReadEvent::RecordedEvent]
|
10
|
-
# @param serializer [#serialize, #deserialize]
|
11
10
|
# @param config [EventStoreClient::Config]
|
11
|
+
# @param serializer [#serialize, #deserialize]
|
12
12
|
# @return [EventStoreClient::DeserializedEvent]
|
13
13
|
def call(raw_event, config:, serializer: Serializer::Json)
|
14
14
|
new(config: config, serializer: serializer).call(raw_event)
|
@@ -19,6 +19,7 @@ module EventStoreClient
|
|
19
19
|
private :serializer, :config
|
20
20
|
|
21
21
|
# @param serializer [#serialize, #deserialize]
|
22
|
+
# @param config [EventStoreClient::Config]
|
22
23
|
def initialize(serializer:, config:)
|
23
24
|
@serializer = serializer
|
24
25
|
@config = config
|
@@ -29,7 +30,7 @@ module EventStoreClient
|
|
29
30
|
def call(raw_event)
|
30
31
|
data = serializer.deserialize(normalize_serialized(raw_event.data))
|
31
32
|
custom_metadata = serializer.deserialize(normalize_serialized(raw_event.custom_metadata))
|
32
|
-
metadata =
|
33
|
+
metadata = raw_event.metadata.to_h
|
33
34
|
|
34
35
|
event_class(metadata['type']).new(
|
35
36
|
skip_validation: true,
|
@@ -38,6 +39,7 @@ module EventStoreClient
|
|
38
39
|
type: metadata['type'],
|
39
40
|
data: data,
|
40
41
|
metadata: metadata,
|
42
|
+
custom_metadata: custom_metadata,
|
41
43
|
stream_revision: raw_event.stream_revision,
|
42
44
|
commit_position: raw_event.commit_position,
|
43
45
|
prepare_position: raw_event.prepare_position,
|
@@ -3,35 +3,39 @@
|
|
3
3
|
module EventStoreClient
|
4
4
|
module Serializer
|
5
5
|
class EventSerializer
|
6
|
-
|
6
|
+
# So far there are only these keys can be persisted in the metadata. You can pass **whatever**
|
7
|
+
# you want into a metadata hash, but all keys, except these - will be rejected. Define
|
8
|
+
# whitelisted keys and cut unwanted keys explicitly(later in this class).
|
9
|
+
ALLOWED_EVENT_METADATA = %w[type content-type].freeze
|
7
10
|
|
8
11
|
class << self
|
9
12
|
# @param event [EventStoreClient::DeserializedEvent]
|
13
|
+
# @param config [EventStoreClient::Config]
|
10
14
|
# @param serializer [#serialize, #deserialize]
|
11
15
|
# @return [EventStoreClient::SerializedEvent]
|
12
|
-
def call(event, serializer: Serializer::Json)
|
13
|
-
new(serializer: serializer).call(event)
|
16
|
+
def call(event, config:, serializer: Serializer::Json)
|
17
|
+
new(serializer: serializer, config: config).call(event)
|
14
18
|
end
|
15
19
|
end
|
16
20
|
|
17
|
-
attr_reader :serializer
|
18
|
-
private :serializer
|
21
|
+
attr_reader :serializer, :config
|
22
|
+
private :serializer, :config
|
19
23
|
|
20
24
|
# @param serializer [#serialize, #deserialize]
|
21
|
-
|
25
|
+
# @param config [EventStoreClient::Config]
|
26
|
+
def initialize(serializer:, config:)
|
22
27
|
@serializer = serializer
|
28
|
+
@config = config
|
23
29
|
end
|
24
30
|
|
25
31
|
# @param event [EventStoreClient::DeserializedEvent]
|
26
32
|
# @return [EventStoreClient::SerializedEvent]
|
27
33
|
def call(event)
|
28
|
-
event_metadata = metadata(event)
|
29
|
-
event_custom_metadata = custom_metadata(event, event_metadata)
|
30
34
|
SerializedEvent.new(
|
31
35
|
id: event.id || SecureRandom.uuid,
|
32
36
|
data: data(event),
|
33
|
-
|
34
|
-
|
37
|
+
metadata: metadata(event),
|
38
|
+
custom_metadata: custom_metadata(event),
|
35
39
|
serializer: serializer
|
36
40
|
)
|
37
41
|
end
|
@@ -42,17 +46,24 @@ module EventStoreClient
|
|
42
46
|
# @return [Hash]
|
43
47
|
def metadata(event)
|
44
48
|
metadata = serializer.deserialize(serializer.serialize(event.metadata))
|
45
|
-
|
46
|
-
|
49
|
+
# 'created' is returned in the metadata hash of the event when reading from a stream. It,
|
50
|
+
# however, can not be overridden - it is always defined automatically by EventStore db when
|
51
|
+
# appending new event. Thus, just ignore it - no need even to mention it in the
|
52
|
+
# #log_metadata_difference method's message.
|
53
|
+
metadata = metadata.slice(*(metadata.keys - ['created']))
|
54
|
+
filtered_metadata = metadata.slice(*ALLOWED_EVENT_METADATA)
|
55
|
+
log_metadata_difference(metadata) unless filtered_metadata == metadata
|
56
|
+
filtered_metadata
|
47
57
|
end
|
48
58
|
|
59
|
+
# Compute custom metadata for the event. **Exactly these** values you can see in ES admin's
|
60
|
+
# web UI under "Metadata" section of the event.
|
49
61
|
# @param event [EventStoreClient::DeserializedEvent]
|
50
|
-
# @param metadata [Hash]
|
51
62
|
# @return [Hash]
|
52
|
-
def custom_metadata(event
|
53
|
-
|
54
|
-
|
55
|
-
|
63
|
+
def custom_metadata(event)
|
64
|
+
custom_metadata = serializer.deserialize(serializer.serialize(event.custom_metadata))
|
65
|
+
custom_metadata['created_at'] ||= Time.now.utc.to_s
|
66
|
+
custom_metadata
|
56
67
|
end
|
57
68
|
|
58
69
|
# @param event [EventStoreClient::DeserializedEvent]
|
@@ -64,6 +75,19 @@ module EventStoreClient
|
|
64
75
|
|
65
76
|
serializer.deserialize(serializer.serialize(event.data))
|
66
77
|
end
|
78
|
+
|
79
|
+
# @param metadata [Hash]
|
80
|
+
# @return [void]
|
81
|
+
def log_metadata_difference(metadata)
|
82
|
+
rest_hash = metadata.slice(*(metadata.keys - ALLOWED_EVENT_METADATA))
|
83
|
+
debug_message = <<~TEXT
|
84
|
+
Next keys were filtered from metadata during serialization: \
|
85
|
+
#{(metadata.keys - ALLOWED_EVENT_METADATA).map(&:inspect).join(', ')}. If you would like \
|
86
|
+
to provide your custom values in the metadata - please provide them via custom_metadata. \
|
87
|
+
Example: EventStoreClient::DeserializedEvent.new(custom_metadata: #{rest_hash.inspect})
|
88
|
+
TEXT
|
89
|
+
config.logger&.debug(debug_message)
|
90
|
+
end
|
67
91
|
end
|
68
92
|
end
|
69
93
|
end
|
data/lib/event_store_client.rb
CHANGED
@@ -2,7 +2,9 @@
|
|
2
2
|
|
3
3
|
require 'json'
|
4
4
|
require 'set'
|
5
|
+
require 'securerandom'
|
5
6
|
|
7
|
+
require 'event_store_client/errors'
|
6
8
|
require 'event_store_client/serializer/json'
|
7
9
|
require 'event_store_client/serializer/event_serializer'
|
8
10
|
require 'event_store_client/serializer/event_deserializer'
|
@@ -29,7 +31,7 @@ module EventStoreClient
|
|
29
31
|
end
|
30
32
|
|
31
33
|
# @param name [Symbol, String]
|
32
|
-
# @return [
|
34
|
+
# @return [EventStoreClient::Config]
|
33
35
|
def config(name = :default)
|
34
36
|
@config[name] ||= Config.new(name: name)
|
35
37
|
end
|