event_store_client 1.4.9 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +30 -145
- data/docs/appending_events.md +155 -0
- data/docs/catch_up_subscriptions.md +253 -0
- data/docs/configuration.md +83 -0
- data/docs/deleting_streams.md +25 -0
- data/docs/encrypting_events.md +84 -0
- data/docs/linking_events.md +149 -0
- data/docs/reading_events.md +200 -0
- data/lib/event_store_client/adapters/grpc/client.rb +244 -105
- data/lib/event_store_client/adapters/grpc/cluster/gossip_discover.rb +131 -0
- data/lib/event_store_client/adapters/grpc/cluster/insecure_connection.rb +21 -0
- data/lib/event_store_client/adapters/grpc/cluster/member.rb +18 -0
- data/lib/event_store_client/adapters/grpc/cluster/queryless_discover.rb +25 -0
- data/lib/event_store_client/adapters/grpc/cluster/secure_connection.rb +71 -0
- data/lib/event_store_client/adapters/grpc/command_registrar.rb +7 -7
- data/lib/event_store_client/adapters/grpc/commands/command.rb +63 -25
- data/lib/event_store_client/adapters/grpc/commands/gossip/cluster_info.rb +24 -0
- data/lib/event_store_client/adapters/grpc/commands/streams/append.rb +43 -68
- data/lib/event_store_client/adapters/grpc/commands/streams/append_multiple.rb +44 -0
- data/lib/event_store_client/adapters/grpc/commands/streams/delete.rb +21 -17
- data/lib/event_store_client/adapters/grpc/commands/streams/hard_delete.rb +39 -0
- data/lib/event_store_client/adapters/grpc/commands/streams/link_to.rb +7 -52
- data/lib/event_store_client/adapters/grpc/commands/streams/link_to_multiple.rb +44 -0
- data/lib/event_store_client/adapters/grpc/commands/streams/read.rb +20 -85
- data/lib/event_store_client/adapters/grpc/commands/streams/read_paginated.rb +174 -0
- data/lib/event_store_client/adapters/grpc/commands/streams/subscribe.rb +31 -106
- data/lib/event_store_client/adapters/grpc/connection.rb +56 -36
- data/lib/event_store_client/adapters/grpc/discover.rb +75 -0
- data/lib/event_store_client/adapters/grpc/generated/cluster_pb.rb +106 -18
- data/lib/event_store_client/adapters/grpc/generated/cluster_services_pb.rb +12 -12
- data/lib/event_store_client/adapters/grpc/generated/code_pb.rb +34 -0
- data/lib/event_store_client/adapters/grpc/generated/gossip_pb.rb +3 -2
- data/lib/event_store_client/adapters/grpc/generated/gossip_services_pb.rb +3 -3
- data/lib/event_store_client/adapters/grpc/generated/monitoring_pb.rb +25 -0
- data/lib/event_store_client/adapters/grpc/generated/monitoring_services_pb.rb +26 -0
- data/lib/event_store_client/adapters/grpc/generated/operations_pb.rb +2 -1
- data/lib/event_store_client/adapters/grpc/generated/operations_services_pb.rb +8 -7
- data/lib/event_store_client/adapters/grpc/generated/persistent_pb.rb +199 -38
- data/lib/event_store_client/adapters/grpc/generated/persistent_services_pb.rb +7 -3
- data/lib/event_store_client/adapters/grpc/generated/projections_pb.rb +9 -26
- data/lib/event_store_client/adapters/grpc/generated/projections_services_pb.rb +4 -3
- data/lib/event_store_client/adapters/grpc/generated/serverfeatures_pb.rb +29 -0
- data/lib/event_store_client/adapters/grpc/generated/serverfeatures_services_pb.rb +26 -0
- data/lib/event_store_client/adapters/grpc/generated/shared_pb.rb +54 -12
- data/lib/event_store_client/adapters/grpc/generated/status_pb.rb +23 -0
- data/lib/event_store_client/adapters/grpc/generated/streams_pb.rb +104 -64
- data/lib/event_store_client/adapters/grpc/generated/streams_services_pb.rb +3 -2
- data/lib/event_store_client/adapters/grpc/generated/users_services_pb.rb +2 -2
- data/lib/event_store_client/adapters/grpc/options/streams/read_options.rb +78 -0
- data/lib/event_store_client/adapters/grpc/options/streams/write_options.rb +39 -0
- data/lib/event_store_client/adapters/grpc/shared/event_deserializer.rb +52 -0
- data/lib/event_store_client/adapters/grpc/shared/options/filter_options.rb +76 -0
- data/lib/event_store_client/adapters/grpc/shared/options/stream_options.rb +91 -0
- data/lib/event_store_client/adapters/grpc/shared/streams/process_response.rb +28 -0
- data/lib/event_store_client/adapters/grpc/shared/streams/process_responses.rb +33 -0
- data/lib/event_store_client/adapters/grpc.rb +28 -12
- data/lib/event_store_client/configuration.rb +39 -54
- data/lib/event_store_client/connection/url.rb +57 -0
- data/lib/event_store_client/connection/url_parser.rb +144 -0
- data/lib/event_store_client/data_decryptor.rb +2 -9
- data/lib/event_store_client/deserialized_event.rb +35 -10
- data/lib/event_store_client/encryption_metadata.rb +0 -1
- data/lib/event_store_client/event.rb +4 -2
- data/lib/event_store_client/extensions/options_extension.rb +87 -0
- data/lib/event_store_client/mapper/default.rb +12 -9
- data/lib/event_store_client/mapper/encrypted.rb +18 -17
- data/lib/event_store_client/types.rb +1 -1
- data/lib/event_store_client/utils.rb +30 -0
- data/lib/event_store_client/version.rb +1 -1
- data/lib/event_store_client.rb +8 -7
- metadata +74 -83
- data/lib/event_store_client/adapters/grpc/Protos/cluster.proto +0 -149
- data/lib/event_store_client/adapters/grpc/Protos/gossip.proto +0 -44
- data/lib/event_store_client/adapters/grpc/Protos/operations.proto +0 -45
- data/lib/event_store_client/adapters/grpc/Protos/persistent.proto +0 -180
- data/lib/event_store_client/adapters/grpc/Protos/projections.proto +0 -174
- data/lib/event_store_client/adapters/grpc/Protos/shared.proto +0 -22
- data/lib/event_store_client/adapters/grpc/Protos/streams.proto +0 -242
- data/lib/event_store_client/adapters/grpc/Protos/users.proto +0 -119
- data/lib/event_store_client/adapters/grpc/README.md +0 -16
- data/lib/event_store_client/adapters/grpc/commands/persistent_subscriptions/create.rb +0 -46
- data/lib/event_store_client/adapters/grpc/commands/persistent_subscriptions/delete.rb +0 -34
- data/lib/event_store_client/adapters/grpc/commands/persistent_subscriptions/read.rb +0 -77
- data/lib/event_store_client/adapters/grpc/commands/persistent_subscriptions/settings_schema.rb +0 -38
- data/lib/event_store_client/adapters/grpc/commands/persistent_subscriptions/update.rb +0 -48
- data/lib/event_store_client/adapters/grpc/commands/projections/create.rb +0 -48
- data/lib/event_store_client/adapters/grpc/commands/projections/delete.rb +0 -34
- data/lib/event_store_client/adapters/grpc/commands/projections/update.rb +0 -44
- data/lib/event_store_client/adapters/grpc/commands/streams/read_all.rb +0 -43
- data/lib/event_store_client/adapters/grpc/commands/streams/tombstone.rb +0 -35
- data/lib/event_store_client/adapters/http/README.md +0 -16
- data/lib/event_store_client/adapters/http/client.rb +0 -161
- data/lib/event_store_client/adapters/http/commands/command.rb +0 -27
- data/lib/event_store_client/adapters/http/commands/persistent_subscriptions/ack.rb +0 -15
- data/lib/event_store_client/adapters/http/commands/persistent_subscriptions/create.rb +0 -35
- data/lib/event_store_client/adapters/http/commands/persistent_subscriptions/read.rb +0 -60
- data/lib/event_store_client/adapters/http/commands/projections/create.rb +0 -33
- data/lib/event_store_client/adapters/http/commands/projections/update.rb +0 -31
- data/lib/event_store_client/adapters/http/commands/streams/append.rb +0 -49
- data/lib/event_store_client/adapters/http/commands/streams/delete.rb +0 -16
- data/lib/event_store_client/adapters/http/commands/streams/link_to.rb +0 -49
- data/lib/event_store_client/adapters/http/commands/streams/read.rb +0 -52
- data/lib/event_store_client/adapters/http/commands/streams/tombstone.rb +0 -17
- data/lib/event_store_client/adapters/http/connection.rb +0 -46
- data/lib/event_store_client/adapters/http/request_method.rb +0 -28
- data/lib/event_store_client/adapters/http.rb +0 -17
- data/lib/event_store_client/adapters/in_memory.rb +0 -144
- data/lib/event_store_client/broker.rb +0 -40
- data/lib/event_store_client/catch_up_subscription.rb +0 -42
- data/lib/event_store_client/catch_up_subscriptions.rb +0 -92
- data/lib/event_store_client/client.rb +0 -73
- data/lib/event_store_client/error_handler.rb +0 -10
- data/lib/event_store_client/subscription.rb +0 -23
- data/lib/event_store_client/subscriptions.rb +0 -38
- data/lib/event_store_client/value_objects/read_direction.rb +0 -43
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 171c9a5524c70a76dacc682fafd219139c4e845c997b59aa3dd26ea63851bd84
|
4
|
+
data.tar.gz: ff53343140523967227696e5cdca580ce7dd8d1a3ab750ac88438d7bb79808b7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 16d7f5ac50fe26de21c289ea236543f96636b5369551724b4284f4f8b3b88de9cf3ab2c5c15df49796ebec7b0129e9c361102d1b6a735c8cafd7c18ed9a4ef7a
|
7
|
+
data.tar.gz: fda744bf5c8c5bf0c03377e02e303fefe53bfea4ce0abb41923507bc515d9c6d45f0a7966386268a9ac70d58d430b98c5a7f90fb4729f4788f629f16f3719ea3
|
data/README.md
CHANGED
@@ -3,19 +3,20 @@
|
|
3
3
|
|
4
4
|
# EventStoreClient
|
5
5
|
|
6
|
-
An easy-to use API client for connecting ruby applications with [EventStoreDB](https://eventstore.com/)
|
6
|
+
An easy-to use GRPC API client for connecting ruby applications with [EventStoreDB](https://eventstore.com/).
|
7
7
|
|
8
|
-
##
|
8
|
+
## Requirements
|
9
9
|
|
10
|
-
|
11
|
-
|
12
|
-
-
|
10
|
+
`event_store_client` gem requires:
|
11
|
+
|
12
|
+
- ruby 2.7 or newer.
|
13
|
+
- EventstoreDB version `>= "20.*"`.
|
13
14
|
|
14
15
|
## Installation
|
15
16
|
Add this line to your application's Gemfile:
|
16
17
|
|
17
18
|
```ruby
|
18
|
-
gem 'event_store_client'
|
19
|
+
gem 'event_store_client'
|
19
20
|
```
|
20
21
|
|
21
22
|
And then execute:
|
@@ -33,159 +34,43 @@ $ gem install event_store_client
|
|
33
34
|
Before you start, make sure you are connecting to a running EventStoreDB instance. For a detailed guide see:
|
34
35
|
[EventStoreServerSetup](https://github.com/yousty/event_store_client/blob/master/docs/eventstore_server_setup.md)
|
35
36
|
|
36
|
-
|
37
|
-
|
38
|
-
To test out the behavior, you'll need a sample event and event handler to work with:
|
39
|
-
|
40
|
-
```ruby
|
41
|
-
|
42
|
-
require 'securerandom'
|
43
|
-
|
44
|
-
class SomethingHappened < EventStoreClient::DeserializedEvent
|
45
|
-
def schema
|
46
|
-
Dry::Schema.Params do
|
47
|
-
required(:user_id).value(:string)
|
48
|
-
required(:title).value(:string)
|
49
|
-
end
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
event = SomethingHappened.new(
|
54
|
-
data: { user_id: SecureRandom.uuid, title: "Something happened" },
|
55
|
-
)
|
56
|
-
```
|
57
|
-
|
58
|
-
Now create a handler. It can be anything, which responds to a `call` method
|
59
|
-
with an event being passed as an argument.
|
60
|
-
|
61
|
-
```ruby
|
62
|
-
class DummyHandler
|
63
|
-
def call(event)
|
64
|
-
puts "Handled #{event.class.name}"
|
65
|
-
end
|
66
|
-
end
|
67
|
-
```
|
68
|
-
|
69
|
-
|
70
|
-
```ruby
|
71
|
-
|
72
|
-
require 'event_store_client'
|
73
|
-
require "event_store_client/adapters/grpc"
|
74
|
-
|
75
|
-
EventStoreClient.configure do |config|
|
76
|
-
config.eventstore_url = ENV['EVENTSTORE_URL']
|
77
|
-
config.eventstore_user = ENV['EVENTSTORE_USER']
|
78
|
-
config.eventstore_password = ENV['EVENTSTORE_PASSWORD']
|
79
|
-
config.verify_ssl = false # remove this line if your server does have the host verified
|
80
|
-
end
|
81
|
-
|
82
|
-
event_store = EventStoreClient::Client.new
|
83
|
-
|
84
|
-
event_store.subscribe(
|
85
|
-
DummyHandler.new,
|
86
|
-
to: [SomethingHappened]
|
87
|
-
)
|
88
|
-
|
89
|
-
event_store.listen
|
90
|
-
```
|
91
|
-
|
92
|
-
## Features
|
93
|
-
|
94
|
-
## Basic Usage
|
95
|
-
|
96
|
-
The main interface allows for actions listed below which is enough for basic useage.
|
97
|
-
The actual adapter allows for more actions. Contributions as always welcome!
|
98
|
-
|
99
|
-
```ruby
|
100
|
-
# Publishing to a stream
|
101
|
-
event_store.publish(stream: 'newstream', events: [event])
|
102
|
-
|
103
|
-
# Reading from a stream
|
104
|
-
events = event_store.read('newstream').value!
|
37
|
+
See documentation chapters for the usage reference:
|
105
38
|
|
106
|
-
|
107
|
-
|
39
|
+
- [Configuration](docs/configuration.md)
|
40
|
+
- [Appending events](docs/appending_events.md)
|
41
|
+
- [Reading events](docs/reading_events.md)
|
42
|
+
- [Catch-up subscriptions](docs/catch_up_subscriptions.md)
|
43
|
+
- [Linking events](docs/linking_events.md)
|
44
|
+
- [Deleting streams](docs/deleting_streams.md)
|
45
|
+
- [Encrypting events](docs/encrypting_events.md)
|
108
46
|
|
109
|
-
|
110
|
-
event_store.link_to(stream_name, events)
|
111
|
-
|
112
|
-
# Subscribing to events
|
113
|
-
event_store.subscribe(DummyHandler.new, to: [SomethingHappened])
|
47
|
+
## Contributing
|
114
48
|
|
115
|
-
|
116
|
-
event_store.listen
|
49
|
+
Do you want to contribute? Welcome!
|
117
50
|
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
# .... wait a little bit ... Your handler should be called for every single event you publish
|
122
|
-
```
|
51
|
+
1. Fork repository
|
52
|
+
2. Create Issue
|
53
|
+
3. Create PR ;)
|
123
54
|
|
124
|
-
###
|
55
|
+
### Re-generating GRPC files from Proto
|
125
56
|
|
126
|
-
|
57
|
+
If you need to re-generate GRPC files from [Proto](https://github.com/EventStore/EventStore/tree/master/src/Protos/Grpc) files - there is a tool to do it. Just run this command:
|
127
58
|
|
59
|
+
```shell
|
60
|
+
bin/rebuild_protos
|
128
61
|
```
|
129
|
-
event_store.connection.delete_stream(stream)
|
130
|
-
event_store.connection.tombstone_stream(stream)
|
131
|
-
```
|
132
|
-
|
133
|
-
See the adapters method list for the possible usage.
|
134
62
|
|
135
|
-
|
136
|
-
- [GRPC](https://github.com/yousty/event_store_client/blob/master/lib/event_store_client/adapters/grpc/client.rb)
|
63
|
+
### Running tests and development console
|
137
64
|
|
138
|
-
|
65
|
+
You will have to install Docker first. It is needed to run EventStore DB. You can run EventStore DB with this command:
|
139
66
|
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
```ruby
|
144
|
-
EventStoreClient.configure do |config|
|
145
|
-
config.adapter = :grpc
|
146
|
-
end
|
67
|
+
```shell
|
68
|
+
docker-compose -f docker-compose-cluster.yml up
|
147
69
|
```
|
148
70
|
|
149
|
-
|
150
|
-
|:-------------:|:-------------:|:-----:|:-------------:|
|
151
|
-
| adapter | `:grpc`, `:http` or `:in_memory` | `:grpc` | different ways to connect with an event_store_db. The in_memory is a mock server useful for testing |
|
152
|
-
| verify_ssl | Boolean | true | Useful for self-signed certificates (Kubernetes, local development) |
|
153
|
-
| error_handler | Any callable ruby object | EvenStoreClient::ErrorHandler | You can pass a custom error handler for reacting on event_handler errors.|
|
154
|
-
| eventstore_url| String| 'http://localhost:2113'| An url for the server instance|
|
155
|
-
| user| String| 'admin' | a user used to connect the application with the server|
|
156
|
-
| password| String| 'changeit'| a password used to connect the application with the server|
|
157
|
-
| per_page| Integer| 20 | a batch size for events subscriptions |
|
158
|
-
| service_name| String| 'default' | a prefix (namespace) added to the subscriptions names|
|
159
|
-
| mapper| `Mapper::Default` or `Mapper::Encrypted`| `Mapper::Default.new` | an engine used to parse events.
|
160
|
-
|
161
|
-
## Event Mappers
|
162
|
-
|
163
|
-
At the moment we offer two types of mappers:
|
164
|
-
|
165
|
-
- default
|
166
|
-
- encrypted
|
167
|
-
|
168
|
-
### Default Mapper
|
169
|
-
|
170
|
-
This is used out of the box. It just translates the EventClass defined in your application to
|
171
|
-
Event parsable by event_store and the other way around.
|
172
|
-
|
173
|
-
### Encrypted Mapper
|
174
|
-
|
175
|
-
This is implemented to match GDPR requirements. It allows you to encrypt any event using your
|
176
|
-
encryption_key repository. For the detailed guide see the: [Encrypting Events](https://github.com/yousty/event_store_client/blob/master/docs/encrypting_events.md).
|
177
|
-
|
178
|
-
## Contributing
|
179
|
-
|
180
|
-
Do you want to contribute? Welcome!
|
181
|
-
|
182
|
-
1. Fork repository
|
183
|
-
2. Create Issue
|
184
|
-
3. Create PR ;)
|
185
|
-
|
186
|
-
For running the client in the dev mode, see: [Development Guide](https://github.com/yousty/event_store_client/blob/master/docs/eventstore_server_setup.md)
|
71
|
+
Now you can enter the dev console by running `bin/console` or run tests by running `rspec` command.
|
187
72
|
|
188
|
-
### Publishing new version
|
73
|
+
### Publishing a new version
|
189
74
|
|
190
75
|
1. Push commit with updated `version.rb` file to the `release` branch. The new version will be automatically pushed to [rubygems](https://rubygems.org).
|
191
76
|
2. Create release on github including change log.
|
@@ -0,0 +1,155 @@
|
|
1
|
+
# Append your first event
|
2
|
+
|
3
|
+
## Append your first event
|
4
|
+
|
5
|
+
The simplest way to append an event to EventStoreDB is to create an `EventStoreClient::DeserializedEvent` object and call the `#append_to_stream` method.
|
6
|
+
|
7
|
+
```ruby
|
8
|
+
class SomethingHappened < EventStoreClient::DeserializedEvent
|
9
|
+
end
|
10
|
+
|
11
|
+
event = SomethingHappened.new(
|
12
|
+
id: SecureRandom.uuid, type: 'some-event', data: { user_id: SecureRandom.uuid, title: "Something happened" }
|
13
|
+
)
|
14
|
+
|
15
|
+
result = EventStoreClient.client.append_to_stream('some-stream', event)
|
16
|
+
# if event was appended successfully
|
17
|
+
if result.success?
|
18
|
+
# result.success => <EventStore::Client::Streams::AppendResp>
|
19
|
+
else # event was not appended, result.failure? => true
|
20
|
+
end
|
21
|
+
```
|
22
|
+
|
23
|
+
## Appending multiple events
|
24
|
+
|
25
|
+
You can pass an array of events to the `#append_to_stream` method. This way events will be appended one-by-one. On each iteration `revision` will be incremented by 1. In case if any of requests fails - all further append requests will be canceled.
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
class SomethingHappened < EventStoreClient::DeserializedEvent
|
29
|
+
end
|
30
|
+
|
31
|
+
event1 = SomethingHappened.new(
|
32
|
+
id: SecureRandom.uuid, type: 'some-event', data: { user_id: SecureRandom.uuid, title: "Something happened 1" }
|
33
|
+
)
|
34
|
+
event2 = SomethingHappened.new(
|
35
|
+
id: SecureRandom.uuid, type: 'some-event', data: { user_id: SecureRandom.uuid, title: "Something happened 2" }
|
36
|
+
)
|
37
|
+
|
38
|
+
results = EventStoreClient.client.append_to_stream('some-stream', [event1, event2])
|
39
|
+
results.each do |result|
|
40
|
+
# Event was appended successfully
|
41
|
+
if result.success?
|
42
|
+
# result.success => <EventStore::Client::Streams::AppendResp>
|
43
|
+
else # event was not appended, result.failure? => true
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
```
|
48
|
+
|
49
|
+
## Working with EventStoreClient::DeserializedEvent
|
50
|
+
|
51
|
+
When appending events to EventStoreDB they must first all be wrapped in an `EventStoreClient::DeserializedEvent` object. This allows you to specify the content of the event and the type of event.
|
52
|
+
|
53
|
+
A sample of creating an event:
|
54
|
+
|
55
|
+
```ruby
|
56
|
+
EventStoreClient::DeserializedEvent.new(
|
57
|
+
# ID of event. Optional. If omitted, its value is generated using `SecureRandom.uuid`
|
58
|
+
id: SecureRandom.uuid,
|
59
|
+
# Event name. Optional. If omitted, its value will be generated using `self.class.to_s`
|
60
|
+
type: 'some-event-name',
|
61
|
+
# Event data. Optional. Will default to `{}` (empty hash) if omitted
|
62
|
+
data: { foo: :bar },
|
63
|
+
# Optional. Defaults to `{ 'type' => event_name_you_provided, 'content-type' => 'application/json' }`
|
64
|
+
metadata: {}
|
65
|
+
)
|
66
|
+
```
|
67
|
+
|
68
|
+
### Duplicated event id
|
69
|
+
|
70
|
+
If two events with the same id are appended to the same stream in quick succession EventStoreDB will only append one copy of the event to the stream.
|
71
|
+
|
72
|
+
```ruby
|
73
|
+
class SomethingHappened < EventStoreClient::DeserializedEvent
|
74
|
+
end
|
75
|
+
|
76
|
+
event = SomethingHappened.new(
|
77
|
+
id: SecureRandom.uuid, type: 'some-event', data: {},
|
78
|
+
)
|
79
|
+
EventStoreClient.client.append_to_stream('some-stream', event)
|
80
|
+
# Attempt to append the same event again. Will return the same result as for previous call
|
81
|
+
EventStoreClient.client.append_to_stream('some-stream', event)
|
82
|
+
```
|
83
|
+
|
84
|
+
|
85
|
+
## Handling concurrency
|
86
|
+
|
87
|
+
When appending events to a stream you can supply a stream state or stream revision. Your client can use this to tell EventStoreDB what state or version you expect the stream to be in when you append. If the stream isn't in that state then an exception will be thrown.
|
88
|
+
|
89
|
+
For example if we try to and append two records expecting both times that the stream doesn't exist we will get an exception on the second:
|
90
|
+
|
91
|
+
```ruby
|
92
|
+
class SomethingHappened < EventStoreClient::DeserializedEvent
|
93
|
+
end
|
94
|
+
|
95
|
+
event1 = SomethingHappened.new(
|
96
|
+
type: 'some-event', data: {},
|
97
|
+
)
|
98
|
+
|
99
|
+
event2 = SomethingHappened.new(
|
100
|
+
type: 'some-event', data: {},
|
101
|
+
)
|
102
|
+
|
103
|
+
stream_name = "some-stream$#{SecureRandom.uuid}"
|
104
|
+
|
105
|
+
EventStoreClient.client.append_to_stream(stream_name, event1, options: { expected_revision: :no_stream })
|
106
|
+
|
107
|
+
EventStoreClient.client.append_to_stream(stream_name, event2, options: { expected_revision: :no_stream })
|
108
|
+
```
|
109
|
+
|
110
|
+
There are three available stream states:
|
111
|
+
|
112
|
+
- `:any`
|
113
|
+
- `:no_stream`
|
114
|
+
- `:stream_exists`
|
115
|
+
|
116
|
+
This check can be used to implement optimistic concurrency. When you retrieve a stream from EventStoreDB, you take note of the current version number, then when you save it back you can determine if somebody else has modified the record in the meantime.
|
117
|
+
|
118
|
+
```ruby
|
119
|
+
class SomethingHappened < EventStoreClient::DeserializedEvent
|
120
|
+
end
|
121
|
+
|
122
|
+
stream_name = "some-stream$#{SecureRandom.uuid}"
|
123
|
+
event1 = SomethingHappened.new(
|
124
|
+
type: 'some-event', data: {}
|
125
|
+
)
|
126
|
+
event2 = SomethingHappened.new(
|
127
|
+
type: 'some-event', data: {}
|
128
|
+
)
|
129
|
+
event3 = SomethingHappened.new(
|
130
|
+
type: 'some-event', data: {}
|
131
|
+
)
|
132
|
+
# Pre-populate stream with some event
|
133
|
+
EventStoreClient.client.append_to_stream(stream_name, event1)
|
134
|
+
# Get the revision number of latest event
|
135
|
+
revision = EventStoreClient.client.read(stream_name).success.last.stream_revision
|
136
|
+
# Expected revision matches => will succeed
|
137
|
+
EventStoreClient.client.append_to_stream(stream_name, event2, options: { expected_revision: revision })
|
138
|
+
# Will fail with revisions mismatch error
|
139
|
+
EventStoreClient.client.append_to_stream(stream_name, event2, options: { expected_revision: revision })
|
140
|
+
```
|
141
|
+
|
142
|
+
## User credentials
|
143
|
+
|
144
|
+
You can provide user credentials to be used to append the data as follows. This will override the default credentials set on the connection.
|
145
|
+
|
146
|
+
```ruby
|
147
|
+
class SomethingHappened < EventStoreClient::DeserializedEvent
|
148
|
+
end
|
149
|
+
|
150
|
+
event = SomethingHappened.new(
|
151
|
+
id: SecureRandom.uuid, type: 'some-event', data: { user_id: SecureRandom.uuid, title: "Something happened" }
|
152
|
+
)
|
153
|
+
|
154
|
+
EventStoreClient.client.append_to_stream('some-stream', event, credentials: { username: 'admin', password: 'changeit' })
|
155
|
+
```
|
@@ -0,0 +1,253 @@
|
|
1
|
+
# Catch-up subscriptions
|
2
|
+
|
3
|
+
Subscriptions allow you to subscribe to a stream and receive notifications about new events added to the stream.
|
4
|
+
|
5
|
+
You provide an event handler and an optional starting point to the subscription. The handler is called for each event from the starting point onward.
|
6
|
+
|
7
|
+
If events already exist, the handler will be called for each event one by one until it reaches the end of the stream. From there, the server will notify the handler whenever a new event appears.
|
8
|
+
|
9
|
+
## Subscribing from the start
|
10
|
+
|
11
|
+
When you need to process all the events in the store, including historical events, you need to subscribe from the beginning. You can either subscribe to receive events from a single stream, or subscribe to `$all` if you need to process all events in the database.
|
12
|
+
|
13
|
+
### Subscribing to a stream
|
14
|
+
|
15
|
+
The most simple stream subscription looks like the following:
|
16
|
+
|
17
|
+
```ruby
|
18
|
+
handler = proc do |result|
|
19
|
+
if result.success?
|
20
|
+
event = result.success # retrieve a result
|
21
|
+
# ... do something with event
|
22
|
+
else # result.failure?
|
23
|
+
puts result.failure # prints error
|
24
|
+
end
|
25
|
+
end
|
26
|
+
EventStoreClient.client.subscribe_to_stream('some-stream', handler: handler)
|
27
|
+
```
|
28
|
+
|
29
|
+
The provided handler will be called for every event in the stream. You may provide **any** object that responds to `#call`:
|
30
|
+
|
31
|
+
```ruby
|
32
|
+
class SomeStreamHandler
|
33
|
+
def call(result)
|
34
|
+
if result.success?
|
35
|
+
event = result.success # retrieve a result
|
36
|
+
# ... do something with event
|
37
|
+
else # result.failure?
|
38
|
+
puts result.failure # prints error
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
EventStoreClient.client.subscribe_to_stream('some-stream', handler: SomeStreamHandler.new)
|
43
|
+
```
|
44
|
+
|
45
|
+
### Subscribing to $all
|
46
|
+
|
47
|
+
Subscribing to `$all` is much the same as subscribing to a single stream. The handler will be called for every event appended after the starting position.
|
48
|
+
|
49
|
+
```ruby
|
50
|
+
handler = proc do |result|
|
51
|
+
if result.success?
|
52
|
+
event = result.success # retrieve a result
|
53
|
+
# ... do something with event
|
54
|
+
else # result.failure?
|
55
|
+
puts result.failure # prints error
|
56
|
+
end
|
57
|
+
end
|
58
|
+
EventStoreClient.client.subscribe_to_all(handler: handler)
|
59
|
+
```
|
60
|
+
|
61
|
+
## Subscribing from a specific position
|
62
|
+
|
63
|
+
The previous examples will subscribe to the stream from the beginning. This will end up calling the handler for every event in the stream and then wait for new events after that.
|
64
|
+
|
65
|
+
Both the stream and $all subscriptions accept a starting position if you want to read from a specific point onward. If events already exist at the position you subscribe to, they will be read on the server side and sent to the subscription.
|
66
|
+
|
67
|
+
Once caught up, the sever will push any new events received on the streams to the client. There is no difference between catching up and live on the client side.
|
68
|
+
|
69
|
+
### Subscribing to a stream
|
70
|
+
|
71
|
+
To subscribe to a stream from a specific position, you need to provide a _stream position_. This can be `:start`, `:end` or an integer position.
|
72
|
+
|
73
|
+
The following subscribes to the stream `some-stream` at position `20`, this means that events `21` and onward will be handled:
|
74
|
+
|
75
|
+
```ruby
|
76
|
+
EventStoreClient.client.subscribe_to_stream('some-stream', handler: proc { |res| }, options: { from_revision: 20 })
|
77
|
+
```
|
78
|
+
|
79
|
+
### Subscribing to $all
|
80
|
+
|
81
|
+
Subscribing to the `$all` stream is much like subscribing to a regular stream. The only difference is how you need to specify the stream position. For the `$all` stream, you have to provide the `:from_position` hash, which consists of two integers, `:commit_position` and `:prepare_position`. The `:from_position` value can accept `:start` and `:end` values as well.
|
82
|
+
|
83
|
+
The following `$all` subscription will subscribe from the event after the one at commit position `1056` and prepare position `1056`:
|
84
|
+
|
85
|
+
```ruby
|
86
|
+
EventStoreClient.client.subscribe_to_all(handler: proc { |res| }, options: { from_position: { commit_position: 1056, prepare_position: 1056 } })
|
87
|
+
```
|
88
|
+
|
89
|
+
Please note that the given position needs to be a legitimate position in the `$all` stream.
|
90
|
+
|
91
|
+
## Subscribing to a stream for live updates
|
92
|
+
|
93
|
+
You can subscribe to a stream to get live updates by subscribing to the end of the stream:
|
94
|
+
|
95
|
+
```ruby
|
96
|
+
EventStoreClient.client.subscribe_to_stream('some-stream', handler: proc { |res| }, options: { from_revision: :end })
|
97
|
+
```
|
98
|
+
|
99
|
+
And the same works with `$all` :
|
100
|
+
|
101
|
+
```ruby
|
102
|
+
EventStoreClient.client.subscribe_to_all(handler: proc { |res| }, options: { from_position: :end })
|
103
|
+
```
|
104
|
+
|
105
|
+
This won't read through the history of the stream, but will rather notify the handler when a new event appears in the respective stream.
|
106
|
+
|
107
|
+
Keep in mind that when you subscribe to a stream from a certain position - you will also get live updates after your subscription catches up (processes all the historical events).
|
108
|
+
|
109
|
+
## Resolving link-to's
|
110
|
+
|
111
|
+
Link-to events point to events in other streams in EventStoreDB. These are generally created by projections such as the `$by_event_type` projection which links events of the same event type into the same stream. This makes it easier to look up all events of a certain type.
|
112
|
+
|
113
|
+
When reading a stream you can specify whether to resolve link-to's or not. By default, link-to events are not resolved. You can change this behaviour by setting the `resolve_link_tos` option to `true`:
|
114
|
+
|
115
|
+
```ruby
|
116
|
+
EventStoreClient.client.subscribe_to_stream('$et-myEventType', handler: proc { |res| }, options: { resolve_link_tos: true })
|
117
|
+
```
|
118
|
+
|
119
|
+
## Handling subscription drops
|
120
|
+
|
121
|
+
An application, which hosts the subscription, can go offline for a period of time for different reasons. It could be a crash, infrastructure failure, or a new version deployment. As you rarely would want to reprocess all the events again, you'd need to store the current position of the subscription somewhere, and then use it to restore the subscription from the point where it dropped off:
|
122
|
+
|
123
|
+
```ruby
|
124
|
+
checkpoint = :start
|
125
|
+
handler = proc do |result|
|
126
|
+
if result.success?
|
127
|
+
event = result.success
|
128
|
+
handle_event(event)
|
129
|
+
checkpoint = event.stream_revision
|
130
|
+
else
|
131
|
+
# do something in case of error
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
EventStoreClient.client.subscribe_to_stream('some-stream', handler: handler, options: { from_revision: checkpoint })
|
136
|
+
```
|
137
|
+
|
138
|
+
When subscribed to `$all` you want to keep the position of the event in the `$all` stream. As mentioned previously, the `$all` stream position consists of two integers (prepare and commit positions), not one:
|
139
|
+
|
140
|
+
```ruby
|
141
|
+
checkpoint = :start
|
142
|
+
handler = proc do |result|
|
143
|
+
if result.success?
|
144
|
+
event = result.success
|
145
|
+
handle_event(event)
|
146
|
+
checkpoint = { prepare_position: event.prepare_position, commit_position: event.commit_position }
|
147
|
+
else
|
148
|
+
# do something in case of error
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
EventStoreClient.client.subscribe_to_all(handler: handler, options: { from_position: checkpoint })
|
153
|
+
```
|
154
|
+
|
155
|
+
### Checkpoints and other responses
|
156
|
+
|
157
|
+
By default `event_store_client` will skip such EventStore DB responses as checkpoints, confirmations, etc. If you would like to handle them in the subscription handler, you can provide the`skip_deserialization` keyword argument, and then handle deserialization by yourself:
|
158
|
+
|
159
|
+
```ruby
|
160
|
+
checkpoint = :start
|
161
|
+
handler = proc do |result|
|
162
|
+
if result.success?
|
163
|
+
response = result.success
|
164
|
+
if response.checkpoint
|
165
|
+
handle_checkpoint(response.checkpoint)
|
166
|
+
else
|
167
|
+
result = EventStoreClient::GRPC::Shared::Streams::ProcessResponse.new.call(
|
168
|
+
response,
|
169
|
+
false,
|
170
|
+
false
|
171
|
+
)
|
172
|
+
if result&.success?
|
173
|
+
event = result.success
|
174
|
+
handle_event(event)
|
175
|
+
end
|
176
|
+
end
|
177
|
+
else
|
178
|
+
# do something in case of error
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
EventStoreClient.client.subscribe_to_all(handler: handler, options: { from_position: checkpoint }, skip_deserialization: true)
|
183
|
+
```
|
184
|
+
|
185
|
+
## User credentials
|
186
|
+
|
187
|
+
The user creating a subscription must have read access to the stream it's subscribing to, and only admin users may subscribe to `$all` or create filtered subscriptions.
|
188
|
+
|
189
|
+
The code below shows how you can provide user credentials for a subscription. When you specify subscription credentials explicitly, it will override the default credentials set for the client. If you don't specify any credentials, the client will use the credentials specified for the client, if you specified those.
|
190
|
+
|
191
|
+
```ruby
|
192
|
+
EventStoreClient.client.subscribe_to_stream('some-stream', handler: proc { |res| }, credentials: { username: 'admin', password: 'changeit' })
|
193
|
+
```
|
194
|
+
|
195
|
+
## Server-side filtering
|
196
|
+
|
197
|
+
EventStoreDB allows you to filter the events whilst you subscribe to the `$all` stream so that you only receive the events that you care about.
|
198
|
+
|
199
|
+
You can filter by event type or stream name using either a regular expression or a prefix. Server-side filtering is currently only available on the `$all` stream.
|
200
|
+
|
201
|
+
A simple stream prefix filter looks like this:
|
202
|
+
|
203
|
+
```ruby
|
204
|
+
EventStoreClient.client.subscribe_to_all(handler: proc { |res| }, options: { filter: { stream_identifier: { prefix: ['test-', 'other-'] } } })
|
205
|
+
```
|
206
|
+
|
207
|
+
### Filtering out system events
|
208
|
+
|
209
|
+
There are a number of events in EventStoreDB called system events. These are prefixed with a `$` and under most circumstances you won't care about these. They can be filtered out by event type.
|
210
|
+
|
211
|
+
```ruby
|
212
|
+
EventStoreClient.client.subscribe_to_all(handler: proc { |res| }, options: { filter: { event_type: { regex: /^[^\$].*/.to_s } } })
|
213
|
+
```
|
214
|
+
|
215
|
+
### Filtering by event type
|
216
|
+
|
217
|
+
If you only want to subscribe to events of a given type there are two options. You can either use a regular expression or a prefix.
|
218
|
+
|
219
|
+
#### Filtering by prefix
|
220
|
+
|
221
|
+
This will only subscribe to events with a type that begin with `customer-`:
|
222
|
+
|
223
|
+
```ruby
|
224
|
+
EventStoreClient.client.subscribe_to_all(handler: proc { |res| }, options: { filter: { event_type: { prefix: ['customer-'] } } })
|
225
|
+
```
|
226
|
+
|
227
|
+
#### Filtering by regular expression
|
228
|
+
|
229
|
+
If you want to subscribe to multiple event types then it might be better to provide a regular expression. This will subscribe to any event that begins with `user` or `company`:
|
230
|
+
|
231
|
+
```ruby
|
232
|
+
EventStoreClient.client.subscribe_to_all(handler: proc { |res| }, options: { filter: { event_type: { regex: '^user|^company' } } })
|
233
|
+
```
|
234
|
+
|
235
|
+
### Filtering by stream name
|
236
|
+
|
237
|
+
If you only want to subscribe to a stream with a given name there are two options. You can either use a regular expression or a prefix.
|
238
|
+
|
239
|
+
#### Filtering by prefix
|
240
|
+
|
241
|
+
This will only subscribe to all streams with a name that begins with `user-`:
|
242
|
+
|
243
|
+
```ruby
|
244
|
+
EventStoreClient.client.subscribe_to_all(handler: proc { |res| }, options: { filter: { stream_identifier: { prefix: ['user-'] } } })
|
245
|
+
```
|
246
|
+
|
247
|
+
#### Filtering by regular expression
|
248
|
+
|
249
|
+
If you want to subscribe to multiple streams then it might be better to provide a regular expression.
|
250
|
+
|
251
|
+
```ruby
|
252
|
+
EventStoreClient.client.subscribe_to_all(handler: proc { |res| }, options: { filter: { stream_identifier: { regex: '^account|^savings' } } })
|
253
|
+
```
|