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 &