Rails Event Store (RES)
repository·master·Indexed 23 days ago
https://github.com/railseventstore/rails_event_storeA comprehensive library for managing events in Rails applications, supporting patterns such as Publish-Subscribe, Event Sourcing, and Read Models. The ecosystem includes the ruby_event_store-cli for event inspection, ruby_event_store-mcp for AI-driven tool integration, ruby_event_store-outbox for transactional background job enqueuing, and integrations for Flipper and Minitest.
What's inside Rails Event Store
- RubyEventStore is a Ruby implementation of an event store. It provides an in-memory event repository, generic instrumentation, and dispatches events synchronously. For more detailed information and ecosystem details, visit https://railseventstore.org.
Overview of Rails Event Store (RES)
masterRails Event Store (RES) is a library designed for publishing, consuming, storing, and retrieving events. It is intended to support event-driven architectures within Rails applications.
Key use cases include:
- Publish-Subscribe bus: Using RES as a central bus for event distribution.
- Decoupling: Separating core business logic from external concerns (e.g., in Hexagonal architectures).
- Alternative to ActiveRecord Callbacks: Moving away from side-effects in models toward explicit domain events.
- Communication Layer: Facilitating interaction between loosely coupled components.
- Synchronous/Asynchronous Reaction: Reacting to events either immediately or via background processing.
- Side-effect Extraction: Moving logic like notifications or metrics out of controllers/services and into dedicated event handlers.
- Audit Logs: Building a reliable history of system changes.
- Read Models: Creating specialized views of data for optimized querying.
- Event Sourcing: Implementing full event-sourcing patterns.
Overview of RailsEventStore gems
masterThe RailsEventStore project consists of several core gems and various contributed gems that extend its functionality.
Core Gems
rails_event_store: The primary gem for Rails integration.ruby_event_store: The core Ruby implementation.ruby_event_store-active_record: ActiveRecord adapter forruby_event_store.ruby_event_store-browser: Browser-based support forruby_event_store.ruby_event_store-rspec: RSpec testing support.aggregate_root: Provides aggregate root functionality.
Contributed Gems
There are many community-contributed gems for specific use cases, including:
ruby_event_store-outbox: Outbox pattern support.ruby_event_store-cli: Command line interface.ruby_event_store-process_manager: Process management.ruby_event_store-sequel: Sequel adapter.ruby_event_store-sidekiq_scheduler: Sidekiq integration.ruby_event_store-profiler: Profiling tools.
Overview of RailsEventStore
masterRailsEventStore is a Rails wrapper for
RubyEventStorethat provides enhanced functionality specifically for Rails applications. It includes several 'batteries included' features to streamline event-driven development in a Rails environment:- Asynchronous Event Dispatch: Uses
ActiveJobto perform after-commit event dispatching. - Instrumentation: Built-in
ActiveSupport::Notificationssupport for monitoring and debugging. - Request Metadata Enrichment: Automatically enriches events with relevant request metadata.
- Bounded Context Scaffolding: Provides an opinionated directory structure generator to help organize code by bounded contexts.
- Asynchronous Event Dispatch: Uses
What is RubyEventStore::Browser
masterRubyEventStore::Browser is a companion application for RubyEventStore designed for inspecting event streams. It allows developers to:
- Inspect stream contents and specific event details.
- Explore connections between events through correlation and causation analysis.
Available transformations in RubyEventStore::Transformations
masterThe
RubyEventStore::Transformationsgem provides additional transformation layers for use withPipelineMapper. It currently includes two specific transformations:- IdentityMap: Loads records into previously-known event instances while retaining their
object_id. This is primarily useful for testing scenarios where you need to assert against specific object identities or inspect output that relies on object stability. - WithIndifferentAccess: Ensures data and metadata are handled with indifferent access. It deep-symbolizes data and metadata before writing to the store and decorates them with
HashWithIndifferentAccessupon reading. This is highly recommended when working with JSON-backed data and metadata to avoid symbol/string key mismatches.
- IdentityMap: Loads records into previously-known event instances while retaining their
Use Minitest assertions for RubyEventStore
masterTheminitest-ruby_event_storegem provides specialized Minitest assertions designed to simplify testing code that interacts withRubyEventStore. These assertions allow you to verify event streams and event store state more expressively in your test suites.Use RubyEventStore::ActiveRecord for persistent event storage
masterRubyEventStore::ActiveRecord provides a persistent event repository implementation for
RubyEventStoreusing ActiveRecord. It is designed to provide log-like properties for event streams on top of SQL database engines by using linearized writes.It includes the necessary database schema and migrations and is compatible with the following database engines:
- PostgreSQL
- MySQL
- SQLite
Use RubyEventStore Sequel Event Repository
masterTheruby_event_store-sequelgem provides a Sequel-based implementation of an events repository for Ruby Event Store. It serves as an alternative to the ActiveRecord-basedEventRepositoryimplementation found in therails_event_storegem, allowing you to use the Sequel ORM for event storage instead of ActiveRecord.What is Bi-Temporal EventSourcing?
masterBi-Temporal EventSourcing is an approach used when knowing when an event was recorded (the
timestamp) is insufficient, and you also need to know when the event was actually valid in the real world.This is useful for handling corrections without mutating immutable event data. Instead of modifying an existing event to fix an error, you publish a new 'Retroactive' event that includes a
valid_atproperty in its metadata. This new event describes the correct value and specifies the time period it applies to.Extend the Browser with Extensions
masterExtensions allow you to plug new pages and links into the Browser UI. An extension is an object passed to
extensions: [...]in the configuration. There is no base class; the Browser discovers functionality usingrespond_to?on several optional hooks.Available Hooks
register_routes(router, context): Add custom routes. Therouterallows adding routes, and thecontextprovides rendering and event store access.stream_links(stream_name, urls): Add links displayed under the header of a stream page.event_links(event, urls): Add links displayed on an event page.nav_links(urls): Add links to the top navigation bar.views_root: Specify the directory for extension templates.assets_root: Specify the directory for extension assets (CSS/JS). Files in this directory are automatically served and linked.
The Context Object
In
register_routes, thecontextprovides:context.render(template, urls:, title: nil, **locals): Renders a template wrapped in the browser layout.context.not_found(urls, message: "Page not found"): Renders a styled 404 page.context.event_store: The event store client.
Building URLs
Always use
urls.app_url_forto build paths to ensure compatibility with different mount points:urls.app_url_for("my_extension", "param_value")run RubyEventStore::Browser::App.for( event_store_locator: -> { event_store }, extensions: [DeploymentsExtension.new], )How upcasting works for evolving events
masterUpcasting is a non-destructive strategy for evolving events where transformations occur at read time without modifying the underlying storage. When an event is loaded from the event store, a mapper applies a chain of upcast functions to convert older event representations into the current shape on the fly.
Use upcasting when:
- You are renaming an event class and want old records to be read as the new class.
- You are evolving the data schema of an event and want to express the evolution as a versioned chain.
Important Constraint: Each upcast function must change the
event_type. If an upcast returns a record with the sameevent_typeas the input, the system will raise aRubyEventStore::InvalidUpcasterror.upcast_map = { "OrderPlaced" => lambda do |record| RubyEventStore::Record.new( event_id: record.event_id, metadata: record.metadata, timestamp: record.timestamp, valid_at: record.valid_at, event_type: record.event_type, data: record.data.merge(currency: "USD"), ) end }