Class: Servus::Events::Bus

Inherits:
Object
  • Object
show all
Defined in:
lib/servus/events/bus.rb

Overview

Central event bus for registering Event classes and dispatching events through configured routers.

The Bus maintains a registry mapping event names to their Event class definitions (one-to-one). On emit, it delegates to the configured routers to resolve invocations, deduplicates by key, and executes. ActiveSupport::Notifications wraps the dispatch cycle for the subscribe_all hook (e.g. forwarding to Eventus).

Examples:

Registering an event class

class UserCreated < Servus::Event
  event_name :user_created
end
# Registration happens automatically via the event_name DSL

Emitting an event

Bus.emit(:user_created, { user_id: 123 })

Forwarding all events to an external system

Bus.subscribe_all do |event_name, payload, started_at:, **|
  ExternalForwarder.perform_later(event: event_name, payload:)
end

See Also:

Class Method Summary collapse

Class Method Details

.clearvoid

This method returns an undefined value.

Clears all registered events.

Useful for testing and development mode reloading.

Examples:

Bus.clear


146
147
148
# File 'lib/servus/events/bus.rb', line 146

def clear
  @events = nil
end

.emit(event_name, payload) ⇒ void

This method returns an undefined value.

Emits an event through the configured routers.

Collects invocations from all routers (in config array order), deduplicates by key (first wins), and executes each. The entire dispatch is wrapped in ActiveSupport::Notifications so that subscribe_all listeners receive timing information.

Examples:

Bus.emit(:user_created, { user_id: 123, email: 'user@example.com' })
# Rails log: servus.events.user_created (1.2ms) {:user_id=>123, :email=>"user@example.com"}

Parameters:

  • event_name (Symbol)

    the name of the event to emit

  • payload (Hash)

    the event payload



82
83
84
85
86
87
88
# File 'lib/servus/events/bus.rb', line 82

def emit(event_name, payload)
  ActiveSupport::Notifications.instrument(notification_name(event_name), payload) do
    resolve_invocations(event_name, payload)
      .uniq(&:key)
      .each(&:execute)
  end
end

.enable_logging!void

This method returns an undefined value.

Installs the internal event logger. Called once at boot via the Railtie. Logs every event emission with its AS::Notifications correlation ID and dispatch duration.

Multiple subscribe_all subscriptions coexist — the app can add its own (e.g. for Eventus forwarding) independently.



127
128
129
130
131
132
133
134
135
136
# File 'lib/servus/events/bus.rb', line 127

def enable_logging!
  return if @logging_enabled

  subscribe_all do |event_name, payload, **meta|
    duration_ms = (meta[:finished_at] - meta[:started_at]) * 1000
    Servus::Support::Logger.log_event(event_name, payload, event_id: meta[:id], duration_ms:)
  end

  @logging_enabled = true
end

.event_for(name) ⇒ Class?

Returns the Event class registered for the given name.

Examples:

event_class = Bus.event_for(:user_created)
event_class.invocations_for(payload)

Parameters:

  • name (Symbol)

    the event name

Returns:

  • (Class, nil)

    the Event class or nil if not registered



64
65
66
# File 'lib/servus/events/bus.rb', line 64

def event_for(name)
  events[name]
end

.register_event(name, event_class) ⇒ void

This method returns an undefined value.

Registers an Event class for a specific event name.

Each event name maps to exactly one Event class. Attempting to register a second class for the same name raises an error.

Event classes are typically registered automatically at boot time via the event_name DSL method or ensure_registered!.

Examples:

Bus.register_event(:user_created, UserCreated)

Parameters:

  • name (Symbol)

    the event name

  • event_class (Class)

    the Event subclass to register

Raises:

  • (RuntimeError)

    if the event name is already registered



48
49
50
51
52
53
54
# File 'lib/servus/events/bus.rb', line 48

def register_event(name, event_class)
  if events.key?(name)
    raise "Event :#{name} is already registered to #{events[name]}. Cannot register #{event_class}"
  end

  events[name] = event_class
end

.subscribe_all {|event_name, payload, started_at:, finished_at:, id:| ... } ⇒ Object

Subscribes to all Servus event emissions.

Yields the clean event name and payload as positional args, plus started_at, finished_at, and id as keyword args. Returns the subscription for manual unsubscribe.

Examples:

Forward all events to an external system

Servus::Events::Bus.subscribe_all do |event_name, payload, started_at:, **|
  EventusForwardJob.perform_later(
    event: event_name.to_s,
    payload: payload.as_json,
    occurred_at: started_at.utc.iso8601(6)
  )
end

Yields:

  • (event_name, payload, started_at:, finished_at:, id:)

Yield Parameters:

  • event_name (Symbol)

    the event name

  • payload (Hash)

    the event payload

  • started_at (Time)

    when the event was emitted

  • finished_at (Time)

    when the instrumented block completed

  • id (String)

    unique notification ID

Returns:

  • (Object)

    the ActiveSupport::Notifications subscription



112
113
114
115
116
117
# File 'lib/servus/events/bus.rb', line 112

def subscribe_all(&block)
  ActiveSupport::Notifications.subscribe(/^servus\.events\./) do |name, started, finished, id, payload|
    event_name = name.delete_prefix('servus.events.').to_sym
    block.call(event_name, payload, started_at: started, finished_at: finished, id: id)
  end
end