Guava-like EventBus for Go

I needed a simple solution to notify components about changes in some global settings. In my Java days, I have been using the Guava Eventbus to solve similar problems, so I decided to cook something that does the basics.

From Guava: “The EventBus allows publish-subscribe-style communication between components requiring the components to explicitly register with one another (and thus be aware of each other).”

The Java implementation uses annotations to register components and subscribe its methods to event types. In my Go version, with lack of such annotations, I decided to keep it simpler; just subscribe a function to an event by name. The singleton EventBus value will be an exposed package variable.

Example


	bus := NewEventBus()
	bus.Subscribe("test", func(e Event) {
		fmt.Printf("%#v\n", e)
	})
	bus.Post("test")
	bus.Post("test", EventData{"pi": 3.14159})

The code


	package lang

	// EventData represents the context of an event, a simple map.
	type EventData map[string]interface{}

	// Event is send on the bus to all subscribed listeners
	type Event struct {
		Name string
		Data EventData
	}

	// EventListener is the signature of functions that can handle an Event.
	type EventListener func(Event)

	// The EventBus allows publish-subscribe-style communication between components
	// without requiring the components to explicitly register with one another (and thus be aware of each other)
	// Inspired by Guava EventBus ; this is a more lightweight implementation.
	type EventBus struct {
		listeners map[string][]EventListener
	}

	func NewEventBus() EventBus {
		return EventBus{map[string][]EventListener{}}
	}

	// Subscribe adds an EventListener to be called when an event is posted.
	func (e EventBus) Subscribe(name string, listener EventListener) {
		list, ok := e.listeners[name]
		if !ok {
			list = []EventListener{}
		}
		list = append(list, listener)
		e.listeners[name] = list
	}

	// Post sends an event to all subscribed listeners.
	// Parameter data is optional ; Post can only have one map parameter.
	func (e EventBus) Post(name string, data ...map[string]interface{}) {
		list, ok := e.listeners[name]
		if !ok {
			return
		}
		event := Event{Name: name}
		if len(data) == 1 {
			event.Data = EventData(data[0])
		}
		for _, each := range list[:] { // iterate over unmodifyable copy
			each(event)
		}
	}