EventStoreClient
An easy-to use API client for connecting ruby applications with https://eventstore.org/
Installation
Add this line to your application's Gemfile:
gem 'event_store_client'
And then execute:
$ bundle
Or install it yourself as:
$ gem install event_store_client
Usage
EventStore engine setup
- Download Event Store From https://eventstore.org/downloads/ or docker
docker pull eventstore/eventstore
- Run the Event Store server
docker run --name eventstore -it -p 2113:2113 -p 1113:1113 eventstore/eventstore
- Set Basic HTTP auth enviornment variables #below are defaults
- export EVENT_STORE_USER=admin
- export EVENT_STORE_PASSWORD=changeit
Ref: https://eventstore.org/docs/http-api/security
- Login to admin panel http://localhost:2113 and enable Projections for Event-Types
Configure EventStoreClient
Before you start, add this to the initializer or to the top of your script:
EventStoreClient.configure
Create Dummy event and dummy Handler
To test out the behavior, you'll need a sample event and handler to work with:
require 'securerandom'
class SomethingHappened < EventStoreClient::DeserializedEvent
def schema
Dry::Schema.Params do
required(:user_id).value(:string)
required(:title).value(:string)
end
end
end
event = SomethingHappened.new(
data: { user_id: SecureRandom.uuid, title: "Something happened" },
)
Now create a handler. It can be anything, which responds to a call method
with an event being passed as an argument.
class DummyHandler
def self.call(event)
puts "Handled #{event.class.name}"
end
end
Usage
# initialize the client
client = EventStoreClient::Client.new
Publishing events
client.publish(stream: 'newstream', events: [event])
Reading from a stream
events = client.read('newstream')
Changing reading direction
events = client.read('newstream', direction: 'backward') #default 'forward'
Reading all events from a stream
events = client.read('newstream', all: true) #default 'false'
Subscribing to events
client.subscribe(DummyHandler, to: [SomethingHappened])
# now try to publish several events
10.times { client.publish(stream: 'newstream', events: [event]) }
You can also publish multiple events at once
events = (1..10).map { event }
client.publish(stream: 'newstream', events: events)
# .... wait a little bit ... Your handler should be called for every single event you publish
Stop polling for new events
client.stop_polling
Linking existing events to the streem
Event to be linked properly has to coantians original event id. Real events could be mixed with linked events in the same stream.
exisiting_event1 = client.read('newstream').last
client.link_to(stream: 'anotherstream', events: [exisiting_event1, ...])
When you read from stream where links are placed. By default Event Store Client always resolve links for you returning the event that points to the link. You can use the ES-ResolveLinkTos: false HTTP header during readin stream to tell Event Store Client to return you the actual link and to not resolve it. More info: ES-ResolveLinkTos.
Event Mappers
We offer two types of mappers - default and encrypted.
Default Mapper
This is used out of the box. It just translates the EventClass defined in your application to Event parsable by event_store and the other way around.
Encrypted Mapper
This is implemented to match GDPR requirements. It allows you to encrypt any event using your encryption_key repository.
mapper = EventStoreClient::Mapper::Encrypted.new(key_repository)
EventStoreClient.configure do |config|
config.mapper = mapper
end
The Encrypted mapper uses the encryption key repository to encrypt data in your events according to the event definition.
Here is the minimal repository interface for this to work.
class DummyRepository
class Key
attr_accessor :iv, :cipher, :id
def initialize(id:, **)
@id = id
end
end
def find(user_id)
Key.new(id: user_id)
end
def encrypt(*)
'darthvader'
end
def decrypt(*)
{ first_name: 'Anakin', last_name: 'Skylwalker'}
end
end
Now, having that, you only need to define the event encryption schema:
class EncryptedEvent < EventStoreClient::DeserializedEvent
def schema
Dry::Schema.Params do
required(:user_id).value(:string)
required(:first_name).value(:string)
required(:last_name).value(:string)
required(:profession).value(:string)
end
end
def self.encryption_schema
{
key: ->(data) { data['user_id'] },
attributes: i[first_name last_name email]
}
end
end
event = EncryptedEvent.new(
user_id: SecureRandom.uuid,
first_name: 'Anakin',
last_name: 'Skylwalker',
profession: 'Jedi'
)
When you'll publish this event, in the store will be saved:
{
'data' => {
'user_id' => 'dab48d26-e4f8-41fc-a9a8-59657e590716',
'first_name' => 'encrypted',
'last_name' => 'encrypted',
'profession' => 'Jedi',
'encrypted' => '2345l423lj1#$!lkj24f1'
},
type: 'EncryptedEvent'
metadata: { ... }
}
Contributing
Do you want to contribute? Welcome!
- Fork repository
- Create Issue
- Create PR ;)
License
The gem is available as open source under the terms of the MIT License.