Skip to content

Instantly share code, notes, and snippets.

@hallgren
Forked from jesjos/proposal.rb
Last active August 29, 2015 14:13
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save hallgren/f7df85d836cdf3a4f580 to your computer and use it in GitHub Desktop.
Save hallgren/f7df85d836cdf3a4f580 to your computer and use it in GitHub Desktop.
# = Example Sandthorn setup
Sandthorn.config do |sand|
snapshot_store = SnapshotStore.new
sand.event_stores = {
default: EventStore.new(event_driver: SequelDriver.new(url: "sqlite://my_db"), snapshot_driver: snapshot_store)
mongo: EventStore.new(event_driver: MongoDBDriver.new(), snapshot_driver: snapshot_store)
alternative: ObjectStore.new(object_driver: InMemoryDriver.new)
aux: InMemoryStore.new
}
# This maps aggregates to event stores.
# Aggregates that aren't exlicitly mapped will be stored in the default store.
# I see the need to be able to define either concrete classes or patterns.
sand.aggregate_mappings = {
alternative: [StoreAggregates::Foo, StoreAggregates::Bar]
aux: /LogAggregates/
}
end
# Example aggregate
class MyAggregate
include EventStore::AggregateRoot
attr_reader :name
def initialize(name: nil)
@name = name
end
def change_name(name)
@name = name
name_changed
end
def name_changed
commit
end
end
# = Committing
# When commit is called, we just construct the appropriate event and save it in the aggregate.
# The events should be in the agreed-upon event format.
# = Saving an aggregate
# Saving means finding the appropriate event store for the aggregate type, and then calling `save_events` on the event store with any unsaved events.
# Example:
# 1. my_aggregate.save calls Sandthorn.event_store_for(my_aggregate) and gets an event_store
# 2. Call event_store.save_events(my_aggregate, my_aggregate.unsaved_events)
# Here's an incomplete implementation of AggregateRoot
module EventStore
module AggregateRoot
attr_reader :unsaved_events
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def event_store
Sandthorn.event_store_for(self)
end
end
def save
if unsaved_events.any?
save_events(unsaved_events)
end
end
private
def save_events(events)
event_store.save_events(self, events)
end
def event_store
self.class.event_store
end
end
end
@hallgren
Copy link
Author

I think that the stores should return objects and the internal data structure is up to the store.

EventStore: returns object but save the objects as events. Its driver saves and loads events.
ObjectStore: returns objects and also stores the objects as a ORM like Active Record.
InMemoryStore: returns object and stores the objects in memory.

If the AggregateRoot is def. in the EventStore its a impl. detail on how the data is stores, in this case events. The ObjectStore and InMemoryStore can impl. their own way on how to structure and store its data.

Sandthorn is more like a DataMapper that handles objects.

@jesjos
Copy link

jesjos commented Jan 13, 2015

So you want Sandthorn to be some kind of very abstract object database interface with a number of possible backends? Can you explain why this is desirable?

I think Sandthorn should be an EventSourcing system. I think that's a reasonable scope for a project like this, and it's something that few others have done in the Ruby community. I really don't see the need for the extra abstraction you want to introduce. I think they make the scope too big and the system too abstract.

I think we want Sandthorn for the benefits that an Event Sourcing System gives us. For example - the ability to create projections and the ability to audit object state at different points in time. Given that assumption, offering storage backends that don't use Event Sourcing seems counter intuitive. I think you should use some other library if that is what you want. I think all aggregates in Sandthorn should be built upon events.

Some notes

  • A Ruby object is by definition already stored in memory, so what's the purpose of an InMemory store for aggregates?
  • When I say that events should have an agreed-upon format, that doesn't mean that all events have to be hashes with exactly the same keys or that they have to be saved in exactly that format. It only means that events coming from different stores must have the same public API. Just to be perfectly clear – I don't care about how the internal storage format of the Event Store.
  • Having different stores use different formats means that for example your projections will be broken if you decide to store events in another store. You should be able to just switch stores without caring.

@hallgren
Copy link
Author

Yes you can be right about taking Sandthorn in the wrong direction and make the scope to big.

It maybe easier to just use Active Record directly for the objects that not have to be backed by events. I have encountered cases there event based object are overkill and a classic state based object could be enough and that it could be queried by default. Events are only an implementation detail that builds the objects.

  • Yes a ruby object lives in memory but a http request is stateless and to be able to access a object in a second request it has to be stored somewhere that be memory or on disk. The InMemory store are persisted as long as its host. In the end of the day it probably used most for testing the domain.
  • I think we mean the same thing.
  • The EventStore should have the same API objects out and event in. Then its up to the driver or that we call it to store the events as it please. An EventStore stores events in a pre defined format. Then the projections is coupled to the EventStore independent of underlying sequel or mongo db.

In the end we want the same thing I maybe take it a step to far.

@hallgren
Copy link
Author

I had some nightly insight around this discussion.

We plan to inject Buckthorn in Sandthorn but the API towards Buckthorn are objects, if I remember it correctly. Sandthorn will not know how the Buckthorn objects are stored. (events or only state). Would´t be a smell if the AggregateRoot are in Sandthorn but the objects from Buckthorn would not include it? As the objects from Buckthorn are modeled in Elixir/Erlang code and not in ruby land.

@jesjos
Copy link

jesjos commented Jan 21, 2015

I wrote a super long comment and then accidentally clicked the back-button.

@hallgren
Copy link
Author

Noooo

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment