ruby_event_store 0.39.0 → 0.40.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/lib/ruby_event_store.rb +24 -6
- data/lib/ruby_event_store/broker.rb +43 -0
- data/lib/ruby_event_store/client.rb +3 -3
- data/lib/ruby_event_store/dispatcher.rb +18 -0
- data/lib/ruby_event_store/errors.rb +13 -12
- data/lib/ruby_event_store/event.rb +1 -0
- data/lib/ruby_event_store/mappers.rb +10 -0
- data/lib/ruby_event_store/mappers/default.rb +8 -24
- data/lib/ruby_event_store/mappers/encryption_key.rb +72 -0
- data/lib/ruby_event_store/mappers/encryption_mapper.rb +8 -239
- data/lib/ruby_event_store/mappers/forgotten_data.rb +28 -0
- data/lib/ruby_event_store/mappers/in_memory_encryption_key_repository.rb +32 -0
- data/lib/ruby_event_store/mappers/null_mapper.rb +2 -18
- data/lib/ruby_event_store/mappers/pipeline.rb +29 -0
- data/lib/ruby_event_store/mappers/pipeline_mapper.rb +20 -0
- data/lib/ruby_event_store/mappers/protobuf.rb +9 -47
- data/lib/ruby_event_store/mappers/transformation/domain_event.rb +24 -0
- data/lib/ruby_event_store/mappers/transformation/encryption.rb +125 -0
- data/lib/ruby_event_store/mappers/transformation/event_class_remapper.rb +22 -0
- data/lib/ruby_event_store/mappers/transformation/item.rb +55 -0
- data/lib/ruby_event_store/mappers/transformation/proto_event.rb +15 -0
- data/lib/ruby_event_store/mappers/transformation/protobuf_encoder.rb +28 -0
- data/lib/ruby_event_store/mappers/transformation/protobuf_nested_struct_metadata.rb +29 -0
- data/lib/ruby_event_store/mappers/transformation/serialization.rb +32 -0
- data/lib/ruby_event_store/mappers/transformation/serialized_record.rb +25 -0
- data/lib/ruby_event_store/mappers/transformation/stringify_metadata_keys.rb +22 -0
- data/lib/ruby_event_store/mappers/transformation/symbolize_metadata_keys.rb +22 -0
- data/lib/ruby_event_store/pub_sub.rb +21 -0
- data/lib/ruby_event_store/spec/broker_lint.rb +3 -3
- data/lib/ruby_event_store/spec/dispatcher_lint.rb +2 -2
- data/lib/ruby_event_store/subscriptions.rb +108 -0
- data/lib/ruby_event_store/version.rb +1 -1
- metadata +24 -6
- data/lib/ruby_event_store/pub_sub/broker.rb +0 -45
- data/lib/ruby_event_store/pub_sub/dispatcher.rb +0 -20
- data/lib/ruby_event_store/pub_sub/subscriptions.rb +0 -110
@@ -0,0 +1,28 @@
|
|
1
|
+
module RubyEventStore
|
2
|
+
module Mappers
|
3
|
+
class ForgottenData
|
4
|
+
FORGOTTEN_DATA = 'FORGOTTEN_DATA'.freeze
|
5
|
+
|
6
|
+
def initialize(string = FORGOTTEN_DATA)
|
7
|
+
@string = string
|
8
|
+
end
|
9
|
+
|
10
|
+
def to_s
|
11
|
+
@string
|
12
|
+
end
|
13
|
+
|
14
|
+
def ==(other)
|
15
|
+
@string == other
|
16
|
+
end
|
17
|
+
alias_method :eql?, :==
|
18
|
+
|
19
|
+
def method_missing(*)
|
20
|
+
self
|
21
|
+
end
|
22
|
+
|
23
|
+
def respond_to_missing?(*)
|
24
|
+
true
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module RubyEventStore
|
2
|
+
module Mappers
|
3
|
+
class InMemoryEncryptionKeyRepository
|
4
|
+
DEFAULT_CIPHER = 'aes-256-gcm'.freeze
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@keys = {}
|
8
|
+
end
|
9
|
+
|
10
|
+
def key_of(identifier, cipher: DEFAULT_CIPHER)
|
11
|
+
@keys[[identifier, cipher]]
|
12
|
+
end
|
13
|
+
|
14
|
+
def create(identifier, cipher: DEFAULT_CIPHER)
|
15
|
+
crypto = prepare_encrypt(cipher)
|
16
|
+
@keys[[identifier, cipher]] = EncryptionKey.new(cipher: cipher, key: crypto.random_key)
|
17
|
+
end
|
18
|
+
|
19
|
+
def forget(identifier)
|
20
|
+
@keys = @keys.reject { |(id, _)| id.eql?(identifier) }
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def prepare_encrypt(cipher)
|
26
|
+
crypto = OpenSSL::Cipher.new(cipher)
|
27
|
+
crypto.encrypt
|
28
|
+
crypto
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -1,26 +1,10 @@
|
|
1
|
-
require 'forwardable'
|
2
|
-
|
3
1
|
module RubyEventStore
|
4
2
|
module Mappers
|
5
|
-
class NullMapper
|
6
|
-
extend Forwardable
|
7
|
-
class NULL
|
8
|
-
def self.dump(event)
|
9
|
-
event
|
10
|
-
end
|
11
|
-
|
12
|
-
def self.load(record)
|
13
|
-
record
|
14
|
-
end
|
15
|
-
end
|
16
|
-
private_constant :NULL
|
17
|
-
|
3
|
+
class NullMapper < PipelineMapper
|
18
4
|
|
19
5
|
def initialize
|
20
|
-
|
6
|
+
super(Pipeline.new)
|
21
7
|
end
|
22
|
-
|
23
|
-
def_delegators :@mapper, :event_to_serialized_record, :serialized_record_to_event
|
24
8
|
end
|
25
9
|
end
|
26
10
|
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module RubyEventStore
|
2
|
+
module Mappers
|
3
|
+
class Pipeline
|
4
|
+
def initialize(to_domain_event: Transformation::DomainEvent.new,
|
5
|
+
to_serialized_record: Transformation::SerializedRecord.new,
|
6
|
+
transformations: nil)
|
7
|
+
@transformations = [
|
8
|
+
to_domain_event,
|
9
|
+
Array(transformations),
|
10
|
+
to_serialized_record
|
11
|
+
].flatten.freeze
|
12
|
+
end
|
13
|
+
|
14
|
+
def dump(domain_event)
|
15
|
+
transformations.reduce(domain_event) do |item, transform|
|
16
|
+
transform.dump(item)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def load(record)
|
21
|
+
transformations.reverse.reduce(record) do |item, transform|
|
22
|
+
transform.load(item)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
attr_reader :transformations
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module RubyEventStore
|
2
|
+
module Mappers
|
3
|
+
class PipelineMapper
|
4
|
+
def initialize(pipeline)
|
5
|
+
@pipeline = pipeline
|
6
|
+
end
|
7
|
+
|
8
|
+
def event_to_serialized_record(domain_event)
|
9
|
+
pipeline.dump(domain_event)
|
10
|
+
end
|
11
|
+
|
12
|
+
def serialized_record_to_event(record)
|
13
|
+
pipeline.load(record)
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
attr_reader :pipeline
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -12,54 +12,16 @@ module RubyEventStore
|
|
12
12
|
end
|
13
13
|
|
14
14
|
module Mappers
|
15
|
-
class Protobuf
|
15
|
+
class Protobuf < PipelineMapper
|
16
16
|
def initialize(events_class_remapping: {})
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
data: encode_data(domain_event.data),
|
26
|
-
event_type: domain_event.type
|
27
|
-
)
|
28
|
-
end
|
29
|
-
|
30
|
-
def serialized_record_to_event(record)
|
31
|
-
event_type = events_class_remapping.fetch(record.event_type) { record.event_type }
|
32
|
-
Proto.new(
|
33
|
-
event_id: record.event_id,
|
34
|
-
data: load_data(event_type, record.data),
|
35
|
-
metadata: load_metadata(record.metadata)
|
36
|
-
)
|
37
|
-
end
|
38
|
-
|
39
|
-
private
|
40
|
-
|
41
|
-
attr_reader :event_id_getter, :events_class_remapping
|
42
|
-
|
43
|
-
def load_metadata(protobuf_metadata)
|
44
|
-
TransformKeys.symbolize(ProtobufNestedStruct::HashMapStringValue.load(protobuf_metadata))
|
45
|
-
end
|
46
|
-
|
47
|
-
def load_data(event_type, protobuf_data)
|
48
|
-
Google::Protobuf::DescriptorPool.generated_pool.lookup(event_type).msgclass.decode(protobuf_data)
|
49
|
-
end
|
50
|
-
|
51
|
-
def encode_data(domain_event_data)
|
52
|
-
begin
|
53
|
-
domain_event_data.class.encode(domain_event_data)
|
54
|
-
rescue NoMethodError
|
55
|
-
raise ProtobufEncodingFailed
|
56
|
-
end
|
57
|
-
end
|
58
|
-
|
59
|
-
def require_optional_dependency
|
60
|
-
require 'protobuf_nested_struct'
|
61
|
-
rescue LoadError
|
62
|
-
raise LoadError, "cannot load such file -- protobuf_nested_struct. Add protobuf_nested_struct gem to Gemfile"
|
17
|
+
super(Pipeline.new(
|
18
|
+
to_domain_event: Transformation::ProtoEvent.new,
|
19
|
+
transformations: [
|
20
|
+
Transformation::ProtobufEncoder.new,
|
21
|
+
Transformation::EventClassRemapper.new(events_class_remapping),
|
22
|
+
Transformation::ProtobufNestedStructMetadata.new,
|
23
|
+
]
|
24
|
+
))
|
63
25
|
end
|
64
26
|
end
|
65
27
|
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module RubyEventStore
|
2
|
+
module Mappers
|
3
|
+
module Transformation
|
4
|
+
class DomainEvent
|
5
|
+
def dump(domain_event)
|
6
|
+
Item.new(
|
7
|
+
event_id: domain_event.event_id,
|
8
|
+
metadata: domain_event.metadata.to_h,
|
9
|
+
data: domain_event.data,
|
10
|
+
event_type: domain_event.type
|
11
|
+
)
|
12
|
+
end
|
13
|
+
|
14
|
+
def load(item)
|
15
|
+
Object.const_get(item.event_type).new(
|
16
|
+
event_id: item.event_id,
|
17
|
+
metadata: item.metadata,
|
18
|
+
data: item.data
|
19
|
+
)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
module RubyEventStore
|
2
|
+
module Mappers
|
3
|
+
module Transformation
|
4
|
+
class Encryption
|
5
|
+
class Leaf
|
6
|
+
def self.===(hash)
|
7
|
+
hash.keys.sort.eql? %i(cipher identifier iv)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
private_constant :Leaf
|
11
|
+
|
12
|
+
class MissingEncryptionKey < StandardError
|
13
|
+
def initialize(key_identifier)
|
14
|
+
super %Q|Could not find encryption key for '#{key_identifier}'|
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def initialize(key_repository, serializer: YAML, forgotten_data: ForgottenData.new)
|
19
|
+
@key_repository = key_repository
|
20
|
+
@serializer = serializer
|
21
|
+
@forgotten_data = forgotten_data
|
22
|
+
end
|
23
|
+
|
24
|
+
def dump(item)
|
25
|
+
data = item.data
|
26
|
+
metadata = item.metadata.dup
|
27
|
+
event_class = Object.const_get(item.event_type)
|
28
|
+
|
29
|
+
crypto_description = encryption_metadata(data, encryption_schema(event_class))
|
30
|
+
metadata[:encryption] = crypto_description unless crypto_description.empty?
|
31
|
+
|
32
|
+
item.merge(
|
33
|
+
data: encrypt_data(deep_dup(data), crypto_description),
|
34
|
+
metadata: metadata
|
35
|
+
)
|
36
|
+
end
|
37
|
+
|
38
|
+
def load(item)
|
39
|
+
metadata = item.metadata.dup
|
40
|
+
crypto_description = Hash(metadata.delete(:encryption))
|
41
|
+
|
42
|
+
item.merge(
|
43
|
+
data: decrypt_data(item.data, crypto_description),
|
44
|
+
metadata: metadata
|
45
|
+
)
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
attr_reader :key_repository, :serializer, :forgotten_data
|
50
|
+
|
51
|
+
def encryption_schema(event_class)
|
52
|
+
event_class.respond_to?(:encryption_schema) ? event_class.encryption_schema : {}
|
53
|
+
end
|
54
|
+
|
55
|
+
def deep_dup(hash)
|
56
|
+
duplicate = hash.dup
|
57
|
+
duplicate.each do |k, v|
|
58
|
+
duplicate[k] = v.instance_of?(Hash) ? deep_dup(v) : v
|
59
|
+
end
|
60
|
+
duplicate
|
61
|
+
end
|
62
|
+
|
63
|
+
def encryption_metadata(data, schema)
|
64
|
+
schema.inject({}) do |acc, (key, value)|
|
65
|
+
case value
|
66
|
+
when Hash
|
67
|
+
acc[key] = encryption_metadata(data, value)
|
68
|
+
when Proc
|
69
|
+
key_identifier = value.call(data)
|
70
|
+
encryption_key = key_repository.key_of(key_identifier) or raise MissingEncryptionKey.new(key_identifier)
|
71
|
+
acc[key] = {
|
72
|
+
cipher: encryption_key.cipher,
|
73
|
+
iv: encryption_key.random_iv,
|
74
|
+
identifier: key_identifier,
|
75
|
+
}
|
76
|
+
end
|
77
|
+
acc
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def encrypt_data(data, meta)
|
82
|
+
meta.reduce(data) do |acc, (key, value)|
|
83
|
+
acc[key] = encrypt_attribute(acc, key, value)
|
84
|
+
acc
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def decrypt_data(data, meta)
|
89
|
+
meta.reduce(data) do |acc, (key, value)|
|
90
|
+
acc[key] = decrypt_attribute(data, key, value)
|
91
|
+
acc
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def encrypt_attribute(data, attribute, meta)
|
96
|
+
case meta
|
97
|
+
when Leaf
|
98
|
+
value = data.fetch(attribute)
|
99
|
+
return unless value
|
100
|
+
|
101
|
+
encryption_key = key_repository.key_of(meta.fetch(:identifier))
|
102
|
+
encryption_key.encrypt(serializer.dump(value), meta.fetch(:iv))
|
103
|
+
when Hash
|
104
|
+
encrypt_data(data.fetch(attribute), meta)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def decrypt_attribute(data, attribute, meta)
|
109
|
+
case meta
|
110
|
+
when Leaf
|
111
|
+
cryptogram = data.fetch(attribute)
|
112
|
+
return unless cryptogram
|
113
|
+
|
114
|
+
encryption_key = key_repository.key_of(meta.fetch(:identifier), cipher: meta.fetch(:cipher)) or return forgotten_data
|
115
|
+
serializer.load(encryption_key.decrypt(cryptogram, meta.fetch(:iv)))
|
116
|
+
when Hash
|
117
|
+
decrypt_data(data.fetch(attribute), meta)
|
118
|
+
end
|
119
|
+
rescue OpenSSL::Cipher::CipherError
|
120
|
+
forgotten_data
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module RubyEventStore
|
2
|
+
module Mappers
|
3
|
+
module Transformation
|
4
|
+
class EventClassRemapper
|
5
|
+
def initialize(class_map)
|
6
|
+
@class_map = class_map
|
7
|
+
end
|
8
|
+
|
9
|
+
def dump(item)
|
10
|
+
item
|
11
|
+
end
|
12
|
+
|
13
|
+
def load(item)
|
14
|
+
item.merge(event_type: class_map[item.fetch(:event_type)] || item.fetch(:event_type))
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
attr_reader :class_map
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
|
3
|
+
module RubyEventStore
|
4
|
+
module Mappers
|
5
|
+
module Transformation
|
6
|
+
class Item
|
7
|
+
include Enumerable
|
8
|
+
extend Forwardable
|
9
|
+
|
10
|
+
def initialize(h)
|
11
|
+
@h = {}
|
12
|
+
h.each do |k, v|
|
13
|
+
@h[k] = (v)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def event_id
|
18
|
+
fetch(:event_id)
|
19
|
+
end
|
20
|
+
|
21
|
+
def metadata
|
22
|
+
fetch(:metadata)
|
23
|
+
end
|
24
|
+
|
25
|
+
def data
|
26
|
+
fetch(:data)
|
27
|
+
end
|
28
|
+
|
29
|
+
def event_type
|
30
|
+
fetch(:event_type)
|
31
|
+
end
|
32
|
+
|
33
|
+
def ==(other_event)
|
34
|
+
other_event.instance_of?(self.class) &&
|
35
|
+
other_event.to_h.eql?(to_h)
|
36
|
+
end
|
37
|
+
alias_method :eql?, :==
|
38
|
+
|
39
|
+
def merge(args)
|
40
|
+
Item.new(@h.merge(args))
|
41
|
+
end
|
42
|
+
|
43
|
+
def to_h
|
44
|
+
@h.dup
|
45
|
+
end
|
46
|
+
|
47
|
+
SAFE_HASH_METHODS = [:[], :fetch]
|
48
|
+
delegate SAFE_HASH_METHODS => :@h
|
49
|
+
|
50
|
+
private
|
51
|
+
private_constant :SAFE_HASH_METHODS
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|