Event Bus et le pattern Observer en Go (Golang) : implémentation pub/sub

Formateur Python en ligne pour débutants

Apprenez Python facilement sans surcharge théorique. Résolvez des tâches pratiques avec vérification automatique et écrivez du code directement dans le navigateur.

Start Course

Event Bus et le pattern Observer en Go (Golang) : de la théorie à la pratique

\n

Le pattern Observer et son implémentation sous forme d'Event Bus sont des outils fondamentaux pour construire des systèmes faiblement couplés et évolutifs. En Go, avec sa philosophie « communiquez via des canaux, pas via la mémoire partagée », ces patterns acquièrent une élégance particulière. Dans cet article, nous allons voir comment créer votre propre Event Bus en Go, en utilisant des goroutines et des canaux, et examiner des scénarios d'utilisation typiques.

\n\n

Pattern Observer : essence et problème

\n

Imaginez que vous ayez un système où un objet (l'éditeur) doit notifier plusieurs autres (les abonnés) d'un changement d'état. Sans Observer, vous devriez lier rigidement l'éditeur à chaque abonné. Observer résout cela en introduisant une interface d'abonnement : l'éditeur stocke une liste d'abonnés et appelle leur méthode Update() lorsqu'un événement se produit.

\n

Event Bus est un bus centralisé qui joue le rôle d'intermédiaire. N'importe quel composant peut publier un événement, et n'importe quel autre peut s'y abonner, sans connaître l'existence de l'autre.

\n\n

Pourquoi Go est-il idéal pour un Event Bus ?

\n
    \n
  • Goroutines et canaux : traitement asynchrone des événements sans blocage.
  • \n
  • Interfaces : permettent de définir facilement des contrats pour les gestionnaires.
  • \n
  • Absence d'héritage : la composition via des canaux et des fonctions est plus naturelle qu'une implémentation OOP classique.
  • \n
\n\n

Implémentation simple d'un Event Bus en Go

\n

Commençons par une version de base, où les événements sont des chaînes de caractères et les abonnés sont des fonctions de gestion.

\n\n
package main\n\nimport (\n    "fmt"\n    "sync"\n    "time"\n)\n\n// EventBus — notre bus d'événements\ntype EventBus struct {\n    subscribers map[string][]func(string)\n    mu          sync.RWMutex\n}\n\n// NewEventBus crée un nouveau bus\nfunc NewEventBus() *EventBus {\n    return &EventBus{\n        subscribers: make(map[string][]func(string)),\n    }\n}\n\n// Subscribe abonne un gestionnaire à un événement\nfunc (eb *EventBus) Subscribe(event string, handler func(string)) {\n    eb.mu.Lock()\n    defer eb.mu.Unlock()\n    eb.subscribers[event] = append(eb.subscribers[event], handler)\n}\n\n// Publish publie un événement et appelle tous les abonnés de manière synchrone\nfunc (eb *EventBus) Publish(event string, data string) {\n    eb.mu.RLock()\n    defer eb.mu.RUnlock()\n    if handlers, ok := eb.subscribers[event]; ok {\n        for _, handler := range handlers {\n            handler(data)\n        }\n    }\n}\n\nfunc main() {\n    bus := NewEventBus()\n    \n    bus.Subscribe("user.login", func(data string) {\n        fmt.Printf("Connexion : %s\\n", data)\n    })\n    \n    bus.Subscribe("user.login", func(data string) {\n        fmt.Printf("Audit : %s s'est connecté\\n", data)\n    })\n    \n    bus.Publish("user.login", "alice@example.com")\n    // Sortie :\n    // Connexion : alice@example.com\n    // Audit : alice@example.com s'est connecté\n}
\n\n

Ce code fonctionne, mais il y a un détail : les gestionnaires sont appelés de manière synchrone. Si un gestionnaire se bloque, il bloquera tout le bus.

\n\n

Version asynchrone avec des canaux

\n

Ajoutons de l'asynchronisme via des canaux tamponnés. Chaque abonné recevra son propre canal.

\n\n
type AsyncEventBus struct {\n    subscribers map[string][]chan string\n    mu          sync.RWMutex\n}\n\nfunc NewAsyncEventBus() *AsyncEventBus {\n    return &AsyncEventBus{\n        subscribers: make(map[string][]chan string),\n    }\n}\n\nfunc (eb *AsyncEventBus) Subscribe(event string, buffer int) <-chan string {\n    eb.mu.Lock()\n    defer eb.mu.Unlock()\n    ch := make(chan string, buffer)\n    eb.subscribers[event] = append(eb.subscribers[event], ch)\n    return ch\n}\n\nfunc (eb *AsyncEventBus) Publish(event string, data string) {\n    eb.mu.RLock()\n    defer eb.mu.RUnlock()\n    if channe                    

Recommendations