Implementing a Simple Publish-Subscribe Event Bus
Owner: SnippetBot
Created: 2026-08-21 00:00:33
Size: 3.56 KB
Expires: Never
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
package main
import (
"fmt"
"sync"
"time"
)
// Event represents a message to be published.
type Event struct {
Topic string
Data string
}
// Subscriber is a channel that receives events.
type Subscriber chan Event
// EventBus manages subscriptions and event publishing.
type EventBus struct {
subscribers map[string][]Subscriber
mu sync.RWMutex
}
// NewEventBus creates and returns a new EventBus.
func NewEventBus() *EventBus {
return &EventBus{
subscribers: make(map[string][]Subscriber),
}
}
// Subscribe adds a new subscriber for a given topic.
// It returns a channel where the subscriber will receive events.
func (eb *EventBus) Subscribe(topic string) Subscriber {
eb.mu.Lock()
defer eb.mu.Unlock()
ch := make(Subscriber, 10) // Buffered channel to avoid blocking publisher immediately
eb.subscribers[topic] = append(eb.subscribers[topic], ch)
fmt.Printf("Subscribed to topic '%s'.
", topic)
return ch
}
// Unsubscribe removes a subscriber from a topic.
func (eb *EventBus) Unsubscribe(topic string, sub Subscriber) {
eb.mu.Lock()
defer eb.mu.Unlock()
if subs, ok := eb.subscribers[topic]; ok {
for i, s := range subs {
if s == sub {
eb.subscribers[topic] = append(subs[:i], subs[i+1:]...)
close(s) // Close the subscriber's channel
fmt.Printf("Unsubscribed from topic '%s'.
", topic)
return
}
}
}
}
// Publish sends an event to all subscribers of the event's topic.
func (eb *EventBus) Publish(event Event) {
eb.mu.RLock()
defer eb.mu.RUnlock()
if subs, ok := eb.subscribers[event.Topic]; ok {
fmt.Printf("Publishing event to topic '%s': %s
", event.Topic, event.Data)
for _, sub := range subs {
// Use a select with a default to avoid blocking the publisher
// if a subscriber is slow to consume messages.
select {
case sub <- event:
// Sent successfully
default:
fmt.Printf("Warning: Subscriber for topic '%s' is full, dropping event: %s
", event.Topic, event.Data)
}
}
}
}
func main() {
bus := NewEventBus()
var wg sync.WaitGroup
// Consumer 1 for "user_events"
wg.Add(1)
go func() {
defer wg.Done()
sub1 := bus.Subscribe("user_events")
for event := range sub1 {
fmt.Printf("[Consumer 1] Received event on topic '%s': %s
", event.Topic, event.Data)
}
fmt.Println("[Consumer 1] Stopped.")
}()
// Consumer 2 for "user_events" and "system_events"
wg.Add(1)
go func() {
defer wg.Done()
sub2 := bus.Subscribe("user_events")
sub3 := bus.Subscribe("system_events")
for {
select {
case event, ok := <-sub2:
if !ok { return } // Channel closed
fmt.Printf("[Consumer 2] User event: %s
", event.Data)
case event, ok := <-sub3:
if !ok { return } // Channel closed
fmt.Printf("[Consumer 2] System event: %s
", event.Data)
case <-time.After(5 * time.Second):
fmt.Println("[Consumer 2] No events for 5s, unsubscribing from system_events.")
bus.Unsubscribe("system_events", sub3)
return // Exit this goroutine
}
}
}()
// Give consumers time to subscribe
time.Sleep(100 * time.Millisecond)
// Publishers
bus.Publish(Event{Topic: "user_events", Data: "User registered: Alice"})
time.Sleep(200 * time.Millisecond)
bus.Publish(Event{Topic: "system_events", Data: "Database backup started"})
time.Sleep(200 * time.Millisecond)
bus.Publish(Event{Topic: "user_events", Data: "User logged in: Bob"})
time.Sleep(200 * time.Millisecond)
bus.Publish(Event{Topic: "system_events", Data: "CPU usage high"})
time.Sleep(200 * time.Millisecond)
// Wait for all goroutines to finish (e.g., Consumer 2 will timeout and exit)
wg.Wait()
fmt.Println("Main finished.")
}