event_store_client 2.3.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/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
|