neverlock
11/28/2013 - 12:23 AM

A Goroutine safe pattern using channels that abstracts away the usage of channels.

A Goroutine safe pattern using channels that abstracts away the usage of channels.

/*
  A Goroutine safe pattern using channels that abstracts away the channels

  This is a concept of creating a goroutine safe object that uses channels under the covers to communicate
  with the internal map[string]string structure.  I know that typically this kind of solution may done
  with mutexes but the excercise was in using channels on purpose although they are heavier.

  Note a couple of points:

  - When using channels, you can still build a public-facing api that nicely abstracts them away, therefore
    somemone using this api for example, doesn't have to understand the paradigm of communicating over channels
  - This example is just a prototype, as an example
  - Notice that all state is mutated internal to the Db's f function
  - Notice that in the Fetch method there is bi-directional communication a send/receive
*/

package main

import "fmt"
import "time"

type KeyValue struct {
	Key   string
	Value string
	Reply chan KeyValue
}

type Db struct {
	db           map[string]string
	storeChannel chan KeyValue
	fetchChannel chan KeyValue
}

func NewDb() *Db {
	d := &Db{}

	d.db = make(map[string]string)

	d.storeChannel = make(chan KeyValue)
	d.fetchChannel = make(chan KeyValue)

	go func() {

		for {
			select {
			case storeValue := <-d.storeChannel:
				d.internalStore(storeValue)

			case fetchKey := <-d.fetchChannel:
				fetchKey.Reply <- d.internalFetch(fetchKey)
			}
		}
	}()

	return d
}

func (d *Db) internalFetch(kv KeyValue) KeyValue {
	v, ok := d.db[kv.Key]
	if ok {
		return KeyValue{Key: kv.Key, Value: v}
	}
	return KeyValue{Key: kv.Key}
}

func (d *Db) internalStore(kv KeyValue) {
	d.db[kv.Key] = kv.Value
	fmt.Println("Just stored: ", kv)
}

func (d *Db) Fetch(key string) KeyValue {

	ch := make(chan KeyValue)
	d.fetchChannel <- KeyValue{Key: key, Reply: ch}

	return <-ch
}

func (d *Db) Store(key string, value string) {

	d.storeChannel <- KeyValue{Key: key, Value: value}
}

func main() {

	myDb := NewDb()

	//myDb can safely be used by many goroutines although in this example it's only used by the main goroutine.
	myDb.Store("id-3383", "John")
	myDb.Store("id-2218", "Ralph")
	myDb.Store("id-7741", "Amy")

	//simulate some time has gone by
	time.Sleep(time.Second * 1)

	fmt.Println(myDb.Fetch("id-3383"))
	fmt.Println(myDb.Fetch("id-7741"))

	//not found returns a KeyValue with an empty value
	fmt.Println(myDb.Fetch("id-9965"))


	var s string
	fmt.Scanln(&s)
}