# Notes: Go Design Patterns - Observer Pattern

Observer Pattern as the name suggests is based on observation of something, maybe a state, maybe an event, or something else. It is a behavioral pattern.

In this pattern, we have two actors, **publishers** and **subscribers**. Publishers' job is to maintain the list of subscribers by adding or removing the subscribers and also notify them about the required changes that these subscribers expect. So, publishers' interface can look something like this:

```
type publisher interface {
	register(subscriber subscriber)
	deregister(subscriber subscriber)
	notifyAll()
}
``` 
Now, as soon as the publisher notifies subscribers, they need to act on it. So, subscribers can have a simple interface like this:

```
type subscriber interface {
	trigger(context context.Context)
}
``` 
Let's try to understand this with an example.
Let's build a notifier for *Out of Stock* products for an e-commerce store. If there is any item that is out of stock, users will be able to subscribe to that item and as soon as it gets available, they'll be notified and users will be able to act on that.

First, we can have an event manager/ publisher who will contain information of product which is being subscribed and the list of its subscribers. It can be something like this:

```
type stockCheckEventManager struct {
	subscribers stockCheckSubscriberMap
	productID   string
	productName string
	inStock     bool
}
``` 
This will implement `publisher` interface we mentioned in the beginning.

Next is subscribers. Subscribers are users, so they can be represented like this:

```
type stockCheckSubscriberObject struct {
	userID   string
	userName string
}
``` 
This will implement `subscriber` interface mentioned in the beginning.

I'm skipping subscriber addition and removal part as it's not that important. Now, let's look into how things will move.

Now, to start the whole process, first, we call `updateStockAvailability` method as soon as stock is available again.

```
func (em *stockCheckEventManager) updateStockAvailability() {
	fmt.Printf("item %v is now in stock\n", em.productName)
	em.inStock = true
	em.notifyAll()
}
``` 
This then calls `notifyAll` method to notify all the subscribers:

```
func (em *stockCheckEventManager) notifyAll() {
	if em.inStock {
		ctx := context.WithValue(context.Background(), productIDKey, em.productID)
		for _, subscriber := range em.subscribers {
			subscriber.trigger(ctx)
		}
	}
}
``` 
Now, `notifyAll` method goes through all the subscribers and executes the `trigger` method:

```
func (s *stockCheckSubscriberObject) trigger(ctx context.Context) {
	productID := ctx.Value(productIDKey)
	fmt.Printf("Product: %v back in Stock. Trigger subsequent events like sending mail, browser notification etc for user: %v\n", productID, s.userID)
}
``` 
And this ends our execution flow.

Full working code:

```
package main

import (
	"context"
	"fmt"
)

type contextKey string

const productIDKey contextKey = "productID"

// central manager interface
type publisher interface {
	register(subscriber subscriber)
	deregister(subscriber subscriber)
	notifyAll()
}

// observer interface
type subscriber interface {
	trigger(context context.Context)
}

type stockCheckSubscriber interface {
	subscriber
	getKey() string
}

type stockCheckSubscriberMap map[string]stockCheckSubscriber

// for subscribers, make sure key is subscriber related
// to avoid subscriber duplication
type stockCheckEventManager struct {
	subscribers stockCheckSubscriberMap
	productID   string
	productName string
	inStock     bool
}

func newStockCheckEventManager(productID string, productName string) *stockCheckEventManager {
	subscribers := make(stockCheckSubscriberMap)

	return &stockCheckEventManager{
		subscribers: subscribers,
		productID:   productID,
		productName: productName,
		inStock:     false,
	}
}

func (em *stockCheckEventManager) register(subscriber subscriber) {
	stockCheckSubscriber := subscriber.(stockCheckSubscriber)
	em.subscribers[stockCheckSubscriber.getKey()] = stockCheckSubscriber

	fmt.Printf("registered %v as subscriber\n", stockCheckSubscriber.getKey())
}

func (em *stockCheckEventManager) deregister(subscriber subscriber) {
	stockCheckSubscriber := subscriber.(stockCheckSubscriber)
	delete(em.subscribers, stockCheckSubscriber.getKey())

	fmt.Printf("deregistered %v as subscriber\n", stockCheckSubscriber.getKey())
}

func (em *stockCheckEventManager) notify(subscribers map[string]subscriber) {
	if em.inStock {
		ctx := context.WithValue(context.Background(), productIDKey, em.productID)
		for _, subscriber := range subscribers {
			subscriber.trigger(ctx)
		}
	}
}

func (em *stockCheckEventManager) notifyAll() {
	if em.inStock {
		ctx := context.WithValue(context.Background(), productIDKey, em.productID)
		for _, subscriber := range em.subscribers {
			subscriber.trigger(ctx)
		}
	}
}

func (em *stockCheckEventManager) updateStockAvailability() {
	fmt.Printf("item %v is now in stock\n", em.productName)
	em.inStock = true
	em.notifyAll()
}

/************************************
**** Stock Check Subscriber Code ****
*************************************/
type stockCheckSubscriberObject struct {
	userID   string
	userName string
}

func newStockCheckSubscribers(userID string, userName string) *stockCheckSubscriberObject {
	return &stockCheckSubscriberObject{
		userID:   userID,
		userName: userName,
	}
}

func (s *stockCheckSubscriberObject) trigger(ctx context.Context) {
	productID := ctx.Value(productIDKey)
	fmt.Printf("Product: %v back in Stock. Trigger subsequent events like sending mail, browser notification etc for user: %v", productID, s.userID)
}

func (s *stockCheckSubscriberObject) getKey() string {
	return s.userID
}

func main() {
	stockCheckSubscriber1 := newStockCheckSubscribers("156", "kshitij")
	stockCheckSubscriber2 := newStockCheckSubscribers("528", "nidhi")
	stockCheckSubscriber3 := newStockCheckSubscribers("339", "coco")

	newStockCheckManager1 := newStockCheckEventManager("107", "shirt")
	newStockCheckManager2 := newStockCheckEventManager("108", "skirt")

	newStockCheckManager1.register(stockCheckSubscriber1)
	newStockCheckManager1.register(stockCheckSubscriber2)

	newStockCheckManager2.register(stockCheckSubscriber2)
	newStockCheckManager2.register(stockCheckSubscriber3)

	newStockCheckManager1.updateStockAvailability()

	newStockCheckManager2.deregister(stockCheckSubscriber2)

	newStockCheckManager2.updateStockAvailability()
}

``` 

