Skip to content

Repository files navigation

EventEngine Example

A minimal, working Rails app wired up to the EventEngine pipeline. It exists to show the whole path end to end — declare an event, compile it, decide how it is processed, handle it — in a codebase small enough to read in one sitting.

Every gem is installed from RubyGems, exactly as a real app would:

gem "event_engine"                    # the runtime
gem "event_engine-event_definition"   # the authoring DSL
gem "event_engine-subscribers"        # a processor

Try it

bin/setup
bin/rails event_definition:generate   # definitions  → helper + schema.json
bin/rails event_engine:catalog        # schema.json  → catalog + rules file
bin/rails event_engine:rules:check    # every event routed to a registered processor?
bin/rails test

Then emit an event:

bin/rails runner 'MarketingEvents.lead_created(lead: Lead.create!(email: "ada@example.com", name: "Ada"))'

and watch log/development.log:

[welcome] emailing ada@example.com
[analytics] lead 1 from nil

The five files that matter

Everything else is stock Rails.

1. Declare an event — app/event_definitions/lead_created.rb

class LeadCreated < EventEngine::EventDefinition
  event_name :lead_created
  event_type :domain
  domain     :marketing
  subject    :lead

  input :lead

  required_payload :lead_id, from: :lead, attr: :id
  required_payload :email,   from: :lead, attr: :email
  optional_payload :source,  from: :lead, attr: :source
end

Read each payload line as "the event carries lead_id, and its value comes from lead.id." You hand the helper the whole lead; the contract decides what is captured off it.

2. Configure the build — Rakefile

load "tasks/event_definition.rake"

EventEngine::Definition.configure do |config|
  config.definitions_path = "app/event_definitions"
  config.helper_path      = "lib/generated/marketing_events.rb"
  config.root_module      = "MarketingEvents"
  config.subject_registry = EventEngine::SubjectRegistry.define { subject :lead }
end

bin/rails event_definition:generate compiles the definitions into lib/generated/marketing_events.rb (a typed method per event) and schema.json (the committed contract). Both are committed — they are build output, not runtime work.

3. Decide how each event is processed — config/event_rules.yml

events:
  lead_created: inline        # run subscribers synchronously, during emit
  lead_converted: background  # enqueue a job, return immediately

bin/rails event_engine:catalog writes this file, listing every catalogued event so a new one shows up needing a decision. Rules you have already made are preserved.

This is the only place processing is declared. The schema says what an event carries; the rules say what happens to it.

4. Handle it — app/subscribers/

class SendWelcomeEmail < EventEngine::Subscribers::Base
  subscribes_to :lead_created

  def handle(event)
    Rails.logger.info("[welcome] emailing #{event.payload[:email]}")
  end
end

Two subscribers listen to lead_created here (SendWelcomeEmail and TrackLeadSignup) to show fan-out; both run.

5. Wire it — config/initializers/event_engine.rb

require "event_engine"
require "event_engine/definition"
require Rails.root.join("lib/generated/marketing_events")

That is the entire host wiring. No publisher to configure, no processors to register. The runtime registers its publisher at boot, and event_engine-subscribers registers itself as the :inline and :background processors. The only explicit require is the generated helper, because it lives outside the autoload paths.

Two things worth copying

Keep the generated helper out of the autoload paths. Rails 7.1+ autoloads lib, so Zeitwerk would expect lib/generated/marketing_events.rb to define Generated::MarketingEvents and raise on eager load — meaning the app boots in development and fails in production. See config/application.rb:

config.autoload_lib(ignore: %w[assets tasks generated])

Run the rules check in CI. A rule naming a processor nothing registered, or a catalogued event nobody routed, otherwise surfaces only when that event is emitted:

$ bin/rails event_engine:rules:check
EventEngine rules OK: :inline, :background

How it fits together

app/event_definitions/*.rb
      │  event_definition:generate
      ▼
lib/generated/marketing_events.rb  +  schema.json     (committed)
      │  event_engine:catalog
      ▼
db/event_schema.json  +  config/event_rules.yml       (committed)
      │
      │  MarketingEvents.lead_created(lead: lead)
      ▼
EventEngine.emit  →  builds the payload from the contract
      │             looks up the rule: lead_created → inline
      ▼
EventEngine::Subscribers::Processor  →  your subscribers

The gems

Gem Role
event_engine-event_definition Declare events, compile the contract. No Rails.
event_engine Build a validated event from the catalog, route it to a processor.
event_engine-subscribers A processor: runs your in-app subscribers, inline or in the background.

License

Available as open source under the terms of the MIT License.

About

A minimal Rails app showing the EventEngine pipeline end to end: declare an event, compile it, declare how it is processed, handle it.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages