Event Bus и паттерн Observer в Go (Golang): реализация и примеры

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Event Bus и паттерн Observer в Go (Golang)

В мире современной разработки на Go (Golang) часто возникает необходимость организовать слабосвязанное взаимодействие между компонентами приложения. Два классических подхода — паттерн Observer и его более продвинутая реализация — Event Bus (шина событий). Эти механизмы позволяют одним частям программы реагировать на действия других, не создавая жёстких зависимостей.

В этой статье мы разберём, как реализовать Event Bus на чистом Go без внешних зависимостей, рассмотрим паттерн Observer, а также покажем примеры кода для синхронной и асинхронной обработки событий.

Что такое паттерн Observer?

Паттерн Observer (Наблюдатель) — это поведенческий паттерн проектирования, который создаёт механизм подписки, позволяющий одним объектам следить и реагировать на события, происходящие в других объектах.

Основные элементы:

  • Subject (Субъект) — объект, который генерирует события.
  • Observer (Наблюдатель) — объект, который подписывается на события.
  • Event (Событие) — данные, передаваемые от субъекта наблюдателям.

Что такое Event Bus?

Event Bus — это централизованная шина, которая управляет передачей событий между издателями (publishers) и подписчиками (subscribers). В отличие от классического Observer, где субъект напрямую хранит список наблюдателей, Event Bus выступает как посредник. Это делает систему ещё более гибкой и масштабируемой.

Реализация Event Bus на Go

Go не имеет встроенной реализации Event Bus, но его можно легко написать с помощью горутин, каналов и интерфейсов. Рассмотрим простую, но полноценную реализацию.

1. Определяем базовые типы

// Event представляет собой структуру события
type Event struct {
    Name string
    Data interface{}
}

// EventHandler — функция-обработчик события
type EventHandler func(Event)

2. Создаём Event Bus

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

// NewEventBus создаёт новую шину событий
func NewEventBus() *EventBus {
    return &EventBus{
        handlers: make(map[string][]EventHandler),
    }
}

// Subscribe подписывает обработчик на событие
func (eb *EventBus) Subscribe(name string, handler EventHandler) {
    eb.mu.Lock()
    defer eb.mu.Unlock()
    eb.handlers[name] = append(eb.handlers[name], handler)
}

// Publish синхронно публикует событие
func (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. Асинхронная публикация

Для асинхронной обработки можно запускать каждый обработчик в отдельной горутине:

// PublishAsync асинхронно публикует событие
func (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)
        }
    }
}

Пример использования

Рассмотрим, как использовать наш Event Bus в реальном приложении. Допустим, у нас есть система, которая логирует действия пользователя и отправляет уведомления.

func main() {
    bus := NewEventBus()

    // Подписываемся на событие "user.login"
    bus.Subscribe("user.login", func(e Event) {
        userData := e.Data.(map[string]string)
        fmt.Printf("Лог: Пользователь %s вошёл в систему\n", userData["username"])
    })

    // Подписываемся на то же событие для отправки email
    bus.Subscribe("user.login", func(e Event) {
        userData := e.Data.(map[string]string)
        fmt.Printf("Email отправлен на %s: 'Добро пожаловать!'\n", userData["email"])
    })

    // Публикуем событие
    bus.Publish("user.login", map[string]string{
        "username": "ivan",
        "email":    "ivan@example.com",
    })

    // Вывод:
    // Лог: Пользователь ivan вошёл в систему
    // Email отправлен на ivan@example.com: 'Добро пожаловать!'
}

Продвинутая реализация: каналы и контекст

Для production-решений часто используют каналы и контекст для graceful shutdown:

type ChannelBus struct {
    handlers map[string][]chan Event
    ctx      context.Context
    cancel   context.CancelFunc
}

func NewChannelBus(ctx context.Context) *ChannelBus {
    ctx, cancel := context.WithCancel(ctx)
    return &                    

Рекомендаций