DEV Community

Cover image for Event Observer Pattern in Go
Elijah Zobenko
Elijah Zobenko

Posted on

Event Observer Pattern in Go

The Observer - is a behavioral design pattern that allows an object, called the subject, to notify a list of observers or subscribers about any changes to its state. In Go, this pattern can be used to maintain loosely coupled relationships between objects.

In a nut shell

The Observer Design Pattern is a great way to design a system where an object can notify a set of observers when its state changes. It's especially useful when we want to decouple objects that depend on each other. In Go, this pattern can be used to maintain loosely coupled relationships between objects. This pattern is part of the GoF patterns, which are a set of design patterns described by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides in their book "Design Patterns: Elements of Reusable Object-Oriented Software".

In the Observer Design Pattern, the subject maintains a list of observers and notifies them when its state changes. The observers register with the subject to receive notifications, and the subject notifies the observers by calling their update method.

Why do we need it

The Observer Design Pattern is useful in several scenarios, including:

  • When there is a one-to-many relationship between objects, and a change to one object requires changes to other objects.
  • When an object should be able to notify other objects without making assumptions about who these objects are.
  • When an object should be able to notify other objects without knowing anything about them.

How can it help us

Observer Design Pattern solves several problems, including:

  • It reduces the coupling between objects.
  • It allows for better separation of concerns.
  • It promotes reusable code.

What to use

Several packages can simplify the implementation of the Observer Design Pattern in Go, including:

RxGo

github.com/reactivex/rxgo/v2 package provides a set of operators to work with observable streams in a similar way to ReactiveX libraries, allowing to easily implement the Observer Design Pattern in Go.

RxGo is a Go implementation of ReactiveX, which is a library for composing asynchronous and event-based programs. It provides a set of operators to work with observable streams, which are sequences of events that can be observed by multiple observers. RxGo allows developers to write code that is declarative, composable, and reusable.

Here is an example of how to use RxGo to implement the Observer Design Pattern in Go:

package main

import (
    "context"
    "fmt"

    "github.com/reactivex/rxgo/v2"
)

type Observer struct{}

func (o Observer) OnNext(item interface{}) {
    fmt.Printf("Received: %v\\n", item)
}

func (o Observer) OnError(err error) {
    fmt.Printf("Error: %v\\n", err)
}

func (o Observer) OnCompleted() {
    fmt.Println("Done!")
}

func main() {
    observable := rxgo.Just("Hello, dev.to!")
    _ = observable.Subscribe(context.Background(), Observer{})
}

Enter fullscreen mode Exit fullscreen mode

In this example, we define an observer Observer that implements the rxgo.Observer interface. We then create an observable observable using the rxgo.Just method and subscribe to it using the Subscribe method. When the observable emits a value, the observer's OnNext method is called with that value. If the observable encounters an error, the observer's OnError method is called with that error. Finally, when the observable completes, the observer's OnCompleted method is called.

Go Cloud Pub/Sub

github.com/google/go-cloud/pubsub package provides a set of interfaces and tools to work with publish/subscribe messaging. This package allows easy communication between independent components by decoupling the sender and the receiver.

Go Cloud Pub/Sub is a set of libraries and tools for building cloud-native applications. It provides a set of interfaces and tools to work with publish/subscribe messaging, which allows for easy communication between independent components by decoupling the sender and the receiver.

Here is an example of how to use Go Cloud Pub/Sub to implement the Observer Design Pattern in Go:

package main

import (
    "context"
    "fmt"
    "log"
    "time"

    "github.com/google/go-cloud/pubsub"
)

func main() {
    ctx := context.Background()

    // Create a Pub/Sub topic.
    topic, err := pubsub.OpenTopic(ctx, "dev-to-topic")
    if err != nil {
        log.Fatal(err)
    }

    // Create a Pub/Sub subscription.
    subscription, err := pubsub.OpenSubscription(ctx, "some-subscription")
    if err != nil {
        log.Fatal(err)
    }

    // Publish a message to the topic.
    msg := &pubsub.Message{
        Body: []byte("Hello, dev.to!"),
        Metadata: map[string]string{
            "key": "value",
        },
    }
    if err := topic.Send(ctx, msg); err != nil {
        log.Fatal(err)
    }

    // Receive the message from the subscription.
    ctx, cancel := context.WithTimeout(ctx, time.Second)
    defer cancel()
    receivedMsg, err := subscription.Receive(ctx)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("Received: %s\\n", receivedMsg.Body)

    // Acknowledge the message.
    receivedMsg.Ack()

    // Close the subscription and topic.
    if err := subscription.Shutdown(ctx); err != nil {
        log.Fatal(err)
    }
    if err := topic.Shutdown(ctx); err != nil {
        log.Fatal(err)
    }
}

Enter fullscreen mode Exit fullscreen mode

In this example, we create a Pub/Sub topic and subscription using the pubsub.OpenTopic and pubsub.OpenSubscription methods. We then publish a message to the topic using the topic.Send method. Finally, we receive the message from the subscription using the subscription.Receive method and acknowledge it using the msg2.Ack method.

Watermill

github.com/ThreeDotsLabs/watermill package provides a framework for building event-driven applications. It allows easy communication between independent components by decoupling the sender and the receiver.

Watermill is a Go library for building event-driven applications. It provides a framework for building event-driven applications and allows for easy communication between independent components by decoupling the sender and the receiver.

Here is an example of how to use Watermill to implement the Observer Design Pattern in Go:

package main

import (
    "context"
    "fmt"
    "log"

    "github.com/ThreeDotsLabs/watermill"
    "github.com/ThreeDotsLabs/watermill/message"
)

func main() {
    logger := watermill.NewStdLogger(false, true)

    // Create a new router.
    router, err := message.NewRouter(message.RouterConfig{}, logger)
    if err != nil {
        log.Fatal(err)
    }

    // Create a new publisher.
    publisher, err := message.NewPublisher(message.PublisherConfig{}, logger)
    if err != nil {
        log.Fatal(err)
    }

    // Subscribe to a topic.
    topic := "my-topic"
    subscriber, err := router.AddNoPublisherHandler(
        "my-subscriber",
        topic,
        func(msg *message.Message) error {
            fmt.Printf("Received: %s\\n", msg.Payload)
            return nil
        },
    )
    if err != nil {
        log.Fatal(err)
    }

    // Publish a message to the topic.
    msg := message.NewMessage(watermill.NewUUID(), []byte("Hello, dev.to!"))
    if err := publisher.Publish(topic, msg); err != nil {
        log.Fatal(err)
    }

    // Wait for the message to be processed.
    if err := subscriber.AwaitClose(); err != nil {
        log.Fatal(err)
    }
}

Enter fullscreen mode Exit fullscreen mode

In this example, we create a new router and publisher using the message.NewRouter and message.NewPublisher methods. We then subscribe to a topic using the router.AddNoPublisherHandler method and publish a message to the topic using the publisher.Publish method. Finally, we wait for the message to be processed using the subscriber.AwaitClose method.

Conclusion

The Observer Design Pattern is a powerful tool that can be used to maintain loosely coupled relationships between objects in Go. By using packages like github.com/reactivex/rxgo/v2, github.com/google/go-cloud/pubsub, and github.com/ThreeDotsLabs/watermill, we can simplify the implementation of this pattern and create reusable, modular code. Happy coding!

Top comments (0)