event_store_client 1.4.9 → 2.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (116) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +30 -145
  3. data/docs/appending_events.md +155 -0
  4. data/docs/catch_up_subscriptions.md +253 -0
  5. data/docs/configuration.md +83 -0
  6. data/docs/deleting_streams.md +25 -0
  7. data/docs/encrypting_events.md +84 -0
  8. data/docs/linking_events.md +149 -0
  9. data/docs/reading_events.md +200 -0
  10. data/lib/event_store_client/adapters/grpc/client.rb +244 -105
  11. data/lib/event_store_client/adapters/grpc/cluster/gossip_discover.rb +131 -0
  12. data/lib/event_store_client/adapters/grpc/cluster/insecure_connection.rb +21 -0
  13. data/lib/event_store_client/adapters/grpc/cluster/member.rb +18 -0
  14. data/lib/event_store_client/adapters/grpc/cluster/queryless_discover.rb +25 -0
  15. data/lib/event_store_client/adapters/grpc/cluster/secure_connection.rb +71 -0
  16. data/lib/event_store_client/adapters/grpc/command_registrar.rb +7 -7
  17. data/lib/event_store_client/adapters/grpc/commands/command.rb +63 -25
  18. data/lib/event_store_client/adapters/grpc/commands/gossip/cluster_info.rb +24 -0
  19. data/lib/event_store_client/adapters/grpc/commands/streams/append.rb +43 -68
  20. data/lib/event_store_client/adapters/grpc/commands/streams/append_multiple.rb +44 -0
  21. data/lib/event_store_client/adapters/grpc/commands/streams/delete.rb +21 -17
  22. data/lib/event_store_client/adapters/grpc/commands/streams/hard_delete.rb +39 -0
  23. data/lib/event_store_client/adapters/grpc/commands/streams/link_to.rb +7 -52
  24. data/lib/event_store_client/adapters/grpc/commands/streams/link_to_multiple.rb +44 -0
  25. data/lib/event_store_client/adapters/grpc/commands/streams/read.rb +20 -85
  26. data/lib/event_store_client/adapters/grpc/commands/streams/read_paginated.rb +174 -0
  27. data/lib/event_store_client/adapters/grpc/commands/streams/subscribe.rb +31 -106
  28. data/lib/event_store_client/adapters/grpc/connection.rb +56 -36
  29. data/lib/event_store_client/adapters/grpc/discover.rb +75 -0
  30. data/lib/event_store_client/adapters/grpc/generated/cluster_pb.rb +106 -18
  31. data/lib/event_store_client/adapters/grpc/generated/cluster_services_pb.rb +12 -12
  32. data/lib/event_store_client/adapters/grpc/generated/code_pb.rb +34 -0
  33. data/lib/event_store_client/adapters/grpc/generated/gossip_pb.rb +3 -2
  34. data/lib/event_store_client/adapters/grpc/generated/gossip_services_pb.rb +3 -3
  35. data/lib/event_store_client/adapters/grpc/generated/monitoring_pb.rb +25 -0
  36. data/lib/event_store_client/adapters/grpc/generated/monitoring_services_pb.rb +26 -0
  37. data/lib/event_store_client/adapters/grpc/generated/operations_pb.rb +2 -1
  38. data/lib/event_store_client/adapters/grpc/generated/operations_services_pb.rb +8 -7
  39. data/lib/event_store_client/adapters/grpc/generated/persistent_pb.rb +199 -38
  40. data/lib/event_store_client/adapters/grpc/generated/persistent_services_pb.rb +7 -3
  41. data/lib/event_store_client/adapters/grpc/generated/projections_pb.rb +9 -26
  42. data/lib/event_store_client/adapters/grpc/generated/projections_services_pb.rb +4 -3
  43. data/lib/event_store_client/adapters/grpc/generated/serverfeatures_pb.rb +29 -0
  44. data/lib/event_store_client/adapters/grpc/generated/serverfeatures_services_pb.rb +26 -0
  45. data/lib/event_store_client/adapters/grpc/generated/shared_pb.rb +54 -12
  46. data/lib/event_store_client/adapters/grpc/generated/status_pb.rb +23 -0
  47. data/lib/event_store_client/adapters/grpc/generated/streams_pb.rb +104 -64
  48. data/lib/event_store_client/adapters/grpc/generated/streams_services_pb.rb +3 -2
  49. data/lib/event_store_client/adapters/grpc/generated/users_services_pb.rb +2 -2
  50. data/lib/event_store_client/adapters/grpc/options/streams/read_options.rb +78 -0
  51. data/lib/event_store_client/adapters/grpc/options/streams/write_options.rb +39 -0
  52. data/lib/event_store_client/adapters/grpc/shared/event_deserializer.rb +52 -0
  53. data/lib/event_store_client/adapters/grpc/shared/options/filter_options.rb +76 -0
  54. data/lib/event_store_client/adapters/grpc/shared/options/stream_options.rb +91 -0
  55. data/lib/event_store_client/adapters/grpc/shared/streams/process_response.rb +28 -0
  56. data/lib/event_store_client/adapters/grpc/shared/streams/process_responses.rb +33 -0
  57. data/lib/event_store_client/adapters/grpc.rb +28 -12
  58. data/lib/event_store_client/configuration.rb +39 -54
  59. data/lib/event_store_client/connection/url.rb +57 -0
  60. data/lib/event_store_client/connection/url_parser.rb +144 -0
  61. data/lib/event_store_client/data_decryptor.rb +2 -9
  62. data/lib/event_store_client/deserialized_event.rb +35 -10
  63. data/lib/event_store_client/encryption_metadata.rb +0 -1
  64. data/lib/event_store_client/event.rb +4 -2
  65. data/lib/event_store_client/extensions/options_extension.rb +87 -0
  66. data/lib/event_store_client/mapper/default.rb +12 -9
  67. data/lib/event_store_client/mapper/encrypted.rb +18 -17
  68. data/lib/event_store_client/types.rb +1 -1
  69. data/lib/event_store_client/utils.rb +30 -0
  70. data/lib/event_store_client/version.rb +1 -1
  71. data/lib/event_store_client.rb +8 -7
  72. metadata +75 -84
  73. data/lib/event_store_client/adapters/grpc/Protos/cluster.proto +0 -149
  74. data/lib/event_store_client/adapters/grpc/Protos/gossip.proto +0 -44
  75. data/lib/event_store_client/adapters/grpc/Protos/operations.proto +0 -45
  76. data/lib/event_store_client/adapters/grpc/Protos/persistent.proto +0 -180
  77. data/lib/event_store_client/adapters/grpc/Protos/projections.proto +0 -174
  78. data/lib/event_store_client/adapters/grpc/Protos/shared.proto +0 -22
  79. data/lib/event_store_client/adapters/grpc/Protos/streams.proto +0 -242
  80. data/lib/event_store_client/adapters/grpc/Protos/users.proto +0 -119
  81. data/lib/event_store_client/adapters/grpc/README.md +0 -16
  82. data/lib/event_store_client/adapters/grpc/commands/persistent_subscriptions/create.rb +0 -46
  83. data/lib/event_store_client/adapters/grpc/commands/persistent_subscriptions/delete.rb +0 -34
  84. data/lib/event_store_client/adapters/grpc/commands/persistent_subscriptions/read.rb +0 -77
  85. data/lib/event_store_client/adapters/grpc/commands/persistent_subscriptions/settings_schema.rb +0 -38
  86. data/lib/event_store_client/adapters/grpc/commands/persistent_subscriptions/update.rb +0 -48
  87. data/lib/event_store_client/adapters/grpc/commands/projections/create.rb +0 -48
  88. data/lib/event_store_client/adapters/grpc/commands/projections/delete.rb +0 -34
  89. data/lib/event_store_client/adapters/grpc/commands/projections/update.rb +0 -44
  90. data/lib/event_store_client/adapters/grpc/commands/streams/read_all.rb +0 -43
  91. data/lib/event_store_client/adapters/grpc/commands/streams/tombstone.rb +0 -35
  92. data/lib/event_store_client/adapters/http/README.md +0 -16
  93. data/lib/event_store_client/adapters/http/client.rb +0 -161
  94. data/lib/event_store_client/adapters/http/commands/command.rb +0 -27
  95. data/lib/event_store_client/adapters/http/commands/persistent_subscriptions/ack.rb +0 -15
  96. data/lib/event_store_client/adapters/http/commands/persistent_subscriptions/create.rb +0 -35
  97. data/lib/event_store_client/adapters/http/commands/persistent_subscriptions/read.rb +0 -60
  98. data/lib/event_store_client/adapters/http/commands/projections/create.rb +0 -33
  99. data/lib/event_store_client/adapters/http/commands/projections/update.rb +0 -31
  100. data/lib/event_store_client/adapters/http/commands/streams/append.rb +0 -49
  101. data/lib/event_store_client/adapters/http/commands/streams/delete.rb +0 -16
  102. data/lib/event_store_client/adapters/http/commands/streams/link_to.rb +0 -49
  103. data/lib/event_store_client/adapters/http/commands/streams/read.rb +0 -52
  104. data/lib/event_store_client/adapters/http/commands/streams/tombstone.rb +0 -17
  105. data/lib/event_store_client/adapters/http/connection.rb +0 -46
  106. data/lib/event_store_client/adapters/http/request_method.rb +0 -28
  107. data/lib/event_store_client/adapters/http.rb +0 -17
  108. data/lib/event_store_client/adapters/in_memory.rb +0 -144
  109. data/lib/event_store_client/broker.rb +0 -40
  110. data/lib/event_store_client/catch_up_subscription.rb +0 -42
  111. data/lib/event_store_client/catch_up_subscriptions.rb +0 -92
  112. data/lib/event_store_client/client.rb +0 -73
  113. data/lib/event_store_client/error_handler.rb +0 -10
  114. data/lib/event_store_client/subscription.rb +0 -23
  115. data/lib/event_store_client/subscriptions.rb +0 -38
  116. 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: a08c9715141340d0ecea4be89e5843fdfeb743bf0e24388801efe240d7d1119b
