Event Bus and Observer Pattern in Go (Golang): Implementation and Examples

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Event Bus and Observer Pattern in Go (Golang)



In the world of modern Go (Golang) development, there is often a need to organize loosely coupled interaction between application components. Two classic approaches are the Observer pattern and its more advanced implementation — the Event Bus. These mechanisms allow some parts of a program to react to actions of others without creating rigid dependencies.



In this article, we will break down how to implement an Event Bus in pure Go without external dependencies, examine the Observer pattern, and show code examples for synchronous and asynchronous event handling.



What is the Observer Pattern?



The Observer pattern is a behavioral design pattern that creates a subscription mechanism allowing one object to watch and react to events occurring in other objects.



Main elements:

  • Subject — the object that generates events.
  • Observer — the object that subscribes to events.
  • Event — the data transmitted from the subject to observers.


What is an Event Bus?



An Event Bus is a centralized bus that manages the transmission of events between publishers and subscribers. Unlike the classic Observer pattern, where the subject directly stores a list of observers, the Event Bus acts as an intermediary. This makes the system even more flexible and scalable.



Implementing an Event Bus in Go



Go does not have a built-in Event Bus implementation, but it can be easily written using goroutines, channels, and interfaces. Let's look at a simple but complete implementation.



1. Define basic types



// Event represents an event structuretype Event struct {    Name string    Data interface{}}

// EventHandler is an event handler functiontype EventHandler func(Event)


2. Create the Event Bus



type EventBus struct {    handlers map[string][]EventHandler    mu       sync.RWMutex}

// NewEventBus creates a new event busfunc NewEventBus() *EventBus { return &EventBus{ handlers: make(map[string][]EventHandler), }}

// Subscribe subscribes a handler to an eventfunc (eb *EventBus) Subscribe(name string, handler EventHandler) { eb.mu.Lock() defer eb.mu.Unlock() eb.handlers[name] = append(eb.handlers[name], handler)}

// Publish synchronously publishes an eventfunc (eb *EventBus) Publish(name string, data interface{}) { eb.mu.RLock() defer eb.mu.RUnlock() if handlers, ok := eb.handlers[name]; ok { event := Event{Name: name, Data: data} for _, handler := range handlers { handler(event) } }}


3. Asynchronous publishing



For asynchronous processing, you can run each handler in a separate goroutine:



// PublishAsync asynchronously publishes an eventfunc (eb *EventBus) PublishAsync(name string, data interface{}) {    eb.mu.RLock()    defer eb.mu.RUnlock()    if handlers, ok := eb.handlers[name]; ok {        event := Event{Name: name, Data: data}        for _, handler := range handlers {            go handler(event)        }    }}


Usage Example



Let's look at how to use our Event Bus in a real application. Suppose we have a system that logs user actions and sends notifications.



func main() {    bus := NewEventBus()

// Subscribe to the "user.login" event bus.Subscribe("user.login", func(e Event) { userData := e.Data.(map[string]string) fmt.Printf("Log: User %s logged in", userData["username"]) })

// Subscribe to the same event for

Recommendations