Should I Stay or Should I Go?

I love working at smaller companies, especially smaller tech companies. There's a sense of freedom for their employees that allows them to explore newer technologies, often leading to a more fitting solution to a problem.
29.10.2014
Jasper Timm
Tags

Should I stay or should I Go?

I love working at smaller companies, especially smaller tech companies. There’s a sense of freedom for their employees that allows them to explore newer technologies, often leading to a more fitting solution to a problem.

We were recently given the task of working on a Single Sign-On (SSO) solution for a client. As a protocol, we chose to go with the Central Authentication Service (CAS), ‘a simple and powerful ticket-based protocol’. Essentially each service that requires users to be authenticated simply redirects its users to us, the CAS server. If the user does not have an existing session with us - which is stored via a session cookie - we provide a login form so they can start one. Once we’ve authenticated the user we redirect back to the original service with a one-time use ‘service ticket’, which the service must then validate with the CAS server.

Picture 2

The basic foundations of the CAS protocol were fairly quick to implement so we decided to create it in a number of different languages, mainly in an effort to benchmark them against each other. Java and Ruby seemed like two obvious choices, but Google’s (relatively) new contribution of Go (also known as Golang) seemed like a worthy option. We conducted the benchmarking using a command line tool called wrk - a simple tool that would hit a given URL as many times as it could over a configurable amount of time. We chose to simply hit the /sso/login endpoint of the CAS server which replies with a simple HTML login page.

The Java (Jetty) and Ruby (Rails) implementations initially stumbled along at around 1K req/s. With a few optimisations we got each up to around 3-4K req/s. I’m not sure any of us could have guessed how well Go would perform though - using the standard Go net/http library, our CAS server averaged between 11-12K req/s.

However, there was another consideration to take into account - given Go’s relative immaturity there was likely to be less documentation, support and third party libraries for it also. But there has been a lot of progress since the release of Go only a few years ago. People were already quite busy on Github creating libraries for common issues and even some advanced ones (such as zero downtime restarts). And it seemed the community that contributed to the most popular Google group for Go (golang-nuts) were fairly friendly, helpful and welcoming.

Screen Shot 2018-06-13 at 14.50.08

Having essentially finished the project now I can say I’m very glad we chose Go. The Go tutorial on the homepage, with it’s interactive editor, is excellent. It really is a simple language - I think this quote from Ken Thompson (one of the 3 creators of Go) says it best:

We started off with the idea that all three of us had to be talked into every feature in the language, so there was no extraneous garbage put into the language for any reason.

There are a limited amount of keywords and more importantly I’ve found - built-in methods. As much as I am a big fan of not re-inventing the wheel, a string of ‘reject, inject and detect’ (sorry Ruby) array methods does not really promote readability for other developers who are not veterans of the language and can recite the result of each method in their sleep.

And readability was obviously a massive factor in saving time when we conducted code reviews for each other. Comprehending the gist of what a fellow developer had created was surprisingly quick, you could picture yourself writing something similar, perhaps because there were only a very limited amount of ways of implementing a solution. Further on in the project it was honestly hard to tell who the author of certain code was, which is saying something considering the developers came from predominantly Java and Ruby backgrounds respectively.

Threading, something well known for causing developers headaches, is also handled in a relatively straight forward fashion - Go routines (effectively a thread) are used to handle asynchronous behaviour. Communication between the routines is handled via Go channels (essentially a pipe through which you can push data from one routine to another).

Take this code, for example, which is called when a response from a backend server was taking too long. We provide the channel which the response will eventually be sent on and the cache entry which should be updated from this response:

func BackgroundUpdateCache(c chan BackendResponse, cacheEntry CacheEntry) {

  //Launch a go-routine to receive response when it comes
  go func() {
    select {

    //Got response from backend, can now update cache
    case resp := <-c:
      if resp.err() != nil {
        //Refresh cacheEntry and then update in cache
        cacheEntry.refresh(resp)
      }

    //The response took too long
    case <-time.After(time.Second * 30):
      logger.Info("Timed out waiting for response from backend.")
    }
  }()
}

We launch a go-routine so we can wait for the response from the backend in the background and not interrupt the main thread. The select keyword is something special to Go - it monitors each of the channels in the case statements and selects the first one to respond. In this example we have two case statements - a channel which we listen on for a response from the backend and a method of the built-in library time, which waits for the given duration to elapse and then sends the current time on the returned channel. So, if the response channel sends something before the timeout (and it’s not an error) we refresh the cache with the response, otherwise we will time out and this fact is logged as info.


Every language has its drawbacks of course and Go is no exception. Coming from a heavy object-oriented point of view you may struggle with the lack of a clear replacement for what you know as inheritance (loosely matched in Go by embedding structs within other structs). Also, if you’re used to using something like Eclipse to debug code within your editor, being forced to use a command line debugger like GDB might be a step back. Having said that though, I found the compiler errors were fairly thorough and quite helpful - you can usually trust code that compiles.

I’d quite happily recommend Go for any fellow developer who is curious to try it, has a relatively small project and is interested in performance.

So that was the tale of our adventures with Go so far. Does anyone have a similar story to share?