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
@@ -0,0 +1,83 @@
|
|
1
|
+
# Configuration
|
2
|
+
|
3
|
+
Currently only one setup is supported. For example, you can't configure a connection to multiple clusters.
|
4
|
+
|
5
|
+
Configuration options:
|
6
|
+
|
7
|
+
| name | value | default value | description |
|
8
|
+
|----------------------|--------------------------------------------------------------------------------------|-----------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
9
|
+
| event_store_url | String | `'esdb://localhost:2113'` | Connection string. See description of possible values below. |
|
10
|
+
| per_page | Integer | `20` | Number of events to return in one response. |
|
11
|
+
| mapper | `EventStoreClient::Mapper::Default.new` or `EventStoreClient::Mapper::Encrypted.new` | `EventStoreClient::Mapper::Default.new` | An object that is responsible for serialization / deserialization and encryption / decryption of events. |
|
12
|
+
| default_event_class | `DeserializedEvent` or any class, inherited from it | `DeserializedEvent` | This class will be used during the deserialization process when deserializer fails to resolve an event's class from response. |
|
13
|
+
| logger | `Logger` | `nil` | A logger that would log messages from `event_store_client` and `grpc` gems. |
|
14
|
+
| skip_deserialization | Boolean | `false` | Whether to skip event deserialization using the given `mapper` setting. If you set it to `true` decryption will be skipped as well. It is useful when you want to defer deserialization and handle it later by yourself. |
|
15
|
+
| skip_decryption | Boolean | `false` | Whether to skip decrypting encrypted event payloads. |
|
16
|
+
|
17
|
+
## Connection string
|
18
|
+
|
19
|
+
Connection string allows you to provide connection options and a set of nodes of your cluster to connect to.
|
20
|
+
|
21
|
+
Structure:
|
22
|
+
|
23
|
+
```
|
24
|
+
protocol://[username:password@]node1[,node2,node3,...,nodeN]/?connectionOptions
|
25
|
+
```
|
26
|
+
|
27
|
+
### Protocol
|
28
|
+
|
29
|
+
There are two possible values:
|
30
|
+
|
31
|
+
- `esdb`. Currently no effect.
|
32
|
+
- `esdb+discover`. `+discover` tells the client that your cluster is setup as [DNS discovery](https://developers.eventstore.com/server/v20.10/cluster.html#cluster-with-dns). This means that the client will perform a lookup of cluster members of the first node you provided in the connection string.
|
33
|
+
|
34
|
+
Examples:
|
35
|
+
```
|
36
|
+
esdb://localhost:2113
|
37
|
+
esdb+discover://localhost:2113
|
38
|
+
```
|
39
|
+
|
40
|
+
### Credentials
|
41
|
+
|
42
|
+
You may provide a username and password to be used to connect to the EventStore DB. Only secure connections will use those credentials. Only credentials defined with first node will be used.
|
43
|
+
|
44
|
+
Examples:
|
45
|
+
|
46
|
+
```
|
47
|
+
esdb://some-admin:some-password@localhost:2113
|
48
|
+
esdb://some-admin:some-password@localhost:2113,localhost:2114
|
49
|
+
```
|
50
|
+
|
51
|
+
### Nodes
|
52
|
+
|
53
|
+
At least one node should be defined in the connection string in order for the client to work properly. You may define as much nodes as you want. However, the behavior of the client may change depending on how many nodes you provided and whether you set the `+discover` flag.
|
54
|
+
|
55
|
+
Possible behaviours:
|
56
|
+
|
57
|
+
- One node is provided. E.g. `esdb://localhost:2113`. The client assumes this setup is a standalone server - no cluster discovery will be done.
|
58
|
+
- One node and `+discover` flag is provided. E.g. `esdb+discover://localhost:2113`. The client assumes this setup is a cluster with DNS discover setup - cluster discovery will be done.
|
59
|
+
- Two or more nodes are provided. E.g. `esdb://localhost:2113,localhost:2114,localhost:2115`. The client assumes this setup is a nodes cluster setup - cluster discovery will be done. If discovery of a node fails, the next node in the list will be picked, and so forth, in round-robin manner, until max discovery attempts number is reached.
|
60
|
+
|
61
|
+
If you provide more than one node and set `+discover` flag - only the first node will be considered.
|
62
|
+
|
63
|
+
### Connection options
|
64
|
+
|
65
|
+
Connection options allows you to adjust parameters such as timeout, security, etc.
|
66
|
+
|
67
|
+
Possible options:
|
68
|
+
|
69
|
+
| name | value | default value | description |
|
70
|
+
|----------------------|-------------------------------------------------|---------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
71
|
+
| tls | Boolean | `true` | Whether to use a secure connection |
|
72
|
+
| tlsVerifyCert | Boolean | `false` | Whether to verify a certificate |
|
73
|
+
| tlsCAFile | String | `nil` | A path to a certificate file (e.g. `ca.crt`). If you set the `tls` option to `true`, but didn't provide this option, the client will try to automatically retrieve an X.509 certificate from the nodes. |
|
74
|
+
| gossipTimeout | Integer | `200` | Milliseconds. Discovery request timeout. Only useful when there are several nodes or when `+discover` flag is set. |
|
75
|
+
| discoverInterval | Integer | `100` | Milliseconds. Interval between discovery attempts. Only useful when there are several nodes or when `+discover` flag is set. |
|
76
|
+
| maxDiscoverAttempts | Integer | `10` | Max attempts before giving up to find a suitable cluster member. Only useful when there are several nodes or when the `+discover` flag is set. |
|
77
|
+
| caLookupInterval | Integer | `100` | Milliseconds. Interval between X.509 certificate lookup attempts. This option is useful when you set the`tls` option to true, but you didn't provide the `tlsCAFile` option. In this case the certificate will be retrieved using the Net::HTTP#peer_cert method. |
|
78
|
+
| caLookupAttempts | Integer | `3` | Number of attempts for X.509 certificate lookup. This option is useful when you set the `tls` option to true, but you didn't provide the `tlsCAFile` option. In this case the certificate will be retrieved using Net::HTTP#peer_cert method. |
|
79
|
+
| nodePreference | `"leader"`, `"follower"` or `"readOnlyReplica"` | `"leader"` | Set which state of cluster members is preferred. Only useful if you provided the `+discover` flag or defined several nodes in the connection string. |
|
80
|
+
| timeout | Integer, `nil` | `nil` | Milliseconds. Defines how long to wait for a response before throwing an error. This option doesn't apply to subscriptions. If set to `nil` - `event_store_client` will be waiting for a response forever (if there is a connection at all). |
|
81
|
+
| grpcRetryAttempts | Integer | `3` | Number of times to retry GRPC requests. Does not apply to discovery requests. Final number of requests in cases of error will be initial request + grpcRetryAttempts. |
|
82
|
+
| grpcRetryInterval | Integer | `100` | Milliseconds. Delay between GRPC requests. retries. |
|
83
|
+
| throwOnAppendFailure | Boolean | `true` | Defines if append requests should immediately raise an error. If set to `false`, request will be retried in case of a server error. |
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# Deleting streams
|
2
|
+
|
3
|
+
## Soft deleting streams
|
4
|
+
|
5
|
+
When you read a soft deleted stream, the read returns `:stream_not_found` or 404 result. After deleting the stream, you are able to append to it again, continuing from where it left off.
|
6
|
+
|
7
|
+
```ruby
|
8
|
+
EventStoreClient.client.delete_stream('some-stream')
|
9
|
+
```
|
10
|
+
|
11
|
+
## Hard deleting streams
|
12
|
+
|
13
|
+
A hard delete of a stream is permanent. You cannot append to the stream or recreate it. As such, you should generally soft delete streams unless you have a specific need to permanently delete the stream.
|
14
|
+
|
15
|
+
```ruby
|
16
|
+
EventStoreClient.client.hard_delete_stream('some-stream')
|
17
|
+
```
|
18
|
+
|
19
|
+
## User credentials
|
20
|
+
|
21
|
+
You can provide user credentials to be used to delete the stream as follows. This will override the default credentials set on the connection.
|
22
|
+
|
23
|
+
```ruby
|
24
|
+
EventStoreClient.client.delete_stream('some-stream', credentials: { username: 'admin', password: 'changeit' })
|
25
|
+
```
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# Encrypting events
|
2
|
+
|
3
|
+
To encrypt/decrypt events payload, you can use an encrypted mapper.
|
4
|
+
|
5
|
+
|
6
|
+
```ruby
|
7
|
+
mapper = EventStoreClient::Mapper::Encrypted.new(key_repository)
|
8
|
+
|
9
|
+
EventStoreClient.configure do |config|
|
10
|
+
config.mapper = mapper
|
11
|
+
end
|
12
|
+
```
|
13
|
+
|
14
|
+
The Encrypted mapper uses the encryption key repository to encrypt data in your events according to the event definition.
|
15
|
+
|
16
|
+
Here is the minimal repository interface for this to work.
|
17
|
+
|
18
|
+
```ruby
|
19
|
+
class DummyRepository
|
20
|
+
class Key
|
21
|
+
attr_accessor :iv, :cipher, :id
|
22
|
+
def initialize(id:, **)
|
23
|
+
@id = id
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def find(user_id)
|
28
|
+
Key.new(id: user_id)
|
29
|
+
end
|
30
|
+
|
31
|
+
def encrypt(*)
|
32
|
+
'darthvader'
|
33
|
+
end
|
34
|
+
|
35
|
+
def decrypt(*)
|
36
|
+
{ first_name: 'Anakin', last_name: 'Skywalker'}
|
37
|
+
end
|
38
|
+
end
|
39
|
+
```
|
40
|
+
|
41
|
+
Now, having that, you only need to define the event encryption schema:
|
42
|
+
|
43
|
+
```ruby
|
44
|
+
class EncryptedEvent < EventStoreClient::DeserializedEvent
|
45
|
+
def schema
|
46
|
+
Dry::Schema.Params do
|
47
|
+
required(:user_id).value(:string)
|
48
|
+
required(:first_name).value(:string)
|
49
|
+
required(:last_name).value(:string)
|
50
|
+
required(:profession).value(:string)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.encryption_schema
|
55
|
+
{
|
56
|
+
key: ->(data) { data['user_id'] },
|
57
|
+
attributes: %i[first_name last_name email]
|
58
|
+
}
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
event = EncryptedEvent.new(
|
63
|
+
user_id: SecureRandom.uuid,
|
64
|
+
first_name: 'Anakin',
|
65
|
+
last_name: 'Skywalker',
|
66
|
+
profession: 'Jedi'
|
67
|
+
)
|
68
|
+
```
|
69
|
+
|
70
|
+
When you publish this event, the eventstore will store this payload:
|
71
|
+
|
72
|
+
```ruby
|
73
|
+
{
|
74
|
+
'data' => {
|
75
|
+
'user_id' => 'dab48d26-e4f8-41fc-a9a8-59657e590716',
|
76
|
+
'first_name' => 'encrypted',
|
77
|
+
'last_name' => 'encrypted',
|
78
|
+
'profession' => 'Jedi',
|
79
|
+
'encrypted' => '2345l423lj1#$!lkj24f1'
|
80
|
+
},
|
81
|
+
type: 'EncryptedEvent'
|
82
|
+
metadata: { ... }
|
83
|
+
}
|
84
|
+
```
|
@@ -0,0 +1,149 @@
|
|
1
|
+
# Linking events
|
2
|
+
|
3
|
+
## Linking single event
|
4
|
+
|
5
|
+
To create a link on an existing event, you need to have a stream name where you want to link that event to and you need to have an event, fetched from the database:
|
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
|
+
stream_name_1 = 'some-stream-1'
|
16
|
+
stream_name_2 = 'some-stream-2'
|
17
|
+
EventStoreClient.client.append_to_stream(stream_name_1, event)
|
18
|
+
# Get persisted event
|
19
|
+
event = EventStoreClient.client.read(stream_name_1).success.first
|
20
|
+
# Link event from first stream into second stream
|
21
|
+
result = EventStoreClient.client.link_to(stream_name_2, event)
|
22
|
+
if result.success? # Event was successfully linked
|
23
|
+
else # event was not linked, result.failure? => true
|
24
|
+
end
|
25
|
+
```
|
26
|
+
|
27
|
+
The linked event can later be fetched by providing the `:resolve_link_tos` option when reading from the stream:
|
28
|
+
|
29
|
+
```ruby
|
30
|
+
EventStoreClient.client.read('some-stream-2', options: { resolve_link_tos: true }).success
|
31
|
+
```
|
32
|
+
|
33
|
+
If you don't provide the `:resolve_link_tos` option, the "linked" event will be returned instead of the original one.
|
34
|
+
|
35
|
+
## Linking multiple events
|
36
|
+
|
37
|
+
You can provide an array of events to link to the target stream:
|
38
|
+
|
39
|
+
```ruby
|
40
|
+
class SomethingHappened < EventStoreClient::DeserializedEvent
|
41
|
+
end
|
42
|
+
|
43
|
+
events =
|
44
|
+
3.times.map do
|
45
|
+
SomethingHappened.new(
|
46
|
+
id: SecureRandom.uuid, type: 'some-event', data: { user_id: SecureRandom.uuid, title: "Something happened" }
|
47
|
+
)
|
48
|
+
end
|
49
|
+
|
50
|
+
stream_name_1 = 'some-stream-1'
|
51
|
+
stream_name_2 = 'some-stream-2'
|
52
|
+
events.each do |event|
|
53
|
+
EventStoreClient.client.append_to_stream(stream_name_1, event)
|
54
|
+
end
|
55
|
+
# Get persisted events
|
56
|
+
events = EventStoreClient.client.read(stream_name_1).success
|
57
|
+
# Link events from first stream into second stream one by one
|
58
|
+
results = EventStoreClient.client.link_to(stream_name_2, events)
|
59
|
+
results.each do |result|
|
60
|
+
if result.success? # Event was successfully linked
|
61
|
+
else # event was not linked, result.failure? => true
|
62
|
+
end
|
63
|
+
end
|
64
|
+
```
|
65
|
+
|
66
|
+
## Handling concurrency
|
67
|
+
|
68
|
+
When linking 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 raised.
|
69
|
+
|
70
|
+
For example if we try and link two records expecting both times that the stream doesn't exist we will get an exception on the second:
|
71
|
+
|
72
|
+
```ruby
|
73
|
+
class SomethingHappened < EventStoreClient::DeserializedEvent
|
74
|
+
end
|
75
|
+
|
76
|
+
event1 = SomethingHappened.new(
|
77
|
+
type: 'some-event', data: {},
|
78
|
+
)
|
79
|
+
|
80
|
+
event2 = SomethingHappened.new(
|
81
|
+
type: 'some-event', data: {},
|
82
|
+
)
|
83
|
+
|
84
|
+
stream_name_1 = "some-stream-1$#{SecureRandom.uuid}"
|
85
|
+
stream_name_2 = "some-stream-2$#{SecureRandom.uuid}"
|
86
|
+
|
87
|
+
EventStoreClient.client.append_to_stream(stream_name_1, [event1, event2])
|
88
|
+
events = EventStoreClient.client.read(stream_name_1).success
|
89
|
+
|
90
|
+
results = EventStoreClient.client.link_to(stream_name_2, events, options: { expected_revision: :no_stream })
|
91
|
+
results[0].success? # => true
|
92
|
+
results[1].success? # => false because second request tries to link the event with `:no_stream` expected revision
|
93
|
+
```
|
94
|
+
|
95
|
+
There are three available stream states:
|
96
|
+
|
97
|
+
- `:any`
|
98
|
+
- `:no_stream`
|
99
|
+
- `:stream_exists`
|
100
|
+
|
101
|
+
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.
|
102
|
+
|
103
|
+
```ruby
|
104
|
+
class SomethingHappened < EventStoreClient::DeserializedEvent
|
105
|
+
end
|
106
|
+
|
107
|
+
stream_name_1 = "some-stream-1$#{SecureRandom.uuid}"
|
108
|
+
stream_name_2 = "some-stream-2$#{SecureRandom.uuid}"
|
109
|
+
event1 = SomethingHappened.new(
|
110
|
+
type: 'some-event', data: {}
|
111
|
+
)
|
112
|
+
event2 = SomethingHappened.new(
|
113
|
+
type: 'some-event', data: {}
|
114
|
+
)
|
115
|
+
|
116
|
+
# Pre-create some events
|
117
|
+
EventStoreClient.client.append_to_stream(stream_name_1, event1)
|
118
|
+
EventStoreClient.client.append_to_stream(stream_name_2, event2)
|
119
|
+
# Load events from DB
|
120
|
+
event1 = EventStoreClient.client.read(stream_name_1).success.first
|
121
|
+
event2 = EventStoreClient.client.read(stream_name_2).success.first
|
122
|
+
# Get the revision number of latest event
|
123
|
+
revision = EventStoreClient.client.read(stream_name_2).success.last.stream_revision
|
124
|
+
# Expected revision matches => will succeed
|
125
|
+
EventStoreClient.client.link_to(stream_name_2, event1, options: { expected_revision: revision })
|
126
|
+
# Will fail with revisions mismatch error
|
127
|
+
EventStoreClient.client.link_to(stream_name_2, event2, options: { expected_revision: revision })
|
128
|
+
```
|
129
|
+
|
130
|
+
## User credentials
|
131
|
+
|
132
|
+
You can provide user credentials to be used to append the data as follows. This will override the default credentials set on the connection.
|
133
|
+
|
134
|
+
```ruby
|
135
|
+
class SomethingHappened < EventStoreClient::DeserializedEvent
|
136
|
+
end
|
137
|
+
|
138
|
+
event = SomethingHappened.new(
|
139
|
+
id: SecureRandom.uuid, type: 'some-event', data: { user_id: SecureRandom.uuid, title: "Something happened" }
|
140
|
+
)
|
141
|
+
|
142
|
+
stream_name_1 = 'some-stream-1'
|
143
|
+
stream_name_2 = 'some-stream-2'
|
144
|
+
EventStoreClient.client.append_to_stream(stream_name_1, event)
|
145
|
+
# Get persisted event
|
146
|
+
event = EventStoreClient.client.read(stream_name_1).success.first
|
147
|
+
# Link event from first stream into second stream
|
148
|
+
result = EventStoreClient.client.link_to(stream_name_2, event, credentials: { username: 'admin', password: 'changeit' })
|
149
|
+
```
|
@@ -0,0 +1,200 @@
|
|
1
|
+
# Reading events
|
2
|
+
|
3
|
+
## Reading from a stream
|
4
|
+
|
5
|
+
The simplest way to read a stream forwards is to supply a stream name.
|
6
|
+
|
7
|
+
```ruby
|
8
|
+
EventStoreClient.client.read('some-stream')
|
9
|
+
# => Success([#<EventStoreClient::DeserializedEvent 0x1>, #<EventStoreClient::DeserializedEvent 0x1>])
|
10
|
+
```
|
11
|
+
|
12
|
+
This will return either `Dry::Monads::Success` with the list of events attached or `Dry::Monads::Failure` with an error. You can handle the result like this:
|
13
|
+
|
14
|
+
```ruby
|
15
|
+
result = EventStoreClient.client.read('some-stream')
|
16
|
+
if result.success?
|
17
|
+
result.success.each do |event|
|
18
|
+
# do something with an event
|
19
|
+
end
|
20
|
+
end
|
21
|
+
```
|
22
|
+
|
23
|
+
### Request options customization
|
24
|
+
|
25
|
+
You can provide a block to the `#read` method. Request options will be yielded right before the request, allowing you to set advanced options:
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
EventStoreClient.client.read('some-stream') do |opts|
|
29
|
+
opts.control_option = EventStore::Client::Streams::ReadReq::Options::ControlOption.new(
|
30
|
+
compatibility: 1
|
31
|
+
)
|
32
|
+
end
|
33
|
+
```
|
34
|
+
|
35
|
+
### max_count
|
36
|
+
|
37
|
+
You can provide the `:max_count` option. This option determines how much records to return in a response:
|
38
|
+
|
39
|
+
```ruby
|
40
|
+
EventStoreClient.client.read('some-stream', options: { max_count: 1000 })
|
41
|
+
```
|
42
|
+
|
43
|
+
### resolve_link_tos
|
44
|
+
|
45
|
+
When using projections to create new events you can set whether the generated events are pointers to existing events. Setting this value to `true` tells EventStoreDB to return the event as well as the event linking to it.
|
46
|
+
|
47
|
+
```ruby
|
48
|
+
EventStoreClient.client.read('some-stream', options: { resolve_link_tos: true })
|
49
|
+
```
|
50
|
+
|
51
|
+
### from_revision
|
52
|
+
|
53
|
+
You can define from which revision number you would like to start to read events:
|
54
|
+
|
55
|
+
```ruby
|
56
|
+
EventStoreClient.client.read('some-stream', options: { from_revision: 2 })
|
57
|
+
```
|
58
|
+
|
59
|
+
Acceptable values are: number, `:start` and `:end`
|
60
|
+
|
61
|
+
### direction
|
62
|
+
|
63
|
+
As well as being able to read a stream forwards you can also go backwards. This can be achieved by providing the `:direction` option:
|
64
|
+
|
65
|
+
```ruby
|
66
|
+
EventStoreClient.client.read('some-stream', options: { direction: 'Backwards', from_revision: :end })
|
67
|
+
```
|
68
|
+
|
69
|
+
## Checking if the stream exists
|
70
|
+
|
71
|
+
In case a stream with given name does not exist, `Dry::Monads::Failure` will be returned with value `:stream_not_found`:
|
72
|
+
|
73
|
+
```ruby
|
74
|
+
result = EventStoreClient.client.read('non-existing-stream')
|
75
|
+
# => Failure(:stream_not_found)
|
76
|
+
result.failure?
|
77
|
+
# => true
|
78
|
+
result.failure
|
79
|
+
# => :stream_not_found
|
80
|
+
```
|
81
|
+
|
82
|
+
## Reading from the $all stream
|
83
|
+
|
84
|
+
Simply supply `"$all"` as the stream name in `#read`:
|
85
|
+
|
86
|
+
```ruby
|
87
|
+
EventStoreClient.client.read('$all')
|
88
|
+
```
|
89
|
+
|
90
|
+
The only difference in reading from `$all` vs reading from specific stream is that you should provide `:from_position` option instead `:from_revision` in order to define a position from which to read:
|
91
|
+
|
92
|
+
```ruby
|
93
|
+
EventStoreClient.client.read('$all', options: { from_position: :start })
|
94
|
+
EventStoreClient.client.read('$all', options: { from_position: :end, direction: 'Backwards' })
|
95
|
+
EventStoreClient.client.read('$all', options: { from_position: { commit_position: 9023, prepare_position: 9023 } })
|
96
|
+
```
|
97
|
+
|
98
|
+
## Result deserialization
|
99
|
+
|
100
|
+
If you would like to skip deserialization of the `#read` result, you should use the `:skip_deserialization` argument. This way you will receive the result from EventStore DB as is, including system events, etc:
|
101
|
+
|
102
|
+
```ruby
|
103
|
+
EventStoreClient.client.read('some-stream', skip_deserialization: true)
|
104
|
+
# => Success([<EventStore::Client::Streams::ReadResp ...>])
|
105
|
+
```
|
106
|
+
|
107
|
+
## Filtering
|
108
|
+
|
109
|
+
The filtering feature is only available for the`$all` stream.
|
110
|
+
|
111
|
+
Retrieve events from streams with name starting with `some-stream`:
|
112
|
+
|
113
|
+
```ruby
|
114
|
+
result =
|
115
|
+
EventStoreClient.client.read('$all', options: { filter: { stream_identifier: { prefix: ['some-stream'] } } })
|
116
|
+
if result.success?
|
117
|
+
result.success.each do |e|
|
118
|
+
# iterate through events
|
119
|
+
end
|
120
|
+
end
|
121
|
+
```
|
122
|
+
|
123
|
+
Retrieve events with name starting with `some-event`:
|
124
|
+
|
125
|
+
```ruby
|
126
|
+
result =
|
127
|
+
EventStoreClient.client.read('$all', options: { event_type: { prefix: ['some-event'] } })
|
128
|
+
if result.success?
|
129
|
+
result.success.each do |e|
|
130
|
+
# iterate through events
|
131
|
+
end
|
132
|
+
end
|
133
|
+
```
|
134
|
+
|
135
|
+
Retrieving events from stream `some-stream-1` and `some-stream-2`:
|
136
|
+
|
137
|
+
```ruby
|
138
|
+
result =
|
139
|
+
EventStoreClient.client.read('$all', options: { filter: { stream_identifier: { prefix: ['some-stream-1', 'some-stream-2'] } } })
|
140
|
+
if result.success?
|
141
|
+
result.success.each do |e|
|
142
|
+
# iterate through events
|
143
|
+
end
|
144
|
+
end
|
145
|
+
```
|
146
|
+
|
147
|
+
## Pagination
|
148
|
+
|
149
|
+
You can use `#read_paginated`, the ready-to-go implementation of pagination which returns an array of result pages:
|
150
|
+
|
151
|
+
```ruby
|
152
|
+
EventStoreClient.client.read_paginated('some-stream').each do |result|
|
153
|
+
if result.success?
|
154
|
+
result.success.each do |event|
|
155
|
+
# do something with event
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
EventStoreClient.client.read_paginated('$all').each do |result|
|
161
|
+
if result.success?
|
162
|
+
result.success.each do |event|
|
163
|
+
# do something with event
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
```
|
168
|
+
|
169
|
+
|
170
|
+
|
171
|
+
### Paginating backward reads
|
172
|
+
|
173
|
+
Just supply a call with `:direction` option and with `:from_position`/`:from_revision` option(depending on what stream you read from):
|
174
|
+
|
175
|
+
```ruby
|
176
|
+
EventStoreClient.client.read_paginated('some-stream', options: { direction: 'Backwards', from_revision: :end }).each do |result|
|
177
|
+
if result.success?
|
178
|
+
result.each do |event|
|
179
|
+
# do something with event
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
EventStoreClient.client.read_paginated('$all', options: { direction: 'Backwards', from_position: :end }).each do |result|
|
185
|
+
if result.success?
|
186
|
+
result.each do |event|
|
187
|
+
# do something with event
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
```
|
192
|
+
|
193
|
+
Note: for some reason when paginating the `$all` stream, EventStoreDB returns duplicate records from page to page (first event of `page N` = last event of `page N - 1`).
|
194
|
+
## User credentials
|
195
|
+
|
196
|
+
You can provide user credentials to be used to read the data as follows. This will override the default credentials set on the connection.
|
197
|
+
|
198
|
+
```ruby
|
199
|
+
EventStoreClient.client.read('some-stream', credentials: { username: 'admin', password: 'changeit' })
|
200
|
+
```
|