EventBroker

The EventBroker provides a lightweight publish/subscribe (pub-sub) mechanism for decoupled event-driven communication between components. It acts as a central dispatcher that routes events to registered subscribers based on event type.

Overview

The broker is implemented as a singleton, so all parts of the application interact with the same event registry. Components can:

  • Register handlers for specific event types

  • Publish events to notify all interested subscribers

  • Rely on a simple recursion guard to prevent runaway event loops

This pattern is useful for cross-cutting concerns such as logging, state updates, UI notifications, or domain events.

Basic Usage

Registering Subscribers

Subscribers are callables that accept a single event instance.

from tavi.meta.event.event_interface import Event
from myapp.events import UserCreatedEvent
from myapp.event_broker import EventBroker

def on_user_created(event: UserCreatedEvent) -> None:
    print(f"User created: {event.user_id}")

broker = EventBroker()
broker.register(UserCreatedEvent, on_user_created)

Publishing Events

When an event is published, all subscribers registered for that event type are invoked.

event = UserCreatedEvent(user_id="123")
broker.publish(event)

Each subscriber receives a deep copy of the event instance. This prevents subscribers from mutating shared state and affecting other listeners.

Event Dispatch Semantics

  • Dispatch is synchronous: subscribers are called in the order they were registered.

  • Dispatch is type-based: only subscribers registered for the exact event class (type(event)) are invoked.

  • Event instances are copied: each subscriber receives an isolated event object.

Recursion Guard

The broker enforces a maximum call depth to prevent infinite or runaway recursion when events trigger other events during handling.

If the maximum depth is exceeded, a RuntimeError is raised:

RuntimeError: Event recursive depth of 1 has been exceeded.

This protects against patterns like:

  • A handler publishing the same event type it is subscribed to

  • Circular event chains between handlers

If deeper event chaining is required, the maximum depth can be increased:

broker = EventBroker()
broker.call_depth_max = 3

Typical Use Cases

  • Emitting domain events from application services

  • Triggering side effects such as logging, metrics, or notifications

  • Decoupling UI updates from core business logic

  • Broadcasting lifecycle events (startup, shutdown, state changes)

Limitations

  • No built-in support for asynchronous handlers

  • No wildcard or base-class subscriptions (exact type matching only)

  • No unregistration mechanism for subscribers

  • No event classification system. It does not validate that a subscriber should receive a specific event class. (Model vs Presenter)

  • Global singleton scope may be undesirable in some testing or multi-tenant contexts

For more complex workflows (async dispatch, filtering, prioritization, or scoped brokers), consider layering a more advanced event bus on top of this interface.