Event Bus et le pattern Observer en Go (Golang)
Dans le monde du développement moderne en Go (Golang), il est souvent nécessaire d'organiser une interaction faiblement couplée entre les composants d'une application. Deux approches classiques — le pattern Observer et son implémentation plus avancée — l'Event Bus (bus d'événements). Ces mécanismes permettent à certaines parties du programme de réagir aux actions d'autres parties, sans créer de dépendances rigides.
Dans cet article, nous allons voir comment implémenter un Event Bus en Go pur sans dépendances externes, examiner le pattern Observer, et montrer des exemples de code pour le traitement synchrone et asynchrone des événements.
Qu'est-ce que le pattern Observer ?
Le pattern Observer (Observateur) est un pattern de conception comportemental qui crée un mécanisme d'abonnement permettant à certains objets de surveiller et de réagir aux événements se produisant dans d'autres objets.
Éléments principaux :
- Subject (Sujet) — l'objet qui génère les événements.
- Observer (Observateur) — l'objet qui s'abonne aux événements.
- Event (Événement) — les données transmises du sujet aux observateurs.
Qu'est-ce qu'un Event Bus ?
Un Event Bus est un bus centralisé qui gère la transmission des événements entre les éditeurs (publishers) et les abonnés (subscribers). Contrairement à l'Observer classique, où le sujet stocke directement la liste des observateurs, l'Event Bus agit comme un intermédiaire. Cela rend le système encore plus flexible et évolutif.
Implémentation d'un Event Bus en Go
Go n'a pas d'implémentation intégrée d'Event Bus, mais on peut facilement en écrire une à l'aide de goroutines, de canaux et d'interfaces. Examinons une implémentation simple mais complète.
1. Définir les types de base
// Event représente une structure d'événementtype Event struct { Name string Data interface{}}
// EventHandler — fonction de gestion d'événementtype EventHandler func(Event)2. Créer l'Event Bus
type EventBus struct { handlers map[string][]EventHandler mu sync.RWMutex}
// NewEventBus crée un nouveau bus d'événementsfunc NewEventBus() *EventBus { return &EventBus{ handlers: make(map[string][]EventHandler), }}
// Subscribe abonne un gestionnaire à un événementfunc (eb *EventBus) Subscribe(name string, handler EventHandler) { eb.mu.Lock() defer eb.mu.Unlock() eb.handlers[name] = append(eb.handlers[name], handler)}
// Publish publie un événement de manière synchronefunc (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. Publication asynchrone
Pour un traitement asynchrone, on peut lancer chaque gestionnaire dans une goroutine séparée :
// PublishAsync publie un événement de manière asynchronefunc (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) } }}Exemple d'utilisation
Voyons comment utiliser notre Event Bus dans une application réelle. Supposons que nous ayons un système qui enregistre les actions des utilisateurs et envoie des notifications.
func main() { bus := NewEventBus()
// Abonnement à l'événement "user.login" bus.Subscribe("user.login", func(e Event) { userData := e.Data.(map[string]string) fmt.Printf("Log : L'utilisateur %s s'est connecté", userData["username"]) })
// Abonnement au même événement pour en Aussi dans la bibliothèque
Mise en cache en TypeScript : Redis, Memcached et solutions en mémoire
Serilog pour C# (.NET) : guide complet avec exemples
Travailler avec les archives ZIP en TypeScript : la bibliothèque JSZip et les utilitaires
Configuration d'applications Swift : la bibliothèque SwiftyConfig
Abseil C++ : guide complet avec exemples pratiques