An implementation of the Event Model from the book Understanding Eventsourcing from Martin Dilger with Ruby (Sinatra + EventStoreRuby)
See the book here: https://leanpub.com/eventmodeling-and-eventsourcing The repository with the implementation example: https://github.com/dilgerma/eventsourcing-book
The application is split into small, independent slices. Each slice lives in its
own directory under slices/ and exposes a single public entry-point file named
slice.rb. That file:
require_relative-s the slice’s internal code (API, projector, listener…).extend Slice– mixes in the minimal framework.- Calls
on_boot { |**deps| … }to wire the slice into the app.
# slices/my_feature/slice.rb
require_relative '../../lib/slice'
require_relative 'api'
module MyFeature
extend Slice
on_boot do |event_store:, app:, conn_str:, register:, resolve:|
# Mount HTTP endpoints
API.set :event_store, event_store
app.use API
# Expose an object that other slices might want
register.call(:my_feature_api, API)
end
endrequire_relative 'slices' (see app.rb) loads every */slice.rb file
automatically; developers don’t touch a central list when adding a new slice.
Most of the time order doesn’t matter, but if a slice must boot first you can
prepend its module constant to Slices::BOOT_ORDER before calling
Slices.boot_all:
# app.rb (or an initializer)
Slices::BOOT_ORDER << Inventories # ensure tables exist early
Slices::BOOT_ORDER << PaymentsSlice # charges cards before emails
Slices.boot_all(
event_store: Application::Container.event_store,
app: WebApp,
conn_str: ENV.fetch('DATABASE_URL')
)Any slice not listed in BOOT_ORDER is appended automatically, so you only care
about the special cases.
Slices.boot_all always provides two lambdas to every slice:
register.(key, value)– publish something (an API object, service, repo…)resolve.(key)– retrieve it later
Example: Payments exposes its public API; Notifications resolves it without knowing how Payments is implemented.
# slices/payments/slice.rb
register.call(:payments_api, Payments::PublicAPI.new)
# slices/notifications/slice.rb
payments = resolve.call(:payments_api)
Notifier.configure(payments)This keeps slices decoupled while remaining explicit and testable.
mkdir slices/search➜touch slices/search/slice.rb- Inside
slice.rb:require_relative '../../lib/slice' module Search extend Slice on_boot { |app:, **_| app.use API } end
- Add whatever internal files you need (
api.rb,projector.rb, …). - Run the app – it loads automatically.
Happy slicing! 🎉
Rebuild projection:
bundle exec rake projections:rebuild_cart_productsQuery via HTTP (assuming CartsWithProducts::API mounted):
curl http://localhost:9292/carts/<cart_id>/products