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.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/lib/ruby_event_store.rb +24 -6
  3. data/lib/ruby_event_store/broker.rb +43 -0
  4. data/lib/ruby_event_store/client.rb +3 -3
  5. data/lib/ruby_event_store/dispatcher.rb +18 -0
  6. data/lib/ruby_event_store/errors.rb +13 -12
  7. data/lib/ruby_event_store/event.rb +1 -0
  8. data/lib/ruby_event_store/mappers.rb +10 -0
  9. data/lib/ruby_event_store/mappers/default.rb +8 -24
  10. data/lib/ruby_event_store/mappers/encryption_key.rb +72 -0
  11. data/lib/ruby_event_store/mappers/encryption_mapper.rb +8 -239
  12. data/lib/ruby_event_store/mappers/forgotten_data.rb +28 -0
  13. data/lib/ruby_event_store/mappers/in_memory_encryption_key_repository.rb +32 -0
  14. data/lib/ruby_event_store/mappers/null_mapper.rb +2 -18
  15. data/lib/ruby_event_store/mappers/pipeline.rb +29 -0
  16. data/lib/ruby_event_store/mappers/pipeline_mapper.rb +20 -0
  17. data/lib/ruby_event_store/mappers/protobuf.rb +9 -47
  18. data/lib/ruby_event_store/mappers/transformation/domain_event.rb +24 -0
  19. data/lib/ruby_event_store/mappers/transformation/encryption.rb +125 -0
  20. data/lib/ruby_event_store/mappers/transformation/event_class_remapper.rb +22 -0
  21. data/lib/ruby_event_store/mappers/transformation/item.rb +55 -0
  22. data/lib/ruby_event_store/mappers/transformation/proto_event.rb +15 -0
  23. data/lib/ruby_event_store/mappers/transformation/protobuf_encoder.rb +28 -0
  24. data/lib/ruby_event_store/mappers/transformation/protobuf_nested_struct_metadata.rb +29 -0
  25. data/lib/ruby_event_store/mappers/transformation/serialization.rb +32 -0
  26. data/lib/ruby_event_store/mappers/transformation/serialized_record.rb +25 -0
  27. data/lib/ruby_event_store/mappers/transformation/stringify_metadata_keys.rb +22 -0
  28. data/lib/ruby_event_store/mappers/transformation/symbolize_metadata_keys.rb +22 -0
  29. data/lib/ruby_event_store/pub_sub.rb +21 -0
  30. data/lib/ruby_event_store/spec/broker_lint.rb +3 -3
  31. data/lib/ruby_event_store/spec/dispatcher_lint.rb +2 -2
  32. data/lib/ruby_event_store/subscriptions.rb +108 -0
  33. data/lib/ruby_event_store/version.rb +1 -1
  34. metadata +24 -6
  35. data/lib/ruby_event_store/pub_sub/broker.rb +0 -45
  36. data/lib/ruby_event_store/pub_sub/dispatcher.rb +0 -20
  37. 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
- @mapper = Default.new(serializer: NULL)
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
- require_optional_dependency
18
- @events_class_remapping = events_class_remapping
19
- end
20
-
21
- def event_to_serialized_record(domain_event)
22
- SerializedRecord.new(
23
- event_id: domain_event.event_id,
24
- metadata: ProtobufNestedStruct::HashMapStringValue.dump(TransformKeys.stringify(domain_event.metadata)),
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
@@ -0,0 +1,15 @@
1
+ module RubyEventStore
2
+ module Mappers
3
+ module Transformation
4
+ class ProtoEvent < DomainEvent
5
+ def load(item)
6
+ Proto.new(
7
+ event_id: item.event_id,
8
+ data: item.data,
9
+ metadata: item.metadata
10
+ )
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end