4
- data.tar.gz: ec61b28b753e74d652af34de20dd2047c739b33267490674498fbde4d277e444
3
+ metadata.gz: 92e37b212f6c1cbdbc11e86031b157f5705c3800b706aff60c055ee74708801e
4
+ data.tar.gz: 0f817ce23a6881b45a4522641273657da4cff42e65837993e2846a3e6b80da7d
5
5
  SHA512:
6
- metadata.gz: e3a927092a217039a2423d1f3aa333d078216f29ab1c09040d142818edc2b3563ddd6cc83e5718370451eb0c9a0e260c0b272e248c98c413880a72968f9cf82e
7
- data.tar.gz: 48f091d550b2d326665896e6ca4fa86e7a9af09dbb314adc6333c7b0265d323172159fb0f462f48d761f9e044bb683b8b472203b3720cbb8fe2e5af419925209
6
+ metadata.gz: 43d3d0810e18543debe3d05b0a5bdbb25fb42cb40673b52ec22d4b027ada18b9182aa60f749d0036547655d3fb628bb3b70ad6d642b4453cff37e617692efb1f
7
+ data.tar.gz: 9aa84cc98d57358e8d4501c4e0b9a9e95b1b6b694bdf4f50b7d2df89b49a2df78c3117d5a206a299e3337afa9f62c03e00f76fefad60bf61796098b8d99fbefe
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
- ## Supported adapters
8
+ ## Requirements
9
9
 
10
- - [GRPC](https://github.com/yousty/event_store_client/tree/master/lib/event_store_client/adapters/grpc/README.md) - default
11
- - [HTTP](https://github.com/yousty/event_store_client/tree/master/lib/event_store_client/adapters/http/README.md) - Deprecated
12
- - InMemory - for testing
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', '~> 1.0'
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
- ### Create Dummy event and dummy Handler
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
- # Reading all events from a stream
107
- events = event_store.read('newstream', options: { all: true }).value! #default 'false'
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
- # Linking existing events to a new stream
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
- # Listening to new events for all registered subscriptions
116
- event_store.listen
49
+ Do you want to contribute? Welcome!
117
50
 
118
- # In the new terminal session publish some events
119
- events = (1..10).map { event }
120
- event_store.publish(stream: 'newstream', events: events)
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
- ### Extended usage
55
+ ### Re-generating GRPC files from Proto
125
56
 
126
- You can get access to more features by calling the adapter directly, for example:
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
- - [HTTP](https://github.com/yousty/event_store_client/blob/master/lib/event_store_client/adapters/http/client.rb)
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
- ### Configuration
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
- There are several configuration options you can pass to customize your client's instance.
141
- All the config options can be passed the same way:
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
- | name | value | default | description |
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
+ ```