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.
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