ruby_event_store 0.30.0 → 0.31.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/Makefile +6 -1
- data/lib/ruby_event_store.rb +5 -0
- data/lib/ruby_event_store/async_dispatcher.rb +20 -0
- data/lib/ruby_event_store/async_proxy_strategy.rb +9 -0
- data/lib/ruby_event_store/client.rb +204 -45
- data/lib/ruby_event_store/correlated_commands.rb +37 -0
- data/lib/ruby_event_store/event.rb +68 -1
- data/lib/ruby_event_store/link_by_metadata.rb +55 -0
- data/lib/ruby_event_store/mappers/null_mapper.rb +0 -1
- data/lib/ruby_event_store/mappers/protobuf.rb +13 -5
- data/lib/ruby_event_store/pub_sub/broker.rb +22 -50
- data/lib/ruby_event_store/pub_sub/dispatcher.rb +1 -1
- data/lib/ruby_event_store/pub_sub/subscriptions.rb +110 -0
- data/lib/ruby_event_store/spec/broker_lint.rb +76 -0
- data/lib/ruby_event_store/spec/dispatcher_lint.rb +8 -9
- data/lib/ruby_event_store/spec/event_repository_lint.rb +1 -0
- data/lib/ruby_event_store/spec/subscriptions_lint.rb +111 -0
- data/lib/ruby_event_store/specification.rb +60 -0
- data/lib/ruby_event_store/version.rb +1 -1
- metadata +9 -3
- data/lib/ruby_event_store/spec/event_broker_lint.rb +0 -211
@@ -0,0 +1,37 @@
|
|
1
|
+
module RubyEventStore
|
2
|
+
class CorrelatedCommands
|
3
|
+
|
4
|
+
def initialize(event_store, command_bus)
|
5
|
+
@event_store = event_store
|
6
|
+
@command_bus = command_bus
|
7
|
+
end
|
8
|
+
|
9
|
+
class MiniEvent < Struct.new(:correlation_id, :message_id)
|
10
|
+
end
|
11
|
+
|
12
|
+
def call(command)
|
13
|
+
if (correlation_id = event_store.metadata[:correlation_id]) && (causation_id = event_store.metadata[:causation_id])
|
14
|
+
command.correlate_with(MiniEvent.new(
|
15
|
+
correlation_id,
|
16
|
+
causation_id,
|
17
|
+
)) if command.respond_to?(:correlate_with)
|
18
|
+
event_store.with_metadata(
|
19
|
+
causation_id: command.message_id,
|
20
|
+
) do
|
21
|
+
command_bus.call(command)
|
22
|
+
end
|
23
|
+
else
|
24
|
+
event_store.with_metadata(
|
25
|
+
correlation_id: command.message_id,
|
26
|
+
causation_id: command.message_id,
|
27
|
+
) do
|
28
|
+
command_bus.call(command)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
attr_reader :event_store, :command_bus
|
36
|
+
end
|
37
|
+
end
|
@@ -1,22 +1,42 @@
|
|
1
1
|
require 'securerandom'
|
2
2
|
|
3
3
|
module RubyEventStore
|
4
|
+
|
5
|
+
# Data structure representing an event
|
4
6
|
class Event
|
7
|
+
# Instantiates a new event
|
8
|
+
#
|
9
|
+
# @param event_id [String] event id
|
10
|
+
# @param data [Hash] event data which belong to your application domain
|
11
|
+
# @param metadata [Hash] event metadata which are technical and not
|
12
|
+
# part of your domain such as remote_ip, request_id, correlation_id,
|
13
|
+
# causation_id etc.
|
14
|
+
# @return [Event]
|
5
15
|
def initialize(event_id: SecureRandom.uuid, metadata: nil, data: nil)
|
6
16
|
@event_id = event_id.to_s
|
7
17
|
@metadata = Metadata.new(metadata.to_h)
|
8
18
|
@data = data.to_h
|
9
19
|
end
|
20
|
+
|
10
21
|
attr_reader :event_id, :metadata, :data
|
11
22
|
|
23
|
+
# Event id
|
24
|
+
# @return [String]
|
12
25
|
def message_id
|
13
26
|
event_id
|
14
27
|
end
|
15
28
|
|
29
|
+
# Type of event. Used when matching with subscribed handlers.
|
30
|
+
# @return [String]
|
16
31
|
def type
|
17
32
|
self.class.name
|
18
33
|
end
|
19
34
|
|
35
|
+
# Returns a hash representation of the event.
|
36
|
+
#
|
37
|
+
# Metadata is converted to hash as well
|
38
|
+
#
|
39
|
+
# @return [Hash] with :event_id, :metadata, :data, :type keys
|
20
40
|
def to_h
|
21
41
|
{
|
22
42
|
event_id: event_id,
|
@@ -26,20 +46,43 @@ module RubyEventStore
|
|
26
46
|
}
|
27
47
|
end
|
28
48
|
|
49
|
+
# Timestamp from metadata
|
50
|
+
#
|
51
|
+
# @return [Time, nil]
|
29
52
|
def timestamp
|
30
53
|
metadata[:timestamp]
|
31
54
|
end
|
32
55
|
|
56
|
+
# Two events are equal if:
|
57
|
+
# * they are of the same class
|
58
|
+
# * have identical event id
|
59
|
+
# * have identical data (verified with eql? method)
|
60
|
+
#
|
61
|
+
# @param other_event [Event, Object] object to compare
|
62
|
+
#
|
63
|
+
# Event equality ignores metadata!
|
64
|
+
# @return [TrueClass, FalseClass]
|
33
65
|
def ==(other_event)
|
34
66
|
other_event.instance_of?(self.class) &&
|
35
67
|
other_event.event_id.eql?(event_id) &&
|
36
68
|
other_event.data.eql?(data)
|
37
69
|
end
|
38
70
|
|
71
|
+
# @private
|
39
72
|
BIG_VALUE = 0b111111100100000010010010110011101011000101010101001100100110000
|
40
73
|
|
41
|
-
#
|
74
|
+
# Generates a Fixnum hash value for this object. This function
|
75
|
+
# have the property that a.eql?(b) implies a.hash == b.hash.
|
76
|
+
#
|
77
|
+
# The hash value is used along with eql? by the Hash class to
|
78
|
+
# determine if two objects reference the same hash key.
|
79
|
+
#
|
80
|
+
# This hash is based on
|
81
|
+
# * class
|
82
|
+
# * event_id
|
83
|
+
# * data
|
42
84
|
def hash
|
85
|
+
# We don't use metadata because == does not use metadata
|
43
86
|
[
|
44
87
|
self.class,
|
45
88
|
event_id,
|
@@ -47,22 +90,46 @@ module RubyEventStore
|
|
47
90
|
].hash ^ BIG_VALUE
|
48
91
|
end
|
49
92
|
|
93
|
+
# Reads correlation_id from metadata.
|
94
|
+
# {http://railseventstore.org/docs/correlation_causation/ Find out more}
|
95
|
+
#
|
96
|
+
# @return [String, nil]
|
50
97
|
def correlation_id
|
51
98
|
metadata[:correlation_id]
|
52
99
|
end
|
53
100
|
|
101
|
+
# Sets correlation_id in metadata.
|
102
|
+
# {http://railseventstore.org/docs/correlation_causation/ Find out more}
|
103
|
+
#
|
104
|
+
# @param val [String]
|
105
|
+
# @return [String]
|
54
106
|
def correlation_id=(val)
|
55
107
|
metadata[:correlation_id] = val
|
56
108
|
end
|
57
109
|
|
110
|
+
# Reads causation_id from metadata.
|
111
|
+
# {http://railseventstore.org/docs/correlation_causation/ Find out more}
|
112
|
+
#
|
113
|
+
# @return [String, nil]
|
58
114
|
def causation_id
|
59
115
|
metadata[:causation_id]
|
60
116
|
end
|
61
117
|
|
118
|
+
# Sets causation_id= in metadata.
|
119
|
+
# {http://railseventstore.org/docs/correlation_causation/ Find out more}
|
120
|
+
#
|
121
|
+
# @param val [String]
|
122
|
+
# @return [String]
|
62
123
|
def causation_id=(val)
|
63
124
|
metadata[:causation_id]= val
|
64
125
|
end
|
65
126
|
|
127
|
+
# Sets correlation_id and causation_id in metadata based
|
128
|
+
# on correlation_id and message_id of the provided message.
|
129
|
+
# {http://railseventstore.org/docs/correlation_causation/ Find out more}
|
130
|
+
#
|
131
|
+
# @param other_message [Event, Proto, command] message to correlate with. Most likely an event or a command. Must respond to correlation_id and message_id.
|
132
|
+
# @return [String] set causation_id
|
66
133
|
def correlate_with(other_message)
|
67
134
|
self.correlation_id = other_message.correlation_id || other_message.message_id
|
68
135
|
self.causation_id = other_message.message_id
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module RubyEventStore
|
2
|
+
class LinkByMetadata
|
3
|
+
|
4
|
+
def initialize(event_store:, key:, prefix: nil)
|
5
|
+
@event_store = event_store
|
6
|
+
@key = key
|
7
|
+
@prefix = prefix || ["$by", key, nil].join("_")
|
8
|
+
end
|
9
|
+
|
10
|
+
def call(event)
|
11
|
+
return unless event.metadata.has_key?(@key)
|
12
|
+
|
13
|
+
@event_store.link_to_stream(
|
14
|
+
[event.message_id],
|
15
|
+
stream_name: "#{@prefix}#{event.metadata.fetch(@key)}"
|
16
|
+
)
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
class LinkByCorrelationId < LinkByMetadata
|
22
|
+
def initialize(event_store:, prefix: nil)
|
23
|
+
super(
|
24
|
+
event_store: event_store,
|
25
|
+
prefix: prefix,
|
26
|
+
key: :correlation_id,
|
27
|
+
)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class LinkByCausationId < LinkByMetadata
|
32
|
+
def initialize(event_store:, prefix: nil)
|
33
|
+
super(
|
34
|
+
event_store: event_store,
|
35
|
+
prefix: prefix,
|
36
|
+
key: :causation_id,
|
37
|
+
)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
class LinkByEventType
|
42
|
+
def initialize(event_store:, prefix: nil)
|
43
|
+
@event_store = event_store
|
44
|
+
@prefix = prefix || "$by_type_"
|
45
|
+
end
|
46
|
+
|
47
|
+
def call(event)
|
48
|
+
@event_store.link_to_stream(
|
49
|
+
[event.message_id],
|
50
|
+
stream_name: "#{@prefix}#{event.type}"
|
51
|
+
)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
@@ -55,19 +55,27 @@ module RubyEventStore
|
|
55
55
|
|
56
56
|
def serialized_record_to_event(record)
|
57
57
|
event_type = events_class_remapping.fetch(record.event_type) { record.event_type }
|
58
|
-
data = Google::Protobuf::DescriptorPool.generated_pool.lookup(event_type).msgclass.decode(record.data)
|
59
58
|
Proto.new(
|
60
59
|
event_id: record.event_id,
|
61
|
-
data: data,
|
62
|
-
|
63
|
-
|
64
|
-
end
|
60
|
+
data: load_data(event_type, record.data),
|
61
|
+
metadata: load_metadata(record.metadata)
|
62
|
+
)
|
65
63
|
end
|
66
64
|
|
67
65
|
private
|
68
66
|
|
69
67
|
attr_reader :event_id_getter, :events_class_remapping
|
70
68
|
|
69
|
+
def load_metadata(protobuf_metadata)
|
70
|
+
ProtobufNestedStruct::HashMapStringValue.load(protobuf_metadata).each_with_object({}) do |(k, v), meta|
|
71
|
+
meta[k.to_sym] = v
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def load_data(event_type, protobuf_data)
|
76
|
+
Google::Protobuf::DescriptorPool.generated_pool.lookup(event_type).msgclass.decode(protobuf_data)
|
77
|
+
end
|
78
|
+
|
71
79
|
def require_optional_dependency
|
72
80
|
require 'protobuf_nested_struct'
|
73
81
|
rescue LoadError
|
@@ -1,73 +1,45 @@
|
|
1
|
-
require 'concurrent'
|
2
|
-
|
3
1
|
module RubyEventStore
|
4
2
|
module PubSub
|
5
3
|
class Broker
|
6
|
-
|
7
|
-
|
8
|
-
def initialize(dispatcher: DEFAULT_DISPATCHER)
|
9
|
-
@subscribers = Hash.new {|hsh, key| hsh[key] = [] }
|
10
|
-
@global_subscribers = []
|
11
|
-
|
12
|
-
@thread_global_subscribers = Concurrent::ThreadLocalVar.new([])
|
13
|
-
@thread_subscribers = Concurrent::ThreadLocalVar.new do
|
14
|
-
Hash.new {|hsh, key| hsh[key] = [] }
|
15
|
-
end
|
16
|
-
|
4
|
+
def initialize(subscriptions:, dispatcher:)
|
5
|
+
@subscriptions = subscriptions
|
17
6
|
@dispatcher = dispatcher
|
18
7
|
end
|
19
8
|
|
20
|
-
def
|
21
|
-
|
22
|
-
|
9
|
+
def call(event, serialized_event)
|
10
|
+
subscribers = subscriptions.all_for(event.type)
|
11
|
+
subscribers.each do |subscriber|
|
12
|
+
dispatcher.call(subscriber, event, serialized_event)
|
13
|
+
end
|
23
14
|
end
|
24
15
|
|
25
|
-
def
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
->() { global_subscribers.delete(subscriber) }
|
16
|
+
def add_subscription(subscriber, event_types)
|
17
|
+
verify_subscription(subscriber)
|
18
|
+
subscriptions.add_subscription(subscriber, event_types)
|
30
19
|
end
|
31
20
|
|
32
|
-
def
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
->() { thread_global_subscribers.value -= [subscriber] }
|
21
|
+
def add_global_subscription(subscriber)
|
22
|
+
verify_subscription(subscriber)
|
23
|
+
subscriptions.add_global_subscription(subscriber)
|
37
24
|
end
|
38
25
|
|
39
|
-
def
|
40
|
-
|
41
|
-
|
42
|
-
->() {event_types.each{ |type| thread_subscribers.value.fetch(type.to_s).delete(subscriber) } }
|
26
|
+
def add_thread_subscription(subscriber, event_types)
|
27
|
+
verify_subscription(subscriber)
|
28
|
+
subscriptions.add_thread_subscription(subscriber, event_types)
|
43
29
|
end
|
44
30
|
|
45
|
-
def
|
46
|
-
|
47
|
-
|
48
|
-
end
|
31
|
+
def add_thread_global_subscription(subscriber)
|
32
|
+
verify_subscription(subscriber)
|
33
|
+
subscriptions.add_thread_global_subscription(subscriber)
|
49
34
|
end
|
50
35
|
|
51
36
|
private
|
37
|
+
attr_reader :subscriptions, :dispatcher
|
52
38
|
|
53
|
-
def
|
54
|
-
raise SubscriberNotExist
|
39
|
+
def verify_subscription(subscriber)
|
40
|
+
raise SubscriberNotExist, "subscriber must be first argument or block" unless subscriber
|
55
41
|
dispatcher.verify(subscriber)
|
56
42
|
end
|
57
|
-
|
58
|
-
def subscribe(subscriber, event_types)
|
59
|
-
event_types.each{ |type| subscribers[type.to_s] << subscriber }
|
60
|
-
->() {event_types.each{ |type| subscribers.fetch(type.to_s).delete(subscriber) } }
|
61
|
-
end
|
62
|
-
|
63
|
-
def all_subscribers_for(event_type)
|
64
|
-
subscribers[event_type] +
|
65
|
-
global_subscribers +
|
66
|
-
thread_global_subscribers.value +
|
67
|
-
thread_subscribers.value[event_type]
|
68
|
-
end
|
69
|
-
|
70
|
-
attr_reader :dispatcher, :subscribers, :global_subscribers, :thread_global_subscribers, :thread_subscribers
|
71
43
|
end
|
72
44
|
end
|
73
45
|
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
require 'concurrent'
|
2
|
+
|
3
|
+
module RubyEventStore
|
4
|
+
module PubSub
|
5
|
+
class Subscriptions
|
6
|
+
def initialize
|
7
|
+
@local = LocalSubscriptions.new
|
8
|
+
@global = GlobalSubscriptions.new
|
9
|
+
@thread = ThreadSubscriptions.new
|
10
|
+
end
|
11
|
+
|
12
|
+
def add_subscription(subscriber, event_types)
|
13
|
+
local.add(subscriber, event_types)
|
14
|
+
end
|
15
|
+
|
16
|
+
def add_global_subscription(subscriber)
|
17
|
+
global.add(subscriber)
|
18
|
+
end
|
19
|
+
|
20
|
+
def add_thread_subscription(subscriber, event_types)
|
21
|
+
thread.local.add(subscriber, event_types)
|
22
|
+
end
|
23
|
+
|
24
|
+
def add_thread_global_subscription(subscriber)
|
25
|
+
thread.global.add(subscriber)
|
26
|
+
end
|
27
|
+
|
28
|
+
def all_for(event_type)
|
29
|
+
[local, global, thread].map{|r| r.all_for(event_type)}.reduce(&:+)
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
attr_reader :local, :global, :thread
|
34
|
+
|
35
|
+
class ThreadSubscriptions
|
36
|
+
def initialize
|
37
|
+
@local = ThreadLocalSubscriptions.new
|
38
|
+
@global = ThreadGlobalSubscriptions.new
|
39
|
+
end
|
40
|
+
attr_reader :local, :global
|
41
|
+
|
42
|
+
def all_for(event_type)
|
43
|
+
[global, local].map{|r| r.all_for(event_type)}.reduce(&:+)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
class LocalSubscriptions
|
48
|
+
def initialize
|
49
|
+
@subscriptions = Hash.new {|hsh, key| hsh[key] = [] }
|
50
|
+
end
|
51
|
+
|
52
|
+
def add(subscription, event_types)
|
53
|
+
event_types.each{ |type| @subscriptions[type.to_s] << subscription }
|
54
|
+
->() {event_types.each{ |type| @subscriptions.fetch(type.to_s).delete(subscription) } }
|
55
|
+
end
|
56
|
+
|
57
|
+
def all_for(event_type)
|
58
|
+
@subscriptions[event_type]
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
class GlobalSubscriptions
|
63
|
+
def initialize
|
64
|
+
@subscriptions = []
|
65
|
+
end
|
66
|
+
|
67
|
+
def add(subscription)
|
68
|
+
@subscriptions << subscription
|
69
|
+
->() { @subscriptions.delete(subscription) }
|
70
|
+
end
|
71
|
+
|
72
|
+
def all_for(_event_type)
|
73
|
+
@subscriptions
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
class ThreadLocalSubscriptions
|
78
|
+
def initialize
|
79
|
+
@subscriptions = Concurrent::ThreadLocalVar.new do
|
80
|
+
Hash.new {|hsh, key| hsh[key] = [] }
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def add(subscription, event_types)
|
85
|
+
event_types.each{ |type| @subscriptions.value[type.to_s] << subscription }
|
86
|
+
->() {event_types.each{ |type| @subscriptions.value.fetch(type.to_s).delete(subscription) } }
|
87
|
+
end
|
88
|
+
|
89
|
+
def all_for(event_type)
|
90
|
+
@subscriptions.value[event_type]
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
class ThreadGlobalSubscriptions
|
95
|
+
def initialize
|
96
|
+
@subscriptions = Concurrent::ThreadLocalVar.new([])
|
97
|
+
end
|
98
|
+
|
99
|
+
def add(subscription)
|
100
|
+
@subscriptions.value += [subscription]
|
101
|
+
->() { @subscriptions.value -= [subscription] }
|
102
|
+
end
|
103
|
+
|
104
|
+
def all_for(_event_type)
|
105
|
+
@subscriptions.value
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|