kelindar/event

repository·master·Indexed 20 days ago

https://github.com/kelindar/event

A high-performance, in-process event dispatcher for Go designed to decouple modules within a single process using a generic pub/sub pattern. It supports asynchronous event handling via goroutines, provides both a global default dispatcher and custom Dispatcher instances, and implements backpressure via consumer queues to prevent memory exhaustion.

Tokens
2.4K
Snippets
14
Records
16
Agent score
67%

What's inside kelindar/event

  1. Overview of kelindar/event

    master

    The kelindar/event package is a high-performance, in-process event dispatcher for Go. It is designed for decoupling modules within a single process using a generic pub/sub pattern.

    Key Characteristics:

    • High Throughput: Capable of processing millions of events per second (often 4x to 10x faster than standard Go channels).
    • Asynchronous: Each subscriber is executed in its own goroutine.
    • Generic: Works with any type implementing the Event interface.

    When to use:

    • Decoupling internal modules in a single Go process.
    • Lightweight, high-throughput, low-latency event-driven patterns.

    When NOT to use:

    • For inter-process or service-to-service communication (use NATS, Kafka, etc.).
    • When you need event persistence, durability, or replay capabilities.
    • For scenarios involving heavy subscribe/unsubscribe churn.
  2. Define custom event types

    master

    To use kelindar/event, you must define event types that implement the Event interface. An event type must provide a Type() uint32 method that returns a unique identifier for that event type. This allows the dispatcher to route events to the correct subscribers.

    // Define a unique event type ID
    const EventA = 0x01
    
    // Define your event structure
    type myEvent struct{
        Data string
    }
    
    // Implement the Event interface
    func (ev myEvent) Type() uint32 {
        return EventA
    }
  3. Use a specific dispatcher instance

    master

    For better isolation or when managing multiple event buses, create a dedicated Dispatcher using event.NewDispatcher().

    When using a specific dispatcher, use the generic event.Subscribe[T]() and event.Publish[T]() functions, passing the dispatcher instance as the first argument. This ensures that events are only routed within that specific bus instance.

    bus := event.NewDispatcher()
    
    // Subscribe to a specific event type on the custom bus
    defer event.Subscribe(bus, func(e Event) {
        println("(consumer 1)", e.Data)
    })()
    
    // Publish an event to the custom bus
    event.Publish(bus, newEventA("event 1"))
  4. Use the default global dispatcher

    master

    For simple use cases, you can use the package-level functions event.On() and event.Emit(). This uses a default global dispatcher.

    event.On() returns an unsubscription function that should be called (typically via defer) to stop receiving events. Subscribers run in their own goroutines, so event handling is asynchronous and non-blocking.

    // Subscribe to events using the global dispatcher
    // The returned function is used to unsubscribe
    defer event.On(func(e Event) {
        println("(consumer)", e.Data)
    })()
    
    // Publish events using the global dispatcher
    event.Emit(newEventA("event 1"))
    event.Emit(newEventA("event 2"))
  5. How the Dispatcher handles backpressure

    master
    The event package implements backpressure via consumer queues. Each subscriber (consumer) has a maxQueue limit. When Publish is called, the dispatcher checks the maxLen (the highest queue length across all consumers in a group). If maxLen reaches maxQueue, the Broadcast operation will block (via s.cond.Wait()) until consumers process enough events to bring the queue length below the threshold. This prevents memory exhaustion during event spikes.
  6. Use the Default dispatcher for event subscription and emission

    master

    The event package provides a pre-initialized Default dispatcher instance, allowing you to subscribe to and emit events without manually managing dispatcher lifecycles. This is useful for simple, in-process pub/sub needs where a single global dispatcher is sufficient.

    • Use On[T Event](handler) to subscribe to an event type. The event type is automatically inferred from the handler's argument type (the type must be a constant).
    • Use OnType[T Event](eventType, handler) to subscribe to a specific event type identified by a uint32 ID.
    • Use Emit[T Event](ev) to publish an event to the default dispatcher.
    package main
    
    import "github.com/kelindar/kelindar/event"
    
    type MyEvent struct {
    	Message string
    }
    
    func main() {
    	// Subscribe using type inference
    	cancel := event.On(func(ev MyEvent) {
    		println("Received:", ev.Message)
    	})
    	defer cancel()
    
    	// Emit the event
    	event.Emit(MyEvent{Message: "Hello World"})
    }
  7. Close the Dispatcher

    master

    Call Close() on the Dispatcher to signal that it is shutting down. This closes the internal done channel, which stops the background processing loops for all subscriber groups.

    err := broker.Close()
    if err != nil {
    	// handle error
    }
  8. Subscribe to events using Subscribe

    master

    The Subscribe[T Event] function allows you to register a handler for a specific event type. The event type is automatically inferred from the type T provided. It returns a context.CancelFunc which, when called, unsubscribes the handler.

    // Define your event
    type UserCreated struct { Name string }
    func (u UserCreated) Type() uint32 { return 101 }
    
    // Subscribe
    cancel := event.Subscribe(broker, func(ev UserCreated) {
    	fmt.Printf("User created: %s\n", ev.Name)
    })
    
    // Later, to unsubscribe:
    cancel()
  9. Initialize a new Dispatcher

    master

    Use NewDispatcher() to create a high-performance, in-process event dispatcher. By default, it uses a flush interval of 500 microseconds and a maximum queue size of 50,000 per consumer to handle high throughput (up to ~1 million events/second).

    import "github.com/kelindar/kelindar/event"
    
    broker := event.NewDispatcher()
    defer broker.Close()
  10. Subscribe to events using SubscribeTo

    master

    If you need to specify the event type explicitly (e.g., if you aren't using type inference), use SubscribeTo. This function takes the broker, the eventType (uint32), and a handler function of type func(T).

    // Explicitly providing the event type
    cancel := event.SubscribeTo(broker, 101, func(ev UserCreated) {
    	fmt.Println(ev.Name)
    })
  11. Publish events to the Dispatcher

    master

    Use Publish(broker, ev) to send an event to the dispatcher. The dispatcher will look up the corresponding subscriber group using the event's Type() and broadcast the event to all active consumers in that group.

    ev := UserCreated{Name: "Alice"}
    event.Publish(broker, ev)
  12. Subscribe to events using On

    master

    The On[T Event](handler func(T)) function subscribes to an event using the Default dispatcher. The event type is automatically inferred from the type T provided in the handler function. For type inference to work correctly, the event type must be a constant.

    Returns a context.CancelFunc which, when called, unsubscribes the handler from the dispatcher.

    cancel := event.On(func(ev MyEventType) {
        // handle event
    })