Skip to content

Repository files navigation

Go Event Bus

An event bus to react to all the things.

MaintainabilityTest CoverageGoDoc

Installation

go get github.com/io-da/event

Overview

  1. Events
  2. Handlers
  3. Error Handlers
  4. The Bus
    1. Tweaking Performance
    2. Shutting Down
    3. Available Errors
  5. Benchmarks
  6. Examples

Introduction

This library is intended for anyone looking to emit events in their application. And it achieves this objective using an event bus architecture.
The Bus will use workers (goroutines) to attempt handling the events in non-blocking manner.
Clean and simple codebase. No reflection, no closures.

Flowchart

Getting Started

Events

This type is used to provide a consistent identity solution for both events and topics.

typeIdentifierint64

Events are any type that implements the Event interface. Ideally they should contain immutable data.

typeEventinterface {
Identifier() Identifier
}

An event may optionally implement the Topic interface. If it does, then it will be handled within that topic.

typeTopicinterface {
EventTopic() Identifier
}

Any event emitted within the same topic is guaranteed to be handled respecting their order of emission.
However, this order is not guaranteed across different topics.
A topic is just an Identifier (int64), the event bus will take care of the rest.
Events that do not implement the Topic interface will be considered concurrent.
The Bus takes advantage of additionalworkers (goroutines) to handle concurrent events faster, but their emission order will not be respected.

Handlers

Handlers are any type that implements the Handler interface. Handlers must be instantiated and provided to the bus on initialization.

typeHandlerinterface {
Handle(evtEvent) error
}

Error Handlers

Error handlers are any type that implements the ErrorHandler interface. Error handlers are optional (but advised) and provided to the bus using the bus.ErrorHandlers function.

typeErrorHandlerinterface {
Handle(evtEvent, errerror)
}

Any time an error occurs within the bus, it will be passed on to the error handlers. This strategy can be used for decoupled error handling.

The Bus

Bus is the struct that will be used to emit all the application's events.
The Bus should be instantiated and initialized on application startup. The initialization is separated from the instantiation for dependency injection purposes.
The application should instantiate the Bus once and then use it's reference in all the places that will be emitting events.
The order in which the handlers are provided to the Bus is always respected.

Tweaking Performance

For applications that take advantage of concurrent events, the number of concurrent workers can be adjusted.

bus.SetConcurrentWorkerPoolSize(10)

If used, this function must be called before the Bus is initialized. And it specifies the number of goroutines used to handle concurrent events.
In some scenarios increasing the value can drastically improve performance.
It defaults to the value returned by runtime.GOMAXPROCS(0).

When aware of the total amount of different topics available in the application. Then that value should be provided with this function.

bus.SetTopicsCapacity(10)

If used, this function must be called before the Bus is initialized.
It defaults to 10.

The buffer size of topics can also be adjusted.
Depending on the use case, this value may greatly impact performance.

bus.SetTopicBuffer(100)

If used, this function must be called before the Bus is initialized.
It defaults to 100.

Shutting Down

The Bus also provides a shutdown function that attempts to gracefully stop the event bus and all its routines.

bus.Shutdown()

This function will block until the bus is fully stopped.

Available Errors

Below is a list of errors that can occur when calling bus.Emit.

event.ErrorInvalidEventevent.ErrorEventBusNotInitializedevent.ErrorEventBusIsShuttingDown

Benchmarks

All the benchmarks are performed against batches of 1 million events.
All the ordered events belong to the same topic.
All the benchmarks contain some overhead due to the usage of sync.WaitGroup.

Benchmarks with simulated handler behavior

The event handlers calculate the fibonacci of 1000 for simulation purposes.

Benchmark TypeTime
Ordered Events17355 ns/op
Concurrent Events2565 ns/op

Examples

An optional constants list of event and topic identifiers (idiomatic enum) for consistency

const (
UnidentifiedIdentifier=iotaFooEventBarEvent
)
const (
UnidentifiedIdentifier=iotaBarTopic
)

Example Events

A simple struct event.

typeFoostruct {
barstring
}
func (*Foo) Identifier() Identifier {
returnFooEvent
}

A string event that implements the Topic interface.

typeBarstringfunc (Bar) Topic() Identifier { returnBarTopic }
func (Bar) Identifier() Identifier {
returnBarEvent
}

Example Handlers

An event handler that logs every event emitted.

typeLoggerHandlerstruct {
}
func (hdl*LoggerHandler) Handle(evtEvent) error {
log.Printf("event %T emitted", evt)
returnnil
}

An event handler that listens to multiple event types.

typeFooBarHandlerstruct {
}
func (hdl*FooBarHandler) Handle(evtEvent) error {
// a convenient way to assert multiple event types.switchevt:=evt.(type) {
case*Foo, Bar:
// handler logic
}
returnnil
}

Putting it together

Initialization and usage of the exemplified events and handlers

import (
"github.com/io-da/event"
)
funcmain() {
// instantiate the bus (returns *event.Bus)bus:=event.NewBus()
// initialize the bus with all the application's event handlersbus.Initialize(
// this handler will always be executed first&LoggerHandler{},
// this one second&FooBarHandler{},
)
// emit eventsbus.Emit(&Foo{})
bus.Emit(Bar("bar"))
}

Contributing

Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.

Please make sure to update tests as appropriate.

License

MIT

About

An event bus to react to all the things.

Topics

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages