4 min read

GoChat - Using Go Channels

GoChat - Using Go Channels

A couple of weeks ago I wrote about using Go with Twitter Flight to write web applications.

But I didn’t put too much emphasis on the go code, it was mostly to highlight some patterns which I think can be applied to any web application.

This post is more about the go code.

Using Go channels.

One of the main features I like from Go is that it has concurrency solutions built into the language itself. Go offers goroutines and channels. In a nutshell, goroutines can spawn tasks to run in the background, they are light weight, so you can safely have thousands of them and not have to worry about overhead, etc. Go Channels are a type-safe way to communicate. If you are used to the Actor model, think of channels as an actor, but they have types, so if you declare a channel to handle Strings, it will only work with strings, which is great.

There is plenty of good documentation about Go’s concurrency features, you may want to visit the Go tour to take a look. And the other day I found this other blog post that was helpful too.

Some code.

The first version of the simple chat application I wrote had a top level var that was a map of Messages. This was fine at the time because I wasn’t too focused on the go code. But a better way to handle this is to use go channels.

First, I defined some structs, which are similar to case classes from Scala.

//This struct hold each message, the `json:"id"` part tells Go to use a lowercase name when serializing to json
type Message struct {
  Id        string `json:"id"`
  Body      string `json:"body"`
  CreatedOn int64  `json:"createdOn"`
}

//This is our map of messages
type ChatMessageResource struct {
  messages map[string]Message
}

//This is the struct that we will send out go channel
type MessageStore struct {
  chatMessages *ChatMessageResource
  msg          Message
}

//This is how you initialize a go channel, pretty simple.
//The * before `MessageStore` means that the channel gets a pointer to a MessageStore, not the actual value.
var messagesChan = make(chan *MessageStore)

Using the channel

To do something with the channel, you need to define a function that will listen for new messages.

// handleAddMessage reads the payload channel and adds a new entry to
// the chat messages map as they become available.
func handleAddMessage(payload chan *MessageStore) {
  for msg := range payload {
    msg.chatMessages.messages[msg.msg.Id] = msg.msg
  }
}

In case the syntax doesn’t look clear, the msg variable is assigned the first value received on the payload channel, and then we add an entry to our map of messages. Because we are using range, this function keep listening for new messages that will come to the channel, until we shut down the app.

Because this function will block waiting for new messages, we use a goroutine to run it, which in code simply means:

go handleAddMessage(messagesChan)

By adding the go keyword in front of a function, that function will end up running on the background. This is like Lift’s Schedule.schedule().

And finally, we handle the http requests that have a new chat message with this function:

func (chatMessages *ChatMessageResource) createChatMessage(request *restful.Request, response *restful.Response) {
  //Generate a guid
  guid, err := uuid.NewV4()
  if err != nil {
    fmt.Println("error:", err)
    return
  }
  //we create a partial message value, with the id we just generated a couple of lines above
  msg := Message{Id: guid.String()}
  //`ReadEntity` reads the json payload from the http request, and tries to parse it using
  //the msg vaue (which is a Struct of type Message)
  parseErr := request.ReadEntity(&msg)
  if parseErr == nil {
    //If no errors, we send the payload to the channel `messagesChan`
    messagesChan <- &MessageStore{chatMessages, msg}
    //The response to a create request is the id of the new message
    ret := map[string]string{"id": guid.String()}
    //with a sttus code of 201
    response.WriteHeader(http.StatusCreated)
    response.WriteEntity(ret)
  } else {
    response.AddHeader("Content-Type", "text/plain")
    response.WriteErrorString(http.StatusInternalServerError, parseErr.Error())
  }
}

Just to highlight it, you send new values to a channel with a syntax like:

messagesChan <- &MessageStore{chatMessages, msg}

messagesChan is our channel, the <- tells go that the value from the right goes towards the channel on the left.

That’s it for this post, you can find the complete source code on my go-examples repository, under the gochat branch.

Thanks for feel free to leave comments.

Diego

comments powered by Disqus