ruby_event_store 0.39.0 → 0.40.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|